[epiphany] bookmarks-import: Add option to import from Firefox



commit 0de094dbceb022e7ef5dd1b28e77ecd76c50fa8b
Author: Iulian Radu <iulian radu67 gmail com>
Date:   Tue Nov 29 15:34:51 2016 +0200

    bookmarks-import: Add option to import from Firefox
    
    Firefox stores bookmarks for each profile in a SQLite file,
    in the .mozilla/firefox/<profile>/ directory, in the users home
    directory.
    
    If the user has no profiles, don't display the 'Firefox' option in
    the impport combo box. If he has one profile, automatically import
    from that one. If he has multiple profiles, display a dialog which
    allows the user to select the profile he wants to import from.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=772423

 src/bookmarks/ephy-bookmarks-import.c |  145 +++++++++++++++++++++++
 src/bookmarks/ephy-bookmarks-import.h |   14 ++-
 src/window-commands.c                 |  207 ++++++++++++++++++++++++++++++++-
 3 files changed, 358 insertions(+), 8 deletions(-)
---
diff --git a/src/bookmarks/ephy-bookmarks-import.c b/src/bookmarks/ephy-bookmarks-import.c
index aa00872..ba4f589 100644
--- a/src/bookmarks/ephy-bookmarks-import.c
+++ b/src/bookmarks/ephy-bookmarks-import.c
@@ -22,6 +22,8 @@
 
 #include "config.h"
 
+#include "ephy-shell.h"
+#include "ephy-sqlite-connection.h"
 #include "gvdb-builder.h"
 #include "gvdb-reader.h"
 
@@ -152,3 +154,146 @@ ephy_bookmarks_import (EphyBookmarksManager  *manager,
 
   return res;
 }
+
+static void
+load_tags_for_bookmark (EphySQLiteConnection  *connection,
+                        EphyBookmark          *bookmark,
+                        int                    bookmark_id)
+{
+  EphyBookmarksManager *manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  const char *statement_str = "SELECT tag.title "
+                              "FROM moz_bookmarks b, moz_bookmarks tag "
+                              "WHERE b.fk=(SELECT fk FROM moz_bookmarks WHERE id=?) "
+                              "AND b.title IS NULL "
+                              "AND tag.id=b.parent "
+                              "ORDER BY tag.title ";
+
+  statement = ephy_sqlite_connection_create_statement (connection,
+                                                       statement_str,
+                                                       &error);
+  if (error) {
+    g_warning ("[Bookmark %d] Could not build tags query statement: %s", bookmark_id, error->message);
+    goto out;
+  }
+
+  if (ephy_sqlite_statement_bind_int (statement, 0, bookmark_id, &error) == FALSE) {
+    g_warning ("[Bookmark %d] Could not bind tag id in statement: %s", bookmark_id, error->message);
+    goto out;
+  }
+
+  while (ephy_sqlite_statement_step (statement, &error)) {
+    const char *tag = ephy_sqlite_statement_get_column_as_string (statement, 0);
+
+    if (!ephy_bookmarks_manager_tag_exists (manager, tag))
+      ephy_bookmarks_manager_create_tag (manager, tag);
+
+    ephy_bookmark_add_tag (bookmark, tag);
+  }
+
+  if (error) {
+    g_warning ("[Bookmark %d] Could not execute tags query statement: %s", bookmark_id, error->message);
+    goto out;
+  }
+
+  out:
+    if (statement)
+      g_object_unref (statement);
+    if (error)
+      g_error_free (error);
+}
+
+gboolean
+ephy_bookmarks_import_from_firefox (EphyBookmarksManager  *manager,
+                                    const gchar           *profile,
+                                    GError               **error)
+{
+  EphySQLiteConnection *connection = NULL;
+  EphySQLiteStatement *statement = NULL;
+  GSequence *bookmarks = NULL;
+  gboolean ret = TRUE;
+  gchar *filename;
+  const char *statement_str = "SELECT b.id, p.url, b.title, b.dateAdded "
+                              "FROM moz_bookmarks b "
+                              "JOIN moz_places p ON b.fk=p.id "
+                              "WHERE b.type=1 AND p.url NOT LIKE 'about%' "
+                              "               AND p.url NOT LIKE 'place%' "
+                              "               AND b.title IS NOT NULL "
+                              "ORDER BY p.url ";
+
+  filename = g_build_filename (g_get_home_dir (),
+                               FIREFOX_PROFILES_DIR,
+                               profile,
+                               FIREFOX_BOOKMARKS_FILE,
+                               NULL);
+
+  connection = ephy_sqlite_connection_new ();
+  ephy_sqlite_connection_open (connection, filename, error);
+  if (*error) {
+    g_warning ("Could not open database at %s: %s", filename, (*error)->message);
+    g_error_free (*error);
+    g_set_error (error,
+                 BOOKMARKS_IMPORT_ERROR,
+                 BOOKMARKS_IMPORT_ERROR_BOOKMARKS,
+                 _("Firefox bookmarks database could not be opened. Close Firefox and try again."));
+    goto out;
+  }
+
+  statement = ephy_sqlite_connection_create_statement (connection,
+                                                       statement_str,
+                                                       error);
+  if (statement == NULL) {
+    g_warning ("Could not build bookmarks query statement: %s", (*error)->message);
+    g_error_free (*error);
+    g_set_error (error,
+                 BOOKMARKS_IMPORT_ERROR,
+                 BOOKMARKS_IMPORT_ERROR_BOOKMARKS,
+                 _("Firefox bookmarks could not be retrieved!"));
+    ret = FALSE;
+    goto out;
+  }
+
+  bookmarks = g_sequence_new (g_object_unref);
+  while (ephy_sqlite_statement_step (statement, error)) {
+    int bookmark_id = ephy_sqlite_statement_get_column_as_int (statement, 0);
+    const char *url = ephy_sqlite_statement_get_column_as_string (statement, 1);
+    const char *title = ephy_sqlite_statement_get_column_as_string (statement, 2);
+    gint64 time_added = ephy_sqlite_statement_get_column_as_int64 (statement, 3);
+    EphyBookmark *bookmark;
+    GSequence *tags;
+
+    tags = g_sequence_new (g_free);
+    bookmark = ephy_bookmark_new (url, title, tags);
+    ephy_bookmark_set_time_added (bookmark, time_added);
+    load_tags_for_bookmark (connection, bookmark, bookmark_id);
+
+    g_sequence_prepend (bookmarks, bookmark);
+  }
+
+  if (*error) {
+    g_warning ("Could not execute bookmarks query statement: %s", (*error)->message);
+    g_error_free (*error);
+    g_set_error (error,
+                 BOOKMARKS_IMPORT_ERROR,
+                 BOOKMARKS_IMPORT_ERROR_BOOKMARKS,
+                 _("Firefox bookmarks could not be retrieved!"));
+    ret = FALSE;
+    goto out;
+  }
+
+  ephy_bookmarks_manager_add_bookmarks (manager, bookmarks);
+
+  out:
+    g_free (filename);
+    if (connection) {
+      ephy_sqlite_connection_close (connection);
+      g_object_unref (connection);
+    }
+    if (statement)
+      g_object_unref (statement);
+    if (bookmarks)
+      g_sequence_free (bookmarks);
+
+  return ret;
+}
diff --git a/src/bookmarks/ephy-bookmarks-import.h b/src/bookmarks/ephy-bookmarks-import.h
index 3f23ffd..4e1a4ff 100644
--- a/src/bookmarks/ephy-bookmarks-import.h
+++ b/src/bookmarks/ephy-bookmarks-import.h
@@ -24,8 +24,16 @@
 
 G_BEGIN_DECLS
 
-gboolean        ephy_bookmarks_import       (EphyBookmarksManager  *manager,
-                                             const char            *filename,
-                                             GError               **error);
+#define FIREFOX_PROFILES_DIR        ".mozilla/firefox"
+#define FIREFOX_PROFILES_FILE       "profiles.ini"
+#define FIREFOX_BOOKMARKS_FILE      "places.sqlite"
+
+gboolean    ephy_bookmarks_import               (EphyBookmarksManager  *manager,
+                                                 const char            *filename,
+                                                 GError               **error);
+
+gboolean    ephy_bookmarks_import_from_firefox  (EphyBookmarksManager  *manager,
+                                                 const gchar           *profile,
+                                                 GError               **error);
 
 G_END_DECLS
\ No newline at end of file
diff --git a/src/window-commands.c b/src/window-commands.c
index 8a0ab28..e602f8a 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -95,10 +95,90 @@ window_cmd_new_incognito_window (GSimpleAction *action,
   ephy_open_incognito_window (NULL);
 }
 
-const gchar *import_option_names[1] = {
-  N_("GVDB File")
+const gchar *import_option_names[2] = {
+  N_("GVDB File"),
+  N_("Firefox")
 };
 
+static void
+combo_box_changed_cb (GtkComboBox *combo_box,
+                      GtkButton   *button)
+{
+  int active;
+
+  g_assert (GTK_IS_COMBO_BOX (combo_box));
+  g_assert (GTK_IS_BUTTON (button));
+
+  active = gtk_combo_box_get_active (combo_box);
+  if (active == 0)
+    gtk_button_set_label (button, _("Ch_oose File"));
+  else if (active == 1)
+    gtk_button_set_label (button, _("I_mport"));
+}
+
+static gchar *
+get_path (GIOChannel *channel)
+{
+  gchar *line;
+  gchar *path;
+  gsize length;
+
+  do {
+    g_io_channel_read_line (channel, &line, &length, NULL, NULL);
+
+    if (g_str_has_prefix (line, "Path")) {
+      path = g_strdup (line);
+
+      /* Extract value (e.g. Path=Value\n -> Value) */
+      path = strchr (path, '=');
+      path++;
+      path[strcspn (path, "\n")] = 0;
+
+      g_free (line);
+      return path;
+    }
+
+    g_free (line);
+    /* Until '\n' */
+  } while (length != 1);
+
+  return NULL;
+}
+
+static GSList *
+get_firefox_profiles (void)
+{
+  GIOChannel *channel;
+  GSList *profiles = NULL;
+  gchar *filename;
+  gchar *line;
+  gchar *profile;
+  int count = 0;
+  gsize length;
+
+  filename = g_build_filename (g_get_home_dir (),
+                               FIREFOX_PROFILES_DIR,
+                               FIREFOX_PROFILES_FILE,
+                               NULL);
+  channel = g_io_channel_new_file (filename, "r", NULL);
+  g_free (filename);
+
+  do {
+    g_io_channel_read_line (channel, &line, &length, NULL, NULL);
+
+    profile = g_strdup_printf ("[Profile%d]\n", count);
+    if (g_strcmp0 (line, profile) == 0) {
+      profiles = g_slist_append (profiles, get_path (channel));
+
+      count++;
+    }
+    g_free (profile);
+    g_free (line);
+  } while (length != 0);
+
+  return profiles;
+}
+
 static GtkTreeModel *
 create_tree_model (void)
 {
@@ -107,10 +187,24 @@ create_tree_model (void)
   };
   GtkListStore *list_store;
   GtkTreeIter iter;
+  GSList *firefox_profiles;
+  gboolean has_firefox_profile;
   int i;
 
+
+  /* Check if user has a firefox profile*/
+  firefox_profiles = get_firefox_profiles ();
+  has_firefox_profile = g_slist_length (firefox_profiles) > 0;
+  g_slist_free (firefox_profiles);
+
   list_store = gtk_list_store_new (1, G_TYPE_STRING);
   for (i = G_N_ELEMENTS (import_option_names) - 1; i >= 0; i--) {
+    /* Skip Firefox option if user doesn't have a Firefox profile */
+    if (g_strcmp0 (import_option_names[i], _("Firefox")) == 0) {
+      if (!has_firefox_profile)
+        continue;
+    }
+
     gtk_list_store_prepend (list_store, &iter);
     gtk_list_store_set (list_store, &iter,
                         TEXT_COL, _(import_option_names[i]),
@@ -120,6 +214,64 @@ create_tree_model (void)
   return GTK_TREE_MODEL (list_store);
 }
 
+static gchar *
+show_profile_selector (GtkWidget *parent, GSList *profiles)
+{
+  GtkWidget *selector;
+  GtkWidget *list_box;
+  GtkWidget *suggested;
+  GtkWidget *content_area;
+  GSList *l;
+  int response;
+  gchar *selected_profile = NULL;
+
+  selector = gtk_dialog_new_with_buttons (_("Select Profile"),
+                                          GTK_WINDOW (parent),
+                                          GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | 
GTK_DIALOG_USE_HEADER_BAR,
+                                          _("_Cancel"),
+                                          GTK_RESPONSE_CANCEL,
+                                          _("_Select"),
+                                          GTK_RESPONSE_OK,
+                                          NULL);
+  gtk_dialog_set_default_response (GTK_DIALOG (selector), GTK_RESPONSE_OK);
+
+  suggested = gtk_dialog_get_widget_for_response (GTK_DIALOG (selector), GTK_RESPONSE_OK);
+  gtk_style_context_add_class (gtk_widget_get_style_context (suggested),
+                               GTK_STYLE_CLASS_SUGGESTED_ACTION);
+
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (selector));
+  gtk_container_set_border_width (GTK_CONTAINER (content_area), 5);
+  gtk_widget_set_valign (content_area, GTK_ALIGN_CENTER);
+
+  list_box = gtk_list_box_new ();
+  for (l = profiles; l != NULL; l = l->next) {
+    const gchar *profile = l->data;
+    GtkWidget *label;
+
+    label = gtk_label_new (strchr (profile, '.') + 1);
+    g_object_set_data (G_OBJECT (label), "profile_path", g_strdup (profile));
+    gtk_widget_set_margin_top (label, 6);
+    gtk_widget_set_margin_bottom (label, 6);
+    gtk_list_box_insert (GTK_LIST_BOX (list_box), label, -1);
+  }
+  gtk_container_add (GTK_CONTAINER (content_area), list_box);
+
+  gtk_widget_show_all (content_area);
+
+  response = gtk_dialog_run (GTK_DIALOG (selector));
+  if (response == GTK_RESPONSE_OK) {
+    GtkListBoxRow *row;
+    GtkWidget *row_widget;
+
+    row = gtk_list_box_get_selected_row (GTK_LIST_BOX (list_box));
+    row_widget = gtk_bin_get_child (GTK_BIN (row));
+    selected_profile = g_object_get_data (G_OBJECT (row_widget), "profile_path");
+  }
+  gtk_widget_destroy (selector);
+
+  return selected_profile;
+}
+
 static void
 dialog_bookmarks_import_cb (GtkDialog   *dialog,
                             int          response,
@@ -130,7 +282,7 @@ dialog_bookmarks_import_cb (GtkDialog   *dialog,
   GtkWidget *import_info_dialog;
   int active;
   int chooser_response;
-  gboolean imported;
+  gboolean imported = FALSE;
 
   if (response == GTK_RESPONSE_OK) {
     active = gtk_combo_box_get_active (combo_box);
@@ -178,10 +330,51 @@ dialog_bookmarks_import_cb (GtkDialog   *dialog,
         gtk_widget_destroy (import_info_dialog);
       }
       gtk_widget_destroy (file_chooser_dialog);
+    } else if (active == 1) {
+      GError *error = NULL;
+      GSList *profiles;
+      gchar *profile;
+      int num_profiles;
+
+      profiles = get_firefox_profiles ();
+
+      /* Import default profile */
+      num_profiles = g_slist_length (profiles);
+      if (num_profiles == 1) {
+        imported = ephy_bookmarks_import_from_firefox (manager, profiles->data, &error);
+      } else if (num_profiles > 1) {
+        profile = show_profile_selector (GTK_WIDGET (dialog), profiles);
+        if (profile) {
+          imported = ephy_bookmarks_import_from_firefox (manager, profile, &error);
+          g_free (profile);
+        }
+      } else {
+        g_assert_not_reached ();
+      }
 
-      if (imported)
-        gtk_widget_destroy (GTK_WIDGET (dialog));
+      g_slist_free (profiles);
+
+      /* If there are multiple profiles, but the user didn't select one in
+       * the profile (he pressed Cancel), don't display the import info dialog
+       * as no import took place
+       */
+      if (profile) {
+        import_info_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
+                                                     GTK_DIALOG_MODAL,
+                                                     imported ? GTK_MESSAGE_INFO : GTK_MESSAGE_WARNING,
+                                                     GTK_BUTTONS_OK,
+                                                     "%s",
+                                                     imported ? _("Bookmarks successfully imported!") :
+                                                                error->message);
+        gtk_dialog_run (GTK_DIALOG (import_info_dialog));
+        gtk_widget_destroy (import_info_dialog);
+      }
+      if (error)
+        g_error_free (error);
     }
+
+    if (imported)
+      gtk_widget_destroy (GTK_WIDGET (dialog));
   } else if (response == GTK_RESPONSE_CANCEL) {
     gtk_widget_destroy (GTK_WIDGET (dialog));
   }
@@ -227,6 +420,10 @@ window_cmd_import_bookmarks (GSimpleAction *action,
   g_object_unref (tree_model);
   gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
 
+  g_signal_connect (GTK_COMBO_BOX (combo_box), "changed",
+                    G_CALLBACK (combo_box_changed_cb),
+                    gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK));
+
   cell_renderer = gtk_cell_renderer_text_new ();
   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell_renderer, TRUE);
   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell_renderer,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]