No issues found
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /*
4 * Nautilus
5 *
6 * Copyright (C) 1999, 2000 Eazel, Inc.
7 *
8 * Nautilus is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
12 *
13 * Nautilus is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 *
22 * Authors: John Sullivan <sullivan@eazel.com>
23 */
24
25 /* nautilus-bookmark-list.c - implementation of centralized list of bookmarks.
26 */
27
28 #include <config.h>
29 #include "nautilus-bookmark-list.h"
30
31 #include <libnautilus-private/nautilus-file-utilities.h>
32 #include <libnautilus-private/nautilus-file.h>
33 #include <libnautilus-private/nautilus-icon-names.h>
34
35 #include <gio/gio.h>
36 #include <string.h>
37
38 #define MAX_BOOKMARK_LENGTH 80
39 #define LOAD_JOB 1
40 #define SAVE_JOB 2
41
42 enum {
43 CHANGED,
44 LAST_SIGNAL
45 };
46
47 static guint signals[LAST_SIGNAL];
48
49 /* forward declarations */
50
51 static void nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks);
52 static void nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks);
53
54 G_DEFINE_TYPE(NautilusBookmarkList, nautilus_bookmark_list, G_TYPE_OBJECT)
55
56 static NautilusBookmark *
57 new_bookmark_from_uri (const char *uri, const char *label)
58 {
59 NautilusBookmark *new_bookmark;
60 GFile *location;
61
62 location = NULL;
63 if (uri) {
64 location = g_file_new_for_uri (uri);
65 }
66
67 new_bookmark = NULL;
68
69 if (location) {
70 new_bookmark = nautilus_bookmark_new (location, label);
71 g_object_unref (location);
72 }
73
74 return new_bookmark;
75 }
76
77 static GFile *
78 nautilus_bookmark_list_get_legacy_file (void)
79 {
80 char *filename;
81 GFile *file;
82
83 filename = g_build_filename (g_get_home_dir (),
84 ".gtk-bookmarks",
85 NULL);
86 file = g_file_new_for_path (filename);
87
88 g_free (filename);
89
90 return file;
91 }
92
93 static GFile *
94 nautilus_bookmark_list_get_file (void)
95 {
96 char *filename;
97 GFile *file;
98
99 filename = g_build_filename (g_get_user_config_dir (),
100 "gtk-3.0",
101 "bookmarks",
102 NULL);
103 file = g_file_new_for_path (filename);
104
105 g_free (filename);
106
107 return file;
108 }
109
110 /* Initialization. */
111
112 static void
113 bookmark_in_list_changed_callback (NautilusBookmark *bookmark,
114 NautilusBookmarkList *bookmarks)
115 {
116 g_assert (NAUTILUS_IS_BOOKMARK (bookmark));
117 g_assert (NAUTILUS_IS_BOOKMARK_LIST (bookmarks));
118
119 /* save changes to the list */
120 nautilus_bookmark_list_save_file (bookmarks);
121 }
122
123 static void
124 bookmark_in_list_notify (GObject *object,
125 GParamSpec *pspec,
126 NautilusBookmarkList *bookmarks)
127 {
128 /* emit the changed signal without saving, as only appearance properties changed */
129 g_signal_emit (bookmarks, signals[CHANGED], 0);
130 }
131
132 static void
133 stop_monitoring_bookmark (NautilusBookmarkList *bookmarks,
134 NautilusBookmark *bookmark)
135 {
136 g_signal_handlers_disconnect_by_func (bookmark,
137 bookmark_in_list_changed_callback,
138 bookmarks);
139 }
140
141 static void
142 stop_monitoring_one (gpointer data, gpointer user_data)
143 {
144 g_assert (NAUTILUS_IS_BOOKMARK (data));
145 g_assert (NAUTILUS_IS_BOOKMARK_LIST (user_data));
146
147 stop_monitoring_bookmark (NAUTILUS_BOOKMARK_LIST (user_data),
148 NAUTILUS_BOOKMARK (data));
149 }
150
151 static void
152 clear (NautilusBookmarkList *bookmarks)
153 {
154 g_list_foreach (bookmarks->list, stop_monitoring_one, bookmarks);
155 g_list_free_full (bookmarks->list, g_object_unref);
156 bookmarks->list = NULL;
157 }
158
159 static void
160 do_finalize (GObject *object)
161 {
162 if (NAUTILUS_BOOKMARK_LIST (object)->monitor != NULL) {
163 g_file_monitor_cancel (NAUTILUS_BOOKMARK_LIST (object)->monitor);
164 NAUTILUS_BOOKMARK_LIST (object)->monitor = NULL;
165 }
166
167 g_queue_free (NAUTILUS_BOOKMARK_LIST (object)->pending_ops);
168
169 clear (NAUTILUS_BOOKMARK_LIST (object));
170
171 G_OBJECT_CLASS (nautilus_bookmark_list_parent_class)->finalize (object);
172 }
173
174 static void
175 nautilus_bookmark_list_class_init (NautilusBookmarkListClass *class)
176 {
177 GObjectClass *object_class = G_OBJECT_CLASS (class);
178
179 object_class->finalize = do_finalize;
180
181 signals[CHANGED] =
182 g_signal_new ("changed",
183 G_TYPE_FROM_CLASS (object_class),
184 G_SIGNAL_RUN_LAST,
185 G_STRUCT_OFFSET (NautilusBookmarkListClass,
186 changed),
187 NULL, NULL,
188 g_cclosure_marshal_VOID__VOID,
189 G_TYPE_NONE, 0);
190 }
191
192 static void
193 bookmark_monitor_changed_cb (GFileMonitor *monitor,
194 GFile *child,
195 GFile *other_file,
196 GFileMonitorEvent eflags,
197 gpointer user_data)
198 {
199 if (eflags == G_FILE_MONITOR_EVENT_CHANGED ||
200 eflags == G_FILE_MONITOR_EVENT_CREATED) {
201 g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (NAUTILUS_BOOKMARK_LIST (user_data)));
202 nautilus_bookmark_list_load_file (NAUTILUS_BOOKMARK_LIST (user_data));
203 }
204 }
205
206 static void
207 nautilus_bookmark_list_init (NautilusBookmarkList *bookmarks)
208 {
209 GFile *file;
210
211 bookmarks->pending_ops = g_queue_new ();
212
213 nautilus_bookmark_list_load_file (bookmarks);
214
215 file = nautilus_bookmark_list_get_file ();
216 bookmarks->monitor = g_file_monitor_file (file, 0, NULL, NULL);
217 g_file_monitor_set_rate_limit (bookmarks->monitor, 1000);
218
219 g_signal_connect (bookmarks->monitor, "changed",
220 G_CALLBACK (bookmark_monitor_changed_cb), bookmarks);
221
222 g_object_unref (file);
223 }
224
225 static void
226 insert_bookmark_internal (NautilusBookmarkList *bookmarks,
227 NautilusBookmark *bookmark,
228 int index)
229 {
230 bookmarks->list = g_list_insert (bookmarks->list, bookmark, index);
231
232 g_signal_connect_object (bookmark, "contents-changed",
233 G_CALLBACK (bookmark_in_list_changed_callback), bookmarks, 0);
234 g_signal_connect_object (bookmark, "notify::icon",
235 G_CALLBACK (bookmark_in_list_notify), bookmarks, 0);
236 g_signal_connect_object (bookmark, "notify::name",
237 G_CALLBACK (bookmark_in_list_notify), bookmarks, 0);
238 }
239
240 /**
241 * nautilus_bookmark_list_append:
242 *
243 * Append a bookmark to a bookmark list.
244 * @bookmarks: NautilusBookmarkList to append to.
245 * @bookmark: Bookmark to append a copy of.
246 **/
247 void
248 nautilus_bookmark_list_append (NautilusBookmarkList *bookmarks,
249 NautilusBookmark *bookmark)
250 {
251 g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks));
252 g_return_if_fail (NAUTILUS_IS_BOOKMARK (bookmark));
253
254 insert_bookmark_internal (bookmarks,
255 nautilus_bookmark_copy (bookmark),
256 -1);
257
258 nautilus_bookmark_list_save_file (bookmarks);
259 }
260
261 /**
262 * nautilus_bookmark_list_contains:
263 *
264 * Check whether a bookmark with matching name and url is already in the list.
265 * @bookmarks: NautilusBookmarkList to check contents of.
266 * @bookmark: NautilusBookmark to match against.
267 *
268 * Return value: TRUE if matching bookmark is in list, FALSE otherwise
269 **/
270 gboolean
271 nautilus_bookmark_list_contains (NautilusBookmarkList *bookmarks,
272 NautilusBookmark *bookmark)
273 {
274 g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), FALSE);
275 g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), FALSE);
276
277 return g_list_find_custom (bookmarks->list,
278 (gpointer)bookmark,
279 nautilus_bookmark_compare_with)
280 != NULL;
281 }
282
283 /**
284 * nautilus_bookmark_list_delete_item_at:
285 *
286 * Delete the bookmark at the specified position.
287 * @bookmarks: the list of bookmarks.
288 * @index: index, must be less than length of list.
289 **/
290 void
291 nautilus_bookmark_list_delete_item_at (NautilusBookmarkList *bookmarks,
292 guint index)
293 {
294 GList *doomed;
295
296 g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks));
297 g_return_if_fail (index < g_list_length (bookmarks->list));
298
299 doomed = g_list_nth (bookmarks->list, index);
300 bookmarks->list = g_list_remove_link (bookmarks->list, doomed);
301
302 g_assert (NAUTILUS_IS_BOOKMARK (doomed->data));
303 stop_monitoring_bookmark (bookmarks, NAUTILUS_BOOKMARK (doomed->data));
304 g_object_unref (doomed->data);
305
306 g_list_free_1 (doomed);
307
308 nautilus_bookmark_list_save_file (bookmarks);
309 }
310
311 /**
312 * nautilus_bookmark_list_move_item:
313 *
314 * Move the item from the given position to the destination.
315 * @index: the index of the first bookmark.
316 * @destination: the index of the second bookmark.
317 **/
318 void
319 nautilus_bookmark_list_move_item (NautilusBookmarkList *bookmarks,
320 guint index,
321 guint destination)
322 {
323 GList *bookmark_item;
324
325 if (index == destination) {
326 return;
327 }
328
329 bookmark_item = g_list_nth (bookmarks->list, index);
330 bookmarks->list = g_list_remove_link (bookmarks->list,
331 bookmark_item);
332
333 bookmarks->list = g_list_insert (bookmarks->list,
334 bookmark_item->data,
335 destination);
336
337 nautilus_bookmark_list_save_file (bookmarks);
338 }
339
340 /**
341 * nautilus_bookmark_list_delete_items_with_uri:
342 *
343 * Delete all bookmarks with the given uri.
344 * @bookmarks: the list of bookmarks.
345 * @uri: The uri to match.
346 **/
347 void
348 nautilus_bookmark_list_delete_items_with_uri (NautilusBookmarkList *bookmarks,
349 const char *uri)
350 {
351 GList *node, *next;
352 gboolean list_changed;
353 char *bookmark_uri;
354
355 g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks));
356 g_return_if_fail (uri != NULL);
357
358 list_changed = FALSE;
359 for (node = bookmarks->list; node != NULL; node = next) {
360 next = node->next;
361
362 bookmark_uri = nautilus_bookmark_get_uri (NAUTILUS_BOOKMARK (node->data));
363 if (g_strcmp0 (bookmark_uri, uri) == 0) {
364 bookmarks->list = g_list_remove_link (bookmarks->list, node);
365 stop_monitoring_bookmark (bookmarks, NAUTILUS_BOOKMARK (node->data));
366 g_object_unref (node->data);
367 g_list_free_1 (node);
368 list_changed = TRUE;
369 }
370 g_free (bookmark_uri);
371 }
372
373 if (list_changed) {
374 nautilus_bookmark_list_save_file (bookmarks);
375 }
376 }
377
378 /**
379 * nautilus_bookmark_list_insert_item:
380 *
381 * Insert a bookmark at a specified position.
382 * @bookmarks: the list of bookmarks.
383 * @index: the position to insert the bookmark at.
384 * @new_bookmark: the bookmark to insert a copy of.
385 **/
386 void
387 nautilus_bookmark_list_insert_item (NautilusBookmarkList *bookmarks,
388 NautilusBookmark *new_bookmark,
389 guint index)
390 {
391 g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks));
392 g_return_if_fail (index <= g_list_length (bookmarks->list));
393
394 insert_bookmark_internal (bookmarks,
395 nautilus_bookmark_copy (new_bookmark),
396 index);
397
398 nautilus_bookmark_list_save_file (bookmarks);
399 }
400
401 /**
402 * nautilus_bookmark_list_item_at:
403 *
404 * Get the bookmark at the specified position.
405 * @bookmarks: the list of bookmarks.
406 * @index: index, must be less than length of list.
407 *
408 * Return value: the bookmark at position @index in @bookmarks.
409 **/
410 NautilusBookmark *
411 nautilus_bookmark_list_item_at (NautilusBookmarkList *bookmarks, guint index)
412 {
413 g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL);
414 g_return_val_if_fail (index < g_list_length (bookmarks->list), NULL);
415
416 return NAUTILUS_BOOKMARK (g_list_nth_data (bookmarks->list, index));
417 }
418
419 /**
420 * nautilus_bookmark_list_item_with_uri:
421 *
422 * Get the bookmark with the specified URI, if any
423 * @bookmarks: the list of bookmarks.
424 * @uri: an URI
425 *
426 * Return value: the bookmark with URI @uri, or %NULL.
427 **/
428 NautilusBookmark *
429 nautilus_bookmark_list_item_with_uri (NautilusBookmarkList *bookmarks,
430 const gchar *uri)
431 {
432 GList *node;
433 gchar *bookmark_uri;
434 NautilusBookmark *bookmark;
435 gboolean found = FALSE;
436
437 g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL);
438 g_return_val_if_fail (uri != NULL, NULL);
439
440 for (node = bookmarks->list; node != NULL; node = node->next) {
441 bookmark = node->data;
442 bookmark_uri = nautilus_bookmark_get_uri (bookmark);
443
444 if (g_strcmp0 (uri, bookmark_uri) == 0) {
445 found = TRUE;
446 }
447
448 g_free (bookmark_uri);
449
450 if (found) {
451 return bookmark;
452 }
453 }
454
455 return NULL;
456 }
457
458 /**
459 * nautilus_bookmark_list_length:
460 *
461 * Get the number of bookmarks in the list.
462 * @bookmarks: the list of bookmarks.
463 *
464 * Return value: the length of the bookmark list.
465 **/
466 guint
467 nautilus_bookmark_list_length (NautilusBookmarkList *bookmarks)
468 {
469 g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST(bookmarks), 0);
470
471 return g_list_length (bookmarks->list);
472 }
473
474 static void
475 process_next_op (NautilusBookmarkList *bookmarks);
476
477 static void
478 op_processed_cb (NautilusBookmarkList *self)
479 {
480 g_queue_pop_tail (self->pending_ops);
481
482 if (!g_queue_is_empty (self->pending_ops)) {
483 process_next_op (self);
484 }
485 }
486
487 static void
488 load_callback (GObject *source,
489 GAsyncResult *res,
490 gpointer user_data)
491 {
492 NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source);
493 gchar *contents;
494 char **lines;
495 int i;
496
497 contents = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
498
499 if (contents == NULL) {
500 return;
501 }
502
503 lines = g_strsplit (contents, "\n", -1);
504 for (i = 0; lines[i]; i++) {
505 /* Ignore empty or invalid lines that cannot be parsed properly */
506 if (lines[i][0] != '\0' && lines[i][0] != ' ') {
507 /* gtk 2.7/2.8 might have labels appended to bookmarks which are separated by a space */
508 /* we must seperate the bookmark uri and the potential label */
509 char *space, *label;
510
511 label = NULL;
512 space = strchr (lines[i], ' ');
513 if (space) {
514 *space = '\0';
515 label = g_strdup (space + 1);
516 }
517
518 insert_bookmark_internal (self, new_bookmark_from_uri (lines[i], label), -1);
519 g_free (label);
520 }
521 }
522
523 g_signal_emit (self, signals[CHANGED], 0);
524 op_processed_cb (self);
525
526 g_strfreev (lines);
527 }
528
529 static void
530 load_io_thread (GSimpleAsyncResult *result,
531 GObject *object,
532 GCancellable *cancellable)
533 {
534 GFile *file;
535 gchar *contents;
536 GError *error = NULL;
537
538 file = nautilus_bookmark_list_get_file ();
539 if (!g_file_query_exists (file, NULL)) {
540 g_object_unref (file);
541 file = nautilus_bookmark_list_get_legacy_file ();
542 }
543
544 g_file_load_contents (file, NULL, &contents, NULL, NULL, &error);
545 g_object_unref (file);
546
547 if (error != NULL) {
548 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
549 g_warning ("Could not load bookmark file: %s\n", error->message);
550 }
551 g_error_free (error);
552 } else {
553 g_simple_async_result_set_op_res_gpointer (result, contents, g_free);
554 }
555 }
556
557 static void
558 load_file_async (NautilusBookmarkList *self)
559 {
560 GSimpleAsyncResult *result;
561
562 /* Wipe out old list. */
563 clear (self);
564
565 result = g_simple_async_result_new (G_OBJECT (self),
566 load_callback, NULL, NULL);
567 g_simple_async_result_run_in_thread (result, load_io_thread,
568 G_PRIORITY_DEFAULT, NULL);
569 g_object_unref (result);
570 }
571
572 static void
573 save_callback (GObject *source,
574 GAsyncResult *res,
575 gpointer user_data)
576 {
577 NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source);
578 GFile *file;
579
580 /* re-enable bookmark file monitoring */
581 file = nautilus_bookmark_list_get_file ();
582 self->monitor = g_file_monitor_file (file, 0, NULL, NULL);
583 g_object_unref (file);
584
585 g_file_monitor_set_rate_limit (self->monitor, 1000);
586 g_signal_connect (self->monitor, "changed",
587 G_CALLBACK (bookmark_monitor_changed_cb), self);
588
589 op_processed_cb (self);
590 }
591
592 static void
593 save_io_thread (GSimpleAsyncResult *result,
594 GObject *object,
595 GCancellable *cancellable)
596 {
597 gchar *contents, *path;
598 GFile *parent, *file;
599 GError *error = NULL;
600
601 file = nautilus_bookmark_list_get_file ();
602 parent = g_file_get_parent (file);
603 path = g_file_get_path (parent);
604 g_mkdir_with_parents (path, 0700);
605 g_free (path);
606 g_object_unref (parent);
607
608 contents = g_simple_async_result_get_op_res_gpointer (result);
609 g_file_replace_contents (file,
610 contents, strlen (contents),
611 NULL, FALSE, 0, NULL,
612 NULL, &error);
613
614 if (error != NULL) {
615 g_warning ("Unable to replace contents of the bookmarks file: %s",
616 error->message);
617 g_error_free (error);
618 }
619
620 g_object_unref (file);
621 }
622
623 static void
624 save_file_async (NautilusBookmarkList *self)
625 {
626 GSimpleAsyncResult *result;
627 GString *bookmark_string;
628 gchar *contents;
629 GList *l;
630
631 bookmark_string = g_string_new (NULL);
632
633 /* temporarily disable bookmark file monitoring when writing file */
634 if (self->monitor != NULL) {
635 g_file_monitor_cancel (self->monitor);
636 self->monitor = NULL;
637 }
638
639 for (l = self->list; l; l = l->next) {
640 NautilusBookmark *bookmark;
641
642 bookmark = NAUTILUS_BOOKMARK (l->data);
643
644 /* make sure we save label if it has one for compatibility with GTK 2.7 and 2.8 */
645 if (nautilus_bookmark_get_has_custom_name (bookmark)) {
646 const char *label;
647 char *uri;
648 label = nautilus_bookmark_get_name (bookmark);
649 uri = nautilus_bookmark_get_uri (bookmark);
650 g_string_append_printf (bookmark_string,
651 "%s %s\n", uri, label);
652 g_free (uri);
653 } else {
654 char *uri;
655 uri = nautilus_bookmark_get_uri (bookmark);
656 g_string_append_printf (bookmark_string, "%s\n", uri);
657 g_free (uri);
658 }
659 }
660
661 result = g_simple_async_result_new (G_OBJECT (self),
662 save_callback, NULL, NULL);
663 contents = g_string_free (bookmark_string, FALSE);
664 g_simple_async_result_set_op_res_gpointer (result, contents, g_free);
665
666 g_simple_async_result_run_in_thread (result, save_io_thread,
667 G_PRIORITY_DEFAULT, NULL);
668 g_object_unref (result);
669 }
670
671 static void
672 process_next_op (NautilusBookmarkList *bookmarks)
673 {
674 gint op;
675
676 op = GPOINTER_TO_INT (g_queue_peek_tail (bookmarks->pending_ops));
677
678 if (op == LOAD_JOB) {
679 load_file_async (bookmarks);
680 } else {
681 save_file_async (bookmarks);
682 }
683 }
684
685 /**
686 * nautilus_bookmark_list_load_file:
687 *
688 * Reads bookmarks from file, clobbering contents in memory.
689 * @bookmarks: the list of bookmarks to fill with file contents.
690 **/
691 static void
692 nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks)
693 {
694 g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (LOAD_JOB));
695
696 if (g_queue_get_length (bookmarks->pending_ops) == 1) {
697 process_next_op (bookmarks);
698 }
699 }
700
701 /**
702 * nautilus_bookmark_list_save_file:
703 *
704 * Save bookmarks to disk.
705 * @bookmarks: the list of bookmarks to save.
706 **/
707 static void
708 nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks)
709 {
710 g_signal_emit (bookmarks, signals[CHANGED], 0);
711
712 g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (SAVE_JOB));
713
714 if (g_queue_get_length (bookmarks->pending_ops) == 1) {
715 process_next_op (bookmarks);
716 }
717 }
718
719 /**
720 * nautilus_bookmark_list_new:
721 *
722 * Create a new bookmark_list, with contents read from disk.
723 *
724 * Return value: A pointer to the new widget.
725 **/
726 NautilusBookmarkList *
727 nautilus_bookmark_list_new (void)
728 {
729 NautilusBookmarkList *list;
730
731 list = NAUTILUS_BOOKMARK_LIST (g_object_new (NAUTILUS_TYPE_BOOKMARK_LIST, NULL));
732
733 return list;
734 }