summaryrefslogtreecommitdiff
path: root/frontends/riscos/download.c
diff options
context:
space:
mode:
Diffstat (limited to 'frontends/riscos/download.c')
-rw-r--r--frontends/riscos/download.c1629
1 files changed, 1629 insertions, 0 deletions
diff --git a/frontends/riscos/download.c b/frontends/riscos/download.c
new file mode 100644
index 000000000..cddb449de
--- /dev/null
+++ b/frontends/riscos/download.c
@@ -0,0 +1,1629 @@
+/*
+ * 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 <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/sys_time.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/string.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);
+ ro_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) {
+ ro_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) {
+ ro_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);
+ ro_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.");
+ ro_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");
+ ro_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);
+ ro_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)
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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;
+ }
+
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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, '.')) {
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_warn_user("SaveError", error->errmess);
+ return false;
+ }
+ }
+
+ if (!ro_gui_download_check_space(dw, file_name, temp_name)) {
+ ro_warn_user("SaveError", messages_get("NoDiscSpace"));
+ return false;
+ }
+
+ error = ro_gui_download_move(dw, file_name, temp_name);
+ if (error) {
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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);
+ ro_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;