1 /*
2  * Copyright 2008 Michael Lester <element3260@gmail.com>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <stdlib.h>
20 #include <string.h>
21 #include <math.h>
22 
23 #include <gtk/gtk.h>
24 #include <glib/gstdio.h>
25 
26 #include "utils/log.h"
27 #include "utils/utils.h"
28 #include "utils/nsurl.h"
29 #include "utils/messages.h"
30 #include "utils/nsoption.h"
31 #include "utils/string.h"
32 #include "desktop/download.h"
33 #include "netsurf/download.h"
34 
35 #include "gtk/warn.h"
36 #include "gtk/scaffolding.h"
37 #include "gtk/toolbar_items.h"
38 #include "gtk/window.h"
39 #include "gtk/compat.h"
40 #include "gtk/resources.h"
41 #include "gtk/download.h"
42 
43 #define UPDATE_RATE 500 /* In milliseconds */
44 
45 struct download_context;
46 
47 enum {
48 	NSGTK_DOWNLOAD_PROGRESS,
49 	NSGTK_DOWNLOAD_INFO,
50 	NSGTK_DOWNLOAD_REMAINING,
51 	NSGTK_DOWNLOAD_SPEED,
52 	NSGTK_DOWNLOAD_PULSE,
53 	NSGTK_DOWNLOAD_STATUS,
54 	NSGTK_DOWNLOAD,
55 
56 	NSGTK_DOWNLOAD_N_COLUMNS
57 };
58 
59 typedef enum {
60 	NSGTK_DOWNLOAD_NONE,
61 	NSGTK_DOWNLOAD_WORKING,
62 	NSGTK_DOWNLOAD_ERROR,
63 	NSGTK_DOWNLOAD_COMPLETE,
64 	NSGTK_DOWNLOAD_CANCELED
65 } nsgtk_download_status;
66 
67 typedef enum {
68 	NSGTK_DOWNLOAD_PAUSE	= 1 << 0,
69 	NSGTK_DOWNLOAD_RESUME	= 1 << 1,
70 	NSGTK_DOWNLOAD_CANCEL	= 1 << 2,
71 	NSGTK_DOWNLOAD_CLEAR	= 1 << 3
72 } nsgtk_download_actions;
73 
74 static const gchar* status_messages[] =	{
75 	NULL,
76 	"gtkWorking",
77 	"gtkError",
78 	"gtkComplete",
79 	"gtkCanceled"
80 };
81 
82 /**
83  * context for each download.
84  */
85 struct gui_download_window {
86 	struct download_context *ctx;
87 	nsgtk_download_actions sensitivity;
88 	nsgtk_download_status status;
89 
90 	GString *name;
91 	GString *time_left;
92 	unsigned long long int size_total;
93 	unsigned long long int size_downloaded;
94 	gint progress;
95 	gfloat time_remaining;
96 	gfloat start_time;
97 	gfloat speed;
98 
99 	GtkTreeRowReference *row;
100 	GIOChannel *write;
101 	GError *error;
102 };
103 
104 typedef	void (*nsgtk_download_selection_action)(struct gui_download_window *dl,
105 						void *user_data);
106 
107 /**
108  * context for a nsgtk download window.
109  */
110 struct download_window_ctx {
111 	GtkWindow *window;
112 	GtkWindow *parent;
113 
114 	GtkProgressBar *progress;
115 
116 	GtkTreeView *tree;
117 	GtkListStore *store;
118 	GtkTreeSelection *selection;
119 	GtkTreeIter iter;
120 
121 	GTimer *timer;
122 	GList *list;
123 	GtkButton *pause;
124 	GtkButton *clear;
125 	GtkButton *cancel;
126 	GtkButton *resume;
127 
128 	gint num_active;
129 };
130 
131 /**
132  * global instance of the download window
133  */
134 static struct download_window_ctx dl_ctx;
135 
136 
nsgtk_download_tree_view_new(GtkBuilder * gladeFile)137 static GtkTreeView* nsgtk_download_tree_view_new(GtkBuilder *gladeFile)
138 {
139 	GtkTreeView *treeview;
140 	GtkCellRenderer *renderer;
141 
142 	treeview = GTK_TREE_VIEW(gtk_builder_get_object(gladeFile,
143 							"treeDownloads"));
144 
145 	/* Progress column */
146 	renderer = gtk_cell_renderer_progress_new();
147 	gtk_tree_view_insert_column_with_attributes(treeview,
148 						    -1,
149 						    messages_get("gtkProgress"),
150 						    renderer,
151 						    "value",
152 						    NSGTK_DOWNLOAD_PROGRESS,
153 						    "pulse",
154 						    NSGTK_DOWNLOAD_PULSE,
155 						    "text",
156 						    NSGTK_DOWNLOAD_STATUS,
157 						    NULL);
158 
159 	/* Information column */
160 	renderer = gtk_cell_renderer_text_new();
161 	g_object_set(G_OBJECT(renderer),
162 		     "wrap-mode",
163 		     PANGO_WRAP_WORD_CHAR,
164 		     "wrap-width",
165 		     300,
166 		     NULL);
167 	gtk_tree_view_insert_column_with_attributes(treeview,
168 						    -1,
169 						    messages_get("gtkDetails"),
170 						    renderer,
171 						    "text",
172 						    NSGTK_DOWNLOAD_INFO,
173 						    NULL);
174 	gtk_tree_view_column_set_expand(
175 			gtk_tree_view_get_column(treeview,
176 						 NSGTK_DOWNLOAD_INFO), TRUE);
177 
178 	/* Time remaining column */
179 	renderer = gtk_cell_renderer_text_new();
180 	gtk_tree_view_insert_column_with_attributes(treeview,
181 						    -1,
182 						    messages_get("gtkRemaining"),
183 						    renderer,
184 						    "text",
185 						    NSGTK_DOWNLOAD_REMAINING,
186 						    NULL);
187 
188 	/* Speed column */
189 	renderer = gtk_cell_renderer_text_new();
190 	gtk_tree_view_insert_column_with_attributes(treeview,
191 						    -1,
192 						    messages_get("gtkSpeed"),
193 						    renderer,
194 						    "text",
195 						    NSGTK_DOWNLOAD_SPEED,
196 						    NULL);
197 
198 	return treeview;
199 }
200 
201 
202 static gint
nsgtk_download_sort(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer userdata)203 nsgtk_download_sort(GtkTreeModel *model,
204 		    GtkTreeIter *a,
205 		    GtkTreeIter *b,
206 		    gpointer userdata)
207 {
208 	struct gui_download_window *dl1, *dl2;
209 
210 	gtk_tree_model_get(model, a, NSGTK_DOWNLOAD, &dl1, -1);
211 	gtk_tree_model_get(model, b, NSGTK_DOWNLOAD, &dl2, -1);
212 
213 	return dl1->status - dl2->status;
214 }
215 
216 
217 static void
nsgtk_download_sensitivity_update_buttons(nsgtk_download_actions sensitivity)218 nsgtk_download_sensitivity_update_buttons(nsgtk_download_actions sensitivity)
219 {
220 	/* Glade seems to pack the buttons in an arbitrary order */
221 	enum { PAUSE_BUTTON, CLEAR_BUTTON, CANCEL_BUTTON, RESUME_BUTTON };
222 
223 	gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.pause),
224 				 sensitivity & NSGTK_DOWNLOAD_PAUSE);
225 	gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.clear),
226 				 sensitivity & NSGTK_DOWNLOAD_CLEAR);
227 	gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.cancel),
228 				 sensitivity & NSGTK_DOWNLOAD_CANCEL);
229 	gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.resume),
230 				 sensitivity & NSGTK_DOWNLOAD_RESUME);
231 }
232 
233 
nsgtk_download_sensitivity_evaluate(GtkTreeSelection * selection)234 static void nsgtk_download_sensitivity_evaluate(GtkTreeSelection *selection)
235 {
236 	GtkTreeIter iter;
237 	GList *rows;
238 	gboolean selected;
239 	GtkTreeModel *model;
240 	nsgtk_download_actions sensitivity = 0;
241 	struct gui_download_window *dl;
242 
243 	model = GTK_TREE_MODEL(dl_ctx.store);
244 
245 	selected = gtk_tree_selection_count_selected_rows(selection);
246 	if (selected) {
247 		rows = gtk_tree_selection_get_selected_rows(selection, &model);
248 		while (rows != NULL) {
249 			gtk_tree_model_get_iter(model,
250 						&iter,
251 						(GtkTreePath*)rows->data);
252 			gtk_tree_model_get(model,
253 					   &iter,
254 					   NSGTK_DOWNLOAD,
255 					   &dl,
256 					   -1);
257 			sensitivity |= dl->sensitivity;
258 			rows = rows->next;
259 		}
260 	} else {
261 		rows = dl_ctx.list;
262 		while (rows != NULL) {
263 			dl = rows->data;
264 			sensitivity |= (dl->sensitivity & NSGTK_DOWNLOAD_CLEAR);
265 			rows = rows->next;
266 		}
267 	}
268 
269 	nsgtk_download_sensitivity_update_buttons(sensitivity);
270 }
271 
272 
273 /**
274  * Wrapper to GFunc-ify gtk_tree_path_free for g_list_foreach.
275  */
276 static void
nsgtk_download_gfunc__gtk_tree_path_free(gpointer data,gpointer user_data)277 nsgtk_download_gfunc__gtk_tree_path_free(gpointer data, gpointer user_data)
278 {
279 	gtk_tree_path_free(data);
280 }
281 
282 
283 /**
284  * Wrapper to GFunc-ify g_free for g_list_foreach.
285  */
286 static void
nsgtk_download_gfunc__g_free(gpointer data,gpointer user_data)287 nsgtk_download_gfunc__g_free(gpointer data, gpointer user_data)
288 {
289 	g_free(data);
290 }
291 
292 
nsgtk_download_do(nsgtk_download_selection_action action)293 static void nsgtk_download_do(nsgtk_download_selection_action action)
294 {
295 	GList *rows, *dls = NULL;
296 	GtkTreeModel *model;
297 
298 	if (gtk_tree_selection_count_selected_rows(dl_ctx.selection)) {
299 		model = GTK_TREE_MODEL(dl_ctx.store);
300 
301 		rows = gtk_tree_selection_get_selected_rows(dl_ctx.selection,
302 							    &model);
303 		while (rows != NULL) {
304 			struct gui_download_window *dl;
305 
306 			gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
307 						&dl_ctx.iter,
308 						(GtkTreePath*)rows->data);
309 
310 			gtk_tree_model_get(GTK_TREE_MODEL(dl_ctx.store),
311 					   &dl_ctx.iter,
312 					   NSGTK_DOWNLOAD,
313 					   &dl,
314 					   -1);
315 
316 			dls = g_list_prepend(dls, dl);
317 
318 			rows = rows->next;
319 		}
320 		g_list_foreach(rows,
321 			       nsgtk_download_gfunc__gtk_tree_path_free,
322 			       NULL);
323 		g_list_foreach(rows,
324 			       nsgtk_download_gfunc__g_free,
325 			       NULL);
326 		g_list_free(rows);
327 	} else {
328 		dls = g_list_copy(dl_ctx.list);
329 	}
330 
331 	g_list_foreach(dls, (GFunc)action, NULL);
332 	g_list_free(dls);
333 }
334 
335 
nsgtk_download_info_to_string(struct gui_download_window * dl)336 static gchar* nsgtk_download_info_to_string(struct gui_download_window *dl)
337 {
338 	gchar *size_info;
339 	gchar *r;
340 
341 	size_info = g_strdup_printf(messages_get("gtkSizeInfo"),
342 				    human_friendly_bytesize(dl->size_downloaded),
343 				    dl->size_total == 0 ?
344 				    messages_get("gtkUnknownSize") :
345 				    human_friendly_bytesize(dl->size_total));
346 
347 	if (dl->status != NSGTK_DOWNLOAD_ERROR) {
348 		r = g_strdup_printf("%s\n%s", dl->name->str, size_info);
349 	} else {
350 		r = g_strdup_printf("%s\n%s", dl->name->str, dl->error->message);
351 	}
352 
353 	g_free(size_info);
354 
355 	return r;
356 }
357 
358 
nsgtk_download_time_to_string(gint seconds)359 static gchar* nsgtk_download_time_to_string(gint seconds)
360 {
361 	gint hours, minutes;
362 
363 	if (seconds < 0) {
364 		return g_strdup("-");
365 	}
366 
367 	hours = seconds / 3600;
368 	seconds -= hours * 3600;
369 	minutes = seconds / 60;
370 	seconds -= minutes * 60;
371 
372 	if (hours > 0) {
373 		return g_strdup_printf("%u:%02u:%02u",
374 				       hours,
375 				       minutes,
376 				       seconds);
377 	} else {
378 		return g_strdup_printf("%u:%02u", minutes, seconds);
379 	}
380 }
381 
382 
nsgtk_download_store_update_item(struct gui_download_window * dl)383 static void nsgtk_download_store_update_item(struct gui_download_window *dl)
384 {
385 	gchar *info = nsgtk_download_info_to_string(dl);
386 	char *human = human_friendly_bytesize(dl->speed);
387 	char speed[strlen(human) + SLEN("/s") + 1];
388 	sprintf(speed, "%s/s", human);
389 	gchar *time = nsgtk_download_time_to_string(dl->time_remaining);
390 	gboolean pulse = dl->status == NSGTK_DOWNLOAD_WORKING;
391 
392 	/* Updates iter (which is needed to set and get data) with the dl row */
393 	gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
394 				&dl_ctx.iter,
395 				gtk_tree_row_reference_get_path(dl->row));
396 
397 	gtk_list_store_set(dl_ctx.store, &dl_ctx.iter,
398 			   NSGTK_DOWNLOAD_PULSE, pulse ? dl->progress : -1,
399 			   NSGTK_DOWNLOAD_PROGRESS, pulse ? 0 : dl->progress,
400 			   NSGTK_DOWNLOAD_INFO, info,
401 			   NSGTK_DOWNLOAD_SPEED, dl->speed == 0 ? "-" : speed,
402 			   NSGTK_DOWNLOAD_REMAINING, time,
403 			   NSGTK_DOWNLOAD, dl,
404 			   -1);
405 
406 	g_free(info);
407 	g_free(time);
408 }
409 
410 
nsgtk_download_update(gboolean force_update)411 static gboolean nsgtk_download_update(gboolean force_update)
412 {
413 	/* Be sure we need to update */
414 	if (!nsgtk_widget_get_visible(GTK_WIDGET(dl_ctx.window))) {
415 		return TRUE;
416 	}
417 
418 	GList *list;
419 	gchar *text;
420 	gboolean update, pulse_mode = FALSE;
421 	unsigned long long int downloaded = 0;
422 	unsigned long long int total = 0;
423 	gint dls = 0;
424 	gfloat percent, elapsed = g_timer_elapsed(dl_ctx.timer, NULL);
425 
426 	dl_ctx.num_active = 0;
427 
428 	for (list = dl_ctx.list; list != NULL; list = list->next) {
429 		struct gui_download_window *dl = list->data;
430 		update = force_update;
431 
432 		switch (dl->status) {
433 		case NSGTK_DOWNLOAD_WORKING:
434 			pulse_mode = TRUE;
435 			/* Fall through */
436 
437 		case NSGTK_DOWNLOAD_NONE:
438 			dl->speed = dl->size_downloaded /
439 				(elapsed - dl->start_time);
440 			if (dl->status == NSGTK_DOWNLOAD_NONE) {
441 				dl->time_remaining = (dl->size_total -
442 						      dl->size_downloaded)/
443 					dl->speed;
444 				dl->progress = (double)dl->size_downloaded /
445 					(double)dl->size_total * 100;
446 			} else {
447 				dl->progress++;
448 			}
449 
450 			dl_ctx.num_active++;
451 			update = TRUE;
452 			/* Fall through */
453 
454 		case NSGTK_DOWNLOAD_COMPLETE:
455 			downloaded += dl->size_downloaded;
456 			total += dl->size_total;
457 			dls++;
458 
459 		default:
460 			;//Do nothing
461 
462 		}
463 		if (update) {
464 			nsgtk_download_store_update_item(dl);
465 		}
466 	}
467 
468 	if (pulse_mode) {
469 		text = g_strdup_printf(
470 			messages_get(dl_ctx.num_active > 1 ?
471 				     "gtkProgressBarPulse" :
472 				     "gtkProgressBarPulseSingle"),
473 			dl_ctx.num_active);
474 		gtk_progress_bar_pulse(dl_ctx.progress);
475 		gtk_progress_bar_set_text(dl_ctx.progress, text);
476 	} else {
477 		percent = total != 0 ? (double)downloaded / (double)total : 0;
478 		text = g_strdup_printf(messages_get("gtkProgressBar"),
479 				       floor(percent * 100), dls);
480 		gtk_progress_bar_set_fraction(dl_ctx.progress,
481 					      percent);
482 		gtk_progress_bar_set_text(dl_ctx.progress, text);
483 	}
484 
485 	g_free(text);
486 
487 	if (dl_ctx.num_active == 0) {
488 		return FALSE; /* Returning FALSE here cancels the g_timeout */
489 	} else {
490 		return TRUE;
491 	}
492 }
493 
494 
495 static void
nsgtk_download_store_clear_item(struct gui_download_window * dl,void * user_data)496 nsgtk_download_store_clear_item(struct gui_download_window *dl,	void *user_data)
497 {
498 	if (dl->sensitivity & NSGTK_DOWNLOAD_CLEAR) {
499 		dl_ctx.list = g_list_remove(dl_ctx.list, dl);
500 
501 		gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
502 					&dl_ctx.iter,
503 					gtk_tree_row_reference_get_path(dl->row));
504 		gtk_list_store_remove(dl_ctx.store,
505 				      &dl_ctx.iter);
506 
507 		download_context_destroy(dl->ctx);
508 		g_string_free(dl->name, TRUE);
509 		g_string_free(dl->time_left, TRUE);
510 		g_free(dl);
511 
512 		nsgtk_download_sensitivity_evaluate(dl_ctx.selection);
513 		nsgtk_download_update(FALSE);
514 	}
515 }
516 
517 
518 static void
nsgtk_download_tree_view_row_activated(GtkTreeView * tree,GtkTreePath * path,GtkTreeViewColumn * column,gpointer data)519 nsgtk_download_tree_view_row_activated(GtkTreeView *tree,
520 				       GtkTreePath *path,
521 				       GtkTreeViewColumn *column,
522 				       gpointer data)
523 {
524 	GtkTreeModel *model;
525 	GtkTreeIter iter;
526 
527 	model = gtk_tree_view_get_model(tree);
528 
529 	if (gtk_tree_model_get_iter(model, &iter, path)) {
530 		/* TODO: This will be a context action (pause, start, clear) */
531 		nsgtk_download_do(nsgtk_download_store_clear_item);
532 	}
533 }
534 
535 
536 static void
nsgtk_download_change_sensitivity(struct gui_download_window * dl,nsgtk_download_actions sensitivity)537 nsgtk_download_change_sensitivity(struct gui_download_window *dl,
538 				  nsgtk_download_actions sensitivity)
539 {
540 	dl->sensitivity = sensitivity;
541 	nsgtk_download_sensitivity_evaluate(dl_ctx.selection);
542 }
543 
544 
545 static void
nsgtk_download_change_status(struct gui_download_window * dl,nsgtk_download_status status)546 nsgtk_download_change_status(struct gui_download_window *dl,
547 			     nsgtk_download_status status)
548 {
549 	dl->status = status;
550 	if (status != NSGTK_DOWNLOAD_NONE) {
551 		gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
552 					&dl_ctx.iter,
553 					gtk_tree_row_reference_get_path(dl->row));
554 
555 		gtk_list_store_set(dl_ctx.store, &dl_ctx.iter,
556 				   NSGTK_DOWNLOAD_STATUS,
557 				   messages_get(status_messages[status]), -1);
558 	}
559 }
560 
561 
562 static void
nsgtk_download_store_cancel_item(struct gui_download_window * dl,void * user_data)563 nsgtk_download_store_cancel_item(struct gui_download_window *dl,
564 				 void *user_data)
565 {
566 	if (dl->sensitivity & NSGTK_DOWNLOAD_CANCEL) {
567 		dl->speed = 0;
568 		dl->size_downloaded = 0;
569 		dl->progress = 0;
570 		dl->time_remaining = -1;
571 		nsgtk_download_change_sensitivity(dl, NSGTK_DOWNLOAD_CLEAR);
572 		nsgtk_download_change_status(dl, NSGTK_DOWNLOAD_CANCELED);
573 
574 		download_context_abort(dl->ctx);
575 
576 		g_unlink(download_context_get_filename(dl->ctx));
577 
578 		nsgtk_download_update(TRUE);
579 	}
580 }
581 
582 
nsgtk_download_hide(GtkWidget * window)583 static gboolean nsgtk_download_hide(GtkWidget *window)
584 {
585 	gtk_widget_hide(window);
586 	return TRUE;
587 }
588 
589 
590 /**
591  * Prompt user for downloaded file name
592  *
593  * \param filename The original name of the file
594  * \param domain the domain the file is being downloaded from
595  * \param size The size of the file being downloaded
596  */
597 static gchar*
nsgtk_download_dialog_show(const gchar * filename,const gchar * domain,const gchar * size)598 nsgtk_download_dialog_show(const gchar *filename,
599 			   const gchar *domain,
600 			   const gchar *size)
601 {
602 	enum { GTK_RESPONSE_DOWNLOAD, GTK_RESPONSE_SAVE_AS };
603 	GtkWidget *dialog;
604 	char *destination = NULL;
605 	gchar *message;
606 	gchar *info;
607 
608 	message = g_strdup(messages_get("gtkStartDownload"));
609 	info = g_strdup_printf(messages_get("gtkInfo"), filename, domain, size);
610 
611 	dialog = gtk_message_dialog_new_with_markup(
612 				dl_ctx.parent,
613 				GTK_DIALOG_DESTROY_WITH_PARENT,
614 				GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
615 				"<span size=\"x-large\" weight=\"ultrabold\">%s</span>"
616 				"\n\n<small>%s</small>",
617 				message,
618 				info);
619 
620 	gtk_dialog_add_buttons(GTK_DIALOG(dialog),
621 			       NSGTK_STOCK_SAVE, GTK_RESPONSE_DOWNLOAD,
622 			       NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
623 			       NSGTK_STOCK_SAVE_AS, GTK_RESPONSE_SAVE_AS,
624 			       NULL);
625 
626 	gint result = gtk_dialog_run(GTK_DIALOG(dialog));
627 	gtk_widget_destroy(dialog);
628 	g_free(message);
629 	g_free(info);
630 
631 	switch (result) {
632 	case GTK_RESPONSE_SAVE_AS: {
633 		dialog = gtk_file_chooser_dialog_new(
634 				messages_get("gtkSave"),
635 				dl_ctx.parent,
636 				GTK_FILE_CHOOSER_ACTION_SAVE,
637 				NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
638 				NSGTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
639 				NULL);
640 		gtk_file_chooser_set_current_name
641 			(GTK_FILE_CHOOSER(dialog), filename);
642 		gtk_file_chooser_set_current_folder
643 			(GTK_FILE_CHOOSER(dialog),
644 			 nsoption_charp(downloads_directory));
645 		gtk_file_chooser_set_do_overwrite_confirmation
646 			(GTK_FILE_CHOOSER(dialog),
647 			 nsoption_bool(request_overwrite));
648 
649 		gint result = gtk_dialog_run(GTK_DIALOG(dialog));
650 		if (result == GTK_RESPONSE_ACCEPT)
651 			destination = gtk_file_chooser_get_filename
652 				(GTK_FILE_CHOOSER(dialog));
653 		gtk_widget_destroy(dialog);
654 		break;
655 	}
656 	case GTK_RESPONSE_DOWNLOAD: {
657 		destination = malloc(strlen(nsoption_charp(downloads_directory))
658 				     + strlen(filename) + SLEN("/") + 1);
659 		if (destination == NULL) {
660 			nsgtk_warning(messages_get("NoMemory"), 0);
661 			break;
662 		}
663 		sprintf(destination, "%s/%s",
664 			nsoption_charp(downloads_directory), filename);
665 		/* Test if file already exists and display overwrite
666 		 * confirmation if needed */
667 		if (g_file_test(destination, G_FILE_TEST_EXISTS) &&
668 		    nsoption_bool(request_overwrite)) {
669 			GtkWidget *button;
670 
671 			message = g_strdup_printf(messages_get("gtkOverwrite"),
672 						  filename);
673 			info = g_strdup_printf(messages_get("gtkOverwriteInfo"),
674 					       nsoption_charp(downloads_directory));
675 
676 			dialog = gtk_message_dialog_new_with_markup(
677 						dl_ctx.parent,
678 						GTK_DIALOG_DESTROY_WITH_PARENT,
679 						GTK_MESSAGE_QUESTION,
680 						GTK_BUTTONS_CANCEL,
681 						"<b>%s</b>",
682 						message);
683 			gtk_message_dialog_format_secondary_markup(
684 						GTK_MESSAGE_DIALOG(dialog),
685 						"%s",
686 						info);
687 
688 			button = gtk_dialog_add_button(GTK_DIALOG(dialog),
689 						       "_Replace",
690 						       GTK_RESPONSE_DOWNLOAD);
691 			gtk_button_set_image(GTK_BUTTON(button),
692 					     nsgtk_image_new_from_stock(
693 							NSGTK_STOCK_SAVE,
694 							GTK_ICON_SIZE_BUTTON));
695 
696 			gint result = gtk_dialog_run(GTK_DIALOG(dialog));
697 			if (result == GTK_RESPONSE_CANCEL)
698 				destination = NULL;
699 
700 			gtk_widget_destroy(dialog);
701 			g_free(message);
702 			g_free(info);
703 		}
704 		break;
705 	}
706 	}
707 	return destination;
708 }
709 
710 
nsgtk_download_handle_error(GError * error)711 static gboolean nsgtk_download_handle_error(GError *error)
712 {
713 	GtkWidget*dialog;
714 	gchar *message;
715 
716 	if (error != NULL) {
717 		message = g_strdup_printf(messages_get("gtkFileError"),
718 					  error->message);
719 
720 		dialog = gtk_message_dialog_new_with_markup(
721 					dl_ctx.parent,
722 					GTK_DIALOG_MODAL,
723 					GTK_MESSAGE_ERROR,
724 					GTK_BUTTONS_OK,
725 					"<big><b>%s</b></big>\n\n"
726 					"<small>%s</small>",
727 					messages_get("gtkFailed"),
728 					message);
729 
730 		gtk_dialog_run(GTK_DIALOG(dialog));
731 		gtk_widget_destroy(dialog);
732 		return TRUE;
733 	}
734 	return FALSE;
735 }
736 
737 
nsgtk_download_store_create_item(struct gui_download_window * dl)738 static void nsgtk_download_store_create_item(struct gui_download_window *dl)
739 {
740 	nsgtk_download_store_update_item(dl);
741 	/* The iter has already been updated to this row */
742 	gtk_list_store_set(dl_ctx.store,
743 			   &dl_ctx.iter,
744 			   NSGTK_DOWNLOAD,
745 			   dl,
746 			   -1);
747 }
748 
749 
750 /**
751  * Wrapper to GSourceFunc-ify nsgtk_download_update.
752  */
753 static gboolean
nsgtk_download_gsourcefunc__nsgtk_download_update(gpointer user_data)754 nsgtk_download_gsourcefunc__nsgtk_download_update(gpointer user_data)
755 {
756 	return nsgtk_download_update(FALSE);
757 }
758 
759 
760 /**
761  * core callback on creating a new download
762  */
763 static struct gui_download_window *
gui_download_window_create(download_context * ctx,struct gui_window * gui)764 gui_download_window_create(download_context *ctx, struct gui_window *gui)
765 {
766 	nsurl *url;
767 	unsigned long long int total_size;
768 	gchar *domain;
769 	gchar *destination;
770 	gboolean unknown_size;
771 	struct gui_download_window *download;
772 	const char *size;
773 
774 	url = download_context_get_url(ctx);
775 	total_size = download_context_get_total_length(ctx);
776 	unknown_size = total_size == 0;
777 	size = (total_size == 0 ?
778 		messages_get("gtkUnknownSize") :
779 		human_friendly_bytesize(total_size));
780 
781 	dl_ctx.parent =	nsgtk_scaffolding_window(nsgtk_get_scaffold(gui));
782 
783 	download = malloc(sizeof *download);
784 	if (download == NULL) {
785 		return NULL;
786 	}
787 
788 	/* set the domain to the host component of the url if it exists */
789 	if (nsurl_has_component(url, NSURL_HOST)) {
790 		domain = g_strdup(lwc_string_data(nsurl_get_component(url, NSURL_HOST)));
791 	} else {
792 		domain = g_strdup(messages_get("gtkUnknownHost"));
793 	}
794 	if (domain == NULL) {
795 		free(download);
796 		return NULL;
797 	}
798 
799 	/* show the dialog */
800 	destination = nsgtk_download_dialog_show(
801 		download_context_get_filename(ctx), domain, size);
802 	if (destination == NULL) {
803 		g_free(domain);
804 		free(download);
805 		return NULL;
806 	}
807 
808 	/* Add the new row and store the reference to it (which keeps track of
809 	 * the tree changes) */
810 	gtk_list_store_prepend(dl_ctx.store, &dl_ctx.iter);
811 	download->row = gtk_tree_row_reference_new(
812 					GTK_TREE_MODEL(dl_ctx.store),
813 					gtk_tree_model_get_path(
814 						GTK_TREE_MODEL(dl_ctx.store),
815 						&dl_ctx.iter));
816 
817 	download->ctx = ctx;
818 	download->name = g_string_new(download_context_get_filename(ctx));
819 	download->time_left = g_string_new("");
820 	download->size_total = total_size;
821 	download->size_downloaded = 0;
822 	download->speed = 0;
823 	download->start_time = g_timer_elapsed(dl_ctx.timer, NULL);
824 	download->time_remaining = -1;
825 	download->status = NSGTK_DOWNLOAD_NONE;
826 	download->progress = 0;
827 	download->error = NULL;
828 	download->write = g_io_channel_new_file(destination,
829 						"w",
830 						&download->error);
831 
832 	if (nsgtk_download_handle_error(download->error)) {
833 		g_string_free(download->name, TRUE);
834 		g_string_free(download->time_left, TRUE);
835 		free(download);
836 		return NULL;
837 	}
838 	g_io_channel_set_encoding(download->write, NULL, &download->error);
839 
840 	nsgtk_download_change_sensitivity(download, NSGTK_DOWNLOAD_CANCEL);
841 
842 	nsgtk_download_store_create_item(download);
843 	nsgtk_download_show(dl_ctx.parent);
844 
845 	if (unknown_size) {
846 		nsgtk_download_change_status(download, NSGTK_DOWNLOAD_WORKING);
847 	}
848 
849 	if (dl_ctx.num_active == 0) {
850 		g_timeout_add(
851 			UPDATE_RATE,
852 			nsgtk_download_gsourcefunc__nsgtk_download_update,
853 			NULL);
854 	}
855 
856 	dl_ctx.list = g_list_prepend(dl_ctx.list, download);
857 
858 	return download;
859 }
860 
861 
862 /**
863  * core callback on receipt of data
864  */
865 static nserror
gui_download_window_data(struct gui_download_window * dw,const char * data,unsigned int size)866 gui_download_window_data(struct gui_download_window *dw,
867 			 const char *data,
868 			 unsigned int size)
869 {
870 	g_io_channel_write_chars(dw->write, data, size, NULL, &dw->error);
871 	if (dw->error != NULL) {
872 		dw->speed = 0;
873 		dw->time_remaining = -1;
874 
875 		nsgtk_download_change_sensitivity(dw, NSGTK_DOWNLOAD_CLEAR);
876 		nsgtk_download_change_status(dw, NSGTK_DOWNLOAD_ERROR);
877 
878 		nsgtk_download_update(TRUE);
879 
880 		gtk_window_present(dl_ctx.window);
881 
882 		return NSERROR_SAVE_FAILED;
883 	}
884 	dw->size_downloaded += size;
885 
886 	return NSERROR_OK;
887 }
888 
889 
890 /**
891  * core callback on error
892  */
893 static void
gui_download_window_error(struct gui_download_window * dw,const char * error_msg)894 gui_download_window_error(struct gui_download_window *dw, const char *error_msg)
895 {
896 }
897 
898 
899 /**
900  * core callback when core download is complete
901  */
gui_download_window_done(struct gui_download_window * dw)902 static void gui_download_window_done(struct gui_download_window *dw)
903 {
904 	g_io_channel_shutdown(dw->write, TRUE, &dw->error);
905 	g_io_channel_unref(dw->write);
906 
907 	dw->speed = 0;
908 	dw->time_remaining = -1;
909 	dw->progress = 100;
910 	dw->size_total = dw->size_downloaded;
911 	nsgtk_download_change_sensitivity(dw, NSGTK_DOWNLOAD_CLEAR);
912 	nsgtk_download_change_status(dw, NSGTK_DOWNLOAD_COMPLETE);
913 
914 	if (nsoption_bool(downloads_clear)) {
915 		nsgtk_download_store_clear_item(dw, NULL);
916 	} else {
917 		nsgtk_download_update(TRUE);
918 	}
919 }
920 
921 
922 static struct gui_download_table download_table = {
923 	.create = gui_download_window_create,
924 	.data = gui_download_window_data,
925 	.error = gui_download_window_error,
926 	.done = gui_download_window_done,
927 };
928 
929 struct gui_download_table *nsgtk_download_table = &download_table;
930 
931 
932 /* exported interface documented in gtk/download.h */
nsgtk_download_init(void)933 nserror nsgtk_download_init(void)
934 {
935 	GtkBuilder* builder;
936 	nserror res;
937 
938 	res = nsgtk_builder_new_from_resname("downloads", &builder);
939 	if (res != NSERROR_OK) {
940 		NSLOG(netsurf, INFO, "Download UI builder init failed");
941 		return res;
942 	}
943 
944 	gtk_builder_connect_signals(builder, NULL);
945 
946 	dl_ctx.pause = GTK_BUTTON(gtk_builder_get_object(builder,
947 							 "buttonPause"));
948 	dl_ctx.clear = GTK_BUTTON(gtk_builder_get_object(builder,
949 							 "buttonClear"));
950 	dl_ctx.cancel = GTK_BUTTON(gtk_builder_get_object(builder,
951 							  "buttonCancel"));
952 	dl_ctx.resume = GTK_BUTTON(gtk_builder_get_object(builder,
953 							  "buttonPlay"));
954 
955 	dl_ctx.progress = GTK_PROGRESS_BAR(gtk_builder_get_object(builder,
956 								  "progressBar"));
957 	dl_ctx.window = GTK_WINDOW(gtk_builder_get_object(builder,
958 							  "wndDownloads"));
959 	dl_ctx.parent = NULL;
960 
961 	gtk_window_set_transient_for(GTK_WINDOW(dl_ctx.window),
962 				     dl_ctx.parent);
963 	gtk_window_set_destroy_with_parent(GTK_WINDOW(dl_ctx.window),
964 					   FALSE);
965 
966 	dl_ctx.timer = g_timer_new();
967 
968 	dl_ctx.tree = nsgtk_download_tree_view_new(builder);
969 
970 	dl_ctx.store = gtk_list_store_new(NSGTK_DOWNLOAD_N_COLUMNS,
971 					  G_TYPE_INT, /* % complete */
972 					  G_TYPE_STRING, /* Description */
973 					  G_TYPE_STRING, /* Time remaining */
974 					  G_TYPE_STRING, /* Speed */
975 					  G_TYPE_INT,	/* Pulse */
976 					  G_TYPE_STRING, /* Status */
977 					  G_TYPE_POINTER /* Download structure */
978 					  );
979 
980 
981 	gtk_tree_view_set_model(dl_ctx.tree, GTK_TREE_MODEL(dl_ctx.store));
982 
983 	gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(dl_ctx.store),
984 					NSGTK_DOWNLOAD_STATUS,
985 					(GtkTreeIterCompareFunc)nsgtk_download_sort, NULL, NULL);
986 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dl_ctx.store),
987 					     NSGTK_DOWNLOAD_STATUS,
988 					     GTK_SORT_ASCENDING);
989 
990 	g_object_unref(dl_ctx.store);
991 
992 	dl_ctx.selection = gtk_tree_view_get_selection(dl_ctx.tree);
993 	gtk_tree_selection_set_mode(dl_ctx.selection, GTK_SELECTION_MULTIPLE);
994 
995 	g_signal_connect(G_OBJECT(dl_ctx.selection),
996 			 "changed",
997 			 G_CALLBACK(nsgtk_download_sensitivity_evaluate),
998 			 NULL);
999 
1000 	g_signal_connect(dl_ctx.tree,
1001 			 "row-activated",
1002 			 G_CALLBACK(nsgtk_download_tree_view_row_activated),
1003 			 NULL);
1004 
1005 	g_signal_connect_swapped(gtk_builder_get_object(builder, "buttonClear"),
1006 				 "clicked",
1007 				 G_CALLBACK(nsgtk_download_do),
1008 				 nsgtk_download_store_clear_item);
1009 
1010 	g_signal_connect_swapped(gtk_builder_get_object(builder, "buttonCancel"),
1011 				 "clicked",
1012 				 G_CALLBACK(nsgtk_download_do),
1013 				 nsgtk_download_store_cancel_item);
1014 
1015 	g_signal_connect(G_OBJECT(dl_ctx.window),
1016 			 "delete-event",
1017 			 G_CALLBACK(nsgtk_download_hide),
1018 			 NULL);
1019 
1020 	return NSERROR_OK;
1021 }
1022 
1023 
1024 /* exported interface documented in gtk/download.h */
nsgtk_download_destroy()1025 void nsgtk_download_destroy ()
1026 {
1027 	nsgtk_download_do(nsgtk_download_store_cancel_item);
1028 }
1029 
1030 
1031 /* exported interface documented in gtk/download.h */
nsgtk_check_for_downloads(GtkWindow * parent)1032 bool nsgtk_check_for_downloads(GtkWindow *parent)
1033 {
1034 	GtkWidget *dialog;
1035 	gint response;
1036 
1037 	if (dl_ctx.num_active == 0) {
1038 		return false;
1039 	}
1040 
1041 	dialog = gtk_message_dialog_new_with_markup(
1042 				parent,
1043 				GTK_DIALOG_MODAL,
1044 				GTK_MESSAGE_WARNING,
1045 				GTK_BUTTONS_NONE,
1046 				"<big><b>%s</b></big>\n\n"
1047 				"<small>%s</small>",
1048 				messages_get("gtkQuit"),
1049 				messages_get("gtkDownloadsRunning"));
1050 
1051 	gtk_dialog_add_buttons(GTK_DIALOG(dialog),
1052 			       "gtk-cancel", GTK_RESPONSE_CANCEL,
1053 			       "gtk-quit", GTK_RESPONSE_CLOSE,
1054 			       NULL);
1055 
1056 	response = gtk_dialog_run(GTK_DIALOG(dialog));
1057 	gtk_widget_destroy(dialog);
1058 
1059 	if (response == GTK_RESPONSE_CANCEL) {
1060 		return true;
1061 	}
1062 
1063 	return false;
1064 }
1065 
1066 
1067 /* exported interface documented in gtk/download.h */
nsgtk_download_show(GtkWindow * parent)1068 void nsgtk_download_show(GtkWindow *parent)
1069 {
1070 	gtk_window_set_transient_for(dl_ctx.window, dl_ctx.parent);
1071 	gtk_window_present(dl_ctx.window);
1072 }
1073