PageRenderTime 18ms CodeModel.GetById 2ms app.highlight 76ms RepoModel.GetById 1ms app.codeStats 1ms

/peek-build/src/netsurf/riscos/download.c

https://bitbucket.org/C0deMaver1ck/peeklinux
C | 1604 lines | 1495 code | 44 blank | 65 comment | 30 complexity | bc9e49153b2322e8dab93236e5034d80 MD5 | raw file
   1/*
   2 * Copyright 2004 James Bursa <bursa@users.sourceforge.net>
   3 * Copyright 2003 Rob Jackson <jacko@xms.ms>
   4 * Copyright 2005 Adrian Lees <adrianl@users.sourceforge.net>
   5 *
   6 * This file is part of NetSurf, http://www.netsurf-browser.org/
   7 *
   8 * NetSurf is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as published by
  10 * the Free Software Foundation; version 2 of the License.
  11 *
  12 * NetSurf is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  19 */
  20
  21/** \file
  22 * Download windows (RISC OS implementation).
  23 *
  24 * This file implements the interface given by desktop/gui.h for download
  25 * windows. Each download window has an associated fetch. Downloads start by
  26 * writing received data to a temporary file. At some point the user chooses
  27 * a destination (by drag & drop), and the temporary file is then moved to the
  28 * destination and the download continues until complete.
  29 */
  30
  31#include <assert.h>
  32#include <string.h>
  33#include <sys/time.h>
  34#include <time.h>
  35#include <curl/curl.h>
  36#include "oslib/mimemap.h"
  37#include "oslib/osargs.h"
  38#include "oslib/osfile.h"
  39#include "oslib/osfind.h"
  40#include "oslib/osfscontrol.h"
  41#include "oslib/osgbpb.h"
  42#include "oslib/wimp.h"
  43#include "oslib/wimpspriteop.h"
  44#include "desktop/gui.h"
  45#include "desktop/netsurf.h"
  46#include "riscos/dialog.h"
  47#include "riscos/options.h"
  48#include "riscos/save.h"
  49#include "riscos/query.h"
  50#include "riscos/wimp.h"
  51#include "riscos/wimp_event.h"
  52#include "utils/log.h"
  53#include "utils/messages.h"
  54#include "utils/url.h"
  55#include "utils/utf8.h"
  56#include "utils/utils.h"
  57
  58#define ICON_DOWNLOAD_ICON 0
  59#define ICON_DOWNLOAD_URL 1
  60#define ICON_DOWNLOAD_PATH 2
  61#define ICON_DOWNLOAD_DESTINATION 3
  62#define ICON_DOWNLOAD_PROGRESS 5
  63#define ICON_DOWNLOAD_STATUS 6
  64
  65typedef enum
  66{
  67	QueryRsn_Quit,
  68	QueryRsn_Abort,
  69	QueryRsn_Overwrite
  70} query_reason;
  71
  72
  73/** Data for a download window. */
  74struct gui_download_window {
  75	/** Associated context, or 0 if the fetch has completed or aborted. */
  76	download_context *ctx;
  77	unsigned int received;	/**< Amount of data received so far. */
  78	unsigned int total_size; /**< Size of resource, or 0 if unknown. */
  79
  80	wimp_w window;		/**< RISC OS window handle. */
  81	bits file_type;		/**< RISC OS file type. */
  82
  83	char url[256];		/**< Buffer for URL icon. */
  84	char sprite_name[20];	/**< Buffer for sprite icon. */
  85	char path[256];		/**< Buffer for pathname icon. */
  86	char status[256];	/**< Buffer for status icon. */
  87
  88	/** User has chosen the destination, and it is being written. */
  89	bool saved;
  90	bool close_confirmed;
  91	bool error;		/**< Error occurred, aborted. */
  92
  93	/** RISC OS file handle, of temporary file when !saved, and of
  94	 * destination when saved. */
  95	os_fw file;
  96
  97	query_id query;
  98	query_reason query_rsn;
  99
 100	struct timeval start_time;	/**< Time download started. */
 101	struct timeval last_time;	/**< Time status was last updated. */
 102	unsigned int last_received;	/**< Value of received at last_time. */
 103
 104	bool send_dataload;	/**< Should send DataLoad message when finished */
 105	wimp_message save_message;	/**< Copy of wimp DataSaveAck message */
 106
 107	struct gui_download_window *prev;	/**< Previous in linked list. */
 108	struct gui_download_window *next;	/**< Next in linked list. */
 109};
 110
 111
 112/** List of all download windows. */
 113static struct gui_download_window *download_window_list = 0;
 114/** Download window with current save operation. */
 115static struct gui_download_window *download_window_current = 0;
 116
 117/** Template for a download window. */
 118static wimp_window *download_template;
 119
 120/** Width of progress bar at 100%. */
 121static int download_progress_width;
 122/** Coordinates of progress bar. */
 123static int download_progress_x0;
 124static int download_progress_y0;
 125static int download_progress_y1;
 126
 127/** Current download directory. */
 128static char *download_dir = NULL;
 129static size_t download_dir_len;
 130
 131
 132static const char *ro_gui_download_temp_name(struct gui_download_window *dw);
 133static void ro_gui_download_update_status(struct gui_download_window *dw);
 134static void ro_gui_download_update_status_wrapper(void *p);
 135static void ro_gui_download_window_hide_caret(struct gui_download_window *dw);
 136static char *ro_gui_download_canonicalise(const char *path);
 137static bool ro_gui_download_check_space(struct gui_download_window *dw,
 138		const char *dest_file, const char *orig_file);
 139static os_error *ro_gui_download_move(struct gui_download_window *dw,
 140		const char *dest_file, const char *src_file);
 141static void ro_gui_download_remember_dir(const char *path);
 142static bool ro_gui_download_save(struct gui_download_window *dw,
 143		const char *file_name, bool force_overwrite);
 144static void ro_gui_download_send_dataload(struct gui_download_window *dw);
 145static void ro_gui_download_window_destroy_wrapper(void *p);
 146static bool ro_gui_download_window_destroy(struct gui_download_window *dw, bool quit);
 147static void ro_gui_download_close_confirmed(query_id, enum query_response res, void *p);
 148static void ro_gui_download_close_cancelled(query_id, enum query_response res, void *p);
 149static void ro_gui_download_overwrite_confirmed(query_id, enum query_response res, void *p);
 150static void ro_gui_download_overwrite_cancelled(query_id, enum query_response res, void *p);
 151
 152static bool ro_gui_download_click(wimp_pointer *pointer);
 153static bool ro_gui_download_keypress(wimp_key *key);
 154static void ro_gui_download_close(wimp_w w);
 155
 156static const query_callback close_funcs =
 157{
 158	ro_gui_download_close_confirmed,
 159	ro_gui_download_close_cancelled
 160};
 161
 162static const query_callback overwrite_funcs =
 163{
 164	ro_gui_download_overwrite_confirmed,
 165	ro_gui_download_overwrite_cancelled
 166};
 167
 168
 169/**
 170 * Load the download window template.
 171 */
 172
 173void ro_gui_download_init(void)
 174{
 175	download_template = ro_gui_dialog_load_template("download");
 176	download_progress_width =
 177		download_template->icons[ICON_DOWNLOAD_STATUS].extent.x1 -
 178		download_template->icons[ICON_DOWNLOAD_STATUS].extent.x0;
 179	download_progress_x0 =
 180		download_template->icons[ICON_DOWNLOAD_PROGRESS].extent.x0;
 181	download_progress_y0 =
 182		download_template->icons[ICON_DOWNLOAD_PROGRESS].extent.y0;
 183	download_progress_y1 =
 184		download_template->icons[ICON_DOWNLOAD_PROGRESS].extent.y1;
 185}
 186
 187
 188/**
 189 * Returns the pathname of a temporary file for this download.
 190 *
 191 * \param  dw   download window
 192 * \return ptr to pathname
 193 */
 194
 195const char *ro_gui_download_temp_name(struct gui_download_window *dw)
 196{
 197	static char temp_name[40];
 198	snprintf(temp_name, sizeof temp_name, "<Wimp$ScrapDir>.ns%x",
 199			(unsigned int) dw);
 200	return temp_name;
 201}
 202
 203
 204/**
 205 * Create and open a download progress window.
 206 *
 207 * \param  ctx         Download context
 208 * \return  a new gui_download_window structure, or 0 on error and error
 209 *          reported
 210 */
 211
 212struct gui_download_window *gui_download_window_create(download_context *ctx,
 213		struct gui_window *gui)
 214{
 215	const char *url = download_context_get_url(ctx);
 216	const char *mime_type = download_context_get_mime_type(ctx);
 217	const char *temp_name;
 218	char *nice, *scheme = NULL;
 219	struct gui_download_window *dw;
 220	bool space_warning = false;
 221	os_error *error;
 222	url_func_result res;
 223	char *local_path;
 224	utf8_convert_ret err;
 225	size_t leaf_ofst;
 226	size_t i;
 227
 228	dw = malloc(sizeof *dw);
 229	if (!dw) {
 230		warn_user("NoMemory", 0);
 231		return 0;
 232	}
 233
 234	dw->ctx = ctx;
 235	dw->saved = false;
 236	dw->close_confirmed = false;
 237	dw->error = false;
 238	dw->query = QUERY_INVALID;
 239	dw->received = 0;
 240	dw->total_size = download_context_get_total_length(ctx);
 241	strncpy(dw->url, url, sizeof dw->url);
 242	dw->url[sizeof dw->url - 1] = 0;
 243	dw->status[0] = 0;
 244	gettimeofday(&dw->start_time, 0);
 245	dw->last_time = dw->start_time;
 246	dw->last_received = 0;
 247	dw->file_type = 0;
 248
 249	/* Get scheme from URL */
 250	res = url_scheme(url, &scheme);
 251	if (res == URL_FUNC_NOMEM) {
 252		warn_user("NoMemory", 0);
 253		free(dw);
 254		return 0;
 255	} else if (res == URL_FUNC_OK) {
 256		/* If we have a scheme and it's "file", then
 257		 * attempt to use the local filetype directly */
 258		if (strcasecmp(scheme, "file") == 0) {
 259			char *path = NULL;
 260			res = url_path(url, &path);
 261			if (res == URL_FUNC_NOMEM) {
 262				warn_user("NoMemory", 0);
 263				free(scheme);
 264				free(dw);
 265				return 0;
 266			} else if (res == URL_FUNC_OK) {
 267				char *raw_path = curl_unescape(path,
 268						strlen(path));
 269				if (raw_path == NULL) {
 270					warn_user("NoMemory", 0);
 271					free(path);
 272					free(scheme);
 273					free(dw);
 274					return 0;
 275				}
 276				dw->file_type =
 277					ro_filetype_from_unix_path(raw_path);
 278				curl_free(raw_path);
 279				free(path);
 280			}
 281		}
 282
 283		free(scheme);
 284	}
 285
 286	/* If we still don't have a filetype (i.e. failed reading local
 287	 * one or fetching a remote object), then use the MIME type */
 288	if (dw->file_type == 0) {
 289		/* convert MIME type to RISC OS file type */
 290		error = xmimemaptranslate_mime_type_to_filetype(mime_type,
 291				&(dw->file_type));
 292		if (error) {
 293			LOG(("xmimemaptranslate_mime_type_to_filetype: 0x%x: %s",
 294					error->errnum, error->errmess));
 295			warn_user("MiscError", error->errmess);
 296			dw->file_type = 0xffd;
 297		}
 298	}
 299
 300	/* open temporary output file */
 301	temp_name = ro_gui_download_temp_name(dw);
 302	if (!ro_gui_download_check_space(dw, temp_name, NULL)) {
 303		/* issue a warning but continue with the download because the
 304		   user can save it to another medium whilst it's downloading */
 305		space_warning = true;
 306	}
 307	error = xosfind_openoutw(osfind_NO_PATH | osfind_ERROR_IF_DIR,
 308			temp_name, 0, &dw->file);
 309	if (error) {
 310		LOG(("xosfind_openoutw: 0x%x: %s",
 311				error->errnum, error->errmess));
 312		warn_user("SaveError", error->errmess);
 313		free(dw);
 314		return 0;
 315	}
 316
 317	/* fill in download window icons */
 318	download_template->icons[ICON_DOWNLOAD_URL].data.indirected_text.text =
 319			dw->url;
 320	download_template->icons[ICON_DOWNLOAD_URL].data.indirected_text.size =
 321			sizeof dw->url;
 322
 323	download_template->icons[ICON_DOWNLOAD_STATUS].data.indirected_text.
 324			text = dw->status;
 325	download_template->icons[ICON_DOWNLOAD_STATUS].data.indirected_text.
 326			size = sizeof dw->status;
 327
 328	sprintf(dw->sprite_name, "file_%.3x", dw->file_type);
 329	if (!ro_gui_wimp_sprite_exists(dw->sprite_name))
 330		strcpy(dw->sprite_name, "file_xxx");
 331	download_template->icons[ICON_DOWNLOAD_ICON].data.indirected_sprite.id =
 332			(osspriteop_id) dw->sprite_name;
 333
 334	if (download_dir) {
 335		memcpy(dw->path, download_dir, download_dir_len);
 336		dw->path[download_dir_len] = '.';
 337		leaf_ofst = download_dir_len + 1;
 338	} else
 339		leaf_ofst = 0;
 340
 341	if (url_nice(url, &nice, option_strip_extensions) == URL_FUNC_OK) {
 342		size_t imax = sizeof dw->path - (leaf_ofst + 1);
 343		for (i = 0; i < imax && nice[i]; i++) {
 344			if (nice[i] == '.')
 345				nice[i] = '/';
 346			else if (nice[i] <= ' ' ||
 347					strchr(":*#$&@^%\\", nice[i]))
 348				nice[i] = '_';
 349		}
 350		memcpy(dw->path + leaf_ofst, nice, i);
 351		dw->path[leaf_ofst + i] = '\0';
 352		free(nice);
 353	} else {
 354		const char *leaf = messages_get("SaveObject");
 355		size_t len = strlen(leaf);
 356		if (len >= sizeof dw->path - leaf_ofst)
 357			len = sizeof dw->path - leaf_ofst - 1;
 358		memcpy(dw->path + leaf_ofst, leaf, len);
 359		dw->path[leaf_ofst + len] = '\0';
 360	}
 361
 362	err = utf8_to_local_encoding(dw->path, 0, &local_path);
 363	if (err != UTF8_CONVERT_OK) {
 364		/* badenc should never happen */
 365		assert(err != UTF8_CONVERT_BADENC);
 366		LOG(("utf8_to_local_encoding failed"));
 367		warn_user("NoMemory", 0);
 368		free(dw);
 369		return 0;
 370	}
 371	else {
 372		strncpy(dw->path, local_path, sizeof dw->path);
 373		free(local_path);
 374	}
 375
 376	download_template->icons[ICON_DOWNLOAD_PATH].data.indirected_text.text =
 377			dw->path;
 378	download_template->icons[ICON_DOWNLOAD_PATH].data.indirected_text.size =
 379			sizeof dw->path;
 380
 381	download_template->icons[ICON_DOWNLOAD_DESTINATION].data.
 382			indirected_text.text = dw->path;
 383	download_template->icons[ICON_DOWNLOAD_DESTINATION].data.
 384			indirected_text.size = sizeof dw->path;
 385
 386	download_template->icons[ICON_DOWNLOAD_DESTINATION].flags |=
 387			wimp_ICON_DELETED;
 388
 389	/* create and open the download window */
 390	error = xwimp_create_window(download_template, &dw->window);
 391	if (error) {
 392		LOG(("xwimp_create_window: 0x%x: %s",
 393				error->errnum, error->errmess));
 394		warn_user("WimpError", error->errmess);
 395		free(dw);
 396		return 0;
 397	}
 398
 399	dw->prev = 0;
 400	dw->next = download_window_list;
 401	if (download_window_list)
 402		download_window_list->prev = dw;
 403	download_window_list = dw;
 404
 405	ro_gui_download_update_status(dw);
 406
 407	ro_gui_dialog_open(dw->window);
 408
 409	ro_gui_wimp_event_set_user_data(dw->window, dw);
 410	ro_gui_wimp_event_register_mouse_click(dw->window, ro_gui_download_click);
 411	ro_gui_wimp_event_register_keypress(dw->window, ro_gui_download_keypress);
 412	ro_gui_wimp_event_register_close_window(dw->window, ro_gui_download_close);
 413
 414	/* issue the warning now, so that it appears in front of the download
 415	 * window! */
 416	if (space_warning)
 417		warn_user("DownloadWarn", messages_get("NoDiscSpace"));
 418
 419	return dw;
 420}
 421
 422
 423/**
 424 * Handle received download data.
 425 *
 426 * \param  dw    download window
 427 * \param  data  pointer to block of data received
 428 * \param  size  size of data
 429 * \return NSERROR_OK on success, appropriate error otherwise
 430 */
 431
 432nserror gui_download_window_data(struct gui_download_window *dw, 
 433		const char *data, unsigned int size)
 434{
 435	while (true) {
 436		const char *msg;
 437		int unwritten;
 438		os_error *error;
 439
 440		error = xosgbpb_writew(dw->file, (const byte *) data, size, 
 441				&unwritten);
 442		if (error) {
 443			LOG(("xosgbpb_writew: 0x%x: %s",
 444					error->errnum, error->errmess));
 445			msg = error->errmess;
 446
 447		} else if (unwritten) {
 448			LOG(("xosgbpb_writew: unwritten %i", unwritten));
 449			msg = messages_get("Unwritten");
 450		}
 451		else {
 452			dw->received += size;
 453			return NSERROR_OK;
 454		}
 455
 456		warn_user("SaveError", msg);
 457
 458		if (dw->saved) {
 459			/* try to continue with the temporary file */
 460			const char *temp_name = ro_gui_download_temp_name(dw);
 461
 462			error = ro_gui_download_move(dw, temp_name, dw->path);
 463			if (!error) {
 464
 465				/* re-allow saving */
 466				dw->saved = false;
 467
 468				error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_ICON,
 469						wimp_ICON_SHADED, 0);
 470				if (error) {
 471					LOG(("xwimp_set_icon_state: 0x%x: %s",
 472							error->errnum, error->errmess));
 473					warn_user("WimpError", error->errmess);
 474				}
 475
 476				error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_DESTINATION,
 477						wimp_ICON_DELETED, wimp_ICON_DELETED);
 478				if (error) {
 479					LOG(("xwimp_set_icon_state: 0x%x: %s",
 480							error->errnum, error->errmess));
 481					warn_user("WimpError", error->errmess);
 482				}
 483				error = xwimp_set_icon_state(dw->window,
 484						ICON_DOWNLOAD_PATH, wimp_ICON_DELETED, 0);
 485				if (error) {
 486					LOG(("xwimp_set_icon_state: 0x%x: %s",
 487							error->errnum, error->errmess));
 488					warn_user("WimpError", error->errmess);
 489				}
 490
 491				continue;
 492			}
 493		}
 494
 495		/* give up then */
 496		assert(dw->ctx);
 497		download_context_abort(dw->ctx);
 498		gui_download_window_error(dw, msg);
 499
 500		return NSERROR_SAVE_FAILED;
 501	}
 502}
 503
 504
 505/**
 506 * Update the status text and progress bar.
 507 *
 508 * \param  dw  download window
 509 */
 510
 511void ro_gui_download_update_status(struct gui_download_window *dw)
 512{
 513	char *received;
 514	char *total_size;
 515	char *speed;
 516	char time[20] = "?";
 517	struct timeval t;
 518	float dt;
 519	unsigned int left;
 520	float rate;
 521	os_error *error;
 522	int width;
 523	char *local_status;
 524	utf8_convert_ret err;
 525
 526	gettimeofday(&t, 0);
 527	dt = (t.tv_sec + 0.000001 * t.tv_usec) - (dw->last_time.tv_sec +
 528			0.000001 * dw->last_time.tv_usec);
 529	if (dt == 0)
 530		dt = 0.001;
 531
 532	total_size = human_friendly_bytesize(max(dw->received, dw->total_size));
 533
 534	if (dw->ctx) {
 535		rate = (dw->received - dw->last_received) / dt;
 536		received = human_friendly_bytesize(dw->received);
 537		speed = human_friendly_bytesize(rate);
 538		if (dw->total_size) {
 539			float f;
 540
 541			if (rate) {
 542				left = (dw->total_size - dw->received) / rate;
 543				sprintf(time, "%u:%.2u", left / 60, left % 60);
 544			}
 545
 546			/* convert to local encoding */
 547			err = utf8_to_local_encoding(
 548				messages_get("Download"), 0, &local_status);
 549			if (err != UTF8_CONVERT_OK) {
 550				/* badenc should never happen */
 551				assert(err != UTF8_CONVERT_BADENC);
 552				/* hide nomem error */
 553				snprintf(dw->status, sizeof dw->status,
 554					messages_get("Download"),
 555					received, total_size, speed, time);
 556			}
 557			else {
 558				snprintf(dw->status, sizeof dw->status,
 559					local_status,
 560					received, total_size, speed, time);
 561				free(local_status);
 562			}
 563
 564			f = (float) dw->received / (float) dw->total_size;
 565			width = download_progress_width * f;
 566		} else {
 567			left = t.tv_sec - dw->start_time.tv_sec;
 568			sprintf(time, "%u:%.2u", left / 60, left % 60);
 569
 570			err = utf8_to_local_encoding(
 571				messages_get("DownloadU"), 0, &local_status);
 572			if (err != UTF8_CONVERT_OK) {
 573				/* badenc should never happen */
 574				assert(err != UTF8_CONVERT_BADENC);
 575				/* hide nomem error */
 576				snprintf(dw->status, sizeof dw->status,
 577					messages_get("DownloadU"),
 578					received, speed, time);
 579			}
 580			else {
 581				snprintf(dw->status, sizeof dw->status,
 582					local_status,
 583					received, speed, time);
 584				free(local_status);
 585			}
 586
 587			/* length unknown, stay at 0 til finished */
 588			width = 0;
 589		}
 590	} else {
 591		left = dw->last_time.tv_sec - dw->start_time.tv_sec;
 592		if (left == 0)
 593			left = 1;
 594		rate = (float) dw->received / (float) left;
 595		sprintf(time, "%u:%.2u", left / 60, left % 60);
 596		speed = human_friendly_bytesize(rate);
 597
 598		err = utf8_to_local_encoding(messages_get("Downloaded"), 0,
 599				&local_status);
 600		if (err != UTF8_CONVERT_OK) {
 601			/* badenc should never happen */
 602			assert(err != UTF8_CONVERT_BADENC);
 603			/* hide nomem error */
 604			snprintf(dw->status, sizeof dw->status,
 605				messages_get("Downloaded"),
 606				total_size, speed, time);
 607		}
 608		else {
 609			snprintf(dw->status, sizeof dw->status, local_status,
 610					total_size, speed, time);
 611			free(local_status);
 612		}
 613
 614		/* all done */
 615		width = download_progress_width;
 616	}
 617
 618	dw->last_time = t;
 619	dw->last_received = dw->received;
 620
 621	error = xwimp_resize_icon(dw->window, ICON_DOWNLOAD_PROGRESS,
 622			download_progress_x0,
 623			download_progress_y0,
 624			download_progress_x0 + width,
 625			download_progress_y1);
 626	if (error) {
 627		LOG(("xwimp_resize_icon: 0x%x: %s",
 628				error->errnum, error->errmess));
 629		warn_user("WimpError", error->errmess);
 630	}
 631
 632	error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_STATUS, 0, 0);
 633	if (error) {
 634		LOG(("xwimp_set_icon_state: 0x%x: %s",
 635				error->errnum, error->errmess));
 636		warn_user("WimpError", error->errmess);
 637	}
 638
 639	if (dw->ctx)
 640		schedule(100, ro_gui_download_update_status_wrapper, dw);
 641	else
 642		schedule_remove(ro_gui_download_update_status_wrapper, dw);
 643}
 644
 645
 646/**
 647 * Wrapper for ro_gui_download_update_status(), suitable for schedule().
 648 */
 649
 650void ro_gui_download_update_status_wrapper(void *p)
 651{
 652	ro_gui_download_update_status((struct gui_download_window *) p);
 653}
 654
 655
 656
 657/**
 658 * Hide the caret but preserve input focus.
 659 *
 660 * \param  dw  download window
 661 */
 662
 663void ro_gui_download_window_hide_caret(struct gui_download_window *dw)
 664{
 665	wimp_caret caret;
 666	os_error *error;
 667
 668	error = xwimp_get_caret_position(&caret);
 669	if (error) {
 670		LOG(("xwimp_get_caret_position: 0x%x : %s",
 671				error->errnum, error->errmess));
 672		warn_user("WimpError", error->errmess);
 673	}
 674	else if (caret.w == dw->window) {
 675		error = xwimp_set_caret_position(dw->window, (wimp_i)-1, 0, 0, 1 << 25, -1);
 676		if (error) {
 677			LOG(("xwimp_get_caret_position: 0x%x : %s",
 678					error->errnum, error->errmess));
 679			warn_user("WimpError", error->errmess);
 680		}
 681	}
 682}
 683
 684
 685/**
 686 * Handle failed downloads.
 687 *
 688 * \param  dw         download window
 689 * \param  error_msg  error message
 690 */
 691
 692void gui_download_window_error(struct gui_download_window *dw,
 693		const char *error_msg)
 694{
 695	os_error *error;
 696
 697	if (dw->ctx != NULL)
 698		download_context_destroy(dw->ctx);
 699	dw->ctx = NULL;
 700	dw->error = true;
 701
 702	schedule_remove(ro_gui_download_update_status_wrapper, dw);
 703
 704	/* place error message in status icon in red */
 705	strncpy(dw->status, error_msg, sizeof dw->status);
 706	error = xwimp_set_icon_state(dw->window,
 707			ICON_DOWNLOAD_STATUS,
 708			wimp_COLOUR_RED << wimp_ICON_FG_COLOUR_SHIFT,
 709			wimp_ICON_FG_COLOUR);
 710	if (error) {
 711		LOG(("xwimp_set_icon_state: 0x%x: %s",
 712				error->errnum, error->errmess));
 713		warn_user("WimpError", error->errmess);
 714	}
 715
 716	/* grey out pathname icon */
 717	error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_PATH,
 718			wimp_ICON_SHADED, 0);
 719	if (error) {
 720		LOG(("xwimp_set_icon_state: 0x%x: %s",
 721				error->errnum, error->errmess));
 722		warn_user("WimpError", error->errmess);
 723	}
 724
 725	/* grey out file icon */
 726	error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_ICON,
 727			wimp_ICON_SHADED, wimp_ICON_SHADED);
 728	if (error) {
 729		LOG(("xwimp_set_icon_state: 0x%x: %s",
 730				error->errnum, error->errmess));
 731		warn_user("WimpError", error->errmess);
 732	}
 733
 734	ro_gui_download_window_hide_caret(dw);
 735}
 736
 737
 738/**
 739 * Handle completed downloads.
 740 *
 741 * \param  dw  download window
 742 */
 743
 744void gui_download_window_done(struct gui_download_window *dw)
 745{
 746	os_error *error;
 747
 748	if (dw->ctx != NULL)
 749		download_context_destroy(dw->ctx);
 750	dw->ctx = NULL;
 751	ro_gui_download_update_status(dw);
 752
 753	error = xosfind_closew(dw->file);
 754	if (error) {
 755		LOG(("xosfind_closew: 0x%x: %s",
 756				error->errnum, error->errmess));
 757		warn_user("SaveError", error->errmess);
 758	}
 759	dw->file = 0;
 760
 761	if (dw->saved) {
 762		error = xosfile_set_type(dw->path,
 763				dw->file_type);
 764		if (error) {
 765			LOG(("xosfile_set_type: 0x%x: %s",
 766				error->errnum, error->errmess));
 767			warn_user("SaveError", error->errmess);
 768		}
 769
 770		if (dw->send_dataload)
 771			ro_gui_download_send_dataload(dw);
 772
 773		schedule(200, ro_gui_download_window_destroy_wrapper, dw);
 774	}
 775}
 776
 777
 778/**
 779 * Handle Mouse_Click events in a download window.
 780 *
 781 * \param  dw       download window
 782 * \param  pointer  block returned by Wimp_Poll
 783 */
 784
 785bool ro_gui_download_click(wimp_pointer *pointer)
 786{
 787  	struct gui_download_window *dw;
 788	char command[256] = "Filer_OpenDir ";
 789	char *dot;
 790	os_error *error;
 791
 792	dw = (struct gui_download_window *)ro_gui_wimp_event_get_user_data(pointer->w);
 793	if (pointer->i == ICON_DOWNLOAD_ICON && !dw->error &&
 794			!dw->saved) {
 795		const char *sprite = ro_gui_get_icon_string(pointer->w, pointer->i);
 796		int x = pointer->pos.x, y = pointer->pos.y;
 797		wimp_window_state wstate;
 798		wimp_icon_state istate;
 799		/* start the drag from the icon's exact location, rather than the pointer */
 800		istate.w = wstate.w = pointer->w;
 801		istate.i = pointer->i;
 802		if (!xwimp_get_window_state(&wstate) && !xwimp_get_icon_state(&istate)) {
 803			x = (istate.icon.extent.x1 + istate.icon.extent.x0)/2 +
 804					wstate.visible.x0 - wstate.xscroll;
 805			y = (istate.icon.extent.y1 + istate.icon.extent.y0)/2 +
 806					wstate.visible.y1 - wstate.yscroll;
 807		}
 808		gui_current_drag_type = GUI_DRAG_DOWNLOAD_SAVE;
 809		download_window_current = dw;
 810		ro_gui_drag_icon(x, y, sprite);
 811
 812	} else if (pointer->i == ICON_DOWNLOAD_DESTINATION) {
 813		strncpy(command + 14, dw->path, 242);
 814		command[255] = 0;
 815		dot = strrchr(command, '.');
 816		if (dot) {
 817			*dot = 0;
 818			error = xos_cli(command);
 819			if (error) {
 820				LOG(("xos_cli: 0x%x: %s",
 821						error->errnum, error->errmess));
 822				warn_user("MiscError", error->errmess);
 823			}
 824		}
 825	}
 826	return true;
 827}
 828
 829
 830/**
 831 * Handler Key_Press events in a download window.
 832 *
 833 * \param  dw       download window
 834 * \param  key  key press returned by Wimp_Poll
 835 * \return true iff key press handled
 836 */
 837
 838bool ro_gui_download_keypress(wimp_key *key)
 839{
 840  	struct gui_download_window *dw;
 841
 842	dw = (struct gui_download_window *)ro_gui_wimp_event_get_user_data(key->w);
 843	switch (key->c)
 844	{
 845		case wimp_KEY_ESCAPE:
 846			ro_gui_download_window_destroy(dw, false);
 847			return true;
 848
 849		case wimp_KEY_RETURN: {
 850			const char *name = ro_gui_get_icon_string(dw->window,
 851					ICON_DOWNLOAD_PATH);
 852			if (!strrchr(name, '.')) {
 853				warn_user("NoPathError", NULL);
 854				return true;
 855			}
 856			ro_gui_convert_save_path(dw->path, sizeof dw->path, name);
 857
 858			dw->send_dataload = false;
 859			if (ro_gui_download_save(dw, dw->path,
 860					!option_confirm_overwrite) && !dw->ctx)
 861			{
 862				/* finished already */
 863				schedule(200, ro_gui_download_window_destroy_wrapper, dw);
 864			}
 865			return true;
 866		}
 867		break;
 868	}
 869
 870	/* ignore all other keypresses (F12 etc) */
 871	return false;
 872}
 873
 874
 875/**
 876 * Handle User_Drag_Box event for a drag from a download window.
 877 *
 878 * \param  drag  block returned by Wimp_Poll
 879 */
 880
 881void ro_gui_download_drag_end(wimp_dragged *drag)
 882{
 883	wimp_pointer pointer;
 884	wimp_message message;
 885	struct gui_download_window *dw = download_window_current;
 886	const char *leaf;
 887	os_error *error;
 888
 889	if (dw->saved || dw->error)
 890		return;
 891
 892	error = xwimp_get_pointer_info(&pointer);
 893	if (error) {
 894		LOG(("xwimp_get_pointer_info: 0x%x: %s",
 895				error->errnum, error->errmess));
 896		warn_user("WimpError", error->errmess);
 897		return;
 898	}
 899
 900	/* ignore drags to the download window itself */
 901	if (pointer.w == dw->window) return;
 902
 903	leaf = strrchr(dw->path, '.');
 904	if (leaf)
 905		leaf++;
 906	else
 907		leaf = dw->path;
 908	ro_gui_convert_save_path(message.data.data_xfer.file_name, 212, leaf);
 909
 910	message.your_ref = 0;
 911	message.action = message_DATA_SAVE;
 912	message.data.data_xfer.w = pointer.w;
 913	message.data.data_xfer.i = pointer.i;
 914	message.data.data_xfer.pos.x = pointer.pos.x;
 915	message.data.data_xfer.pos.y = pointer.pos.y;
 916	message.data.data_xfer.est_size = dw->total_size ? dw->total_size :
 917			dw->received;
 918	message.data.data_xfer.file_type = dw->file_type;
 919	message.size = 44 + ((strlen(message.data.data_xfer.file_name) + 4) &
 920			(~3u));
 921
 922	error = xwimp_send_message_to_window(wimp_USER_MESSAGE, &message,
 923			pointer.w, pointer.i, 0);
 924	if (error) {
 925		LOG(("xwimp_send_message_to_window: 0x%x: %s",
 926				error->errnum, error->errmess));
 927		warn_user("WimpError", error->errmess);
 928	}
 929}
 930
 931
 932/**
 933 * Handle Message_DataSaveAck for a drag from a download window.
 934 *
 935 * \param  message  block returned by Wimp_Poll
 936 */
 937
 938void ro_gui_download_datasave_ack(wimp_message *message)
 939{
 940	struct gui_download_window *dw = download_window_current;
 941
 942	dw->send_dataload = true;
 943	memcpy(&dw->save_message, message, sizeof(wimp_message));
 944
 945	if (!ro_gui_download_save(dw, message->data.data_xfer.file_name,
 946			!option_confirm_overwrite))
 947		return;
 948
 949	if (!dw->ctx) {
 950		/* Ack successful completed save with message_DATA_LOAD immediately
 951		   to reduce the chance of the target app getting confused by it
 952		   being delayed */
 953
 954		ro_gui_download_send_dataload(dw);
 955
 956		schedule(200, ro_gui_download_window_destroy_wrapper, dw);
 957	}
 958}
 959
 960
 961/**
 962 * Return a pathname in canonical form
 963 *
 964 * \param  path  pathnamee to be canonicalised
 965 * \return ptr to pathname in malloc block, or NULL
 966 */
 967
 968char *ro_gui_download_canonicalise(const char *path)
 969{
 970	os_error *error;
 971	int spare = 0;
 972	char *buf;
 973
 974	error = xosfscontrol_canonicalise_path(path, NULL, NULL, NULL, 0, &spare);
 975	if (error) {
 976		LOG(("xosfscontrol_canonicalise_path: 0x%x: %s",
 977			error->errnum, error->errmess));
 978		return NULL;
 979	}
 980
 981	buf = malloc(1 - spare);
 982	if (buf) {
 983		error = xosfscontrol_canonicalise_path(path, buf, NULL, NULL,
 984				1 - spare, NULL);
 985		if (error) {
 986			LOG(("xosfscontrol_canonicalise_path: 0x%x: %s",
 987				error->errnum, error->errmess));
 988
 989			free(buf);
 990			return NULL;
 991		}
 992	}
 993
 994	return buf;
 995}
 996
 997
 998/**
 999 * Check the available space on the medium containing the destination file,
1000 * taking into account any space currently occupied by the file at its
1001 * original location.
1002 *
1003 * \param  dw         download window
1004 * \param  dest_file  destination pathname
1005 * \param  orig_file  current pathname, NULL if no existing file
1006 * \return true iff there's enough space
1007 */
1008
1009bool ro_gui_download_check_space(struct gui_download_window *dw,
1010		const char *dest_file, const char *orig_file)
1011{
1012	/* is there enough free space for this file? */
1013	int dest_len = strlen(dest_file);
1014	os_error *error;
1015	int max_file;
1016	bits free_lo;
1017	int free_hi;
1018	char *dir;
1019
1020	dir = malloc(dest_len + 1);
1021	if (!dir) return true;
1022
1023	while (dest_len > 0 && dest_file[--dest_len] != '.');
1024
1025	memcpy(dir, dest_file, dest_len);
1026	dir[dest_len] = '\0';
1027
1028	/* try the 64-bit variant first (RO 3.6+) */
1029	error = xosfscontrol_free_space64(dir, &free_lo, &free_hi,
1030			&max_file, NULL, NULL);
1031	if (error) {
1032		LOG(("xosfscontrol_free_space64: 0x%x: %s",
1033			error->errnum, error->errmess));
1034
1035		free_hi = 0;
1036		error = xosfscontrol_free_space(dir, (int*)&free_lo,
1037				&max_file, NULL);
1038		if (error) {
1039			LOG(("xosfscontrol_free_space: 0x%x: %s",
1040				error->errnum, error->errmess));
1041			/* close our eyes and hope */
1042			free(dir);
1043			return true;
1044		}
1045	}
1046
1047	free(dir);
1048
1049	if ((bits)max_file < dw->total_size || (!free_hi && free_lo < dw->total_size)) {
1050		char *dest_canon, *orig_canon;
1051		bits space;
1052
1053		if (!orig_file || !dw->file) {
1054			/* no original file to take into account */
1055			return false;
1056		}
1057
1058		space = min((bits)max_file, free_lo);
1059
1060		dest_canon = ro_gui_download_canonicalise(dest_file);
1061		if (!dest_canon) dest_canon = (char*)dest_file;
1062
1063		orig_canon = ro_gui_download_canonicalise(orig_file);
1064		if (!orig_canon) orig_canon = (char*)orig_file;
1065
1066		/* not enough space; allow for the file's original location
1067		   when space is tight by comparing the first part of the two
1068		   pathnames (and assuming the FS isn't brain damaged!) */
1069
1070		char *dot = strchr(orig_canon, '.');
1071		if (dot && !strncasecmp(dest_canon, orig_canon, (dot + 1) - orig_canon)) {
1072			int allocation;
1073
1074			error = xosargs_read_allocation(dw->file,
1075					&allocation);
1076			if (error) {
1077				LOG(("xosargs_read_allocation: 0x%x : %s",
1078					error->errnum, error->errmess));
1079			}
1080			else {
1081				space += allocation;
1082			}
1083		}
1084
1085		if (dest_canon != dest_file) free(dest_canon);
1086		if (orig_canon != orig_file) free(orig_canon);
1087
1088		if (space >= dw->total_size) {
1089			/* OK, renaming should work */
1090			return true;
1091		}
1092
1093		return false;
1094	}
1095	return true;
1096}
1097
1098/**
1099 * Move the downloading file to a new location and continue downloading there.
1100 *
1101 * \param  dw         download window
1102 * \param  dest_file  new location
1103 * \param  src_file   old location
1104 * \return error iff failed to move file
1105 */
1106
1107os_error *ro_gui_download_move(struct gui_download_window *dw,
1108		const char *dest_file, const char *src_file)
1109{
1110	os_error *error;
1111
1112	/* close temporary file */
1113	if (dw->file) {
1114		error = xosfind_closew(dw->file);
1115		dw->file = 0;
1116		if (error) {
1117			LOG(("xosfind_closew: 0x%x: %s",
1118					error->errnum, error->errmess));
1119			return error;
1120		}
1121	}
1122
1123	/* move or copy temporary file to destination file */
1124	error = xosfscontrol_rename(src_file, dest_file);
1125	/* Errors from a filing system have number 0x1XXnn, where XX is the FS
1126	 * number, and nn the error number. 0x9F is "Not same disc". */
1127	if (error && (error->errnum == error_BAD_RENAME ||
1128			(error->errnum & 0xFF00FFu) == 0x1009Fu)) {
1129		/* rename failed: copy with delete */
1130		error = xosfscontrol_copy(src_file, dest_file,
1131				osfscontrol_COPY_FORCE |
1132				osfscontrol_COPY_DELETE |
1133				osfscontrol_COPY_LOOK,
1134				0, 0, 0, 0, 0);
1135		if (error) {
1136			LOG(("xosfscontrol_copy: 0x%x: %s",
1137					error->errnum, error->errmess));
1138			return error;
1139		}
1140	} else if (error) {
1141		LOG(("xosfscontrol_rename: 0x%x: %s",
1142				error->errnum, error->errmess));
1143		return error;
1144	}
1145
1146	if (dw->ctx) {
1147		/* open new destination file if still fetching */
1148		error = xosfile_write(dest_file, 0xdeaddead, 0xdeaddead,
1149				fileswitch_ATTR_OWNER_READ |
1150				fileswitch_ATTR_OWNER_WRITE);
1151		if (error) {
1152			LOG(("xosfile_write: 0x%x: %s",
1153					error->errnum, error->errmess));
1154			warn_user("SaveError", error->errmess);
1155		}
1156
1157		error = xosfind_openupw(osfind_NO_PATH | osfind_ERROR_IF_DIR,
1158				dest_file, 0, &dw->file);
1159		if (error) {
1160			LOG(("xosfind_openupw: 0x%x: %s",
1161					error->errnum, error->errmess));
1162			return error;
1163		}
1164
1165		error = xosargs_set_ptrw(dw->file, dw->received);
1166		if (error) {
1167			LOG(("xosargs_set_ptrw: 0x%x: %s",
1168					error->errnum, error->errmess));
1169			return error;
1170		}
1171
1172	} else {
1173		/* otherwise just set the file type */
1174		error = xosfile_set_type(dest_file,
1175				dw->file_type);
1176		if (error) {
1177			LOG(("xosfile_set_type: 0x%x: %s",
1178					error->errnum, error->errmess));
1179			warn_user("SaveError", error->errmess);
1180		}
1181	}
1182
1183	/* success */
1184	return NULL;
1185}
1186
1187
1188/**
1189 * Remember the directory containing the given file,
1190 * for use in further downloads.
1191 *
1192 * \param  path  pathname of downloaded file
1193 * \return none
1194 */
1195
1196void ro_gui_download_remember_dir(const char *path)
1197{
1198	const char *lastdot = NULL;
1199	const char *p = path;
1200
1201	while (*p >= 0x20) {
1202		if (*p == '.') {
1203			/* don't remember the directory if it's a temporary file */
1204			if (!lastdot && p == path + 12 &&
1205				!memcmp(path, "<Wimp$Scrap>", 12)) break;
1206			lastdot = p;
1207		}
1208		p++;
1209	}
1210
1211	if (lastdot) {
1212		/* remember the directory */
1213		char *new_dir = realloc(download_dir, (lastdot+1)-path);
1214		if (new_dir) {
1215			download_dir_len = lastdot - path;
1216			memcpy(new_dir, path, download_dir_len);
1217			new_dir[download_dir_len] = '\0';
1218			download_dir = new_dir;
1219		}
1220	}
1221}
1222
1223/**
1224 * Start of save operation, user has specified where the file should be saved.
1225 *
1226 * \param  dw               download window
1227 * \param  file_name        pathname of destination file
1228 & \param  force_overwrite  true iff required to overwrite without prompting
1229 * \return true iff save successfully initiated
1230 */
1231
1232bool ro_gui_download_save(struct gui_download_window *dw,
1233		const char *file_name, bool force_overwrite)
1234{
1235	fileswitch_object_type obj_type;
1236	const char *temp_name;
1237	os_error *error;
1238
1239	if (dw->saved || dw->error)
1240		return true;
1241
1242	temp_name = ro_gui_download_temp_name(dw);
1243
1244	/* does the user want to check for collisions when saving? */
1245	if (!force_overwrite) {
1246		/* check whether the destination file/dir already exists */
1247		error = xosfile_read_stamped(file_name, &obj_type,
1248				NULL, NULL, NULL, NULL, NULL);
1249		if (error) {
1250			LOG(("xosfile_read_stamped: 0x%x:%s", error->errnum, error->errmess));
1251			return false;
1252		}
1253
1254		switch (obj_type) {
1255			case osfile_NOT_FOUND:
1256				break;
1257
1258			case osfile_IS_FILE:
1259				dw->query = query_user("OverwriteFile", NULL, &overwrite_funcs, dw,
1260							messages_get("Replace"), messages_get("DontReplace"));
1261				dw->query_rsn = QueryRsn_Overwrite;
1262				return false;
1263
1264			default:
1265				error = xosfile_make_error(file_name, obj_type);
1266				assert(error);
1267				warn_user("SaveError", error->errmess);
1268				return false;
1269		}
1270	}
1271
1272	if (!ro_gui_download_check_space(dw, file_name, temp_name)) {
1273		warn_user("SaveError", messages_get("NoDiscSpace"));
1274		return false;
1275	}
1276
1277	error = ro_gui_download_move(dw, file_name, temp_name);
1278	if (error) {
1279		warn_user("SaveError", error->errmess);
1280
1281		/* try to reopen at old location so that the download can continue
1282		   to the temporary file */
1283		error = xosfind_openupw(osfind_NO_PATH | osfind_ERROR_IF_DIR,
1284				temp_name, 0, &dw->file);
1285		if (error) {
1286			LOG(("xosfind_openupw: 0x%x: %s",
1287					error->errnum, error->errmess));
1288
1289		} else {
1290			error = xosargs_set_ptrw(dw->file, dw->received);
1291			if (error) {
1292				LOG(("xosargs_set_ptrw: 0x%x: %s",
1293						error->errnum, error->errmess));
1294			}
1295		}
1296
1297		if (error) {
1298			if (dw->ctx) 
1299				download_context_abort(dw->ctx);
1300			gui_download_window_error(dw, error->errmess);
1301		}
1302		return false;
1303	}
1304
1305	dw->saved = true;
1306	strncpy(dw->path, file_name, sizeof dw->path);
1307
1308	if (!dw->send_dataload || dw->save_message.data.data_xfer.est_size != -1)
1309		ro_gui_download_remember_dir(file_name);
1310
1311	/* grey out file icon */
1312	error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_ICON,
1313			wimp_ICON_SHADED, wimp_ICON_SHADED);
1314	if (error) {
1315		LOG(("xwimp_set_icon_state: 0x%x: %s",
1316				error->errnum, error->errmess));
1317		warn_user("WimpError", error->errmess);
1318	}
1319
1320	/* hide writeable path icon and show destination icon
1321	   Note: must redraw icon bounding box because the destination icon
1322		has rounded edges on RISC OS Select/Adjust and doesn't
1323		completely cover the writeable icon */
1324
1325	ro_gui_force_redraw_icon(dw->window, ICON_DOWNLOAD_PATH);
1326	error = xwimp_set_icon_state(dw->window, ICON_DOWNLOAD_PATH,
1327			wimp_ICON_DELETED, wimp_ICON_DELETED);
1328	if (error) {
1329		LOG(("xwimp_set_icon_state: 0x%x: %s",
1330				error->errnum, error->errmess));
1331		warn_user("WimpError", error->errmess);
1332	}
1333	error = xwimp_set_icon_state(dw->window,
1334			ICON_DOWNLOAD_DESTINATION, wimp_ICON_DELETED, 0);
1335	if (error) {
1336		LOG(("xwimp_set_icon_state: 0x%x: %s",
1337				error->errnum, error->errmess));
1338		warn_user("WimpError", error->errmess);
1339	}
1340
1341	ro_gui_download_window_hide_caret(dw);
1342
1343	return true;
1344}
1345
1346
1347/**
1348 * Send DataLoad message in response to DataSaveAck, informing the
1349 * target application that the transfer is complete.
1350 *
1351 * \param  dw  download window
1352 */
1353
1354void ro_gui_download_send_dataload(struct gui_download_window *dw)
1355{
1356	/* Ack successful save with message_DATA_LOAD */
1357	wimp_message *message = &dw->save_message;
1358	os_error *error;
1359
1360	assert(dw->send_dataload);
1361	dw->send_dataload = false;
1362
1363	message->action = message_DATA_LOAD;
1364	message->your_ref = message->my_ref;
1365	error = xwimp_send_message_to_window(wimp_USER_MESSAGE, message,
1366			message->data.data_xfer.w,
1367			message->data.data_xfer.i, 0);
1368	/* The window we just attempted to send a message to may
1369	 * have been closed before the message was sent. As we've
1370	 * no clean way of detecting this, we'll just detect the
1371	 * error return from the message send attempt and judiciously
1372	 * ignore it.
1373	 *
1374	 * Ideally, we would have registered to receive Message_WindowClosed
1375	 * and then cleared dw->send_dataload flag for the appropriate
1376	 * window. Unfortunately, however, a long-standing bug in the
1377	 * Pinboard module prevents this from being a viable solution.
1378	 *
1379	 * See http://groups.google.co.uk/group/comp.sys.acorn.tech/msg/e3fbf70d8393e6cf?dmode=source&hl=en
1380	 * for the rather depressing details.
1381	 */
1382	if (error && error->errnum != error_WIMP_BAD_HANDLE) {
1383		LOG(("xwimp_set_icon_state: 0x%x: %s",
1384				error->errnum, error->errmess));
1385		warn_user("WimpError", error->errmess);
1386	}
1387
1388	schedule(200, ro_gui_download_window_destroy_wrapper, dw);
1389}
1390
1391
1392/**
1393 * Handle closing of download window
1394 */
1395void ro_gui_download_close(wimp_w w)
1396{
1397	struct gui_download_window *dw;
1398
1399	dw = (struct gui_download_window *)ro_gui_wimp_event_get_user_data(w);
1400	ro_gui_download_window_destroy(dw, false);
1401}
1402
1403
1404/**
1405 * Close a download window and free any related resources.
1406 *
1407 * \param  dw   download window
1408 * \param  quit destroying because we're quitting the whole app
1409 * \return true if window destroyed, not waiting for user confirmation
1410 */
1411
1412bool ro_gui_download_window_destroy(struct gui_download_window *dw, bool quit)
1413{
1414	bool safe = dw->saved && !dw->ctx;
1415	os_error *error;
1416
1417	if (!safe && !dw->close_confirmed)
1418	{
1419		query_reason rsn = quit ? QueryRsn_Quit : QueryRsn_Abort;
1420
1421		if (dw->query != QUERY_INVALID) {
1422
1423			/* can we just reuse the existing query? */
1424			if (rsn == dw->query_rsn) {
1425				ro_gui_query_window_bring_to_front(dw->query);
1426				return false;
1427			}
1428
1429			query_close(dw->query);
1430			dw->query = QUERY_INVALID;
1431		}
1432
1433		if (quit) {
1434			/* bring all download windows to the front of the desktop as
1435			   a convenience if there are lots of windows open */
1436
1437			struct gui_download_window *d = download_window_list;
1438			while (d) {
1439				ro_gui_dialog_open_top(d->window, NULL, 0, 0);
1440				d = d->next;
1441			}
1442		}
1443
1444		dw->query_rsn = rsn;
1445		dw->query = query_user(quit ? "QuitDownload" : "AbortDownload",
1446				NULL, &close_funcs, dw, NULL, NULL);
1447
1448		return false;
1449	}
1450
1451	schedule_remove(ro_gui_download_update_status_wrapper, dw);
1452	schedule_remove(ro_gui_download_window_destroy_wrapper, dw);
1453
1454	/* remove from list */
1455	if (dw->prev)
1456		dw->prev->next = dw->next;
1457	else
1458		download_window_list = dw->next;
1459	if (dw->next)
1460		dw->next->prev = dw->prev;
1461
1462	/* delete window */
1463	error = xwimp_delete_window(dw->window);
1464	if (error) {
1465		LOG(("xwimp_delete_window: 0x%x: %s",
1466				error->errnum, error->errmess));
1467		warn_user("WimpError", error->errmess);
1468	}
1469	ro_gui_wimp_event_finalise(dw->window);
1470
1471	/* close download file */
1472	if (dw->file) {
1473		error = xosfind_closew(dw->file);
1474		if (error) {
1475			LOG(("xosfind_closew: 0x%x: %s",
1476					error->errnum, error->errmess));
1477			warn_user("SaveError", error->errmess);
1478		}
1479	}
1480
1481	/* delete temporary file */
1482	if (!dw->saved) {
1483		const char *temp_name = ro_gui_download_temp_name(dw);
1484
1485		error = xosfile_delete(temp_name, 0, 0, 0, 0, 0);
1486		if (error) {
1487			LOG(("xosfile_delete: 0x%x: %s",
1488					error->errnum, error->errmess));
1489			warn_user("SaveError", error->errmess);
1490		}
1491	}
1492
1493	if (dw->ctx) {
1494		download_context_abort(dw->ctx);
1495		download_context_destroy(dw->ctx);
1496	}
1497
1498	free(dw);
1499
1500	return true;
1501}
1502
1503
1504/**
1505 * Wrapper for ro_gui_download_window_destroy(), suitable for schedule().
1506 */
1507
1508void ro_gui_download_window_destroy_wrapper(void *p)
1509{
1510	struct gui_download_window *dw = p;
1511	if (dw->query != QUERY_INVALID)
1512		query_close(dw->query);
1513	dw->query = QUERY_INVALID;
1514	dw->close_confirmed = true;
1515	ro_gui_download_window_destroy(dw, false);
1516}
1517
1518
1519/**
1520 * User has opted to cancel the close, leaving the download to continue.
1521 */
1522
1523void ro_gui_download_close_cancelled(query_id id, enum query_response res, void *p)
1524{
1525	struct gui_download_window *dw = p;
1526	dw->query = QUERY_INVALID;
1527}
1528
1529
1530/**
1531 * Download aborted, close window and tidy up.
1532 */
1533
1534void ro_gui_download_close_confirmed(query_id id, enum query_response res, void *p)
1535{
1536	struct gui_download_window *dw = p;
1537	dw->query = QUERY_INVALID;
1538	dw->close_confirmed = true;
1539	if (dw->query_rsn == QueryRsn_Quit) {
1540
1541		/* destroy all our downloads */
1542		while (download_window_list)
1543			ro_gui_download_window_destroy_wrapper(download_window_list);
1544
1545		/* and restart the shutdown */
1546		if (ro_gui_prequit())
1547			netsurf_quit = true;
1548	}
1549	else
1550		ro_gui_download_window_destroy(dw, false);
1551}
1552
1553
1554/**
1555 * User has opted not to overwrite the existing file.
1556 */
1557
1558void ro_gui_download_overwrite_cancelled(query_id id, enum query_response res, void *p)
1559{
1560	struct gui_download_window *dw = p;
1561	dw->query = QUERY_INVALID;
1562}
1563
1564
1565/**
1566 * Overwrite of existing file confirmed, proceed with the save.
1567 */
1568
1569void ro_gui_download_overwrite_confirmed(query_id id, enum query_response res, void *p)
1570{
1571	struct gui_download_window *dw = p;
1572	dw->query = QUERY_INVALID;
1573
1574	if (!ro_gui_download_save(dw, dw->save_message.data.data_xfer.file_name, true))
1575		return;
1576
1577	if (!dw->ctx) {
1578		/* Ack successful completed save with message_DATA_LOAD immediately
1579		   to reduce the chance of the target app getting confused by it
1580		   being delayed */
1581
1582		ro_gui_download_send_dataload(dw);
1583
1584		schedule(200, ro_gui_download_window_destroy_wrapper, dw);
1585	}
1586}
1587
1588
1589/**
1590 * Respond to PreQuit message, displaying a prompt message if we need
1591 * the user to confirm the shutdown.
1592 *
1593 * \return true if we can shutdown straightaway
1594 */
1595
1596bool ro_gui_download_prequit(void)
1597{
1598	while (download_window_list)
1599	{
1600		if (!ro_gui_download_window_destroy(download_window_list, true))
1601			return false;	/* awaiting user confirmation */
1602	}
1603	return true;
1604}