}
#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,
gchar** extensions;
MidoriWebSettings* settings;
gchar* config_file;
- GKeyFile* speeddial;
+ MidoriSpeedDial* dial;
MidoriStartup load_on_startup;
KatzeArray* search_engines;
KatzeArray* bookmarks;
}
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)
"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);
}
g_object_unref (settings);
- g_key_file_free (speeddial);
+ g_object_unref (dial);
g_object_unref (app);
g_free (config_file);
return 0;
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);
--- /dev/null
+/*
+ Copyright (C) 2011-2012 Christian Dywan <christian@twotoats.de>
+
+ 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<style>.cross { display:none }</style>%s" +
+ "<style> div.shortcut { height: %d%%; width: %d%%; }</style>\n",
+ Paths.is_readonly () ? "" : "<noscript>",
+ Paths.is_readonly () ? "" : "</noscript>",
+ 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 (
+ "<style> body { overflow:hidden } #content { margin-left: %u%%; }</style>", margin);
+ if (close_buttons_left)
+ markup.append_printf (
+ "<style>.cross { left: -14px }</style>");
+
+ 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 ("""
+ <div class="shortcut" id="s%u"><div class="preview">
+ <a class="cross" href="#" onclick='clearShortcut("s%u");'></a>
+ <a href="%s"><img src="data:image/png;base64,%s" title='%s'></a>
+ </div><div class="title" onclick='renameShortcut("s%u");'>%s</div></div>
+ """,
+ slot, slot, uri, encoded ?? "", title, slot, title ?? "");
+ }
+ else if (tile != "settings")
+ keyfile.remove_group (tile);
+ }
+ catch (KeyFileError error) { }
+ }
+
+ markup.append_printf ("""
+ <div class="shortcut" id="s%u"><div class="preview new">
+ <a class="add" href="#" onclick='return getAction("s%u");'></a>
+ </div><div class="title">%s</div></div>
+ """,
+ slot_count + 1, slot_count + 1, _("Click to add a shortcut"));
+ markup.append_printf ("</div>\n</body>\n</html>\n");
+ return markup.str;
+ }
+
+ return null;
+ }
+ }
+}
+
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<style>.cross { display:none }</style>%s"
- "<style> div.shortcut { height: %d%%; width: %d%%; }</style>\n",
- midori_paths_is_readonly () ? "" : "<noscript>",
- midori_paths_is_readonly () ? "" : "</noscript>",
- 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,
- "<style> body { overflow:hidden } #content { margin-left: %d%%; }</style>",
- margin);
-
- if (katze_object_get_boolean (view->settings, "close-buttons-left"))
- g_string_append_printf (markup,
- "<style>.cross { left: -14px }</style>");
-
- 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,
- "<div class=\"shortcut\" id=\"s%d\"><div class=\"preview\">"
- "<a class=\"cross\" href=\"#\" onclick='clearShortcut(\"s%d\");'></a>"
- "<a href=\"%s\"><img src=\"data:image/png;base64,%s\" title='%s'></a>"
- "</div><div class=\"title\" onclick='renameShortcut(\"s%d\");'>%s</div></div>\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,
- "<div class=\"shortcut\" id=\"s%d\"><div class=\"preview new\">"
- "<a class=\"add\" href=\"#\" onclick='return getAction(\"s%d\");'></a>"
- "</div><div class=\"title\">%s</div></div>\n",
- slot_count + 1, slot_count + 1, _("Click to add a shortcut"));
- g_string_append_printf (markup,
- "</div>\n</body>\n</html>\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);
}
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);
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;
}
--- /dev/null
+/*
+ Copyright (C) 2012 Christian Dywan <christian@twotoasts.de>
+
+ 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);
+}
+