From db0ae6ab6082f83d314d54492a38eb098c40106d Mon Sep 17 00:00:00 2001 From: Christian Dywan Date: Fri, 31 Aug 2012 23:47:03 +0200 Subject: [PATCH] Introduce Midori.SpeedDial and unit test Fixes: https://bugs.launchpad.net/midori/+bug/1038634 --- midori/main.c | 96 +-------------- midori/midori-browser.c | 64 ++-------- midori/midori-speeddial.vala | 232 +++++++++++++++++++++++++++++++++++ midori/midori-view.c | 132 +------------------- midori/sokoke.c | 9 +- tests/speeddial.vala | 55 +++++++++ 6 files changed, 312 insertions(+), 276 deletions(-) create mode 100644 midori/midori-speeddial.vala create mode 100644 tests/speeddial.vala diff --git a/midori/main.c b/midori/main.c index 3d93e031..e69670a3 100644 --- a/midori/main.c +++ b/midori/main.c @@ -1578,92 +1578,6 @@ signal_handler (int signal_id) } #endif -static GKeyFile* -speeddial_new_from_file (const gchar* config, - GError** error) -{ - - GKeyFile* key_file = g_key_file_new (); - gchar* config_file = g_build_filename (config, "speeddial", NULL); - guint i = 0; - gchar* json_content; - gsize json_length; - GString* script; - JSGlobalContextRef js_context; - gchar* keyfile; - gchar* thumb_dir; - gchar** tiles; - - if (g_key_file_load_from_file (key_file, config_file, G_KEY_FILE_NONE, error)) - { - g_free (config_file); - return key_file; - } - - katze_assign (config_file, g_build_filename (config, "speeddial.json", NULL)); - if (!g_file_get_contents (config_file, &json_content, &json_length, NULL)) - { - katze_assign (json_content, g_strdup ("'{}'")); - json_length = strlen ("'{}'"); - } - - script = g_string_sized_new (json_length); - g_string_append (script, "var json = JSON.parse ("); - g_string_append_len (script, json_content, json_length); - g_string_append (script, "); " - "var keyfile = '';" - "for (var i in json['shortcuts']) {" - "var tile = json['shortcuts'][i];" - "keyfile += '[Dial ' + tile['id'].substring (1) + ']\\n'" - " + 'uri=' + tile['href'] + '\\n'" - " + 'img=' + tile['img'] + '\\n'" - " + 'title=' + tile['title'] + '\\n\\n';" - "} " - "var columns = json['width'] ? json['width'] : 3;" - "var rows = json['shortcuts'] ? json['shortcuts'].length / columns : 0;" - "keyfile += '[settings]\\n'" - " + 'columns=' + columns + '\\n'" - " + 'rows=' + (rows > 3 ? rows : 3) + '\\n\\n';" - "keyfile;"); - g_free (json_content); - js_context = JSGlobalContextCreateInGroup (NULL, NULL); - keyfile = sokoke_js_script_eval (js_context, script->str, NULL); - JSGlobalContextRelease (js_context); - g_string_free (script, TRUE); - g_key_file_load_from_data (key_file, keyfile, -1, 0, NULL); - g_free (keyfile); - tiles = g_key_file_get_groups (key_file, NULL); - thumb_dir = g_build_path (G_DIR_SEPARATOR_S, midori_paths_get_cache_dir (), "thumbnails", NULL); - if (!g_file_test (thumb_dir, G_FILE_TEST_EXISTS)) - katze_mkdir_with_parents (thumb_dir, 0700); - g_free (thumb_dir); - - while (tiles[i] != NULL) - { - gsize sz; - gchar* uri = g_key_file_get_string (key_file, tiles[i], "uri", NULL); - gchar* img = g_key_file_get_string (key_file, tiles[i], "img", NULL); - if (img != NULL && (uri && *uri && *uri != '#')) - { - guchar* decoded = g_base64_decode (img, &sz); - gchar* thumb_path = sokoke_build_thumbnail_path (uri); - g_file_set_contents (thumb_path, (gchar*)decoded, sz, NULL); - g_free (thumb_path); - g_free (decoded); - } - g_free (img); - g_free (uri); - g_key_file_remove_key (key_file, tiles[i], "img", NULL); - i++; - } - g_strfreev (tiles); - - katze_assign (config_file, g_build_filename (config, "speeddial", NULL)); - sokoke_key_file_save_to_file (key_file, config_file, NULL); - g_free (config_file); - return key_file; -} - static void midori_soup_session_block_uris_cb (SoupSession* session, SoupMessage* msg, @@ -1970,7 +1884,7 @@ main (int argc, gchar** extensions; MidoriWebSettings* settings; gchar* config_file; - GKeyFile* speeddial; + MidoriSpeedDial* dial; MidoriStartup load_on_startup; KatzeArray* search_engines; KatzeArray* bookmarks; @@ -2476,8 +2390,8 @@ main (int argc, } midori_startup_timer ("History read: \t%f"); - error = NULL; - speeddial = speeddial_new_from_file (config, &error); + katze_assign (config_file, g_build_filename (config, "speeddial", NULL)); + dial = midori_speed_dial_new (config_file, NULL); /* In case of errors */ if (error_messages->len) @@ -2594,7 +2508,7 @@ main (int argc, "trash", trash, "search-engines", search_engines, "history", history, - "speed-dial", speeddial, + "speed-dial", dial->keyfile, NULL); g_object_unref (history); g_object_unref (search_engines); @@ -2657,7 +2571,7 @@ main (int argc, } g_object_unref (settings); - g_key_file_free (speeddial); + g_object_unref (dial); g_object_unref (app); g_free (config_file); return 0; diff --git a/midori/midori-browser.c b/midori/midori-browser.c index ddd523fe..7127d241 100644 --- a/midori/midori-browser.c +++ b/midori/midori-browser.c @@ -1234,75 +1234,27 @@ midori_view_save_as_cb (GtkWidget* menuitem, midori_browser_save_uri (browser, MIDORI_VIEW (view), uri); } -static gchar* -midori_browser_speed_dial_get_next_free_slot (MidoriView* view) -{ - MidoriBrowser* browser = midori_browser_get_for_widget (GTK_WIDGET (view)); - GKeyFile* key_file; - guint slot_count = 0, slot = 1, i; - gchar** groups; - - g_object_get (browser, "speed-dial", &key_file, NULL); - - groups = g_key_file_get_groups (key_file, NULL); - for (i = 0; groups[i]; i++) - { - if (g_key_file_has_key (key_file, groups[i], "uri", NULL)) - slot_count++; - } - - while (slot <= slot_count) - { - gchar* dial_id = g_strdup_printf ("Dial %d", slot); - if (!g_key_file_has_group (key_file, dial_id)) - { - g_free (dial_id); - return g_strdup_printf ("s%d", slot); - } - g_free (dial_id); - slot++; - } - return g_strdup_printf ("s%d", slot_count + 1); -} - static void midori_browser_add_speed_dial (MidoriBrowser* browser) { GdkPixbuf* img; GtkWidget* view = midori_browser_get_current_tab (browser); - gchar* uri = g_strdup (midori_view_get_display_uri (MIDORI_VIEW (view))); - gchar* title = g_strdup (midori_view_get_display_title (MIDORI_VIEW (view))); - gchar* slot_id = midori_browser_speed_dial_get_next_free_slot (MIDORI_VIEW (view)); + gchar* slot_id = midori_speed_dial_get_next_free_slot_fk (browser->speeddial); + gchar* uri; + gchar* title; if (slot_id == NULL) - { - g_free (uri); - g_free (title); return; - } + uri = g_strdup (midori_view_get_display_uri (MIDORI_VIEW (view))); + title = g_strdup (midori_view_get_display_title (MIDORI_VIEW (view))); if ((img = midori_view_get_snapshot (MIDORI_VIEW (view), 240, 160))) { - GKeyFile* key_file; gchar* dial_id = g_strdup_printf ("Dial %s", slot_id + 1); - gchar* file_path = sokoke_build_thumbnail_path (uri); - gchar* thumb_dir = g_build_path (G_DIR_SEPARATOR_S, midori_paths_get_cache_dir (), "thumbnails", NULL); - g_object_get (browser, "speed-dial", &key_file, NULL); - - g_key_file_set_string (key_file, dial_id, "uri", uri); - g_key_file_set_string (key_file, dial_id, "title", title); - - if (!g_file_test (thumb_dir, G_FILE_TEST_EXISTS)) - katze_mkdir_with_parents (thumb_dir, 0700); - - gdk_pixbuf_save (img, file_path, "png", NULL, "compression", "7", NULL); - - midori_view_save_speed_dial_config (MIDORI_VIEW (view), key_file); - - g_object_unref (img); - g_free (file_path); - g_free (thumb_dir); + midori_speed_dial_add_fk (dial_id, uri, title, img, browser->speeddial); g_free (dial_id); + midori_view_save_speed_dial_config (MIDORI_VIEW (view), browser->speeddial); + g_object_unref (img); } g_free (uri); g_free (title); diff --git a/midori/midori-speeddial.vala b/midori/midori-speeddial.vala new file mode 100644 index 00000000..1538981d --- /dev/null +++ b/midori/midori-speeddial.vala @@ -0,0 +1,232 @@ +/* + Copyright (C) 2011-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 Katze { + extern static string mkdir_with_parents (string pathname, int mode); +} + +namespace Sokoke { + extern static string js_script_eval (void* ctx, string script, void* error); + extern static string build_thumbnail_path (string uri); +} + +namespace Midori { + public class SpeedDial : GLib.Object { + public GLib.KeyFile keyfile; + + public SpeedDial (string filename, string? fallback = null) { + keyfile = new GLib.KeyFile (); + try { + keyfile.load_from_file (filename, GLib.KeyFileFlags.NONE); + } + catch (GLib.Error io_error) { + string json; + size_t len; + try { + FileUtils.get_contents (fallback ?? (filename + ".json"), + out json, out len); + } + catch (GLib.Error fallback_error) { + json = "'{}'"; + len = 4; + } + + var script = new StringBuilder.sized (len); + script.append ("var json = JSON.parse ("); + script.append_len (json, (ssize_t)len); + script.append (""" + ); + var keyfile = ''; + for (var i in json['shortcuts']) { + var tile = json['shortcuts'][i]; + keyfile += '[Dial ' + tile['id'].substring (1) + ']\n' + + 'uri=' + tile['href'] + '\n' + + 'img=' + tile['img'] + '\n' + + 'title=' + tile['title'] + '\n\n'; + } + var columns = json['width'] ? json['width'] : 3; + var rows = json['shortcuts'] ? json['shortcuts'].length / columns : 0; + keyfile += '[settings]\n' + + 'columns=' + columns + '\n' + + 'rows=' + (rows > 3 ? rows : 3) + '\n\n'; + keyfile; + """); + + try { + keyfile.load_from_data ( + Sokoke.js_script_eval (null, script.str, null), + -1, 0); + } + catch (GLib.Error eval_error) { + GLib.critical ("Failed to parse %s as speed dial JSON: %s", + fallback ?? (filename + ".json"), eval_error.message); + } + Katze.mkdir_with_parents ( + Path.build_path (Path.DIR_SEPARATOR_S, + Environment.get_user_cache_dir (), + "midori", "thumbnails"), 0700); + + foreach (string tile in keyfile.get_groups ()) { + try { + string img = keyfile.get_string (tile, "img"); + string uri = keyfile.get_string (tile, "uri"); + if (img != null && uri[0] != '\0' && uri[0] != '#') { + uchar[] decoded = Base64.decode (img); + FileUtils.set_data (Sokoke.build_thumbnail_path (uri), decoded); + } + keyfile.remove_key (tile, "img"); + } + catch (GLib.Error img_error) { + /* img and uri can be missing */ + } + } + } + } + + public static string get_next_free_slot_fk (KeyFile keyfile) { + uint slot_count = 0; + foreach (string tile in keyfile.get_groups ()) { + try { + if (keyfile.has_key (tile, "uri")) + slot_count++; + } + catch (KeyFileError error) { } + } + + uint slot = 1; + while (slot <= slot_count) { + string tile = "Dial %u".printf (slot); + if (!keyfile.has_group (tile)) + return "s%u".printf (slot); + slot++; + } + return "s%u".printf (slot_count + 1); + } + + public static void add_fk (string id, string uri, string title, Gdk.Pixbuf img, KeyFile keyfile) { + keyfile.set_string (id, "uri", uri); + keyfile.set_string (id, "title", title); + + Katze.mkdir_with_parents (Path.build_path (Path.DIR_SEPARATOR_S, + Paths.get_cache_dir (), "thumbnails"), 0700); + string filename = Sokoke.build_thumbnail_path (uri); + try { + img.save (filename, "png", null, "compression", "7", null); + } + catch (Error error) { + critical ("Failed to save speed dial thumbnail: %s", error.message); + } + } + + public static string? get_html_fk (KeyFile? keyfile, + bool close_buttons_left, GLib.Object view, bool load_missing) throws Error { + + string? head = null; + string filename = Paths.get_res_filename ("speeddial-head.html"); + if (keyfile != null + && FileUtils.get_contents (filename, out head, null)) { + string header = head.replace ("{title}", _("Speed Dial")). + replace ("{click_to_add}", _("Click to add a shortcut")). + replace ("{enter_shortcut_address}", _("Enter shortcut address")). + replace ("{enter_shortcut_name}", _("Enter shortcut title")). + replace ("{are_you_sure}", _("Are you sure you want to delete this shortcut?")); + var markup = new StringBuilder (header); + + uint slot_count = 1; + foreach (string tile in keyfile.get_groups ()) { + try { + if (keyfile.has_key (tile, "uri")) + slot_count++; + } + catch (KeyFileError error) { } + } + + /* Try to guess the best X by X grid size */ + uint grid_index = 3; + while ((grid_index * grid_index) < slot_count) + grid_index++; + + /* Percent width size of one slot */ + uint slot_size = (100 / grid_index); + + /* No editing in private/ app mode or without scripts */ + markup.append_printf ( + "%s%s" + + "\n", + Paths.is_readonly () ? "" : "", + slot_size + 1, slot_size - 4); + + /* Combined width of slots should always be less than 100%. + * Use half of the remaining percentage as a margin size */ + uint div_factor; + if (slot_size * grid_index >= 100 && grid_index > 4) + div_factor = 8; + else + div_factor = 2; + uint margin = (100 - ((slot_size - 4) * grid_index)) / div_factor; + if (margin > 9) + margin = margin % 10; + + markup.append_printf ( + "", margin); + if (close_buttons_left) + markup.append_printf ( + ""); + + foreach (string tile in keyfile.get_groups ()) { + try { + string uri = keyfile.get_string (tile, "uri"); + if (uri != null && uri.str ("://") != null && tile.has_prefix ("Dial ")) { + string title = keyfile.get_string (tile, "title"); + string thumb_filename = Sokoke.build_thumbnail_path (uri); + uint slot = tile.substring (5, -1).to_int (); + string encoded; + try { + uint8[] thumb; + FileUtils.get_data (thumb_filename, out thumb); + encoded = Base64.encode (thumb); + } + catch (FileError error) { + encoded = null; + if (load_missing) + /* FIXME: midori_view_speed_dial_get_thumb (view, tile, uri); */ + critical ("FIXME midori_view_speed_dial_get_thumb"); + } + markup.append_printf (""" +
+ + +
%s
+ """, + slot, slot, uri, encoded ?? "", title, slot, title ?? ""); + } + else if (tile != "settings") + keyfile.remove_group (tile); + } + catch (KeyFileError error) { } + } + + markup.append_printf (""" +
+ +
%s
+ """, + slot_count + 1, slot_count + 1, _("Click to add a shortcut")); + markup.append_printf ("\n\n\n"); + return markup.str; + } + + return null; + } + } +} + diff --git a/midori/midori-view.c b/midori/midori-view.c index 190d273b..9df799ee 100644 --- a/midori/midori-view.c +++ b/midori/midori-view.c @@ -4230,134 +4230,10 @@ prepare_speed_dial_html (MidoriView* view, gboolean load_missing) { MidoriBrowser* browser = midori_browser_get_for_widget (GTK_WIDGET (view)); - GKeyFile* key_file; - GString* markup = NULL; - guint slot_count = 1, i, grid_index = 3, slot_size; - guint margin, div_factor; - gchar* speed_dial_head; - gchar* file_path; - gchar** groups; - - g_object_get (browser, "speed-dial", &key_file, NULL); - file_path = midori_paths_get_res_filename ("speeddial-head.html"); - - if (key_file != NULL - && g_access (file_path, F_OK) == 0 - && g_file_get_contents (file_path, &speed_dial_head, NULL, NULL)) - { - gchar* header = sokoke_replace_variables (speed_dial_head, - "{title}", _("Speed Dial"), - "{click_to_add}", _("Click to add a shortcut"), - "{enter_shortcut_address}", _("Enter shortcut address"), - "{enter_shortcut_name}", _("Enter shortcut title"), - "{are_you_sure}", _("Are you sure you want to delete this shortcut?"), - NULL); - - markup = g_string_new (header); - - g_free (speed_dial_head); - g_free (file_path); - g_free (header); - } - else - { - g_free (file_path); - return NULL; - } - - groups = g_key_file_get_groups (key_file, NULL); - for (i = 0; groups[i]; i++) - { - if (g_key_file_has_key (key_file, groups[i], "uri", NULL)) - slot_count++; - } - - /* try to guess the best X by X grid size */ - while ((grid_index * grid_index) < slot_count) - grid_index++; - - /* percent width size of one slot */ - slot_size = (100 / grid_index); - /* No editing in private/ app mode or without scripts */ - g_string_append_printf (markup, - "%s%s" - "\n", - midori_paths_is_readonly () ? "" : "", - slot_size + 1, slot_size - 4); - - /* Combined width of slots should always be less than 100%. - * Use half of the remaining percentage as a margin size */ - if (slot_size * grid_index >= 100 && grid_index > 4) - div_factor = 8; - else - div_factor = 2; - - margin = (100 - ((slot_size - 4) * grid_index)) / div_factor; - - if (margin > 9) - margin = margin % 10; - - g_string_append_printf (markup, - "", - margin); - - if (katze_object_get_boolean (view->settings, "close-buttons-left")) - g_string_append_printf (markup, - ""); - - for (i = 0; groups[i]; i++) - { - gchar* uri = g_key_file_get_string (key_file, groups[i], "uri", NULL); - if (uri && strstr (uri, "://")) - { - gchar* title = g_key_file_get_string (key_file, groups[i], "title", NULL); - gchar* thumb_file = sokoke_build_thumbnail_path (uri); - gchar* encoded; - guint slot = atoi (groups[i] + strlen ("Dial ")); - - if (g_access (thumb_file, F_OK) == 0) - { - gsize sz; - gchar* thumb_content; - g_file_get_contents (thumb_file, &thumb_content, &sz, NULL); - encoded = g_base64_encode ((guchar*)thumb_content, sz); - g_free (thumb_content); - } - else - { - encoded = NULL; - if (load_missing) - midori_view_speed_dial_get_thumb (view, groups[i], uri); - } - g_free (thumb_file); - - g_string_append_printf (markup, - "
" - "" - "" - "
%s
\n", - slot, slot, uri, encoded ? encoded : "", title, slot, title ? title : ""); - - g_free (title); - g_free (encoded); - } - else if (strcmp (groups[i], "settings")) - g_key_file_remove_group (key_file, groups[i], NULL); - - g_free (uri); - } - g_strfreev (groups); - - g_string_append_printf (markup, - "
" - "" - "
%s
\n", - slot_count + 1, slot_count + 1, _("Click to add a shortcut")); - g_string_append_printf (markup, - "\n\n\n"); - - return g_string_free (markup, FALSE); + GKeyFile* key_file = katze_object_get_object (browser, "speed-dial"); + return midori_speed_dial_get_html_fk (key_file, + katze_object_get_boolean (view->settings, "close-buttons-left"), + G_OBJECT (view), load_missing, NULL); } diff --git a/midori/sokoke.c b/midori/sokoke.c index 5d3ca237..3d1cbd1f 100644 --- a/midori/sokoke.c +++ b/midori/sokoke.c @@ -64,15 +64,18 @@ sokoke_js_script_eval (JSContextRef js_context, const gchar* script, gchar** exception) { + JSGlobalContextRef temporary_context = NULL; gchar* value; JSStringRef js_value_string; JSStringRef js_script; JSValueRef js_exception = NULL; JSValueRef js_value; - g_return_val_if_fail (js_context, FALSE); g_return_val_if_fail (script, FALSE); + if (!js_context) + js_context = temporary_context = JSGlobalContextCreateInGroup (NULL, NULL); + js_script = JSStringCreateWithUTF8CString (script); js_value = JSEvaluateScript (js_context, js_script, JSContextGetGlobalObject (js_context), NULL, 0, &js_exception); @@ -91,12 +94,16 @@ sokoke_js_script_eval (JSContextRef js_context, g_free (value); } JSStringRelease (js_message); + if (temporary_context) + JSGlobalContextRelease (temporary_context); return NULL; } js_value_string = JSValueToStringCopy (js_context, js_value, NULL); value = sokoke_js_string_utf8 (js_value_string); JSStringRelease (js_value_string); + if (temporary_context) + JSGlobalContextRelease (temporary_context); return value; } diff --git a/tests/speeddial.vala b/tests/speeddial.vala new file mode 100644 index 00000000..8a5d7460 --- /dev/null +++ b/tests/speeddial.vala @@ -0,0 +1,55 @@ +/* + 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. +*/ +string get_test_file (string contents) { + string file; + int fd = FileUtils.open_tmp ("speeddialXXXXXX", out file); + FileUtils.set_contents (file, contents, -1); + FileUtils.close (fd); + return file; +} + +namespace Katze { + extern static string assert_str_equal (string input, string result, string expected); +} + +static void speeddial_load () { + string data = get_test_file (""" + [Dial 1] + uri=http://example.com + title=Example + [settings] + columns=3 + rows=3 + """); + + string json = get_test_file (""" + '{"shortcuts":[{"id":"s1","href":"http://example.com","title":"Example","img":"a2F0emU="}]}' + """); + + var dial_data = new Midori.SpeedDial (data, ""); + var dial_json = new Midori.SpeedDial ("", json); + FileUtils.remove (data); + FileUtils.remove (json); + + Katze.assert_str_equal (json, dial_data.keyfile.to_data (), dial_json.keyfile.to_data ()); + Katze.assert_str_equal (json, Midori.SpeedDial.get_next_free_slot_fk (dial_data.keyfile), "s2"); + Katze.assert_str_equal (json, Midori.SpeedDial.get_next_free_Slot_fk (dial_json), "s2"); +} + +void main (string[] args) { + string temporary_cache = DirUtils.make_tmp ("cacheXXXXXX"); + Environment.set_variable ("XDG_CACHE_HOME", temporary_cache, true); + Test.init (ref args); + Test.add_func ("/speeddial/load", speeddial_load); + Test.run (); + DirUtils.remove (temporary_cache); +} + -- 2.39.5