From: Christian Dywan Date: Sun, 9 Sep 2012 11:42:59 +0000 (+0200) Subject: Rework downloading into Midori.Download interface X-Git-Url: https://spindle.queued.net/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e3a71596bc740b41c60dc54f8642470c1641e371;p=midori Rework downloading into Midori.Download interface Midori.URI get_folder Midori.Download is_finished get/set_type get_progress get_tooltip Consistent estimation of remaining time and speed. get_content_type Icons in dialog and panel. has_wrong_checksum Verification in both panel and toolbar. action_clear, action_stock_id, open Consistent behavior in both panel and toolbar. fallback_extension clean_filename Merged two separate functions. get_suggested_filename get_filename_suggestion_for_uri Now handles MIME types. get_extension_for_uri get_unique_filename Renamed from prepare_filename. prepare_destination_uri has_enough_space --- diff --git a/katze/midori-uri.vala b/katze/midori-uri.vala index 21681cb0..adefff83 100644 --- a/katze/midori-uri.vala +++ b/katze/midori-uri.vala @@ -145,6 +145,21 @@ namespace Midori { && uri.chr (-1, ' ') == null && (URI.is_location (uri) || uri.chr (-1, '.') != null); } + + public static string? get_folder (string uri) { + /* Base the start folder on the current view's uri if it is local */ + try { + string? filename = Filename.from_uri (uri); + if (filename != null) { + string? dirname = Path.get_dirname (filename); + if (dirname != null && FileUtils.test (dirname, FileTest.IS_DIR)) + return dirname; + } + } + catch (Error error) { } + return null; + } + public static GLib.ChecksumType get_fingerprint (string uri, out string checksum, out string label) { diff --git a/midori/midori-browser.c b/midori/midori-browser.c index 0255b023..a2d7ea24 100644 --- a/midori/midori-browser.c +++ b/midori/midori-browser.c @@ -983,54 +983,8 @@ midori_browser_prepare_download (MidoriBrowser* browser, const gchar* uri) { - guint64 total_size = webkit_download_get_total_size (download); - GFile* file = g_file_new_for_uri (uri); - GFile* folder = g_file_get_parent (file); - GError* error = NULL; - GFileInfo* info = g_file_query_filesystem_info (folder, - G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, &error); - guint64 free_space = g_file_info_get_attribute_uint64 (info, - G_FILE_ATTRIBUTE_FILESYSTEM_FREE); - gchar* path = g_file_get_path (folder); - gboolean can_write = g_access (path, W_OK) == 0; - g_free (path); - g_object_unref (file); - g_object_unref (folder); - if (free_space < total_size || !can_write) - { - gchar* message; - gchar* detailed_message; - - if (!can_write) - { - message = g_strdup_printf ( - _("The file \"%s\" can't be saved in this folder."), &uri[7]); - detailed_message = g_strdup_printf ( - _("You don't have permission to write in this location.")); - } - else if (free_space < total_size) - { - gchar* total_size_string = g_format_size (total_size); - gchar* free_space_string = g_format_size (free_space); - message = g_strdup_printf ( - _("There is not enough free space to download \"%s\"."), - &uri[7]); - detailed_message = g_strdup_printf ( - _("The file needs %s but only %s are left."), - total_size_string, free_space_string); - g_free (total_size_string); - g_free (free_space_string); - } - else - g_assert_not_reached (); - - sokoke_message_dialog (GTK_MESSAGE_ERROR, message, detailed_message, FALSE); - g_free (message); - g_free (detailed_message); - g_object_unref (download); + if (!midori_download_has_enough_space (download, uri)) return FALSE; - } - webkit_download_set_destination_uri (download, uri); g_signal_emit (browser, signals[ADD_DOWNLOAD], 0, download); midori_transferbar_add_download_item (MIDORI_TRANSFERBAR (browser->transferbar), download); @@ -1039,61 +993,6 @@ midori_browser_prepare_download (MidoriBrowser* browser, return TRUE; } -static gchar* -midori_browser_get_folder_for_uri (MidoriBrowser* browser, - const gchar* uri) -{ - if (uri) - { - /* Base the start folder on the current view's uri if it is local */ - gchar* filename = g_filename_from_uri (uri, NULL, NULL); - if (filename) - { - gchar* dirname = g_path_get_dirname (filename); - g_free (filename); - if (dirname && g_file_test (dirname, G_FILE_TEST_IS_DIR)) - return dirname; - } - } - return katze_object_get_string (browser->settings, "download-folder"); -} - -static gchar* -midori_browser_fixup_filename (gchar* filename) -{ - #ifdef G_OS_WIN32 - g_strdelimit (filename, "/\\<>:\"|?*", '_'); - #else - g_strdelimit (filename, "/", '_'); - #endif - return filename; -} - -static gchar* -midori_browser_get_filename_suggestion_for_uri (MidoriView* view, - const gchar* uri) -{ - /* Try to provide a good default filename, UTF-8 encoded */ - gchar* filename = soup_uri_decode (uri); - gchar* last_slash = g_strrstr (filename, "/") + 1; - /* Take the rest of the URI if needed */ - if (*last_slash == '\0') - { - const gchar* extension = midori_view_fallback_extension (view, NULL); - gchar* guessed; - last_slash = midori_browser_fixup_filename (filename); - guessed = g_strconcat (filename, extension, NULL); - g_free (filename); - return guessed; - } - last_slash = g_strdup (last_slash); - g_free (filename); - return midori_browser_fixup_filename (last_slash); -} - -static gchar* -midori_browser_download_prepare_filename (gchar* filename); - static void midori_browser_save_resources (MidoriView* view, const gchar* folder) @@ -1115,11 +1014,11 @@ midori_browser_save_resources (MidoriView* view, if (!g_strcmp0 (webkit_web_resource_get_uri (resource), "about:blank")) continue; - /* FIXME: mime type fallback should respect the resource's type */ - gchar* sub_filename = midori_browser_get_filename_suggestion_for_uri ( - view, webkit_web_resource_get_uri (resource)); + gchar* sub_filename = midori_download_get_filename_suggestion_for_uri ( + webkit_web_resource_get_mime_type (resource), + webkit_web_resource_get_uri (resource)); gchar* sub_path = g_build_filename (folder, sub_filename, NULL); - sub_path = midori_browser_download_prepare_filename (sub_path); + sub_path = midori_download_get_unique_filename (sub_path); if (data) { GError* error = NULL; @@ -1155,6 +1054,10 @@ midori_browser_save_uri (MidoriBrowser* browser, dialog = sokoke_file_chooser_dialog_new (_("Save file as"), GTK_WINDOW (browser), GTK_FILE_CHOOSER_ACTION_SAVE); gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); + + if (uri == NULL) + uri = midori_view_get_display_uri (view); + if (midori_view_can_view_source (view)) { file_only = FALSE; @@ -1162,21 +1065,26 @@ midori_browser_save_uri (MidoriBrowser* browser, gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbox), TRUE); gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), checkbox); } + if (last_dir && *last_dir) gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), last_dir); else { - gchar* dirname = midori_browser_get_folder_for_uri (browser, uri); + gchar* dirname = midori_uri_get_folder (uri); + if (dirname == NULL) + dirname = katze_object_get_string (browser->settings, "download-folder"); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), dirname); g_free (dirname); } - if (uri == NULL) - uri = midori_view_get_display_uri (view); if (!file_only && !g_str_equal (title, uri)) - filename = midori_browser_fixup_filename (g_strdup (title)); + filename = midori_download_clean_filename (title); else - filename = midori_browser_get_filename_suggestion_for_uri (view, uri); + { + gchar* mime_type = katze_object_get_object (view, "mime-type"); + filename = midori_download_get_filename_suggestion_for_uri (mime_type, uri); + g_free (mime_type); + } gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename); g_free (filename); @@ -1437,70 +1345,6 @@ midori_browser_download_status_cb (WebKitDownload* download, } } -static gchar* -midori_browser_download_prepare_filename (gchar* filename) -{ - if (g_file_test (filename, G_FILE_TEST_EXISTS)) - { - int i = 1; - const gchar* dot_pos; - const gchar* last_separator; - gchar* serial; - GString* tmp_filename; - gssize position; - - last_separator = strrchr (filename, G_DIR_SEPARATOR); - dot_pos = strrchr ((last_separator) ? last_separator : filename, '.'); - position = dot_pos ? (dot_pos - filename) : (gssize) strlen (filename); - tmp_filename = g_string_new (NULL); - - do - { - serial = g_strdup_printf ("-%d", i++); - g_string_assign (tmp_filename, filename); - g_string_insert (tmp_filename, position, serial); - g_free (serial); - } while (g_file_test (tmp_filename->str, G_FILE_TEST_EXISTS)); - - g_free (filename); - filename = g_string_free (tmp_filename, FALSE); - } - return filename; -} - -static gchar* -midori_browser_download_prepare_destination_uri (WebKitDownload* download, - const gchar* folder) -{ - gchar* suggested_filename; - GFile* file_source; - gchar* file_basename; - const gchar* download_dir = NULL; - gchar* destination_uri; - gchar* destination_filename; - - suggested_filename = sokoke_get_download_filename (download); - file_source = g_file_new_for_uri (suggested_filename); - g_free (suggested_filename); - file_basename = g_file_get_basename (file_source); - if (folder == NULL) - { - download_dir = midori_paths_get_tmp_dir (); - katze_mkdir_with_parents (download_dir, 0700); - } - else - download_dir = folder; - destination_filename = g_build_filename (download_dir, file_basename, NULL); - destination_filename = midori_browser_download_prepare_filename (destination_filename); - destination_uri = g_filename_to_uri (destination_filename, NULL, NULL); - - g_free (file_basename); - g_free (destination_filename); - g_object_unref (file_source); - - return destination_uri; -} - static gboolean midori_browser_remove_tab_idle (gpointer view) { @@ -1517,8 +1361,7 @@ midori_view_download_requested_cb (GtkWidget* view, WebKitDownload* download, MidoriBrowser* browser) { - MidoriDownloadType type = GPOINTER_TO_INT ( - g_object_get_data (G_OBJECT (download), "midori-download-type")); + MidoriDownloadType type = midori_download_get_type (download); GtkWidget* web_view; WebKitWebFrame* web_frame; WebKitWebDataSource* datasource; @@ -1533,7 +1376,7 @@ midori_view_download_requested_cb (GtkWidget* view, else if (type == MIDORI_DOWNLOAD_OPEN_IN_VIEWER) { gchar* destination_uri = - midori_browser_download_prepare_destination_uri (download, NULL); + midori_download_prepare_destination_uri (download, NULL); midori_browser_prepare_download (browser, download, destination_uri); g_signal_connect (download, "notify::status", G_CALLBACK (midori_browser_download_status_cb), GTK_WIDGET (browser)); @@ -1555,15 +1398,16 @@ midori_view_download_requested_cb (GtkWidget* view, gtk_file_chooser_set_do_overwrite_confirmation ( GTK_FILE_CHOOSER (dialog), TRUE); gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); - folder = midori_browser_get_folder_for_uri (browser, - webkit_download_get_uri (download)); + folder = midori_uri_get_folder (webkit_download_get_uri (download)); + if (folder == NULL) + folder = katze_object_get_string (browser->settings, "download-folder"); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), folder); g_free (folder); g_signal_connect (dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &dialog); } g_object_set_data (G_OBJECT (dialog), "download", download); - filename = sokoke_get_download_filename (download); + filename = midori_download_get_suggested_filename (download); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename); g_free (filename); @@ -1589,7 +1433,7 @@ midori_view_download_requested_cb (GtkWidget* view, gchar* folder = type == MIDORI_DOWNLOAD_OPEN ? NULL : katze_object_get_string (browser->settings, "download-folder"); gchar* destination_uri = - midori_browser_download_prepare_destination_uri (download, folder); + midori_download_prepare_destination_uri (download, folder); midori_browser_prepare_download (browser, download, destination_uri); g_free (destination_uri); } diff --git a/midori/midori-download.vala b/midori/midori-download.vala new file mode 100644 index 00000000..994a7ce0 --- /dev/null +++ b/midori/midori-download.vala @@ -0,0 +1,310 @@ +/* + Copyright (C) 2012 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +namespace Sokoke { + extern static bool show_uri (Gdk.Screen screen, string uri, uint32 timestamp) throws Error; + extern static bool message_dialog (Gtk.MessageType type, string short, string detailed, bool modal); +} + +namespace Midori { + namespace Download { + public static bool is_finished (WebKit.Download download) { + switch (download.status) { + case WebKit.DownloadStatus.FINISHED: + case WebKit.DownloadStatus.CANCELLED: + case WebKit.DownloadStatus.ERROR: + return true; + default: + return false; + } + } + + public static int get_type (WebKit.Download download) { + return download.get_data ("midori-download-type"); + } + + public static void set_type (WebKit.Download download, int type) { + download.set_data ("midori-download-type", type); + } + + public static double get_progress (WebKit.Download download) { + /* Avoid a bug in WebKit */ + if (download.status == WebKit.DownloadStatus.CREATED) + return 0.0; + return download.progress; + } + + public static string get_tooltip (WebKit.Download download) { + string filename = Path.get_basename (download.destination_uri); + /* i18n: Download tooltip (size): 4KB of 43MB */ + string size = _("%s of %s").printf ( + format_size (download.current_size), + format_size (download.total_size)); + + uint64 total_size = download.total_size, current_size = download.current_size; + double elapsed = download.get_elapsed_time (), + diff = elapsed / current_size, + estimated = (total_size - current_size) * diff; + int hour = 3600, minute = 60; + int hours = (int)(estimated / hour), + minutes = (int)((estimated - (hours * hour)) / minute), + seconds = (int)((estimated - (hours * hour) - (minutes * minute))); + string hours_ = ngettext ("%d hour", "%d hours", hours).printf (hours); + string minutes_ = ngettext ("%d minute", "%d minutes", minutes).printf (minutes); + string seconds_ = ngettext ("%d second", "%d seconds", seconds).printf (seconds); + double last_time = download.get_data ("last-time"); + uint64 last_size = download.get_data ("last-size"); + + string eta = ""; + if (estimated > 0) { + if (hours > 0) + eta = hours_ + ", " + minutes_; + else if (minutes >= 10) + eta = minutes_; + else if (minutes < 10 && minutes > 0) + eta = minutes_ + ", " + seconds_; + else if (seconds > 0) + eta = seconds_; + if (eta != "") + /* i18n: Download tooltip (estimated time) : - 1 hour, 5 minutes remaning */ + eta = _(" - %s remaining").printf (eta); + } + + string speed; + if (elapsed != last_time) + speed = format_size ((int)((current_size - last_size) / (elapsed - last_time))); + else + /* i18n: Unknown number of bytes, used for transfer rate like ?B/s */ + speed = _("?B"); + /* i18n: Download tooltip (transfer rate): (130KB/s) */ + speed = _(" (%s/s)").printf (speed); + if (elapsed - last_time > 5.0) { + download.set_data ("last-time", (int)elapsed); + download.set_data ("last-size", current_size); + } + + return "%s\n%s %s%s".printf (filename, size, speed, eta); + } + + public static string get_content_type (WebKit.Download download, string? mime_type) { + string? content_type = ContentType.guess (download.suggested_filename, null, null); + if (content_type == null) { + content_type = ContentType.from_mime_type (mime_type); + if (content_type == null) + content_type = ContentType.from_mime_type ("application/octet-stream"); + } + return content_type; + } + + public static bool has_wrong_checksum (WebKit.Download download) { + int status = download.get_data ("checksum-status"); + if (status == 0) { + /* Link Fingerprint */ + string? original_uri = download.network_request.get_data ("midori-original-uri"); + if (original_uri == null) + original_uri = download.get_uri (); + string? fingerprint; + ChecksumType checksum_type = URI.get_fingerprint (original_uri, out fingerprint, null); + /* By default, no wrong checksum */ + status = 2; + if (fingerprint != null) { + try { + string filename = Filename.from_uri (download.destination_uri); + string contents; + size_t length; + bool y = FileUtils.get_contents (filename, out contents, out length); + string checksum = Checksum.compute_for_string (checksum_type, contents, length); + /* Checksums are case-insensitive */ + if (!y || fingerprint.ascii_casecmp (checksum) != 0) + status = 1; /* wrong checksum */ + } + catch (Error error) { + status = 1; /* wrong checksum */ + } + } + download.set_data ("checksum-status", status); + } + return status == 1; + } + + public static bool action_clear (WebKit.Download download, Gtk.Widget widget) throws Error { + switch (download.status) { + case WebKit.DownloadStatus.CREATED: + case WebKit.DownloadStatus.STARTED: + download.cancel (); + break; + case WebKit.DownloadStatus.FINISHED: + if (open (download, widget)) + return true; + break; + case WebKit.DownloadStatus.CANCELLED: + return true; + default: + critical ("action_clear: %d", download.status); + warn_if_reached (); + break; + } + return false; + } + + public static string action_stock_id (WebKit.Download download) { + switch (download.status) { + case WebKit.DownloadStatus.CREATED: + case WebKit.DownloadStatus.STARTED: + return Gtk.Stock.CANCEL; + case WebKit.DownloadStatus.FINISHED: + if (has_wrong_checksum (download)) + return Gtk.Stock.DIALOG_WARNING; + return Gtk.Stock.OPEN; + case WebKit.DownloadStatus.CANCELLED: + return Gtk.Stock.CLEAR; + default: + critical ("action_stock_id: %d", download.status); + warn_if_reached (); + return Gtk.Stock.MISSING_IMAGE; + } + } + + public static bool open (WebKit.Download download, Gtk.Widget widget) throws Error { + if (!has_wrong_checksum (download)) + return Sokoke.show_uri (widget.get_screen (), + download.destination_uri, Gtk.get_current_event_time ()); + + Sokoke.message_dialog (Gtk.MessageType.WARNING, + _("The downloaded file is erroneous."), + _("The checksum provided with the link did not match. This means the file is probably incomplete or was modified afterwards."), + true); + return false; + } + + public unowned string fallback_extension (string? extension, string mime_type) { + if (extension != null && extension[0] != '\0') + return extension; + if ("css" in mime_type) + return ".css"; + if ("javascript" in mime_type) + return ".js"; + if ("html" in mime_type) + return ".htm"; + if ("plain" in mime_type) + return ".txt"; + return ""; + } + + public string clean_filename (string filename) { + #if HAVE_WIN32 + return filename.delimit ("/\\<>:\"|?*", '_'); + #else + return filename.delimit ("/", '_'); + #endif + } + + public string get_suggested_filename (WebKit.Download download) { + /* https://bugs.webkit.org/show_bug.cgi?id=83161 + https://d19vezwu8eufl6.cloudfront.net/nlp/slides%2F03-01-FormalizingNB.pdf */ + return clean_filename (download.get_suggested_filename ()); + } + + public string get_filename_suggestion_for_uri (string mime_type, string uri) { + /* Try to provide a good default filename, UTF-8 encoded */ + string filename = clean_filename (Soup.URI.decode (uri)); + /* Take the rest of the URI if needed */ + if (filename.has_suffix ("/")) + return filename + fallback_extension (null, mime_type); + return filename; + } + + public static string? get_extension_for_uri (string uri, out string basename) { + if (&basename != null) + basename = null; + /* Find the last slash and the last period *after* the last slash. */ + int last_slash = uri.last_index_of_char ('/'); + /* Huh, URI without slashes? */ + if (last_slash == -1) + return null; + int period = uri.last_index_of_char ('.', last_slash); + if (period == -1) + return null; + /* The extension, or "." if it ended with a period */ + string extension = uri.substring (period, -1); + if (&basename != null) + basename = uri.substring (0, period); + return extension; + + } + + public string get_unique_filename (string filename) { + if (Posix.access (filename, Posix.F_OK) != 0) { + string basename; + string? extension = get_extension_for_uri (filename, out basename); + string? new_filename = null; + int i = -1; + do { + new_filename = "%s-%d%s".printf (basename, i++, extension ?? ""); + } while (Posix.access (new_filename, Posix.F_OK) == 0); + return new_filename; + } + return filename; + } + + public string prepare_destination_uri (WebKit.Download download, string? folder) { + string suggested_filename = get_suggested_filename (download); + string basename = File.new_for_uri (suggested_filename).get_basename (); + string download_dir; + if (folder == null) { + download_dir = Paths.get_tmp_dir (); + Katze.mkdir_with_parents (download_dir, 0700); + } + else + download_dir = folder; + string destination_filename = Path.build_filename (download_dir, basename); + try { + return Filename.to_uri (get_unique_filename (destination_filename)); + } + catch (Error error) { + return "file://" + destination_filename; + } + } + + public static bool has_enough_space (WebKit.Download download, string uri) { + var folder = File.new_for_uri (uri).get_parent (); + uint64 free_space; + try { + var info = folder.query_filesystem_info ("filesystem::free"); + free_space = info.get_attribute_uint64 ("filesystem::free"); + } + catch (Error error) { + free_space = 0; + } + bool can_write = Posix.access (folder.get_path (), Posix.F_OK) == 0; + if (free_space < download.total_size || !can_write) { + string message; + string detailed_message; + if (!can_write) { + message = _("The file \"%s\" can't be saved in this folder.").printf ( + Path.get_basename (uri)); + detailed_message = _("You don't have permission to write in this location."); + } + else if (free_space < download.total_size) { + message = _("There is not enough free space to download \"%s\".").printf ( + Path.get_basename (uri)); + detailed_message = _("The file needs %s but only %s are left.").printf ( + format_size (download.total_size), format_size (free_space)); + } + else + assert_not_reached (); + Sokoke.message_dialog (Gtk.MessageType.ERROR, message, detailed_message, false); + return false; + } + return true; + } + } +} diff --git a/midori/midori-view.c b/midori/midori-view.c index 9766485f..808d34c2 100644 --- a/midori/midori-view.c +++ b/midori/midori-view.c @@ -2270,8 +2270,7 @@ midori_view_download_uri (MidoriView* view, WebKitDownload* download = webkit_download_new (request); gboolean handled; g_object_unref (request); - g_object_set_data (G_OBJECT (download), "midori-download-type", - GINT_TO_POINTER (type)); + midori_download_set_type (download, type); g_signal_emit (view, signals[DOWNLOAD_REQUESTED], 0, download, &handled); } @@ -3185,9 +3184,7 @@ webkit_web_view_download_requested_cb (GtkWidget* web_view, gchar* content_type; gchar* description; WebKitWebFrame* web_frame; - gchar* mime_type; WebKitWebDataSource* datasource; - WebKitNetworkRequest* request; GString* details; GIcon* icon; GtkWidget* image; @@ -3205,18 +3202,8 @@ webkit_web_view_download_requested_cb (GtkWidget* web_view, dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("Open or download file from %s"), hostname); g_free (hostname); - mime_type = g_object_get_data (G_OBJECT (view), "download-mime-type"); - request = webkit_download_get_network_request (download); - if (mime_type != NULL) - content_type = g_content_type_from_mime_type (mime_type); - else - content_type = NULL; - if (!content_type) - content_type = g_content_type_guess ( - webkit_download_get_suggested_filename (download), NULL, 0, NULL); - if (!content_type) - content_type = g_content_type_from_mime_type ("application/octet-stream"); - mime_type = g_content_type_get_mime_type (content_type); + content_type = midori_download_get_content_type (download, + g_object_get_data (G_OBJECT (view), "download-mime-type")); description = g_content_type_get_description (content_type); icon = g_content_type_get_icon (content_type); g_themed_icon_append_name (G_THEMED_ICON (icon), "text-html"); @@ -3224,20 +3211,19 @@ webkit_web_view_download_requested_cb (GtkWidget* web_view, g_object_unref (icon); gtk_widget_show (image); gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image); - g_free (content_type); details = g_string_sized_new (20 * 4); g_string_append_printf (details, _("File Name: %s"), webkit_download_get_suggested_filename (download)); g_string_append_c (details, '\n'); - if (g_strrstr (description, mime_type)) - g_string_append_printf (details, _("File Type: '%s'"), mime_type); + if (g_strrstr (description, content_type)) + g_string_append_printf (details, _("File Type: '%s'"), content_type); else - g_string_append_printf (details, _("File Type: %s ('%s')"), description, mime_type); + g_string_append_printf (details, _("File Type: %s ('%s')"), description, content_type); g_string_append_c (details, '\n'); g_free (description); - g_free (mime_type); + g_free (content_type); /* Link Fingerprint */ /* We look at the original URI because redirection would lose the fragment */ @@ -3252,18 +3238,20 @@ webkit_web_view_download_requested_cb (GtkWidget* web_view, midori_uri_get_fingerprint (original_uri, &fingerprint, &fplabel); if (fplabel && fingerprint) { + WebKitNetworkRequest* request = webkit_download_get_network_request (download); + g_string_append (details, fplabel); g_string_append_c (details, ' '); g_string_append (details, fingerprint); g_string_append_c (details, '\n'); + + /* Propagate original URI to make it available when the download finishes */ + g_object_set_data_full (G_OBJECT (request), "midori-original-uri", + g_strdup (original_uri), g_free); } g_free (fplabel); g_free (fingerprint); - /* Propagate original URI to make it available when the download finishes */ - g_object_set_data_full (G_OBJECT (request), "midori-original-uri", - g_strdup (original_uri), g_free); - } if (webkit_download_get_total_size (download) > webkit_download_get_current_size (download)) @@ -3280,8 +3268,7 @@ webkit_web_view_download_requested_cb (GtkWidget* web_view, gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE); /* i18n: A file open dialog title, ie. "Open http://fila.com/manual.tgz" */ - title = g_strdup_printf (_("Open %s"), - webkit_network_request_get_uri (request)); + title = g_strdup_printf (_("Open %s"), webkit_download_get_uri (download)); gtk_window_set_title (GTK_WINDOW (dialog), title); g_free (title); screen = gtk_widget_get_screen (dialog); @@ -3304,7 +3291,7 @@ webkit_web_view_download_requested_cb (GtkWidget* web_view, gtk_widget_destroy (dialog); if (response == GTK_RESPONSE_DELETE_EVENT) response = MIDORI_DOWNLOAD_CANCEL; - g_object_set_data (G_OBJECT (download), "midori-download-type", GINT_TO_POINTER (response)); + midori_download_set_type (download, response); g_signal_emit (view, signals[DOWNLOAD_REQUESTED], 0, download, &handled); return handled; @@ -5447,56 +5434,6 @@ midori_view_can_save (MidoriView* view) return FALSE; } -static gchar* -midori_view_get_uri_extension (const gchar* uri) -{ - gchar* slash; - gchar* period; - gchar* ext_end; - - /* Find the last slash in the URI and search for the last period - *after* the last slash. This is not completely accurate - but should cover most (simple) URIs */ - slash = strrchr (uri, '/'); - /* Huh, URI without slashes? */ - if (!slash) - return NULL; - - ext_end = period = strrchr (slash, '.'); - if (!period) - return NULL; - - /* Skip the period */ - ext_end++; - /* If *ext_end is 0 here, the URI ended with a period, so skip */ - if (!*ext_end) - return NULL; - - /* Find the end of the extension */ - while (*ext_end && g_ascii_isalnum (*ext_end)) - ext_end++; - - return g_strdup (period); -} - -const gchar* -midori_view_fallback_extension (MidoriView* view, - const gchar* extension) -{ - if (extension && *extension) - return extension; - g_return_val_if_fail (view->mime_type != NULL, ""); - if (strstr (view->mime_type, "css")) - return ".css"; - if (strstr (view->mime_type, "javascript")) - return ".js"; - if (strstr (view->mime_type, "html")) - return ".htm"; - if (strstr (view->mime_type, "plain")) - return ".txt"; - return ""; -} - /** * midori_view_save_source: * @view: a #MidoriView @@ -5531,9 +5468,9 @@ midori_view_save_source (MidoriView* view, if (!outfile) { - gchar* extension = midori_view_get_uri_extension (uri); + gchar* extension = midori_download_get_extension_for_uri (uri, NULL); unique_filename = g_strdup_printf ("%s/%uXXXXXX%s", midori_paths_get_tmp_dir (), - g_str_hash (uri), midori_view_fallback_extension (view, extension)); + g_str_hash (uri), midori_download_fallback_extension (view->mime_type, extension)); g_free (extension); katze_mkdir_with_parents (midori_paths_get_tmp_dir (), 0700); fd = g_mkstemp (unique_filename); diff --git a/midori/sokoke.c b/midori/sokoke.c index 3d1cbd1f..8bca3e26 100644 --- a/midori/sokoke.c +++ b/midori/sokoke.c @@ -1302,85 +1302,6 @@ sokoke_build_thumbnail_path (const gchar* name) return path; } -gchar* -midori_download_prepare_tooltip_text (WebKitDownload* download) -{ - gdouble* last_time; - guint64* last_size; - gint hour = 3600, min = 60; - gint hours_left, minutes_left, seconds_left; - guint64 total_size = webkit_download_get_total_size (download); - guint64 current_size = webkit_download_get_current_size (download); - gdouble time_elapsed = webkit_download_get_elapsed_time (download); - gdouble time_estimated, time_diff; - gchar* current, *total, *download_speed; - gchar* hours_str, *minutes_str, *seconds_str; - GString* tooltip = g_string_new (NULL); - - time_diff = time_elapsed / current_size; - time_estimated = (total_size - current_size) * time_diff; - - hours_left = time_estimated / hour; - minutes_left = (time_estimated - (hours_left * hour)) / min; - seconds_left = (time_estimated - (hours_left * hour) - (minutes_left * min)); - - hours_str = g_strdup_printf (ngettext ("%d hour", "%d hours", hours_left), hours_left); - minutes_str = g_strdup_printf (ngettext ("%d minute", "%d minutes", minutes_left), minutes_left); - seconds_str = g_strdup_printf (ngettext ("%d second", "%d seconds", seconds_left), seconds_left); - - current = g_format_size (current_size); - total = g_format_size (total_size); - last_time = g_object_get_data (G_OBJECT (download), "last-time"); - last_size = g_object_get_data (G_OBJECT (download), "last-size"); - - /* i18n: Download tooltip (size): 4KB of 43MB */ - g_string_append_printf (tooltip, _("%s of %s"), current, total); - g_free (current); - g_free (total); - - if (time_elapsed != *last_time) - download_speed = g_format_size ( - (current_size - *last_size) / (time_elapsed - *last_time)); - else - /* i18n: Unknown number of bytes, used for transfer rate like ?B/s */ - download_speed = g_strdup (_("?B")); - - /* i18n: Download tooltip (transfer rate): (130KB/s) */ - g_string_append_printf (tooltip, _(" (%s/s)"), download_speed); - g_free (download_speed); - - if (time_estimated > 0) - { - gchar* eta = NULL; - if (hours_left > 0) - eta = g_strdup_printf ("%s, %s", hours_str, minutes_str); - else if (minutes_left >= 10) - eta = g_strdup_printf ("%s", minutes_str); - else if (minutes_left < 10 && minutes_left > 0) - eta = g_strdup_printf ("%s, %s", minutes_str, seconds_str); - else if (seconds_left > 0) - eta = g_strdup_printf ("%s", seconds_str); - if (eta != NULL) - { - /* i18n: Download tooltip (estimated time) : - 1 hour, 5 minutes remaning */ - g_string_append_printf (tooltip, _(" - %s remaining"), eta); - g_free (eta); - } - } - - g_free (hours_str); - g_free (seconds_str); - g_free (minutes_str); - - if (time_elapsed - *last_time > 5.0) - { - *last_time = time_elapsed; - *last_size = current_size; - } - - return g_string_free (tooltip, FALSE); -} - static gboolean sokoke_entry_has_placeholder_text (GtkEntry* entry) { @@ -1452,17 +1373,3 @@ sokoke_search_entry_new (const gchar* placeholder_text) return entry; } -gchar* -sokoke_get_download_filename (WebKitDownload* download) -{ - /* https://bugs.webkit.org/show_bug.cgi?id=83161 */ - /* https://d19vezwu8eufl6.cloudfront.net/nlp/slides%2F03-01-FormalizingNB.pdf */ - gchar* filename = g_strdup (webkit_download_get_suggested_filename (download)); - #ifdef G_OS_WIN32 - g_strdelimit (filename, "/\\<>:\"|?*", '_'); - #else - g_strdelimit (filename, "/", '_'); - #endif - return filename; -} - diff --git a/midori/sokoke.h b/midori/sokoke.h index 754ed2cb..9a25ad64 100644 --- a/midori/sokoke.h +++ b/midori/sokoke.h @@ -161,8 +161,6 @@ sokoke_resolve_hostname (const gchar* hostname); gboolean sokoke_recursive_fork_protection (const gchar* uri, gboolean set_uri); -gchar* -sokoke_get_download_filename (WebKitDownload* download); typedef struct { @@ -183,9 +181,6 @@ sokoke_widget_copy_clipboard (GtkWidget* widget, gchar* sokoke_build_thumbnail_path (const gchar* name); -gchar* -midori_download_prepare_tooltip_text (WebKitDownload* download); - GtkWidget* sokoke_search_entry_new (const gchar* placeholder_text); diff --git a/panels/midori-transfers.c b/panels/midori-transfers.c index 44e8873b..b10953fb 100644 --- a/panels/midori-transfers.c +++ b/panels/midori-transfers.c @@ -104,13 +104,10 @@ midori_transfers_button_clear_clicked_cb (GtkToolItem* toolitem, while ((gtk_tree_model_iter_nth_child (model, &iter, NULL, n++))) { WebKitDownload* download; - WebKitDownloadStatus status; gtk_tree_model_get (model, &iter, 1, &download, -1); - status = webkit_download_get_status (download); - if (status == WEBKIT_DOWNLOAD_STATUS_FINISHED - || status == WEBKIT_DOWNLOAD_STATUS_CANCELLED) + if (midori_download_is_finished (download)) { gtk_list_store_remove (GTK_LIST_STORE (model), &iter); n--; /* Decrement n since we just removed it */ @@ -241,9 +238,21 @@ midori_transfers_treeview_render_icon_cb (GtkTreeViewColumn* column, GtkTreeIter* iter, GtkWidget* treeview) { - g_object_set (renderer, "stock-id", STOCK_TRANSFER, + WebKitDownload* download; + gchar* content_type; + GIcon* icon; + + gtk_tree_model_get (model, iter, 1, &download, -1); + content_type = midori_download_get_content_type (download, NULL); + icon = g_content_type_get_icon (content_type); + g_themed_icon_append_name (G_THEMED_ICON (icon), "text-html"); + + g_object_set (renderer, "gicon", icon, "stock-size", GTK_ICON_SIZE_DND, "xpad", 1, "ypad", 12, NULL); + g_free (content_type); + g_object_unref (icon); + g_object_unref (download); } static void @@ -254,34 +263,17 @@ midori_transfers_treeview_render_text_cb (GtkTreeViewColumn* column, GtkWidget* treeview) { WebKitDownload* download; - gchar* current; - gchar* total; - gchar* size_text; - gchar* text; - gchar* filename; + gchar* tooltip; gdouble progress; gtk_tree_model_get (model, iter, 1, &download, -1); - current = g_format_size (webkit_download_get_current_size (download)); - total = g_format_size (webkit_download_get_total_size (download)); - size_text = g_strdup_printf (_("%s of %s"), current, total); - g_free (current); - g_free (total); - filename = sokoke_get_download_filename (download); - text = g_strdup_printf ("%s\n%s", filename, size_text); - g_free (filename); - g_free (size_text); - /* Avoid a bug in WebKit */ - if (webkit_download_get_status (download) != WEBKIT_DOWNLOAD_STATUS_CREATED) - progress = webkit_download_get_progress (download); - else - progress = 0.0; - g_object_set (renderer, "text", text, - "ellipsize", PANGO_ELLIPSIZE_END, + tooltip = midori_download_get_tooltip (download); + progress = midori_download_get_progress (download); + g_object_set (renderer, "text", tooltip, "value", (gint)(progress * 100), "xpad", 1, "ypad", 6, NULL); - g_free (text); + g_free (tooltip); g_object_unref (download); } @@ -297,17 +289,7 @@ midori_transfers_treeview_render_button_cb (GtkTreeViewColumn* column, gtk_tree_model_get (model, iter, 1, &download, -1); - switch (webkit_download_get_status (download)) - { - case WEBKIT_DOWNLOAD_STATUS_STARTED: - stock_id = GTK_STOCK_CANCEL; - break; - case WEBKIT_DOWNLOAD_STATUS_FINISHED: - stock_id = GTK_STOCK_OPEN; - break; - default: - stock_id = GTK_STOCK_CLEAR; - } + stock_id = midori_download_action_stock_id (download); g_object_set (renderer, "stock-id", stock_id, "stock-size", GTK_ICON_SIZE_MENU, NULL); @@ -328,25 +310,8 @@ midori_transfers_treeview_row_activated_cb (GtkTreeView* treeview, gtk_tree_model_get (model, &iter, 1, &download, -1); - switch (webkit_download_get_status (download)) - { - case WEBKIT_DOWNLOAD_STATUS_STARTED: - webkit_download_cancel (download); - break; - case WEBKIT_DOWNLOAD_STATUS_FINISHED: - { - const gchar* uri; - - uri = webkit_download_get_destination_uri (download); - sokoke_show_uri (gtk_widget_get_screen (GTK_WIDGET ( - treeview)), uri, gtk_get_current_event_time (), NULL); - break; - } - case WEBKIT_DOWNLOAD_STATUS_CANCELLED: - /* FIXME: Remove this item from the model */ - default: - break; - } + if (midori_download_action_clear (download, GTK_WIDGET (treeview), NULL)) + ; /* FIXME: Remove this item from the model */ g_object_unref (download); } } diff --git a/po/POTFILES.in b/po/POTFILES.in index 95375830..6f26ccaa 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -12,6 +12,7 @@ midori/midori-panel.c midori/midori-settings.vala midori/midori-websettings.c midori/midori-view.c +midori/midori-download.vala midori/midori-speeddial.vala midori/midori-preferences.c midori/midori-searchaction.c diff --git a/toolbars/midori-transferbar.c b/toolbars/midori-transferbar.c index 5896fa10..76e2c8e0 100644 --- a/toolbars/midori-transferbar.c +++ b/toolbars/midori-transferbar.c @@ -76,17 +76,11 @@ midori_transferbar_download_notify_progress_cb (WebKitDownload* download, GtkWidget* progress) { gchar* tooltip; - gchar* text; - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), - webkit_download_get_progress (download)); - tooltip = midori_download_prepare_tooltip_text (download); - text = g_strdup_printf ("%s\n%s", - gtk_progress_bar_get_text (GTK_PROGRESS_BAR (progress)), tooltip); - gtk_widget_set_tooltip_text (progress, text); - + midori_download_get_progress (download)); + tooltip = midori_download_get_tooltip (download); + gtk_widget_set_tooltip_text (progress, tooltip); g_free (tooltip); - g_free (text); } static void @@ -95,31 +89,22 @@ midori_transferbar_download_notify_status_cb (WebKitDownload* download, TransferInfo* info) { GtkWidget* button = info->button; - GtkWidget* icon; - switch (webkit_download_get_status (download)) + const gchar* stock_id = midori_download_action_stock_id (download); + GtkWidget* icon = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_MENU); + gtk_button_set_image (GTK_BUTTON (button), icon); + + if (webkit_download_get_status (download) == WEBKIT_DOWNLOAD_STATUS_FINISHED) { - case WEBKIT_DOWNLOAD_STATUS_FINISHED: - { MidoriBrowser* browser = midori_browser_get_for_widget (button); - MidoriDownloadType type = GPOINTER_TO_INT ( - g_object_get_data (G_OBJECT (download), "midori-download-type")); - WebKitNetworkRequest* request; - const gchar* original_uri; - GChecksumType checksum_type; - gchar* fingerprint; - gboolean verified = TRUE; - - icon = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU); - gtk_button_set_image (GTK_BUTTON (button), icon); + MidoriDownloadType type = midori_download_get_type (download); + if (type == MIDORI_DOWNLOAD_OPEN) gtk_button_clicked (GTK_BUTTON (button)); - if (1) { const gchar* uri = webkit_download_get_destination_uri (download); - gchar* path = soup_uri_decode (uri); - gchar* filename = g_strrstr (path, "/") + 1; + gchar* filename = g_path_get_basename (uri); gchar* msg = g_strdup_printf ( _("The file '%s' has been downloaded."), filename); KatzeItem* item = katze_item_new (); @@ -131,47 +116,12 @@ midori_transferbar_download_notify_status_cb (WebKitDownload* download, midori_browser_update_history (item, "download", "create"); item->uri = item->name = NULL; g_object_unref (item); - g_free (path); - } - - /* Link Fingerprint */ - request = webkit_download_get_network_request (download); - original_uri = g_object_get_data (G_OBJECT (request), "midori-original-uri"); - if (!original_uri) - original_uri = webkit_download_get_uri (download); - checksum_type = midori_uri_get_fingerprint (original_uri, &fingerprint, NULL); - if (fingerprint != NULL) - { - gchar* filename = g_filename_from_uri ( - webkit_download_get_destination_uri (download), NULL, NULL); - gchar* contents; - gsize length; - gboolean y = g_file_get_contents (filename, &contents, &length, NULL); - gchar* checksum = g_compute_checksum_for_data (checksum_type, - (guchar*)contents, length); g_free (filename); - g_free (contents); - /* Checksums are case-insensitive */ - if (!y || g_ascii_strcasecmp (fingerprint, checksum) != 0) - verified = FALSE; - g_free (checksum); } - g_free (fingerprint); - if (verified) + + if (!midori_download_has_wrong_checksum (download)) gtk_recent_manager_add_item (gtk_recent_manager_get_default (), webkit_download_get_destination_uri (download)); - else - gtk_image_set_from_stock (GTK_IMAGE (icon), - GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU); - break; - } - case WEBKIT_DOWNLOAD_STATUS_CANCELLED: - case WEBKIT_DOWNLOAD_STATUS_ERROR: - icon = gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU); - gtk_button_set_image (GTK_BUTTON (button), icon); - break; - default: - break; } } @@ -180,37 +130,8 @@ midori_transferbar_download_button_clicked_cb (GtkWidget* button, TransferInfo* info) { WebKitDownload* download = info->download; - - switch (webkit_download_get_status (download)) - { - case WEBKIT_DOWNLOAD_STATUS_STARTED: - webkit_download_cancel (download); - break; - case WEBKIT_DOWNLOAD_STATUS_FINISHED: - { - const gchar* uri = webkit_download_get_destination_uri (download); - GtkWidget* icon = gtk_button_get_image (GTK_BUTTON (button)); - gchar* stock_id; - gtk_image_get_stock (GTK_IMAGE (icon), &stock_id, NULL); - if (g_str_equal (stock_id, GTK_STOCK_DIALOG_WARNING)) - { - sokoke_message_dialog (GTK_MESSAGE_WARNING, - _("The downloaded file is erroneous."), - _("The checksum provided with the link did not match. " \ - "This means the file is probably incomplete or was " \ - "modified afterwards."), - TRUE); - } - else if (sokoke_show_uri (gtk_widget_get_screen (button), - uri, gtk_get_current_event_time (), NULL)) - gtk_widget_destroy (button); - break; - } - case WEBKIT_DOWNLOAD_STATUS_CANCELLED: - gtk_widget_destroy (button); - default: - break; - } + if (midori_download_action_clear (download, button, NULL)) + gtk_widget_destroy (button); } void @@ -231,13 +152,9 @@ midori_transferbar_check_size (GtkWidget* statusbar, for (list = transferbar->infos; list != NULL; list = g_list_next (list)) { TransferInfo* info = list->data; - WebKitDownloadStatus status = webkit_download_get_status (info->download); - if (status == WEBKIT_DOWNLOAD_STATUS_ERROR - || status == WEBKIT_DOWNLOAD_STATUS_CANCELLED - || status == WEBKIT_DOWNLOAD_STATUS_FINISHED) - { + if (midori_download_is_finished (info->download) + || webkit_download_get_status (info->download) == WEBKIT_DOWNLOAD_STATUS_STARTED) gtk_widget_destroy (info->button); - } } } } @@ -251,7 +168,7 @@ midori_transferbar_add_download_item (MidoriTransferbar* transferbar, GtkToolItem* toolitem; GtkWidget* button; GtkWidget* progress; - const gchar* uri; + const gchar* filename; gint width; TransferInfo* info; @@ -260,29 +177,16 @@ midori_transferbar_add_download_item (MidoriTransferbar* transferbar, #if GTK_CHECK_VERSION (3, 0, 0) gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR (progress), TRUE); #endif - gtk_progress_bar_set_ellipsize (GTK_PROGRESS_BAR (progress), - PANGO_ELLIPSIZE_MIDDLE); - if ((uri = webkit_download_get_destination_uri (download))) - { - gchar* path = soup_uri_decode (uri); - gchar* filename = g_strrstr (path, "/") + 1; - gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), filename); - g_free (path); - } - else - { - gchar* filename = sokoke_get_download_filename (download); - gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), filename); - g_free (filename); - } + gtk_progress_bar_set_ellipsize (GTK_PROGRESS_BAR (progress), PANGO_ELLIPSIZE_MIDDLE); + filename = g_strrstr (webkit_download_get_destination_uri (download), "/") + 1; + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), filename); sokoke_widget_get_text_size (progress, "M", &width, NULL); gtk_widget_set_size_request (progress, width * 10, 1); - /* Avoid a bug in WebKit */ - if (webkit_download_get_status (download) != WEBKIT_DOWNLOAD_STATUS_CREATED) - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), - webkit_download_get_progress (download)); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), + midori_download_get_progress (download)); gtk_box_pack_start (GTK_BOX (box), progress, FALSE, FALSE, 0); - icon = gtk_image_new_from_stock (GTK_STOCK_CANCEL, GTK_ICON_SIZE_MENU); + icon = gtk_image_new_from_stock ( + midori_download_action_stock_id (download), GTK_ICON_SIZE_MENU); button = gtk_button_new (); gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE); @@ -322,13 +226,8 @@ midori_transferbar_clear_clicked_cb (GtkWidget* button, for (list = transferbar->infos; list != NULL; list = g_list_next (list)) { TransferInfo* info = list->data; - WebKitDownloadStatus status = webkit_download_get_status (info->download); - if (status == WEBKIT_DOWNLOAD_STATUS_ERROR - || status == WEBKIT_DOWNLOAD_STATUS_CANCELLED - || status == WEBKIT_DOWNLOAD_STATUS_FINISHED) - { + if (midori_download_is_finished (info->download)) gtk_widget_destroy (info->button); - } } } @@ -359,11 +258,7 @@ midori_transferbar_confirm_delete (MidoriTransferbar* transferbar) for (list = transferbar->infos; list != NULL; list = g_list_next (list)) { TransferInfo* info = list->data; - WebKitDownloadStatus status = webkit_download_get_status (info->download); - - if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED - && status != WEBKIT_DOWNLOAD_STATUS_CANCELLED - && status != WEBKIT_DOWNLOAD_STATUS_ERROR) + if (!midori_download_is_finished (info->download)) { all_done = FALSE; break;