mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-11-29 09:43:09 +03:00
1073 lines
26 KiB
C
1073 lines
26 KiB
C
/*
|
|
* Copyright 2008 Michael Lester <element3260@gmail.com>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* NetSurf is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include "utils/log.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/nsurl.h"
|
|
#include "utils/messages.h"
|
|
#include "utils/nsoption.h"
|
|
#include "utils/string.h"
|
|
#include "desktop/download.h"
|
|
#include "netsurf/download.h"
|
|
|
|
#include "gtk/warn.h"
|
|
#include "gtk/scaffolding.h"
|
|
#include "gtk/toolbar_items.h"
|
|
#include "gtk/window.h"
|
|
#include "gtk/compat.h"
|
|
#include "gtk/resources.h"
|
|
#include "gtk/download.h"
|
|
|
|
#define UPDATE_RATE 500 /* In milliseconds */
|
|
|
|
struct download_context;
|
|
|
|
enum {
|
|
NSGTK_DOWNLOAD_PROGRESS,
|
|
NSGTK_DOWNLOAD_INFO,
|
|
NSGTK_DOWNLOAD_REMAINING,
|
|
NSGTK_DOWNLOAD_SPEED,
|
|
NSGTK_DOWNLOAD_PULSE,
|
|
NSGTK_DOWNLOAD_STATUS,
|
|
NSGTK_DOWNLOAD,
|
|
|
|
NSGTK_DOWNLOAD_N_COLUMNS
|
|
};
|
|
|
|
typedef enum {
|
|
NSGTK_DOWNLOAD_NONE,
|
|
NSGTK_DOWNLOAD_WORKING,
|
|
NSGTK_DOWNLOAD_ERROR,
|
|
NSGTK_DOWNLOAD_COMPLETE,
|
|
NSGTK_DOWNLOAD_CANCELED
|
|
} nsgtk_download_status;
|
|
|
|
typedef enum {
|
|
NSGTK_DOWNLOAD_PAUSE = 1 << 0,
|
|
NSGTK_DOWNLOAD_RESUME = 1 << 1,
|
|
NSGTK_DOWNLOAD_CANCEL = 1 << 2,
|
|
NSGTK_DOWNLOAD_CLEAR = 1 << 3
|
|
} nsgtk_download_actions;
|
|
|
|
static const gchar* status_messages[] = {
|
|
NULL,
|
|
"gtkWorking",
|
|
"gtkError",
|
|
"gtkComplete",
|
|
"gtkCanceled"
|
|
};
|
|
|
|
/**
|
|
* context for each download.
|
|
*/
|
|
struct gui_download_window {
|
|
struct download_context *ctx;
|
|
nsgtk_download_actions sensitivity;
|
|
nsgtk_download_status status;
|
|
|
|
GString *name;
|
|
GString *time_left;
|
|
unsigned long long int size_total;
|
|
unsigned long long int size_downloaded;
|
|
gint progress;
|
|
gfloat time_remaining;
|
|
gfloat start_time;
|
|
gfloat speed;
|
|
|
|
GtkTreeRowReference *row;
|
|
GIOChannel *write;
|
|
GError *error;
|
|
};
|
|
|
|
typedef void (*nsgtk_download_selection_action)(struct gui_download_window *dl,
|
|
void *user_data);
|
|
|
|
/**
|
|
* context for a nsgtk download window.
|
|
*/
|
|
struct download_window_ctx {
|
|
GtkWindow *window;
|
|
GtkWindow *parent;
|
|
|
|
GtkProgressBar *progress;
|
|
|
|
GtkTreeView *tree;
|
|
GtkListStore *store;
|
|
GtkTreeSelection *selection;
|
|
GtkTreeIter iter;
|
|
|
|
GTimer *timer;
|
|
GList *list;
|
|
GtkButton *pause;
|
|
GtkButton *clear;
|
|
GtkButton *cancel;
|
|
GtkButton *resume;
|
|
|
|
gint num_active;
|
|
};
|
|
|
|
/**
|
|
* global instance of the download window
|
|
*/
|
|
static struct download_window_ctx dl_ctx;
|
|
|
|
|
|
static GtkTreeView* nsgtk_download_tree_view_new(GtkBuilder *gladeFile)
|
|
{
|
|
GtkTreeView *treeview;
|
|
GtkCellRenderer *renderer;
|
|
|
|
treeview = GTK_TREE_VIEW(gtk_builder_get_object(gladeFile,
|
|
"treeDownloads"));
|
|
|
|
/* Progress column */
|
|
renderer = gtk_cell_renderer_progress_new();
|
|
gtk_tree_view_insert_column_with_attributes(treeview,
|
|
-1,
|
|
messages_get("gtkProgress"),
|
|
renderer,
|
|
"value",
|
|
NSGTK_DOWNLOAD_PROGRESS,
|
|
"pulse",
|
|
NSGTK_DOWNLOAD_PULSE,
|
|
"text",
|
|
NSGTK_DOWNLOAD_STATUS,
|
|
NULL);
|
|
|
|
/* Information column */
|
|
renderer = gtk_cell_renderer_text_new();
|
|
g_object_set(G_OBJECT(renderer),
|
|
"wrap-mode",
|
|
PANGO_WRAP_WORD_CHAR,
|
|
"wrap-width",
|
|
300,
|
|
NULL);
|
|
gtk_tree_view_insert_column_with_attributes(treeview,
|
|
-1,
|
|
messages_get("gtkDetails"),
|
|
renderer,
|
|
"text",
|
|
NSGTK_DOWNLOAD_INFO,
|
|
NULL);
|
|
gtk_tree_view_column_set_expand(
|
|
gtk_tree_view_get_column(treeview,
|
|
NSGTK_DOWNLOAD_INFO), TRUE);
|
|
|
|
/* Time remaining column */
|
|
renderer = gtk_cell_renderer_text_new();
|
|
gtk_tree_view_insert_column_with_attributes(treeview,
|
|
-1,
|
|
messages_get("gtkRemaining"),
|
|
renderer,
|
|
"text",
|
|
NSGTK_DOWNLOAD_REMAINING,
|
|
NULL);
|
|
|
|
/* Speed column */
|
|
renderer = gtk_cell_renderer_text_new();
|
|
gtk_tree_view_insert_column_with_attributes(treeview,
|
|
-1,
|
|
messages_get("gtkSpeed"),
|
|
renderer,
|
|
"text",
|
|
NSGTK_DOWNLOAD_SPEED,
|
|
NULL);
|
|
|
|
return treeview;
|
|
}
|
|
|
|
|
|
static gint
|
|
nsgtk_download_sort(GtkTreeModel *model,
|
|
GtkTreeIter *a,
|
|
GtkTreeIter *b,
|
|
gpointer userdata)
|
|
{
|
|
struct gui_download_window *dl1, *dl2;
|
|
|
|
gtk_tree_model_get(model, a, NSGTK_DOWNLOAD, &dl1, -1);
|
|
gtk_tree_model_get(model, b, NSGTK_DOWNLOAD, &dl2, -1);
|
|
|
|
return dl1->status - dl2->status;
|
|
}
|
|
|
|
|
|
static void
|
|
nsgtk_download_sensitivity_update_buttons(nsgtk_download_actions sensitivity)
|
|
{
|
|
/* Glade seems to pack the buttons in an arbitrary order */
|
|
enum { PAUSE_BUTTON, CLEAR_BUTTON, CANCEL_BUTTON, RESUME_BUTTON };
|
|
|
|
gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.pause),
|
|
sensitivity & NSGTK_DOWNLOAD_PAUSE);
|
|
gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.clear),
|
|
sensitivity & NSGTK_DOWNLOAD_CLEAR);
|
|
gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.cancel),
|
|
sensitivity & NSGTK_DOWNLOAD_CANCEL);
|
|
gtk_widget_set_sensitive(GTK_WIDGET(dl_ctx.resume),
|
|
sensitivity & NSGTK_DOWNLOAD_RESUME);
|
|
}
|
|
|
|
|
|
static void nsgtk_download_sensitivity_evaluate(GtkTreeSelection *selection)
|
|
{
|
|
GtkTreeIter iter;
|
|
GList *rows;
|
|
gboolean selected;
|
|
GtkTreeModel *model;
|
|
nsgtk_download_actions sensitivity = 0;
|
|
struct gui_download_window *dl;
|
|
|
|
model = GTK_TREE_MODEL(dl_ctx.store);
|
|
|
|
selected = gtk_tree_selection_count_selected_rows(selection);
|
|
if (selected) {
|
|
rows = gtk_tree_selection_get_selected_rows(selection, &model);
|
|
while (rows != NULL) {
|
|
gtk_tree_model_get_iter(model,
|
|
&iter,
|
|
(GtkTreePath*)rows->data);
|
|
gtk_tree_model_get(model,
|
|
&iter,
|
|
NSGTK_DOWNLOAD,
|
|
&dl,
|
|
-1);
|
|
sensitivity |= dl->sensitivity;
|
|
rows = rows->next;
|
|
}
|
|
} else {
|
|
rows = dl_ctx.list;
|
|
while (rows != NULL) {
|
|
dl = rows->data;
|
|
sensitivity |= (dl->sensitivity & NSGTK_DOWNLOAD_CLEAR);
|
|
rows = rows->next;
|
|
}
|
|
}
|
|
|
|
nsgtk_download_sensitivity_update_buttons(sensitivity);
|
|
}
|
|
|
|
|
|
/**
|
|
* Wrapper to GFunc-ify gtk_tree_path_free for g_list_foreach.
|
|
*/
|
|
static void
|
|
nsgtk_download_gfunc__gtk_tree_path_free(gpointer data, gpointer user_data)
|
|
{
|
|
gtk_tree_path_free(data);
|
|
}
|
|
|
|
|
|
/**
|
|
* Wrapper to GFunc-ify g_free for g_list_foreach.
|
|
*/
|
|
static void
|
|
nsgtk_download_gfunc__g_free(gpointer data, gpointer user_data)
|
|
{
|
|
g_free(data);
|
|
}
|
|
|
|
|
|
static void nsgtk_download_do(nsgtk_download_selection_action action)
|
|
{
|
|
GList *rows, *dls = NULL;
|
|
GtkTreeModel *model;
|
|
|
|
if (gtk_tree_selection_count_selected_rows(dl_ctx.selection)) {
|
|
model = GTK_TREE_MODEL(dl_ctx.store);
|
|
|
|
rows = gtk_tree_selection_get_selected_rows(dl_ctx.selection,
|
|
&model);
|
|
while (rows != NULL) {
|
|
struct gui_download_window *dl;
|
|
|
|
gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
|
|
&dl_ctx.iter,
|
|
(GtkTreePath*)rows->data);
|
|
|
|
gtk_tree_model_get(GTK_TREE_MODEL(dl_ctx.store),
|
|
&dl_ctx.iter,
|
|
NSGTK_DOWNLOAD,
|
|
&dl,
|
|
-1);
|
|
|
|
dls = g_list_prepend(dls, dl);
|
|
|
|
rows = rows->next;
|
|
}
|
|
g_list_foreach(rows,
|
|
nsgtk_download_gfunc__gtk_tree_path_free,
|
|
NULL);
|
|
g_list_foreach(rows,
|
|
nsgtk_download_gfunc__g_free,
|
|
NULL);
|
|
g_list_free(rows);
|
|
} else {
|
|
dls = g_list_copy(dl_ctx.list);
|
|
}
|
|
|
|
g_list_foreach(dls, (GFunc)action, NULL);
|
|
g_list_free(dls);
|
|
}
|
|
|
|
|
|
static gchar* nsgtk_download_info_to_string(struct gui_download_window *dl)
|
|
{
|
|
gchar *size_info;
|
|
gchar *r;
|
|
|
|
size_info = g_strdup_printf(messages_get("gtkSizeInfo"),
|
|
human_friendly_bytesize(dl->size_downloaded),
|
|
dl->size_total == 0 ?
|
|
messages_get("gtkUnknownSize") :
|
|
human_friendly_bytesize(dl->size_total));
|
|
|
|
if (dl->status != NSGTK_DOWNLOAD_ERROR) {
|
|
r = g_strdup_printf("%s\n%s", dl->name->str, size_info);
|
|
} else {
|
|
r = g_strdup_printf("%s\n%s", dl->name->str, dl->error->message);
|
|
}
|
|
|
|
g_free(size_info);
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
static gchar* nsgtk_download_time_to_string(gint seconds)
|
|
{
|
|
gint hours, minutes;
|
|
|
|
if (seconds < 0) {
|
|
return g_strdup("-");
|
|
}
|
|
|
|
hours = seconds / 3600;
|
|
seconds -= hours * 3600;
|
|
minutes = seconds / 60;
|
|
seconds -= minutes * 60;
|
|
|
|
if (hours > 0) {
|
|
return g_strdup_printf("%u:%02u:%02u",
|
|
hours,
|
|
minutes,
|
|
seconds);
|
|
} else {
|
|
return g_strdup_printf("%u:%02u", minutes, seconds);
|
|
}
|
|
}
|
|
|
|
|
|
static void nsgtk_download_store_update_item(struct gui_download_window *dl)
|
|
{
|
|
gchar *info = nsgtk_download_info_to_string(dl);
|
|
char *human = human_friendly_bytesize(dl->speed);
|
|
char speed[strlen(human) + SLEN("/s") + 1];
|
|
sprintf(speed, "%s/s", human);
|
|
gchar *time = nsgtk_download_time_to_string(dl->time_remaining);
|
|
gboolean pulse = dl->status == NSGTK_DOWNLOAD_WORKING;
|
|
|
|
/* Updates iter (which is needed to set and get data) with the dl row */
|
|
gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
|
|
&dl_ctx.iter,
|
|
gtk_tree_row_reference_get_path(dl->row));
|
|
|
|
gtk_list_store_set(dl_ctx.store, &dl_ctx.iter,
|
|
NSGTK_DOWNLOAD_PULSE, pulse ? dl->progress : -1,
|
|
NSGTK_DOWNLOAD_PROGRESS, pulse ? 0 : dl->progress,
|
|
NSGTK_DOWNLOAD_INFO, info,
|
|
NSGTK_DOWNLOAD_SPEED, dl->speed == 0 ? "-" : speed,
|
|
NSGTK_DOWNLOAD_REMAINING, time,
|
|
NSGTK_DOWNLOAD, dl,
|
|
-1);
|
|
|
|
g_free(info);
|
|
g_free(time);
|
|
}
|
|
|
|
|
|
static gboolean nsgtk_download_update(gboolean force_update)
|
|
{
|
|
/* Be sure we need to update */
|
|
if (!nsgtk_widget_get_visible(GTK_WIDGET(dl_ctx.window))) {
|
|
return TRUE;
|
|
}
|
|
|
|
GList *list;
|
|
gchar *text;
|
|
gboolean update, pulse_mode = FALSE;
|
|
unsigned long long int downloaded = 0;
|
|
unsigned long long int total = 0;
|
|
gint dls = 0;
|
|
gfloat percent, elapsed = g_timer_elapsed(dl_ctx.timer, NULL);
|
|
|
|
dl_ctx.num_active = 0;
|
|
|
|
for (list = dl_ctx.list; list != NULL; list = list->next) {
|
|
struct gui_download_window *dl = list->data;
|
|
update = force_update;
|
|
|
|
switch (dl->status) {
|
|
case NSGTK_DOWNLOAD_WORKING:
|
|
pulse_mode = TRUE;
|
|
/* Fall through */
|
|
|
|
case NSGTK_DOWNLOAD_NONE:
|
|
dl->speed = dl->size_downloaded /
|
|
(elapsed - dl->start_time);
|
|
if (dl->status == NSGTK_DOWNLOAD_NONE) {
|
|
dl->time_remaining = (dl->size_total -
|
|
dl->size_downloaded)/
|
|
dl->speed;
|
|
dl->progress = (double)dl->size_downloaded /
|
|
(double)dl->size_total * 100;
|
|
} else {
|
|
dl->progress++;
|
|
}
|
|
|
|
dl_ctx.num_active++;
|
|
update = TRUE;
|
|
/* Fall through */
|
|
|
|
case NSGTK_DOWNLOAD_COMPLETE:
|
|
downloaded += dl->size_downloaded;
|
|
total += dl->size_total;
|
|
dls++;
|
|
|
|
default:
|
|
;//Do nothing
|
|
|
|
}
|
|
if (update) {
|
|
nsgtk_download_store_update_item(dl);
|
|
}
|
|
}
|
|
|
|
if (pulse_mode) {
|
|
text = g_strdup_printf(
|
|
messages_get(dl_ctx.num_active > 1 ?
|
|
"gtkProgressBarPulse" :
|
|
"gtkProgressBarPulseSingle"),
|
|
dl_ctx.num_active);
|
|
gtk_progress_bar_pulse(dl_ctx.progress);
|
|
gtk_progress_bar_set_text(dl_ctx.progress, text);
|
|
} else {
|
|
percent = total != 0 ? (double)downloaded / (double)total : 0;
|
|
text = g_strdup_printf(messages_get("gtkProgressBar"),
|
|
floor(percent * 100), dls);
|
|
gtk_progress_bar_set_fraction(dl_ctx.progress,
|
|
percent);
|
|
gtk_progress_bar_set_text(dl_ctx.progress, text);
|
|
}
|
|
|
|
g_free(text);
|
|
|
|
if (dl_ctx.num_active == 0) {
|
|
return FALSE; /* Returning FALSE here cancels the g_timeout */
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
nsgtk_download_store_clear_item(struct gui_download_window *dl, void *user_data)
|
|
{
|
|
if (dl->sensitivity & NSGTK_DOWNLOAD_CLEAR) {
|
|
dl_ctx.list = g_list_remove(dl_ctx.list, dl);
|
|
|
|
gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
|
|
&dl_ctx.iter,
|
|
gtk_tree_row_reference_get_path(dl->row));
|
|
gtk_list_store_remove(dl_ctx.store,
|
|
&dl_ctx.iter);
|
|
|
|
download_context_destroy(dl->ctx);
|
|
g_string_free(dl->name, TRUE);
|
|
g_string_free(dl->time_left, TRUE);
|
|
g_free(dl);
|
|
|
|
nsgtk_download_sensitivity_evaluate(dl_ctx.selection);
|
|
nsgtk_download_update(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
nsgtk_download_tree_view_row_activated(GtkTreeView *tree,
|
|
GtkTreePath *path,
|
|
GtkTreeViewColumn *column,
|
|
gpointer data)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
model = gtk_tree_view_get_model(tree);
|
|
|
|
if (gtk_tree_model_get_iter(model, &iter, path)) {
|
|
/* TODO: This will be a context action (pause, start, clear) */
|
|
nsgtk_download_do(nsgtk_download_store_clear_item);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
nsgtk_download_change_sensitivity(struct gui_download_window *dl,
|
|
nsgtk_download_actions sensitivity)
|
|
{
|
|
dl->sensitivity = sensitivity;
|
|
nsgtk_download_sensitivity_evaluate(dl_ctx.selection);
|
|
}
|
|
|
|
|
|
static void
|
|
nsgtk_download_change_status(struct gui_download_window *dl,
|
|
nsgtk_download_status status)
|
|
{
|
|
dl->status = status;
|
|
if (status != NSGTK_DOWNLOAD_NONE) {
|
|
gtk_tree_model_get_iter(GTK_TREE_MODEL(dl_ctx.store),
|
|
&dl_ctx.iter,
|
|
gtk_tree_row_reference_get_path(dl->row));
|
|
|
|
gtk_list_store_set(dl_ctx.store, &dl_ctx.iter,
|
|
NSGTK_DOWNLOAD_STATUS,
|
|
messages_get(status_messages[status]), -1);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
nsgtk_download_store_cancel_item(struct gui_download_window *dl,
|
|
void *user_data)
|
|
{
|
|
if (dl->sensitivity & NSGTK_DOWNLOAD_CANCEL) {
|
|
dl->speed = 0;
|
|
dl->size_downloaded = 0;
|
|
dl->progress = 0;
|
|
dl->time_remaining = -1;
|
|
nsgtk_download_change_sensitivity(dl, NSGTK_DOWNLOAD_CLEAR);
|
|
nsgtk_download_change_status(dl, NSGTK_DOWNLOAD_CANCELED);
|
|
|
|
download_context_abort(dl->ctx);
|
|
|
|
g_unlink(download_context_get_filename(dl->ctx));
|
|
|
|
nsgtk_download_update(TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean nsgtk_download_hide(GtkWidget *window)
|
|
{
|
|
gtk_widget_hide(window);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Prompt user for downloaded file name
|
|
*
|
|
* \param filename The original name of the file
|
|
* \param domain the domain the file is being downloaded from
|
|
* \param size The size of the file being downloaded
|
|
*/
|
|
static gchar*
|
|
nsgtk_download_dialog_show(const gchar *filename,
|
|
const gchar *domain,
|
|
const gchar *size)
|
|
{
|
|
enum { GTK_RESPONSE_DOWNLOAD, GTK_RESPONSE_SAVE_AS };
|
|
GtkWidget *dialog;
|
|
char *destination = NULL;
|
|
gchar *message;
|
|
gchar *info;
|
|
|
|
message = g_strdup(messages_get("gtkStartDownload"));
|
|
info = g_strdup_printf(messages_get("gtkInfo"), filename, domain, size);
|
|
|
|
dialog = gtk_message_dialog_new_with_markup(
|
|
dl_ctx.parent,
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
|
|
"<span size=\"x-large\" weight=\"ultrabold\">%s</span>"
|
|
"\n\n<small>%s</small>",
|
|
message,
|
|
info);
|
|
|
|
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
|
|
NSGTK_STOCK_SAVE, GTK_RESPONSE_DOWNLOAD,
|
|
NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
NSGTK_STOCK_SAVE_AS, GTK_RESPONSE_SAVE_AS,
|
|
NULL);
|
|
|
|
gint result = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
g_free(message);
|
|
g_free(info);
|
|
|
|
switch (result) {
|
|
case GTK_RESPONSE_SAVE_AS: {
|
|
dialog = gtk_file_chooser_dialog_new(
|
|
messages_get("gtkSave"),
|
|
dl_ctx.parent,
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
NSGTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
gtk_file_chooser_set_current_name
|
|
(GTK_FILE_CHOOSER(dialog), filename);
|
|
gtk_file_chooser_set_current_folder
|
|
(GTK_FILE_CHOOSER(dialog),
|
|
nsoption_charp(downloads_directory));
|
|
gtk_file_chooser_set_do_overwrite_confirmation
|
|
(GTK_FILE_CHOOSER(dialog),
|
|
nsoption_bool(request_overwrite));
|
|
|
|
gint result = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
if (result == GTK_RESPONSE_ACCEPT)
|
|
destination = gtk_file_chooser_get_filename
|
|
(GTK_FILE_CHOOSER(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
break;
|
|
}
|
|
case GTK_RESPONSE_DOWNLOAD: {
|
|
destination = malloc(strlen(nsoption_charp(downloads_directory))
|
|
+ strlen(filename) + SLEN("/") + 1);
|
|
if (destination == NULL) {
|
|
nsgtk_warning(messages_get("NoMemory"), 0);
|
|
break;
|
|
}
|
|
sprintf(destination, "%s/%s",
|
|
nsoption_charp(downloads_directory), filename);
|
|
/* Test if file already exists and display overwrite
|
|
* confirmation if needed */
|
|
if (g_file_test(destination, G_FILE_TEST_EXISTS) &&
|
|
nsoption_bool(request_overwrite)) {
|
|
GtkWidget *button;
|
|
|
|
message = g_strdup_printf(messages_get("gtkOverwrite"),
|
|
filename);
|
|
info = g_strdup_printf(messages_get("gtkOverwriteInfo"),
|
|
nsoption_charp(downloads_directory));
|
|
|
|
dialog = gtk_message_dialog_new_with_markup(
|
|
dl_ctx.parent,
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION,
|
|
GTK_BUTTONS_CANCEL,
|
|
"<b>%s</b>",
|
|
message);
|
|
gtk_message_dialog_format_secondary_markup(
|
|
GTK_MESSAGE_DIALOG(dialog),
|
|
"%s",
|
|
info);
|
|
|
|
button = gtk_dialog_add_button(GTK_DIALOG(dialog),
|
|
"_Replace",
|
|
GTK_RESPONSE_DOWNLOAD);
|
|
gtk_button_set_image(GTK_BUTTON(button),
|
|
nsgtk_image_new_from_stock(
|
|
NSGTK_STOCK_SAVE,
|
|
GTK_ICON_SIZE_BUTTON));
|
|
|
|
gint result = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
if (result == GTK_RESPONSE_CANCEL)
|
|
destination = NULL;
|
|
|
|
gtk_widget_destroy(dialog);
|
|
g_free(message);
|
|
g_free(info);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return destination;
|
|
}
|
|
|
|
|
|
static gboolean nsgtk_download_handle_error(GError *error)
|
|
{
|
|
GtkWidget*dialog;
|
|
gchar *message;
|
|
|
|
if (error != NULL) {
|
|
message = g_strdup_printf(messages_get("gtkFileError"),
|
|
error->message);
|
|
|
|
dialog = gtk_message_dialog_new_with_markup(
|
|
dl_ctx.parent,
|
|
GTK_DIALOG_MODAL,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_OK,
|
|
"<big><b>%s</b></big>\n\n"
|
|
"<small>%s</small>",
|
|
messages_get("gtkFailed"),
|
|
message);
|
|
|
|
gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void nsgtk_download_store_create_item(struct gui_download_window *dl)
|
|
{
|
|
nsgtk_download_store_update_item(dl);
|
|
/* The iter has already been updated to this row */
|
|
gtk_list_store_set(dl_ctx.store,
|
|
&dl_ctx.iter,
|
|
NSGTK_DOWNLOAD,
|
|
dl,
|
|
-1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Wrapper to GSourceFunc-ify nsgtk_download_update.
|
|
*/
|
|
static gboolean
|
|
nsgtk_download_gsourcefunc__nsgtk_download_update(gpointer user_data)
|
|
{
|
|
return nsgtk_download_update(FALSE);
|
|
}
|
|
|
|
|
|
/**
|
|
* core callback on creating a new download
|
|
*/
|
|
static struct gui_download_window *
|
|
gui_download_window_create(download_context *ctx, struct gui_window *gui)
|
|
{
|
|
nsurl *url;
|
|
unsigned long long int total_size;
|
|
gchar *domain;
|
|
gchar *destination;
|
|
gboolean unknown_size;
|
|
struct gui_download_window *download;
|
|
const char *size;
|
|
|
|
url = download_context_get_url(ctx);
|
|
total_size = download_context_get_total_length(ctx);
|
|
unknown_size = total_size == 0;
|
|
size = (total_size == 0 ?
|
|
messages_get("gtkUnknownSize") :
|
|
human_friendly_bytesize(total_size));
|
|
|
|
dl_ctx.parent = nsgtk_scaffolding_window(nsgtk_get_scaffold(gui));
|
|
|
|
download = malloc(sizeof *download);
|
|
if (download == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* set the domain to the host component of the url if it exists */
|
|
if (nsurl_has_component(url, NSURL_HOST)) {
|
|
domain = g_strdup(lwc_string_data(nsurl_get_component(url, NSURL_HOST)));
|
|
} else {
|
|
domain = g_strdup(messages_get("gtkUnknownHost"));
|
|
}
|
|
if (domain == NULL) {
|
|
free(download);
|
|
return NULL;
|
|
}
|
|
|
|
/* show the dialog */
|
|
destination = nsgtk_download_dialog_show(
|
|
download_context_get_filename(ctx), domain, size);
|
|
if (destination == NULL) {
|
|
g_free(domain);
|
|
free(download);
|
|
return NULL;
|
|
}
|
|
|
|
/* Add the new row and store the reference to it (which keeps track of
|
|
* the tree changes) */
|
|
gtk_list_store_prepend(dl_ctx.store, &dl_ctx.iter);
|
|
download->row = gtk_tree_row_reference_new(
|
|
GTK_TREE_MODEL(dl_ctx.store),
|
|
gtk_tree_model_get_path(
|
|
GTK_TREE_MODEL(dl_ctx.store),
|
|
&dl_ctx.iter));
|
|
|
|
download->ctx = ctx;
|
|
download->name = g_string_new(download_context_get_filename(ctx));
|
|
download->time_left = g_string_new("");
|
|
download->size_total = total_size;
|
|
download->size_downloaded = 0;
|
|
download->speed = 0;
|
|
download->start_time = g_timer_elapsed(dl_ctx.timer, NULL);
|
|
download->time_remaining = -1;
|
|
download->status = NSGTK_DOWNLOAD_NONE;
|
|
download->progress = 0;
|
|
download->error = NULL;
|
|
download->write = g_io_channel_new_file(destination,
|
|
"w",
|
|
&download->error);
|
|
|
|
if (nsgtk_download_handle_error(download->error)) {
|
|
g_string_free(download->name, TRUE);
|
|
g_string_free(download->time_left, TRUE);
|
|
free(download);
|
|
return NULL;
|
|
}
|
|
g_io_channel_set_encoding(download->write, NULL, &download->error);
|
|
|
|
nsgtk_download_change_sensitivity(download, NSGTK_DOWNLOAD_CANCEL);
|
|
|
|
nsgtk_download_store_create_item(download);
|
|
nsgtk_download_show(dl_ctx.parent);
|
|
|
|
if (unknown_size) {
|
|
nsgtk_download_change_status(download, NSGTK_DOWNLOAD_WORKING);
|
|
}
|
|
|
|
if (dl_ctx.num_active == 0) {
|
|
g_timeout_add(
|
|
UPDATE_RATE,
|
|
nsgtk_download_gsourcefunc__nsgtk_download_update,
|
|
NULL);
|
|
}
|
|
|
|
dl_ctx.list = g_list_prepend(dl_ctx.list, download);
|
|
|
|
return download;
|
|
}
|
|
|
|
|
|
/**
|
|
* core callback on receipt of data
|
|
*/
|
|
static nserror
|
|
gui_download_window_data(struct gui_download_window *dw,
|
|
const char *data,
|
|
unsigned int size)
|
|
{
|
|
g_io_channel_write_chars(dw->write, data, size, NULL, &dw->error);
|
|
if (dw->error != NULL) {
|
|
dw->speed = 0;
|
|
dw->time_remaining = -1;
|
|
|
|
nsgtk_download_change_sensitivity(dw, NSGTK_DOWNLOAD_CLEAR);
|
|
nsgtk_download_change_status(dw, NSGTK_DOWNLOAD_ERROR);
|
|
|
|
nsgtk_download_update(TRUE);
|
|
|
|
gtk_window_present(dl_ctx.window);
|
|
|
|
return NSERROR_SAVE_FAILED;
|
|
}
|
|
dw->size_downloaded += size;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* core callback on error
|
|
*/
|
|
static void
|
|
gui_download_window_error(struct gui_download_window *dw, const char *error_msg)
|
|
{
|
|
}
|
|
|
|
|
|
/**
|
|
* core callback when core download is complete
|
|
*/
|
|
static void gui_download_window_done(struct gui_download_window *dw)
|
|
{
|
|
g_io_channel_shutdown(dw->write, TRUE, &dw->error);
|
|
g_io_channel_unref(dw->write);
|
|
|
|
dw->speed = 0;
|
|
dw->time_remaining = -1;
|
|
dw->progress = 100;
|
|
dw->size_total = dw->size_downloaded;
|
|
nsgtk_download_change_sensitivity(dw, NSGTK_DOWNLOAD_CLEAR);
|
|
nsgtk_download_change_status(dw, NSGTK_DOWNLOAD_COMPLETE);
|
|
|
|
if (nsoption_bool(downloads_clear)) {
|
|
nsgtk_download_store_clear_item(dw, NULL);
|
|
} else {
|
|
nsgtk_download_update(TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
static struct gui_download_table download_table = {
|
|
.create = gui_download_window_create,
|
|
.data = gui_download_window_data,
|
|
.error = gui_download_window_error,
|
|
.done = gui_download_window_done,
|
|
};
|
|
|
|
struct gui_download_table *nsgtk_download_table = &download_table;
|
|
|
|
|
|
/* exported interface documented in gtk/download.h */
|
|
nserror nsgtk_download_init(void)
|
|
{
|
|
GtkBuilder* builder;
|
|
nserror res;
|
|
|
|
res = nsgtk_builder_new_from_resname("downloads", &builder);
|
|
if (res != NSERROR_OK) {
|
|
NSLOG(netsurf, INFO, "Download UI builder init failed");
|
|
return res;
|
|
}
|
|
|
|
gtk_builder_connect_signals(builder, NULL);
|
|
|
|
dl_ctx.pause = GTK_BUTTON(gtk_builder_get_object(builder,
|
|
"buttonPause"));
|
|
dl_ctx.clear = GTK_BUTTON(gtk_builder_get_object(builder,
|
|
"buttonClear"));
|
|
dl_ctx.cancel = GTK_BUTTON(gtk_builder_get_object(builder,
|
|
"buttonCancel"));
|
|
dl_ctx.resume = GTK_BUTTON(gtk_builder_get_object(builder,
|
|
"buttonPlay"));
|
|
|
|
dl_ctx.progress = GTK_PROGRESS_BAR(gtk_builder_get_object(builder,
|
|
"progressBar"));
|
|
dl_ctx.window = GTK_WINDOW(gtk_builder_get_object(builder,
|
|
"wndDownloads"));
|
|
dl_ctx.parent = NULL;
|
|
|
|
gtk_window_set_transient_for(GTK_WINDOW(dl_ctx.window),
|
|
dl_ctx.parent);
|
|
gtk_window_set_destroy_with_parent(GTK_WINDOW(dl_ctx.window),
|
|
FALSE);
|
|
|
|
dl_ctx.timer = g_timer_new();
|
|
|
|
dl_ctx.tree = nsgtk_download_tree_view_new(builder);
|
|
|
|
dl_ctx.store = gtk_list_store_new(NSGTK_DOWNLOAD_N_COLUMNS,
|
|
G_TYPE_INT, /* % complete */
|
|
G_TYPE_STRING, /* Description */
|
|
G_TYPE_STRING, /* Time remaining */
|
|
G_TYPE_STRING, /* Speed */
|
|
G_TYPE_INT, /* Pulse */
|
|
G_TYPE_STRING, /* Status */
|
|
G_TYPE_POINTER /* Download structure */
|
|
);
|
|
|
|
|
|
gtk_tree_view_set_model(dl_ctx.tree, GTK_TREE_MODEL(dl_ctx.store));
|
|
|
|
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(dl_ctx.store),
|
|
NSGTK_DOWNLOAD_STATUS,
|
|
(GtkTreeIterCompareFunc)nsgtk_download_sort, NULL, NULL);
|
|
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dl_ctx.store),
|
|
NSGTK_DOWNLOAD_STATUS,
|
|
GTK_SORT_ASCENDING);
|
|
|
|
g_object_unref(dl_ctx.store);
|
|
|
|
dl_ctx.selection = gtk_tree_view_get_selection(dl_ctx.tree);
|
|
gtk_tree_selection_set_mode(dl_ctx.selection, GTK_SELECTION_MULTIPLE);
|
|
|
|
g_signal_connect(G_OBJECT(dl_ctx.selection),
|
|
"changed",
|
|
G_CALLBACK(nsgtk_download_sensitivity_evaluate),
|
|
NULL);
|
|
|
|
g_signal_connect(dl_ctx.tree,
|
|
"row-activated",
|
|
G_CALLBACK(nsgtk_download_tree_view_row_activated),
|
|
NULL);
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "buttonClear"),
|
|
"clicked",
|
|
G_CALLBACK(nsgtk_download_do),
|
|
nsgtk_download_store_clear_item);
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "buttonCancel"),
|
|
"clicked",
|
|
G_CALLBACK(nsgtk_download_do),
|
|
nsgtk_download_store_cancel_item);
|
|
|
|
g_signal_connect(G_OBJECT(dl_ctx.window),
|
|
"delete-event",
|
|
G_CALLBACK(nsgtk_download_hide),
|
|
NULL);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported interface documented in gtk/download.h */
|
|
void nsgtk_download_destroy ()
|
|
{
|
|
nsgtk_download_do(nsgtk_download_store_cancel_item);
|
|
}
|
|
|
|
|
|
/* exported interface documented in gtk/download.h */
|
|
bool nsgtk_check_for_downloads(GtkWindow *parent)
|
|
{
|
|
GtkWidget *dialog;
|
|
gint response;
|
|
|
|
if (dl_ctx.num_active == 0) {
|
|
return false;
|
|
}
|
|
|
|
dialog = gtk_message_dialog_new_with_markup(
|
|
parent,
|
|
GTK_DIALOG_MODAL,
|
|
GTK_MESSAGE_WARNING,
|
|
GTK_BUTTONS_NONE,
|
|
"<big><b>%s</b></big>\n\n"
|
|
"<small>%s</small>",
|
|
messages_get("gtkQuit"),
|
|
messages_get("gtkDownloadsRunning"));
|
|
|
|
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
|
|
"gtk-cancel", GTK_RESPONSE_CANCEL,
|
|
"gtk-quit", GTK_RESPONSE_CLOSE,
|
|
NULL);
|
|
|
|
response = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
|
|
if (response == GTK_RESPONSE_CANCEL) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* exported interface documented in gtk/download.h */
|
|
void nsgtk_download_show(GtkWindow *parent)
|
|
{
|
|
gtk_window_set_transient_for(dl_ctx.window, dl_ctx.parent);
|
|
gtk_window_present(dl_ctx.window);
|
|
}
|