diff options
Diffstat (limited to '')
-rw-r--r-- | src/nautilus-bookmark-list.c | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/src/nautilus-bookmark-list.c b/src/nautilus-bookmark-list.c new file mode 100644 index 0000000..b2d7e66 --- /dev/null +++ b/src/nautilus-bookmark-list.c @@ -0,0 +1,670 @@ +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: John Sullivan <sullivan@eazel.com> + */ + +/* nautilus-bookmark-list.c - implementation of centralized list of bookmarks. + */ + +#include <config.h> +#include "nautilus-bookmark-list.h" + +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" +#include "nautilus-icon-names.h" + +#include <gio/gio.h> +#include <string.h> +#include <errno.h> + +#define MAX_BOOKMARK_LENGTH 80 +#define LOAD_JOB 1 +#define SAVE_JOB 2 + +struct _NautilusBookmarkList +{ + GObject parent_instance; + + GList *list; + GFileMonitor *monitor; + GQueue *pending_ops; +}; + +enum +{ + CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +/* forward declarations */ +#define NAUTILUS_BOOKMARK_LIST_ERROR (nautilus_bookmark_list_error_quark ()) +static GQuark nautilus_bookmark_list_error_quark (void); + +static void nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks); +static void nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks); + +G_DEFINE_TYPE (NautilusBookmarkList, nautilus_bookmark_list, G_TYPE_OBJECT) + +static GQuark +nautilus_bookmark_list_error_quark (void) +{ + return g_quark_from_static_string ("nautilus-bookmark-list-error-quark"); +} + +static NautilusBookmark * +new_bookmark_from_uri (const char *uri, + const char *label) +{ + NautilusBookmark *new_bookmark = NULL; + g_autoptr (GFile) location = NULL; + + if (uri) + { + location = g_file_new_for_uri (uri); + new_bookmark = nautilus_bookmark_new (location, label); + } + + return new_bookmark; +} + +static GFile * +nautilus_bookmark_list_get_legacy_file (void) +{ + g_autofree char *filename = NULL; + + filename = g_build_filename (g_get_home_dir (), + ".gtk-bookmarks", + NULL); + + return g_file_new_for_path (filename); +} + +static GFile * +nautilus_bookmark_list_get_file (void) +{ + g_autofree char *filename = NULL; + + filename = g_build_filename (g_get_user_config_dir (), + "gtk-3.0", + "bookmarks", + NULL); + + return g_file_new_for_path (filename); +} + +/* Initialization. */ + +static void +bookmark_in_list_changed_callback (NautilusBookmark *bookmark, + NautilusBookmarkList *bookmarks) +{ + g_assert (NAUTILUS_IS_BOOKMARK (bookmark)); + g_assert (NAUTILUS_IS_BOOKMARK_LIST (bookmarks)); + + /* save changes to the list */ + nautilus_bookmark_list_save_file (bookmarks); +} + +static void +bookmark_in_list_notify (GObject *object, + GParamSpec *pspec, + NautilusBookmarkList *bookmarks) +{ + /* emit the changed signal without saving, as only appearance properties changed */ + g_signal_emit (bookmarks, signals[CHANGED], 0); +} + +static void +stop_monitoring_bookmark (NautilusBookmarkList *bookmarks, + NautilusBookmark *bookmark) +{ + g_signal_handlers_disconnect_by_func (bookmark, + bookmark_in_list_changed_callback, + bookmarks); +} + +static void +stop_monitoring_one (gpointer data, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_BOOKMARK (data)); + g_assert (NAUTILUS_IS_BOOKMARK_LIST (user_data)); + + stop_monitoring_bookmark (NAUTILUS_BOOKMARK_LIST (user_data), + NAUTILUS_BOOKMARK (data)); +} + +static void +clear (NautilusBookmarkList *bookmarks) +{ + g_list_foreach (bookmarks->list, stop_monitoring_one, bookmarks); + g_list_free_full (bookmarks->list, g_object_unref); + bookmarks->list = NULL; +} + +static void +do_finalize (GObject *object) +{ + NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (object); + + if (self->monitor != NULL) + { + g_file_monitor_cancel (self->monitor); + g_clear_object (&self->monitor); + } + + g_queue_free (self->pending_ops); + + clear (self); + + G_OBJECT_CLASS (nautilus_bookmark_list_parent_class)->finalize (object); +} + +static void +nautilus_bookmark_list_class_init (NautilusBookmarkListClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = do_finalize; + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +bookmark_monitor_changed_cb (GFileMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent eflags, + gpointer user_data) +{ + if (eflags == G_FILE_MONITOR_EVENT_CHANGED || + eflags == G_FILE_MONITOR_EVENT_CREATED) + { + g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (NAUTILUS_BOOKMARK_LIST (user_data))); + nautilus_bookmark_list_load_file (NAUTILUS_BOOKMARK_LIST (user_data)); + } +} + +static void +nautilus_bookmark_list_init (NautilusBookmarkList *bookmarks) +{ + g_autoptr (GFile) file = NULL; + + bookmarks->pending_ops = g_queue_new (); + + nautilus_bookmark_list_load_file (bookmarks); + + file = nautilus_bookmark_list_get_file (); + bookmarks->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_file_monitor_set_rate_limit (bookmarks->monitor, 1000); + + g_signal_connect (bookmarks->monitor, "changed", + G_CALLBACK (bookmark_monitor_changed_cb), bookmarks); +} + +static void +insert_bookmark_internal (NautilusBookmarkList *bookmarks, + NautilusBookmark *bookmark, + int index) +{ + bookmarks->list = g_list_insert (bookmarks->list, bookmark, index); + + g_signal_connect_object (bookmark, "contents-changed", + G_CALLBACK (bookmark_in_list_changed_callback), bookmarks, 0); + g_signal_connect_object (bookmark, "notify::icon", + G_CALLBACK (bookmark_in_list_notify), bookmarks, 0); + g_signal_connect_object (bookmark, "notify::name", + G_CALLBACK (bookmark_in_list_notify), bookmarks, 0); +} + +/** + * nautilus_bookmark_list_item_with_location: + * + * Get the bookmark with the specified location, if any + * @bookmarks: the list of bookmarks. + * @location: a #GFile + * @index: location where to store bookmark index, or %NULL + * + * Return value: the bookmark with location @location, or %NULL. + **/ +NautilusBookmark * +nautilus_bookmark_list_item_with_location (NautilusBookmarkList *bookmarks, + GFile *location, + guint *index) +{ + GList *node; + NautilusBookmark *bookmark; + guint idx; + + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL); + g_return_val_if_fail (G_IS_FILE (location), NULL); + + idx = 0; + + for (node = bookmarks->list; node != NULL; node = node->next) + { + g_autoptr (GFile) bookmark_location = NULL; + + bookmark = node->data; + bookmark_location = nautilus_bookmark_get_location (bookmark); + + if (g_file_equal (location, bookmark_location)) + { + if (index) + { + *index = idx; + } + + return bookmark; + } + + idx++; + } + + return NULL; +} + +/** + * nautilus_bookmark_list_append: + * + * Append a bookmark to a bookmark list. + * @bookmarks: NautilusBookmarkList to append to. + * @bookmark: Bookmark to append a copy of. + **/ +void +nautilus_bookmark_list_append (NautilusBookmarkList *bookmarks, + NautilusBookmark *bookmark) +{ + g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks)); + g_return_if_fail (NAUTILUS_IS_BOOKMARK (bookmark)); + + if (g_list_find_custom (bookmarks->list, bookmark, + nautilus_bookmark_compare_with) != NULL) + { + return; + } + + insert_bookmark_internal (bookmarks, g_object_ref (bookmark), -1); + nautilus_bookmark_list_save_file (bookmarks); +} + +static void +process_next_op (NautilusBookmarkList *bookmarks); + +static void +op_processed_cb (NautilusBookmarkList *self) +{ + g_queue_pop_tail (self->pending_ops); + + if (!g_queue_is_empty (self->pending_ops)) + { + process_next_op (self); + } +} + +static void +load_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source_object); + g_autoptr (GError) error = NULL; + g_autofree gchar *contents = NULL; + char **lines; + int i; + + contents = g_task_propagate_pointer (G_TASK (res), &error); + if (error != NULL) + { + g_warning ("Unable to get contents of the bookmarks file: %s", + error->message); + op_processed_cb (self); + return; + } + + lines = g_strsplit (contents, "\n", -1); + for (i = 0; lines[i]; i++) + { + /* Ignore empty or invalid lines that cannot be parsed properly */ + if (lines[i][0] != '\0' && lines[i][0] != ' ') + { + /* gtk 2.7/2.8 might have labels appended to bookmarks which are separated by a space + * we must seperate the bookmark uri and the potential label + */ + char *space; + g_autofree char *label = NULL; + + space = strchr (lines[i], ' '); + if (space) + { + *space = '\0'; + label = g_strdup (space + 1); + } + + insert_bookmark_internal (self, new_bookmark_from_uri (lines[i], label), -1); + } + } + + g_signal_emit (self, signals[CHANGED], 0); + op_processed_cb (self); + + g_strfreev (lines); +} + +static void +load_io_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GFile *file; + gchar *contents; + GError *error = NULL; + + file = nautilus_bookmark_list_get_file (); + if (!g_file_query_exists (file, NULL)) + { + g_object_unref (file); + file = nautilus_bookmark_list_get_legacy_file (); + } + + g_file_load_contents (file, NULL, &contents, NULL, NULL, &error); + g_object_unref (file); + + if (error != NULL) + { + g_task_return_error (task, error); + } + else + { + g_task_return_pointer (task, contents, g_free); + } +} + +static void +load_file_async (NautilusBookmarkList *self) +{ + g_autoptr (GTask) task = NULL; + + /* Wipe out old list. */ + clear (self); + + task = g_task_new (G_OBJECT (self), + NULL, + load_callback, NULL); + g_task_run_in_thread (task, load_io_thread); +} + +static void +save_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source_object); + g_autoptr (GError) error = NULL; + gboolean success; + g_autoptr (GFile) file = NULL; + + success = g_task_propagate_boolean (G_TASK (res), &error); + + if (error != NULL) + { + g_warning ("Unable to replace contents of the bookmarks file: %s", + error->message); + } + + /* g_file_replace_contents() returned FALSE, but did not set an error. */ + if (!success) + { + g_warning ("Unable to replace contents of the bookmarks file."); + } + + /* re-enable bookmark file monitoring */ + file = nautilus_bookmark_list_get_file (); + self->monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_file_monitor_set_rate_limit (self->monitor, 1000); + g_signal_connect (self->monitor, "changed", + G_CALLBACK (bookmark_monitor_changed_cb), self); + + op_processed_cb (self); +} + +static void +save_io_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + gchar *contents; + g_autofree gchar *path = NULL; + g_autoptr (GFile) parent = NULL; + g_autoptr (GFile) file = NULL; + gboolean success; + GError *error = NULL; + + file = nautilus_bookmark_list_get_file (); + parent = g_file_get_parent (file); + path = g_file_get_path (parent); + + if (g_mkdir_with_parents (path, 0700) == -1) + { + int saved_errno = errno; + + g_set_error (&error, NAUTILUS_BOOKMARK_LIST_ERROR, 0, + "Failed to create bookmarks folder %s: %s", + path, g_strerror (saved_errno)); + g_task_return_error (task, error); + return; + } + + contents = (gchar *) g_task_get_task_data (task); + + success = g_file_replace_contents (file, + contents, strlen (contents), + NULL, FALSE, 0, NULL, + NULL, &error); + + if (error != NULL) + { + g_task_return_error (task, error); + } + else + { + g_task_return_boolean (task, success); + } +} + +static void +save_file_async (NautilusBookmarkList *self) +{ + g_autoptr (GTask) task = NULL; + GString *bookmark_string; + gchar *contents; + GList *l; + + bookmark_string = g_string_new (NULL); + + /* temporarily disable bookmark file monitoring when writing file */ + if (self->monitor != NULL) + { + g_file_monitor_cancel (self->monitor); + g_clear_object (&self->monitor); + } + + for (l = self->list; l; l = l->next) + { + NautilusBookmark *bookmark; + + bookmark = NAUTILUS_BOOKMARK (l->data); + + /* make sure we save label if it has one for compatibility with GTK 2.7 and 2.8 */ + if (nautilus_bookmark_get_has_custom_name (bookmark)) + { + const char *label; + g_autofree char *uri = NULL; + + label = nautilus_bookmark_get_name (bookmark); + uri = nautilus_bookmark_get_uri (bookmark); + + g_string_append_printf (bookmark_string, + "%s %s\n", uri, label); + } + else + { + g_autofree char *uri = NULL; + + uri = nautilus_bookmark_get_uri (bookmark); + + g_string_append_printf (bookmark_string, "%s\n", uri); + } + } + + task = g_task_new (G_OBJECT (self), + NULL, + save_callback, NULL); + contents = g_string_free (bookmark_string, FALSE); + g_task_set_task_data (task, contents, g_free); + + g_task_run_in_thread (task, save_io_thread); +} + +static void +process_next_op (NautilusBookmarkList *bookmarks) +{ + gint op; + + op = GPOINTER_TO_INT (g_queue_peek_tail (bookmarks->pending_ops)); + + if (op == LOAD_JOB) + { + load_file_async (bookmarks); + } + else + { + save_file_async (bookmarks); + } +} + +/** + * nautilus_bookmark_list_load_file: + * + * Reads bookmarks from file, clobbering contents in memory. + * @bookmarks: the list of bookmarks to fill with file contents. + **/ +static void +nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks) +{ + g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (LOAD_JOB)); + + if (g_queue_get_length (bookmarks->pending_ops) == 1) + { + process_next_op (bookmarks); + } +} + +/** + * nautilus_bookmark_list_save_file: + * + * Save bookmarks to disk. + * @bookmarks: the list of bookmarks to save. + **/ +static void +nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks) +{ + g_signal_emit (bookmarks, signals[CHANGED], 0); + + g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (SAVE_JOB)); + + if (g_queue_get_length (bookmarks->pending_ops) == 1) + { + process_next_op (bookmarks); + } +} + +gboolean +nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list, + GFile *location) +{ + g_autoptr (NautilusBookmark) bookmark = NULL; + + if (nautilus_bookmark_list_item_with_location (list, location, NULL)) + { + /* Already bookmarked */ + return FALSE; + } + + if (nautilus_is_search_directory (location)) + { + return FALSE; + } + + if (nautilus_is_recent_directory (location) || + nautilus_is_starred_directory (location) || + nautilus_is_home_directory (location) || + nautilus_is_trash_directory (location) || + nautilus_is_other_locations_directory (location)) + { + /* Already in the sidebar */ + return FALSE; + } + + bookmark = nautilus_bookmark_new (location, NULL); + return !nautilus_bookmark_get_is_builtin (bookmark); +} + +/** + * nautilus_bookmark_list_new: + * + * Create a new bookmark_list, with contents read from disk. + * + * Return value: A pointer to the new widget. + **/ +NautilusBookmarkList * +nautilus_bookmark_list_new (void) +{ + NautilusBookmarkList *list; + + list = NAUTILUS_BOOKMARK_LIST (g_object_new (NAUTILUS_TYPE_BOOKMARK_LIST, NULL)); + + return list; +} + +/** + * nautilus_bookmark_list_get_all: + * + * Get a GList of all NautilusBookmark. + * @bookmarks: NautilusBookmarkList from where to get the bookmarks. + **/ +GList * +nautilus_bookmark_list_get_all (NautilusBookmarkList *bookmarks) +{ + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL); + + return bookmarks->list; +} |