1670 lines
44 KiB
C
1670 lines
44 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
|
|
* \return a new gui_download_window structure, or 0 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 dw 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 dw 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;
|