netsurf/frontends/riscos/download.c
Michael Drake fa2e3b7784 URL unescape: return the new length to the caller.
The avoids situations were we threw away the length, only for
the caller to have to strlen the returned string.

Note, there seems to be a case of the amiga front end writing
beyond end of allocation.  Added a TODO for now.
2016-07-24 14:03:16 +01:00

1631 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 netsurf/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 <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/url.h"
#include "utils/corestrings.h"
#include "netsurf/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;
if (url_unescape(lwc_string_data(path),
lwc_string_length(path),
NULL,
&raw_path) == NSERROR_OK) {
ftype = ro_filetype_from_unix_path(raw_path);
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;