/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * 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 . * * Authors: John Sullivan */ /* nautilus-bookmark-list.c - implementation of centralized list of bookmarks. */ #include #include "nautilus-bookmark-list.h" #include #include #include #include #include #define MAX_BOOKMARK_LENGTH 80 #define LOAD_JOB 1 #define SAVE_JOB 2 enum { CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; /* forward declarations */ 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 NautilusBookmark * new_bookmark_from_uri (const char *uri, const char *label) { NautilusBookmark *new_bookmark; GFile *location; location = NULL; if (uri) { location = g_file_new_for_uri (uri); } new_bookmark = NULL; if (location) { new_bookmark = nautilus_bookmark_new (location, label); g_object_unref (location); } return new_bookmark; } static GFile * nautilus_bookmark_list_get_legacy_file (void) { char *filename; GFile *file; filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL); file = g_file_new_for_path (filename); g_free (filename); return file; } static GFile * nautilus_bookmark_list_get_file (void) { char *filename; GFile *file; filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL); file = g_file_new_for_path (filename); g_free (filename); return file; } /* 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) { if (NAUTILUS_BOOKMARK_LIST (object)->monitor != NULL) { g_file_monitor_cancel (NAUTILUS_BOOKMARK_LIST (object)->monitor); NAUTILUS_BOOKMARK_LIST (object)->monitor = NULL; } g_queue_free (NAUTILUS_BOOKMARK_LIST (object)->pending_ops); clear (NAUTILUS_BOOKMARK_LIST (object)); 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, G_STRUCT_OFFSET (NautilusBookmarkListClass, changed), 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) { GFile *file; 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, 0, NULL, NULL); g_file_monitor_set_rate_limit (bookmarks->monitor, 1000); g_signal_connect (bookmarks->monitor, "changed", G_CALLBACK (bookmark_monitor_changed_cb), bookmarks); g_object_unref (file); } 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_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); } /** * nautilus_bookmark_list_delete_item_at: * * Delete the bookmark at the specified position. * @bookmarks: the list of bookmarks. * @index: index, must be less than length of list. **/ void nautilus_bookmark_list_delete_item_at (NautilusBookmarkList *bookmarks, guint index) { GList *doomed; g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks)); g_return_if_fail (index < g_list_length (bookmarks->list)); doomed = g_list_nth (bookmarks->list, index); bookmarks->list = g_list_remove_link (bookmarks->list, doomed); g_assert (NAUTILUS_IS_BOOKMARK (doomed->data)); stop_monitoring_bookmark (bookmarks, NAUTILUS_BOOKMARK (doomed->data)); g_object_unref (doomed->data); g_list_free_1 (doomed); nautilus_bookmark_list_save_file (bookmarks); } /** * nautilus_bookmark_list_move_item: * * Move the item from the given position to the destination. * @index: the index of the first bookmark. * @destination: the index of the second bookmark. **/ void nautilus_bookmark_list_move_item (NautilusBookmarkList *bookmarks, guint index, guint destination) { GList *bookmark_item; if (index == destination) { return; } bookmark_item = g_list_nth (bookmarks->list, index); bookmarks->list = g_list_remove_link (bookmarks->list, bookmark_item); bookmarks->list = g_list_insert (bookmarks->list, bookmark_item->data, destination); nautilus_bookmark_list_save_file (bookmarks); } /** * nautilus_bookmark_list_insert_item: * * Insert a bookmark at a specified position. * @bookmarks: the list of bookmarks. * @index: the position to insert the bookmark at. * @new_bookmark: the bookmark to insert a copy of. **/ void nautilus_bookmark_list_insert_item (NautilusBookmarkList *bookmarks, NautilusBookmark *new_bookmark, guint index) { g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks)); g_return_if_fail (index <= g_list_length (bookmarks->list)); insert_bookmark_internal (bookmarks, g_object_ref (new_bookmark), index); nautilus_bookmark_list_save_file (bookmarks); } /** * nautilus_bookmark_list_item_at: * * Get the bookmark at the specified position. * @bookmarks: the list of bookmarks. * @index: index, must be less than length of list. * * Return value: the bookmark at position @index in @bookmarks. **/ NautilusBookmark * nautilus_bookmark_list_item_at (NautilusBookmarkList *bookmarks, guint index) { g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL); g_return_val_if_fail (index < g_list_length (bookmarks->list), NULL); return NAUTILUS_BOOKMARK (g_list_nth_data (bookmarks->list, index)); } /** * 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; GFile *bookmark_location; NautilusBookmark *bookmark; gboolean found = FALSE; 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) { bookmark = node->data; bookmark_location = nautilus_bookmark_get_location (bookmark); if (g_file_equal (location, bookmark_location)) { found = TRUE; } g_object_unref (bookmark_location); if (found) { if (index) { *index = idx; } return bookmark; } idx++; } return NULL; } /** * nautilus_bookmark_list_length: * * Get the number of bookmarks in the list. * @bookmarks: the list of bookmarks. * * Return value: the length of the bookmark list. **/ guint nautilus_bookmark_list_length (NautilusBookmarkList *bookmarks) { g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST(bookmarks), 0); return g_list_length (bookmarks->list); } 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, GAsyncResult *res, gpointer user_data) { NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source); gchar *contents; char **lines; int i; contents = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); if (contents == NULL) { 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, *label; 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_free (label); } } g_signal_emit (self, signals[CHANGED], 0); op_processed_cb (self); g_strfreev (lines); } static void load_io_thread (GSimpleAsyncResult *result, GObject *object, 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) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_warning ("Could not load bookmark file: %s\n", error->message); } g_error_free (error); } else { g_simple_async_result_set_op_res_gpointer (result, contents, g_free); } } static void load_file_async (NautilusBookmarkList *self) { GSimpleAsyncResult *result; /* Wipe out old list. */ clear (self); result = g_simple_async_result_new (G_OBJECT (self), load_callback, NULL, NULL); g_simple_async_result_run_in_thread (result, load_io_thread, G_PRIORITY_DEFAULT, NULL); g_object_unref (result); } static void save_callback (GObject *source, GAsyncResult *res, gpointer user_data) { NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source); GFile *file; /* re-enable bookmark file monitoring */ file = nautilus_bookmark_list_get_file (); self->monitor = g_file_monitor_file (file, 0, NULL, NULL); g_object_unref (file); 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 (GSimpleAsyncResult *result, GObject *object, GCancellable *cancellable) { gchar *contents, *path; GFile *parent, *file; GError *error = NULL; file = nautilus_bookmark_list_get_file (); parent = g_file_get_parent (file); path = g_file_get_path (parent); g_mkdir_with_parents (path, 0700); g_free (path); g_object_unref (parent); contents = g_simple_async_result_get_op_res_gpointer (result); g_file_replace_contents (file, contents, strlen (contents), NULL, FALSE, 0, NULL, NULL, &error); if (error != NULL) { g_warning ("Unable to replace contents of the bookmarks file: %s", error->message); g_error_free (error); } g_object_unref (file); } static void save_file_async (NautilusBookmarkList *self) { GSimpleAsyncResult *result; 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); self->monitor = NULL; } 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; char *uri; label = nautilus_bookmark_get_name (bookmark); uri = nautilus_bookmark_get_uri (bookmark); g_string_append_printf (bookmark_string, "%s %s\n", uri, label); g_free (uri); } else { char *uri; uri = nautilus_bookmark_get_uri (bookmark); g_string_append_printf (bookmark_string, "%s\n", uri); g_free (uri); } } result = g_simple_async_result_new (G_OBJECT (self), save_callback, NULL, NULL); contents = g_string_free (bookmark_string, FALSE); g_simple_async_result_set_op_res_gpointer (result, contents, g_free); g_simple_async_result_run_in_thread (result, save_io_thread, G_PRIORITY_DEFAULT, NULL); g_object_unref (result); } 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) { NautilusBookmark *bookmark; gboolean is_builtin; if (nautilus_bookmark_list_item_with_location (list, location, NULL)) { return FALSE; } if (nautilus_is_home_directory (location)) { return FALSE; } bookmark = nautilus_bookmark_new (location, NULL); is_builtin = nautilus_bookmark_get_is_builtin (bookmark); g_object_unref (bookmark); return !is_builtin; } /** * 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; }