netsurf/riscos/download.c

1629 lines
43 KiB
C

/*
* Copyright 2004 James Bursa <bursa@users.sourceforge.net>
* Copyright 2003 Rob Jackson <jacko@xms.ms>
* Copyright 2005 Adrian Lees <adrianl@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file
* RISC OS download windows implementation.
*
* This file implements the interface given by desktop/gui_download.h
* for download windows. Each download window has an associated
* fetch. Downloads start by writing received data to a temporary
* file. At some point the user chooses a destination (by drag &
* drop), and the temporary file is then moved to the destination and
* the download continues until complete.
*/
#include <assert.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <curl/curl.h>
#include <libwapcaplet/libwapcaplet.h>
#include "oslib/mimemap.h"
#include "oslib/osargs.h"
#include "oslib/osfile.h"
#include "oslib/osfind.h"
#include "oslib/osfscontrol.h"
#include "oslib/osgbpb.h"
#include "oslib/wimp.h"
#include "oslib/wimpspriteop.h"
#include "utils/nsoption.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/nsurl.h"
#include "utils/utf8.h"
#include "utils/utils.h"
#include "utils/corestrings.h"
#include "desktop/gui_download.h"
#include "desktop/download.h"
#include "riscos/gui.h"
#include "riscos/dialog.h"
#include "riscos/mouse.h"
#include "riscos/save.h"
#include "riscos/query.h"
#include "riscos/wimp.h"
#include "riscos/wimp_event.h"
#include "riscos/ucstables.h"
#include "riscos/filetype.h"
#define ICON_DOWNLOAD_ICON 0
#define ICON_DOWNLOAD_URL 1
#define ICON_DOWNLOAD_PATH 2
#define ICON_DOWNLOAD_DESTINATION 3
#define ICON_DOWNLOAD_PROGRESS 5
#define ICON_DOWNLOAD_STATUS 6
#define RO_DOWNLOAD_MAX_PATH_LEN 255
typedef enum
{
QueryRsn_Quit,
QueryRsn_Abort,
QueryRsn_Overwrite
} query_reason;
/** Data for a download window. */
struct gui_download_window {
/** Associated context, or 0 if the fetch has completed or aborted. */
download_context *ctx;
unsigned int received; /**< Amount of data received so far. */
unsigned int total_size; /**< Size of resource, or 0 if unknown. */
wimp_w window; /**< RISC OS window handle. */
bits file_type; /**< RISC OS file type. */
char url[256]; /**< Buffer for URL icon. */
char sprite_name[20]; /**< Buffer for sprite icon. */
char path[RO_DOWNLOAD_MAX_PATH_LEN]; /**< Buffer for pathname icon. */
char status[256]; /**< Buffer for status icon. */
/** User has chosen the destination, and it is being written. */
bool saved;
bool close_confirmed;
bool error; /**< Error occurred, aborted. */
/** RISC OS file handle, of temporary file when !saved, and of
* destination when saved. */
os_fw file;
query_id query;
query_reason query_rsn;
struct timeval start_time; /**< Time download started. */
struct timeval last_time; /**< Time status was last updated. */
unsigned int last_received; /**< Value of received at last_time. */
float average_rate; /**< Moving average download rate. */
unsigned int average_points; /**< Number of points in the average. */
bool send_dataload; /**< Should send DataLoad message when finished */
wimp_message save_message; /**< Copy of wimp DataSaveAck message */
struct gui_download_window *prev; /**< Previous in linked list. */
struct gui_download_window *next; /**< Next in linked list. */
};
/** List of all download windows. */
static struct gui_download_window *download_window_list = 0;
/** Download window with current save operation. */
static struct gui_download_window *download_window_current = 0;
/** Template for a download window. */
static wimp_window *download_template;
/** Width of progress bar at 100%. */
static int download_progress_width;
/** Coordinates of progress bar. */
static int download_progress_x0;
static int download_progress_y0;
static int download_progress_y1;
/** Current download directory. */
static char *download_dir = NULL;
static size_t download_dir_len;
static void ro_gui_download_drag_end(wimp_dragged *drag, void *data);
static const char *ro_gui_download_temp_name(struct gui_download_window *dw);
static void ro_gui_download_update_status(struct gui_download_window *dw);
static void ro_gui_download_update_status_wrapper(void *p);
static void ro_gui_download_window_hide_caret(struct gui_download_window *dw);
static char *ro_gui_download_canonicalise(const char *path);
static bool ro_gui_download_check_space(struct gui_download_window *dw,
const char *dest_file, const char *orig_file);
static os_error *ro_gui_download_move(struct gui_download_window *dw,
const char *dest_file, const char *src_file);
static void ro_gui_download_remember_dir(const char *path);
static bool ro_gui_download_save(struct gui_download_window *dw,
const char *file_name, bool force_overwrite);
static void ro_gui_download_send_dataload(struct gui_download_window *dw);
static void ro_gui_download_window_destroy_wrapper(void *p);
static bool ro_gui_download_window_destroy(struct gui_download_window *dw, bool quit);
static void ro_gui_download_close_confirmed(query_id, enum query_response res, void *p);
static void ro_gui_download_close_cancelled(query_id, enum query_response res, void *p);
static void ro_gui_download_overwrite_confirmed(query_id, enum query_response res, void *p);
static void ro_gui_download_overwrite_cancelled(query_id, enum query_response res, void *p);
static bool ro_gui_download_click(wimp_pointer *pointer);
static bool ro_gui_download_keypress(wimp_key *key);
static void ro_gui_download_close(wimp_w w);
static const query_callback close_funcs =
{
ro_gui_download_close_confirmed,
ro_gui_download_close_cancelled
};
static const query_callback overwrite_funcs =
{
ro_gui_download_overwrite_confirmed,
ro_gui_download_overwrite_cancelled
};
/**
* Load the download window template.
*/
void ro_gui_download_init(void)
{
download_template = ro_gui_dialog_load_template("download");
download_progress_width =
download_template->icons[ICON_DOWNLOAD_STATUS].extent.x1 -
download_template->icons[ICON_DOWNLOAD_STATUS].extent.x0;
download_progress_x0 =
download_template->icons[ICON_DOWNLOAD_PROGRESS].extent.x0;
download_progress_y0 =
download_template->icons[ICON_DOWNLOAD_PROGRESS].extent.y0;
download_progress_y1 =
download_template->icons[ICON_DOWNLOAD_PROGRESS].extent.y1;
}
/**
* Returns the pathname of a temporary file for this download.
*
* \param dw download window
* \return ptr to pathname
*/
const char *ro_gui_download_temp_name(struct gui_download_window *dw)
{
static char temp_name[40];
snprintf(temp_name, sizeof temp_name, "<Wimp$ScrapDir>.ns%x",
(unsigned int) dw);
return temp_name;
}
/**
* Try and find the correct RISC OS filetype from a download context.
*/
static nserror download_ro_filetype(download_context *ctx, bits *ftype_out)
{
nsurl *url = download_context_get_url(ctx);
bits ftype = 0;
lwc_string *scheme;
/* If the file is local try and read its filetype */
scheme = nsurl_get_component(url, NSURL_SCHEME);
if (scheme != NULL) {
bool filescheme;
if (lwc_string_isequal(scheme,
corestring_lwc_file,
&filescheme) != lwc_error_ok) {
filescheme = false;
}
if (filescheme) {
lwc_string *path = nsurl_get_component(url, NSURL_PATH);
if (path != NULL && lwc_string_length(path) != 0) {
char *raw_path;
raw_path = curl_unescape(lwc_string_data(path),
lwc_string_length(path));
if (raw_path != NULL) {
ftype = ro_filetype_from_unix_path(raw_path);
curl_free(raw_path);
}
}
}
}
/* If we still don't have a filetype (i.e. failed reading local
* one or fetching a remote object), then use the MIME type.
*/
if (ftype == 0) {
/* convert MIME type to RISC OS file type */
os_error *error;
const char *mime_type;
mime_type = download_context_get_mime_type(ctx);
error = xmimemaptranslate_mime_type_to_filetype(mime_type, &ftype);
if (error) {
LOG("xmimemaptranslate_mime_type_to_filetype: 0x%x: %s", error->errnum, error->errmess);
warn_user("MiscError", error->errmess);
ftype = 0xffd;
}
}
*ftype_out = ftype;
return NSERROR_OK;
}
/**
* Create and open a download progress window.
*
* \param ctx Download context
* \param gui The RISCOS gui window to download for.
* \return A new gui_download_window structure, or NULL on error and error
* reported
*/
static struct gui_download_window *
gui_download_window_create(download_context *ctx, struct gui_window *gui)
{
nsurl *url = download_context_get_url(ctx);
const char *temp_name;
char *filename = NULL;
struct gui_download_window *dw;
bool space_warning = false;
os_error *error;
char *local_path;
nserror err;
size_t i, last_dot;
dw = malloc(sizeof *dw);
if (!dw) {
warn_user("NoMemory", 0);
return 0;
}
dw->ctx = ctx;
dw->saved = false;
dw->close_confirmed = false;
dw->error = false;
dw->query = QUERY_INVALID;
dw->received = 0;
dw->total_size = download_context_get_total_length(ctx);
/** @todo change this to take a reference to the nsurl and use
* that value directly rather than using a fixed buffer.
*/
strncpy(dw->url, nsurl_access(url), sizeof dw->url);
dw->url[sizeof dw->url - 1] = 0;
dw->status[0] = 0;
gettimeofday(&dw->start_time, 0);
dw->last_time = dw->start_time;
dw->last_received = 0;
dw->file_type = 0;
dw->average_rate = 0;
dw->average_points = 0;
/* get filetype */
err = download_ro_filetype(ctx, &dw->file_type);
if (err != NSERROR_OK) {
warn_user(messages_get_errorcode(err), 0);
free(dw);
return 0;
}
/* open temporary output file */
temp_name = ro_gui_download_temp_name(dw);
if (!ro_gui_download_check_space(dw, temp_name, NULL)) {
/* issue a warning but continue with the download because the
user can save it to another medium whilst it's downloading */
space_warning = true;
}
error = xosfind_openoutw(osfind_NO_PATH | osfind_ERROR_IF_DIR,
temp_name, 0, &dw->file);
if (error) {
LOG("xosfind_openoutw: 0x%x: %s", error->errnum, error->errmess);
warn_user("SaveError", error->errmess);
free(dw);
return 0;
}
/* fill in download window icons */
download_template->icons[ICON_DOWNLOAD_URL].data.indirected_text.text =
dw->url;
download_template->icons[ICON_DOWNLOAD_URL].data.indirected_text.size =
sizeof dw->url;
download_template->icons[ICON_DOWNLOAD_STATUS].data.indirected_text.
text = dw->status;
download_template->icons[ICON_DOWNLOAD_STATUS].data.indirected_text.
size = sizeof dw->status;
sprintf(dw->sprite_name, "file_%.3x", dw->file_type);
if (!ro_gui_wimp_sprite_exists(dw->sprite_name))
strcpy(dw->sprite_name, "file_xxx");
download_template->icons[ICON_DOWNLOAD_ICON].data.indirected_sprite.id =
(osspriteop_id) dw->sprite_name;
/* Get a suitable path- and leafname for the download. */
temp_name = download_context_get_filename(dw->ctx);
if (temp_name == NULL)
temp_name = messages_get("SaveObject");
if (temp_name != NULL)
filename = strdup(temp_name);
if (filename == NULL) {
LOG("Failed to establish download filename.");
warn_user("SaveError", error->errmess);
free(dw);
return 0;
}
for (i = 0, last_dot = (size_t) -1; filename[i] != '\0'; i++) {
const char c = filename[i];
if (c == '.') {
last_dot = i;
filename[i] = '/';
} else if (c <= ' ' || strchr(":*#$&@^%\\", c) != NULL)
filename[i] = '_';
}
if (nsoption_bool(strip_extensions) && last_dot != (size_t) -1)
filename[last_dot] = '\0';
if (download_dir != NULL && strlen(download_dir) > 0)
snprintf(dw->path, RO_DOWNLOAD_MAX_PATH_LEN, "%s.%s",
download_dir, filename);
else
snprintf(dw->path, RO_DOWNLOAD_MAX_PATH_LEN, "%s",
filename);
free(filename);
err = utf8_to_local_encoding(dw->path, 0, &local_path);
if (err != NSERROR_OK) {
/* badenc should never happen */
assert(err !=NSERROR_BAD_ENCODING);
LOG("utf8_to_local_encoding failed");
warn_user("NoMemory", 0);
free(dw);
return 0;
}
else {
strncpy(dw->path, local_path, sizeof dw->path);
free(local_path);
}
download_template->icons[ICON_DOWNLOAD_PATH].data.indirected_text.text =
dw->path;
download_template->icons[ICON_DOWNLOAD_PATH].data.indirected_text.size =
sizeof dw->path;
download_template->icons[ICON_DOWNLOAD_DESTINATION].data.
indirected_text.text = dw->path;
download_template->icons[ICON_DOWNLOAD_DESTINATION].data.
indirected_text.size = sizeof dw->path;
download_template->icons[ICON_DOWNLOAD_DESTINATION].flags |=
wimp_ICON_DELETED;
/* create and open the download window */
error = xwimp_create_window(download_template, &dw->window);
if (error) {
LOG("xwimp_create_window: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
free(dw);
return 0;
}
dw->prev = 0;
dw->next = download_window_list;
if (download_window_list)
download_window_list->prev = dw;
download_window_list = dw;
ro_gui_download_update_status(dw);
ro_gui_dialog_open(dw->window);
ro_gui_wimp_event_set_user_data(dw->window, dw);
ro_gui_wimp_event_register_mouse_click(dw->window, ro_gui_download_click);
ro_gui_wimp_event_register_keypress(dw->window, ro_gui_download_keypress);
ro_gui_wimp_event_register_close_window(dw->window, ro_gui_download_close);
/* issue the warning now, so that it appears in front of the download
* window! */
if (space_warning)
warn_user("DownloadWarn", messages_get("NoDiscSpace"));
return dw;
}
/**
* Handle failed downloads.
*
* \param dw download window
* \param error_msg error message
*/
static void gui_download_window_error(struct gui_download_window *dw,
const char *error_msg)
{
os_error *error;
if (dw->ctx != NULL)
download_context_destroy(dw->ctx);
dw->ctx = NULL;
dw->error = true;
riscos_schedule(-1, ro_gui_download_update_status_wrapper, dw);
/* place error message in status icon in red */
strncpy(dw->status, error_msg, sizeof dw->status);
error = xwimp_set_icon_state(dw->window,
ICON_DOWNLOAD_STATUS,
wimp_COLOUR_RED << wimp_ICON_FG_COLOUR_SHIFT,
wimp_ICON_FG_COLOUR);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
/* grey out pathname icon */
error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_PATH,
wimp_ICON_SHADED, 0);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
/* grey out file icon */
error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_ICON,
wimp_ICON_SHADED, wimp_ICON_SHADED);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
ro_gui_download_window_hide_caret(dw);
}
/**
* Handle received download data.
*
* \param dw download window
* \param data pointer to block of data received
* \param size size of data
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror gui_download_window_data(struct gui_download_window *dw,
const char *data, unsigned int size)
{
while (true) {
const char *msg;
int unwritten;
os_error *error;
error = xosgbpb_writew(dw->file, (const byte *) data, size,
&unwritten);
if (error) {
LOG("xosgbpb_writew: 0x%x: %s", error->errnum, error->errmess);
msg = error->errmess;
} else if (unwritten) {
LOG("xosgbpb_writew: unwritten %i", unwritten);
msg = messages_get("Unwritten");
}
else {
dw->received += size;
return NSERROR_OK;
}
warn_user("SaveError", msg);
if (dw->saved) {
/* try to continue with the temporary file */
const char *temp_name = ro_gui_download_temp_name(dw);
error = ro_gui_download_move(dw, temp_name, dw->path);
if (!error) {
/* re-allow saving */
dw->saved = false;
error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_ICON,
wimp_ICON_SHADED, 0);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_DESTINATION,
wimp_ICON_DELETED, wimp_ICON_DELETED);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
error = xwimp_set_icon_state(dw->window,
ICON_DOWNLOAD_PATH, wimp_ICON_DELETED, 0);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
continue;
}
}
/* give up then */
assert(dw->ctx);
download_context_abort(dw->ctx);
gui_download_window_error(dw, msg);
return NSERROR_SAVE_FAILED;
}
}
/**
* Update the status text and progress bar.
*
* \param dw download window
*/
void ro_gui_download_update_status(struct gui_download_window *dw)
{
char *total_size;
char *speed;
char time[20] = "?";
struct timeval t;
float dt;
unsigned int left;
float rate;
os_error *error;
int width;
char *local_status;
nserror err;
gettimeofday(&t, 0);
dt = (t.tv_sec + 0.000001 * t.tv_usec) - (dw->last_time.tv_sec +
0.000001 * dw->last_time.tv_usec);
if (dt == 0)
dt = 0.001;
total_size = human_friendly_bytesize(max(dw->received, dw->total_size));
if (dw->ctx) {
char *received;
rate = (dw->received - dw->last_received) / dt;
received = human_friendly_bytesize(dw->received);
/* A simple 'modified moving average' download rate calculation
* to smooth out rate fluctuations: chosen for simplicity.
*/
dw->average_points++;
dw->average_rate =
((dw->average_points - 1) *
dw->average_rate + rate) /
dw->average_points;
speed = human_friendly_bytesize(dw->average_rate);
if (dw->total_size) {
float f;
if (dw->average_rate > 0) {
left = (dw->total_size - dw->received) /
dw->average_rate;
sprintf(time, "%u:%.2u", left / 60, left % 60);
}
/* convert to local encoding */
err = utf8_to_local_encoding(
messages_get("Download"), 0, &local_status);
if (err != NSERROR_OK) {
/* badenc should never happen */
assert(err != NSERROR_BAD_ENCODING);
/* hide nomem error */
snprintf(dw->status, sizeof dw->status,
messages_get("Download"),
received, total_size, speed, time);
}
else {
snprintf(dw->status, sizeof dw->status,
local_status,
received, total_size, speed, time);
free(local_status);
}
f = (float) dw->received / (float) dw->total_size;
width = download_progress_width * f;
} else {
left = t.tv_sec - dw->start_time.tv_sec;
sprintf(time, "%u:%.2u", left / 60, left % 60);
err = utf8_to_local_encoding(
messages_get("DownloadU"), 0, &local_status);
if (err != NSERROR_OK) {
/* badenc should never happen */
assert(err != NSERROR_BAD_ENCODING);
/* hide nomem error */
snprintf(dw->status, sizeof dw->status,
messages_get("DownloadU"),
received, speed, time);
}
else {
snprintf(dw->status, sizeof dw->status,
local_status,
received, speed, time);
free(local_status);
}
/* length unknown, stay at 0 til finished */
width = 0;
}
} else {
left = dw->last_time.tv_sec - dw->start_time.tv_sec;
if (left == 0)
left = 1;
rate = (float) dw->received / (float) left;
sprintf(time, "%u:%.2u", left / 60, left % 60);
speed = human_friendly_bytesize(rate);
err = utf8_to_local_encoding(messages_get("Downloaded"), 0,
&local_status);
if (err != NSERROR_OK) {
/* badenc should never happen */
assert(err != NSERROR_BAD_ENCODING);
/* hide nomem error */
snprintf(dw->status, sizeof dw->status,
messages_get("Downloaded"),
total_size, speed, time);
}
else {
snprintf(dw->status, sizeof dw->status, local_status,
total_size, speed, time);
free(local_status);
}
/* all done */
width = download_progress_width;
}
dw->last_time = t;
dw->last_received = dw->received;
error = xwimp_resize_icon(dw->window, ICON_DOWNLOAD_PROGRESS,
download_progress_x0,
download_progress_y0,
download_progress_x0 + width,
download_progress_y1);
if (error) {
LOG("xwimp_resize_icon: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_STATUS, 0, 0);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
if (dw->ctx) {
riscos_schedule(1000, ro_gui_download_update_status_wrapper, dw);
} else {
riscos_schedule(-1, ro_gui_download_update_status_wrapper, dw);
}
}
/**
* Wrapper for ro_gui_download_update_status(), suitable for riscos_schedule().
*/
void ro_gui_download_update_status_wrapper(void *p)
{
ro_gui_download_update_status((struct gui_download_window *) p);
}
/**
* Hide the caret but preserve input focus.
*
* \param dw download window
*/
void ro_gui_download_window_hide_caret(struct gui_download_window *dw)
{
wimp_caret caret;
os_error *error;
error = xwimp_get_caret_position(&caret);
if (error) {
LOG("xwimp_get_caret_position: 0x%x : %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
else if (caret.w == dw->window) {
error = xwimp_set_caret_position(dw->window, (wimp_i)-1, 0, 0, 1 << 25, -1);
if (error) {
LOG("xwimp_get_caret_position: 0x%x : %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
}
}
/**
* Handle completed downloads.
*
* \param dw download window
*/
static void gui_download_window_done(struct gui_download_window *dw)
{
os_error *error;
if (dw->ctx != NULL)
download_context_destroy(dw->ctx);
dw->ctx = NULL;
ro_gui_download_update_status(dw);
error = xosfind_closew(dw->file);
if (error) {
LOG("xosfind_closew: 0x%x: %s", error->errnum, error->errmess);
warn_user("SaveError", error->errmess);
}
dw->file = 0;
if (dw->saved) {
error = xosfile_set_type(dw->path,
dw->file_type);
if (error) {
LOG("xosfile_set_type: 0x%x: %s", error->errnum, error->errmess);
warn_user("SaveError", error->errmess);
}
if (dw->send_dataload) {
ro_gui_download_send_dataload(dw);
}
riscos_schedule(2000, ro_gui_download_window_destroy_wrapper, dw);
}
}
/**
* Handle Mouse_Click events in a download window.
*
* \param pointer block returned by Wimp_Poll
*/
bool ro_gui_download_click(wimp_pointer *pointer)
{
struct gui_download_window *dw;
dw = (struct gui_download_window *)ro_gui_wimp_event_get_user_data(pointer->w);
if ((pointer->buttons & (wimp_DRAG_SELECT | wimp_DRAG_ADJUST)) &&
pointer->i == ICON_DOWNLOAD_ICON &&
!dw->error && !dw->saved) {
const char *sprite = ro_gui_get_icon_string(pointer->w, pointer->i);
int x = pointer->pos.x, y = pointer->pos.y;
wimp_window_state wstate;
wimp_icon_state istate;
/* start the drag from the icon's exact location, rather than the pointer */
istate.w = wstate.w = pointer->w;
istate.i = pointer->i;
if (!xwimp_get_window_state(&wstate) && !xwimp_get_icon_state(&istate)) {
x = (istate.icon.extent.x1 + istate.icon.extent.x0)/2 +
wstate.visible.x0 - wstate.xscroll;
y = (istate.icon.extent.y1 + istate.icon.extent.y0)/2 +
wstate.visible.y1 - wstate.yscroll;
}
ro_mouse_drag_start(ro_gui_download_drag_end, NULL, NULL, NULL);
download_window_current = dw;
ro_gui_drag_icon(x, y, sprite);
} else if (pointer->i == ICON_DOWNLOAD_DESTINATION) {
char command[256] = "Filer_OpenDir ";
char *dot;
strncpy(command + 14, dw->path, 242);
command[255] = 0;
dot = strrchr(command, '.');
if (dot) {
os_error *error;
*dot = 0;
error = xos_cli(command);
if (error) {
LOG("xos_cli: 0x%x: %s", error->errnum, error->errmess);
warn_user("MiscError", error->errmess);
}
}
}
return true;
}
/**
* Handler Key_Press events in a download window.
*
* \param key key press returned by Wimp_Poll
* \return true iff key press handled
*/
bool ro_gui_download_keypress(wimp_key *key)
{
struct gui_download_window *dw;
dw = (struct gui_download_window *)ro_gui_wimp_event_get_user_data(key->w);
switch (key->c)
{
case wimp_KEY_ESCAPE:
ro_gui_download_window_destroy(dw, false);
return true;
case wimp_KEY_RETURN: {
const char *name = ro_gui_get_icon_string(dw->window,
ICON_DOWNLOAD_PATH);
if (!strrchr(name, '.')) {
warn_user("NoPathError", NULL);
return true;
}
ro_gui_convert_save_path(dw->path, sizeof dw->path, name);
dw->send_dataload = false;
if (ro_gui_download_save(dw, dw->path,
!nsoption_bool(confirm_overwrite)) && !dw->ctx)
{
/* finished already */
riscos_schedule(2000, ro_gui_download_window_destroy_wrapper, dw);
}
return true;
}
break;
}
/* ignore all other keypresses (F12 etc) */
return false;
}
/**
* Handle User_Drag_Box event for a drag from a download window.
*
* \param *drag block returned by Wimp_Poll
* \param *data NULL data to allow use as callback from ro_mouse.
*/
static void ro_gui_download_drag_end(wimp_dragged *drag, void *data)
{
wimp_pointer pointer;
wimp_message message;
struct gui_download_window *dw = download_window_current;
const char *leaf;
os_error *error;
if (dw->saved || dw->error)
return;
error = xwimp_get_pointer_info(&pointer);
if (error) {
LOG("xwimp_get_pointer_info: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
return;
}
/* ignore drags to the download window itself */
if (pointer.w == dw->window) return;
leaf = strrchr(dw->path, '.');
if (leaf)
leaf++;
else
leaf = dw->path;
ro_gui_convert_save_path(message.data.data_xfer.file_name, 212, leaf);
message.your_ref = 0;
message.action = message_DATA_SAVE;
message.data.data_xfer.w = pointer.w;
message.data.data_xfer.i = pointer.i;
message.data.data_xfer.pos.x = pointer.pos.x;
message.data.data_xfer.pos.y = pointer.pos.y;
message.data.data_xfer.est_size = dw->total_size ? dw->total_size :
dw->received;
message.data.data_xfer.file_type = dw->file_type;
message.size = 44 + ((strlen(message.data.data_xfer.file_name) + 4) &
(~3u));
error = xwimp_send_message_to_window(wimp_USER_MESSAGE, &message,
pointer.w, pointer.i, 0);
if (error) {
LOG("xwimp_send_message_to_window: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
gui_current_drag_type = GUI_DRAG_DOWNLOAD_SAVE;
}
/**
* Handle Message_DataSaveAck for a drag from a download window.
*
* \param message block returned by Wimp_Poll
*/
void ro_gui_download_datasave_ack(wimp_message *message)
{
struct gui_download_window *dw = download_window_current;
dw->send_dataload = true;
memcpy(&dw->save_message, message, sizeof(wimp_message));
if (!ro_gui_download_save(dw, message->data.data_xfer.file_name,
!nsoption_bool(confirm_overwrite)))
return;
if (!dw->ctx) {
/* Ack successful completed save with message_DATA_LOAD immediately
to reduce the chance of the target app getting confused by it
being delayed */
ro_gui_download_send_dataload(dw);
riscos_schedule(2000, ro_gui_download_window_destroy_wrapper, dw);
}
}
/**
* Return a pathname in canonical form
*
* \param path pathnamee to be canonicalised
* \return ptr to pathname in malloc block, or NULL
*/
char *ro_gui_download_canonicalise(const char *path)
{
os_error *error;
int spare = 0;
char *buf;
error = xosfscontrol_canonicalise_path(path, NULL, NULL, NULL, 0, &spare);
if (error) {
LOG("xosfscontrol_canonicalise_path: 0x%x: %s", error->errnum, error->errmess);
return NULL;
}
buf = malloc(1 - spare);
if (buf) {
error = xosfscontrol_canonicalise_path(path, buf, NULL, NULL,
1 - spare, NULL);
if (error) {
LOG("xosfscontrol_canonicalise_path: 0x%x: %s", error->errnum, error->errmess);
free(buf);
return NULL;
}
}
return buf;
}
/**
* Check the available space on the medium containing the destination file,
* taking into account any space currently occupied by the file at its
* original location.
*
* \param dw download window
* \param dest_file destination pathname
* \param orig_file current pathname, NULL if no existing file
* \return true iff there's enough space
*/
bool ro_gui_download_check_space(struct gui_download_window *dw,
const char *dest_file, const char *orig_file)
{
/* is there enough free space for this file? */
int dest_len = strlen(dest_file);
os_error *error;
int max_file;
bits free_lo;
int free_hi;
char *dir;
dir = malloc(dest_len + 1);
if (!dir) return true;
while (dest_len > 0 && dest_file[--dest_len] != '.');
memcpy(dir, dest_file, dest_len);
dir[dest_len] = '\0';
/* try the 64-bit variant first (RO 3.6+) */
error = xosfscontrol_free_space64(dir, &free_lo, &free_hi,
&max_file, NULL, NULL);
if (error) {
LOG("xosfscontrol_free_space64: 0x%x: %s", error->errnum, error->errmess);
free_hi = 0;
error = xosfscontrol_free_space(dir, (int*)&free_lo,
&max_file, NULL);
if (error) {
LOG("xosfscontrol_free_space: 0x%x: %s", error->errnum, error->errmess);
/* close our eyes and hope */
free(dir);
return true;
}
}
free(dir);
if ((bits)max_file < dw->total_size || (!free_hi && free_lo < dw->total_size)) {
char *dest_canon, *orig_canon;
bits space;
if (!orig_file || !dw->file) {
/* no original file to take into account */
return false;
}
space = min((bits)max_file, free_lo);
dest_canon = ro_gui_download_canonicalise(dest_file);
if (!dest_canon) dest_canon = (char*)dest_file;
orig_canon = ro_gui_download_canonicalise(orig_file);
if (!orig_canon) orig_canon = (char*)orig_file;
/* not enough space; allow for the file's original location
when space is tight by comparing the first part of the two
pathnames (and assuming the FS isn't brain damaged!) */
char *dot = strchr(orig_canon, '.');
if (dot && !strncasecmp(dest_canon, orig_canon, (dot + 1) - orig_canon)) {
int allocation;
error = xosargs_read_allocation(dw->file,
&allocation);
if (error) {
LOG("xosargs_read_allocation: 0x%x : %s", error->errnum, error->errmess);
}
else {
space += allocation;
}
}
if (dest_canon != dest_file) free(dest_canon);
if (orig_canon != orig_file) free(orig_canon);
if (space >= dw->total_size) {
/* OK, renaming should work */
return true;
}
return false;
}
return true;
}
/**
* Move the downloading file to a new location and continue downloading there.
*
* \param dw download window
* \param dest_file new location
* \param src_file old location
* \return error iff failed to move file
*/
os_error *ro_gui_download_move(struct gui_download_window *dw,
const char *dest_file, const char *src_file)
{
os_error *error;
/* close temporary file */
if (dw->file) {
error = xosfind_closew(dw->file);
dw->file = 0;
if (error) {
LOG("xosfind_closew: 0x%x: %s", error->errnum, error->errmess);
return error;
}
}
/* move or copy temporary file to destination file */
error = xosfscontrol_rename(src_file, dest_file);
/* Errors from a filing system have number 0x1XXnn, where XX is the FS
* number, and nn the error number. 0x9F is "Not same disc". */
if (error && (error->errnum == error_BAD_RENAME ||
(error->errnum & 0xFF00FFu) == 0x1009Fu)) {
/* rename failed: copy with delete */
error = xosfscontrol_copy(src_file, dest_file,
osfscontrol_COPY_FORCE |
osfscontrol_COPY_DELETE |
osfscontrol_COPY_LOOK,
0, 0, 0, 0, 0);
if (error) {
LOG("xosfscontrol_copy: 0x%x: %s", error->errnum, error->errmess);
return error;
}
} else if (error) {
LOG("xosfscontrol_rename: 0x%x: %s", error->errnum, error->errmess);
return error;
}
if (dw->ctx) {
/* open new destination file if still fetching */
error = xosfile_write(dest_file, 0xdeaddead, 0xdeaddead,
fileswitch_ATTR_OWNER_READ |
fileswitch_ATTR_OWNER_WRITE);
if (error) {
LOG("xosfile_write: 0x%x: %s", error->errnum, error->errmess);
warn_user("SaveError", error->errmess);
}
error = xosfind_openupw(osfind_NO_PATH | osfind_ERROR_IF_DIR,
dest_file, 0, &dw->file);
if (error) {
LOG("xosfind_openupw: 0x%x: %s", error->errnum, error->errmess);
return error;
}
error = xosargs_set_ptrw(dw->file, dw->received);
if (error) {
LOG("xosargs_set_ptrw: 0x%x: %s", error->errnum, error->errmess);
return error;
}
} else {
/* otherwise just set the file type */
error = xosfile_set_type(dest_file,
dw->file_type);
if (error) {
LOG("xosfile_set_type: 0x%x: %s", error->errnum, error->errmess);
warn_user("SaveError", error->errmess);
}
}
/* success */
return NULL;
}
/**
* Remember the directory containing the given file,
* for use in further downloads.
*
* \param path pathname of downloaded file
* \return none
*/
void ro_gui_download_remember_dir(const char *path)
{
const char *lastdot = NULL;
const char *p = path;
while (*p >= 0x20) {
if (*p == '.') {
/* don't remember the directory if it's a temporary file */
if (!lastdot && p == path + 12 &&
!memcmp(path, "<Wimp$Scrap>", 12)) break;
lastdot = p;
}
p++;
}
if (lastdot) {
/* remember the directory */
char *new_dir = realloc(download_dir, (lastdot+1)-path);
if (new_dir) {
download_dir_len = lastdot - path;
memcpy(new_dir, path, download_dir_len);
new_dir[download_dir_len] = '\0';
download_dir = new_dir;
}
}
}
/**
* Start of save operation, user has specified where the file should be saved.
*
* \param dw download window
* \param file_name pathname of destination file
& \param force_overwrite true iff required to overwrite without prompting
* \return true iff save successfully initiated
*/
bool ro_gui_download_save(struct gui_download_window *dw,
const char *file_name, bool force_overwrite)
{
fileswitch_object_type obj_type;
const char *temp_name;
os_error *error;
if (dw->saved || dw->error)
return true;
temp_name = ro_gui_download_temp_name(dw);
/* does the user want to check for collisions when saving? */
if (!force_overwrite) {
/* check whether the destination file/dir already exists */
error = xosfile_read_stamped(file_name, &obj_type,
NULL, NULL, NULL, NULL, NULL);
if (error) {
LOG("xosfile_read_stamped: 0x%x:%s", error->errnum, error->errmess);
return false;
}
switch (obj_type) {
case osfile_NOT_FOUND:
break;
case osfile_IS_FILE:
dw->query = query_user("OverwriteFile", NULL, &overwrite_funcs, dw,
messages_get("Replace"), messages_get("DontReplace"));
dw->query_rsn = QueryRsn_Overwrite;
return false;
default:
error = xosfile_make_error(file_name, obj_type);
assert(error);
warn_user("SaveError", error->errmess);
return false;
}
}
if (!ro_gui_download_check_space(dw, file_name, temp_name)) {
warn_user("SaveError", messages_get("NoDiscSpace"));
return false;
}
error = ro_gui_download_move(dw, file_name, temp_name);
if (error) {
warn_user("SaveError", error->errmess);
/* try to reopen at old location so that the download can continue
to the temporary file */
error = xosfind_openupw(osfind_NO_PATH | osfind_ERROR_IF_DIR,
temp_name, 0, &dw->file);
if (error) {
LOG("xosfind_openupw: 0x%x: %s", error->errnum, error->errmess);
} else {
error = xosargs_set_ptrw(dw->file, dw->received);
if (error) {
LOG("xosargs_set_ptrw: 0x%x: %s", error->errnum, error->errmess);
}
}
if (error) {
if (dw->ctx)
download_context_abort(dw->ctx);
gui_download_window_error(dw, error->errmess);
}
return false;
}
dw->saved = true;
strncpy(dw->path, file_name, sizeof dw->path);
if (!dw->send_dataload || dw->save_message.data.data_xfer.est_size != -1)
ro_gui_download_remember_dir(file_name);
/* grey out file icon */
error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_ICON,
wimp_ICON_SHADED, wimp_ICON_SHADED);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
/* hide writeable path icon and show destination icon
Note: must redraw icon bounding box because the destination icon
has rounded edges on RISC OS Select/Adjust and doesn't
completely cover the writeable icon */
ro_gui_force_redraw_icon(dw->window, ICON_DOWNLOAD_PATH);
error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_PATH,
wimp_ICON_DELETED, wimp_ICON_DELETED);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
error = xwimp_set_icon_state(dw->window,
ICON_DOWNLOAD_DESTINATION, wimp_ICON_DELETED, 0);
if (error) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
ro_gui_download_window_hide_caret(dw);
return true;
}
/**
* Send DataLoad message in response to DataSaveAck, informing the
* target application that the transfer is complete.
*
* \param dw download window
*/
void ro_gui_download_send_dataload(struct gui_download_window *dw)
{
/* Ack successful save with message_DATA_LOAD */
wimp_message *message = &dw->save_message;
os_error *error;
assert(dw->send_dataload);
dw->send_dataload = false;
message->action = message_DATA_LOAD;
message->your_ref = message->my_ref;
error = xwimp_send_message_to_window(wimp_USER_MESSAGE, message,
message->data.data_xfer.w,
message->data.data_xfer.i, 0);
/* The window we just attempted to send a message to may
* have been closed before the message was sent. As we've
* no clean way of detecting this, we'll just detect the
* error return from the message send attempt and judiciously
* ignore it.
*
* Ideally, we would have registered to receive Message_WindowClosed
* and then cleared dw->send_dataload flag for the appropriate
* window. Unfortunately, however, a long-standing bug in the
* Pinboard module prevents this from being a viable solution.
*
* See http://groups.google.co.uk/group/comp.sys.acorn.tech/msg/e3fbf70d8393e6cf?dmode=source&hl=en
* for the rather depressing details.
*/
if (error && error->errnum != error_WIMP_BAD_HANDLE) {
LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
riscos_schedule(2000, ro_gui_download_window_destroy_wrapper, dw);
}
/**
* Handle closing of download window
*/
void ro_gui_download_close(wimp_w w)
{
struct gui_download_window *dw;
dw = (struct gui_download_window *)ro_gui_wimp_event_get_user_data(w);
ro_gui_download_window_destroy(dw, false);
}
/**
* Close a download window and free any related resources.
*
* \param dw download window
* \param quit destroying because we're quitting the whole app
* \return true if window destroyed, not waiting for user confirmation
*/
bool ro_gui_download_window_destroy(struct gui_download_window *dw, bool quit)
{
bool safe = dw->saved && !dw->ctx;
os_error *error;
if (!safe && !dw->close_confirmed)
{
query_reason rsn = quit ? QueryRsn_Quit : QueryRsn_Abort;
if (dw->query != QUERY_INVALID) {
/* can we just reuse the existing query? */
if (rsn == dw->query_rsn) {
ro_gui_query_window_bring_to_front(dw->query);
return false;
}
query_close(dw->query);
dw->query = QUERY_INVALID;
}
if (quit) {
/* bring all download windows to the front of the desktop as
a convenience if there are lots of windows open */
struct gui_download_window *d = download_window_list;
while (d) {
ro_gui_dialog_open_top(d->window, NULL, 0, 0);
d = d->next;
}
}
dw->query_rsn = rsn;
dw->query = query_user(quit ? "QuitDownload" : "AbortDownload",
NULL, &close_funcs, dw, NULL, NULL);
return false;
}
riscos_schedule(-1, ro_gui_download_update_status_wrapper, dw);
riscos_schedule(-1, ro_gui_download_window_destroy_wrapper, dw);
/* remove from list */
if (dw->prev)
dw->prev->next = dw->next;
else
download_window_list = dw->next;
if (dw->next)
dw->next->prev = dw->prev;
/* delete window */
error = xwimp_delete_window(dw->window);
if (error) {
LOG("xwimp_delete_window: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
ro_gui_wimp_event_finalise(dw->window);
/* close download file */
if (dw->file) {
error = xosfind_closew(dw->file);
if (error) {
LOG("xosfind_closew: 0x%x: %s", error->errnum, error->errmess);
warn_user("SaveError", error->errmess);
}
}
/* delete temporary file */
if (!dw->saved) {
const char *temp_name = ro_gui_download_temp_name(dw);
error = xosfile_delete(temp_name, 0, 0, 0, 0, 0);
if (error) {
LOG("xosfile_delete: 0x%x: %s", error->errnum, error->errmess);
warn_user("SaveError", error->errmess);
}
}
if (dw->ctx) {
download_context_abort(dw->ctx);
download_context_destroy(dw->ctx);
}
free(dw);
return true;
}
/**
* Wrapper for ro_gui_download_window_destroy(), suitable for riscos_schedule().
*/
void ro_gui_download_window_destroy_wrapper(void *p)
{
struct gui_download_window *dw = p;
if (dw->query != QUERY_INVALID)
query_close(dw->query);
dw->query = QUERY_INVALID;
dw->close_confirmed = true;
ro_gui_download_window_destroy(dw, false);
}
/**
* User has opted to cancel the close, leaving the download to continue.
*/
void ro_gui_download_close_cancelled(query_id id, enum query_response res, void *p)
{
struct gui_download_window *dw = p;
dw->query = QUERY_INVALID;
}
/**
* Download aborted, close window and tidy up.
*/
void ro_gui_download_close_confirmed(query_id id, enum query_response res, void *p)
{
struct gui_download_window *dw = p;
dw->query = QUERY_INVALID;
dw->close_confirmed = true;
if (dw->query_rsn == QueryRsn_Quit) {
/* destroy all our downloads */
while (download_window_list)
ro_gui_download_window_destroy_wrapper(download_window_list);
/* and restart the shutdown */
if (ro_gui_prequit())
riscos_done = true;
}
else
ro_gui_download_window_destroy(dw, false);
}
/**
* User has opted not to overwrite the existing file.
*/
void ro_gui_download_overwrite_cancelled(query_id id, enum query_response res, void *p)
{
struct gui_download_window *dw = p;
dw->query = QUERY_INVALID;
}
/**
* Overwrite of existing file confirmed, proceed with the save.
*/
void ro_gui_download_overwrite_confirmed(query_id id, enum query_response res, void *p)
{
struct gui_download_window *dw = p;
dw->query = QUERY_INVALID;
if (!ro_gui_download_save(dw, dw->save_message.data.data_xfer.file_name, true))
return;
if (!dw->ctx) {
/* Ack successful completed save with message_DATA_LOAD immediately
to reduce the chance of the target app getting confused by it
being delayed */
ro_gui_download_send_dataload(dw);
riscos_schedule(2000, ro_gui_download_window_destroy_wrapper, dw);
}
}
/**
* Respond to PreQuit message, displaying a prompt message if we need
* the user to confirm the shutdown.
*
* \return true if we can shutdown straightaway
*/
bool ro_gui_download_prequit(void)
{
while (download_window_list)
{
if (!ro_gui_download_window_destroy(download_window_list, true))
return false; /* awaiting user confirmation */
}
return true;
}
static struct gui_download_table download_table = {
.create = gui_download_window_create,
.data = gui_download_window_data,
.error = gui_download_window_error,
.done = gui_download_window_done,
};
struct gui_download_table *riscos_download_table = &download_table;