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