1/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2/* GTK - The GIMP Toolkit
3 * gtkbookmarksmanager.c: Utilities to manage and monitor ~/.gtk-bookmarks
4 * Copyright (C) 2003, Red Hat, Inc.
5 * Copyright (C) 2007-2008 Carlos Garnacho
6 * Copyright (C) 2011 Suse
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as
10 * published by the Free Software Foundation; either version 2.1 of the
11 * License, or (at your option) any later version.
12 *
13 * This program 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
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
20 *
21 * Authors: Federico Mena Quintero <federico@gnome.org>
22 */
23
24#include "config.h"
25
26#include <string.h>
27
28#include <glib/gi18n-lib.h>
29
30#include "gtkbookmarksmanagerprivate.h"
31#include "gtkfilechooser.h" /* for the GError types */
32
33static void
34_gtk_bookmark_free (gpointer data)
35{
36 GtkBookmark *bookmark = data;
37
38 g_object_unref (object: bookmark->file);
39 g_free (mem: bookmark->label);
40 g_slice_free (GtkBookmark, bookmark);
41}
42
43static void
44set_error_bookmark_doesnt_exist (GFile *file, GError **error)
45{
46 char *uri = g_file_get_uri (file);
47
48 g_set_error (err: error,
49 GTK_FILE_CHOOSER_ERROR,
50 code: GTK_FILE_CHOOSER_ERROR_NONEXISTENT,
51 _("%s does not exist in the bookmarks list"),
52 uri);
53
54 g_free (mem: uri);
55}
56
57static GFile *
58get_legacy_bookmarks_file (void)
59{
60 GFile *file;
61 char *filename;
62
63 filename = g_build_filename (first_element: g_get_home_dir (), ".gtk-bookmarks", NULL);
64 file = g_file_new_for_path (path: filename);
65 g_free (mem: filename);
66
67 return file;
68}
69
70static GFile *
71get_bookmarks_file (void)
72{
73 GFile *file;
74 char *filename;
75
76 /* Use gtk-3.0's bookmarks file as the format didn't change.
77 * Add the 3.0 file format to get_legacy_bookmarks_file() when
78 * the format does change.
79 */
80 filename = g_build_filename (first_element: g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL);
81 file = g_file_new_for_path (path: filename);
82 g_free (mem: filename);
83
84 return file;
85}
86
87static GSList *
88parse_bookmarks (const char *contents)
89{
90 char **lines, *space;
91 GSList *bookmarks = NULL;
92 int i;
93
94 lines = g_strsplit (string: contents, delimiter: "\n", max_tokens: -1);
95
96 for (i = 0; lines[i]; i++)
97 {
98 GtkBookmark *bookmark;
99
100 if (!*lines[i])
101 continue;
102
103 if (!g_utf8_validate (str: lines[i], max_len: -1, NULL))
104 continue;
105
106 bookmark = g_slice_new0 (GtkBookmark);
107
108 if ((space = strchr (s: lines[i], c: ' ')) != NULL)
109 {
110 space[0] = '\0';
111 bookmark->label = g_strdup (str: space + 1);
112 }
113
114 bookmark->file = g_file_new_for_uri (uri: lines[i]);
115 bookmarks = g_slist_prepend (list: bookmarks, data: bookmark);
116 }
117
118 bookmarks = g_slist_reverse (list: bookmarks);
119 g_strfreev (str_array: lines);
120
121 return bookmarks;
122}
123
124static GSList *
125read_bookmarks (GFile *file)
126{
127 char *contents;
128 GSList *bookmarks = NULL;
129
130 if (!g_file_load_contents (file, NULL, contents: &contents,
131 NULL, NULL, NULL))
132 return NULL;
133
134 bookmarks = parse_bookmarks (contents);
135
136 g_free (mem: contents);
137
138 return bookmarks;
139}
140
141static void
142notify_changed (GtkBookmarksManager *manager)
143{
144 if (manager->changed_func)
145 manager->changed_func (manager->changed_func_data);
146}
147
148static void
149read_bookmarks_finish (GObject *source,
150 GAsyncResult *result,
151 gpointer data)
152{
153 GFile *file = G_FILE (source);
154 GtkBookmarksManager *manager = data;
155 char *contents = NULL;
156 GError *error = NULL;
157
158 if (!g_file_load_contents_finish (file, res: result, contents: &contents, NULL, NULL, error: &error))
159 {
160 if (!g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED))
161 g_warning ("Failed to load '%s': %s", g_file_peek_path (file), error->message);
162 g_error_free (error);
163 return;
164 }
165
166 g_slist_free_full (list: manager->bookmarks, free_func: _gtk_bookmark_free);
167 manager->bookmarks = parse_bookmarks (contents);
168
169 g_free (mem: contents);
170
171 notify_changed (manager);
172}
173
174static void
175save_bookmarks (GFile *bookmarks_file,
176 GSList *bookmarks)
177{
178 GError *error = NULL;
179 GString *contents;
180 GSList *l;
181 GFile *parent = NULL;
182
183 contents = g_string_new (init: "");
184
185 for (l = bookmarks; l; l = l->next)
186 {
187 GtkBookmark *bookmark = l->data;
188 char *uri;
189
190 uri = g_file_get_uri (file: bookmark->file);
191 if (!uri)
192 continue;
193
194 g_string_append (string: contents, val: uri);
195
196 if (bookmark->label && g_utf8_validate (str: bookmark->label, max_len: -1, NULL))
197 g_string_append_printf (string: contents, format: " %s", bookmark->label);
198
199 g_string_append_c (contents, '\n');
200 g_free (mem: uri);
201 }
202
203 parent = g_file_get_parent (file: bookmarks_file);
204 if (!g_file_make_directory_with_parents (file: parent, NULL, error: &error))
205 {
206 if (g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_EXISTS))
207 g_clear_error (err: &error);
208 else
209 goto out;
210 }
211 if (!g_file_replace_contents (file: bookmarks_file,
212 contents: contents->str,
213 length: contents->len,
214 NULL, FALSE, flags: 0, NULL,
215 NULL, error: &error))
216 goto out;
217
218 out:
219 if (error)
220 {
221 g_critical ("%s", error->message);
222 g_error_free (error);
223 }
224 g_clear_object (&parent);
225 g_string_free (string: contents, TRUE);
226}
227
228static void
229bookmarks_file_changed (GFileMonitor *monitor,
230 GFile *file,
231 GFile *other_file,
232 GFileMonitorEvent event,
233 gpointer data)
234{
235 GtkBookmarksManager *manager = data;
236
237 switch (event)
238 {
239 case G_FILE_MONITOR_EVENT_CHANGED:
240 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
241 case G_FILE_MONITOR_EVENT_CREATED:
242 g_file_load_contents_async (file, cancellable: manager->cancellable, callback: read_bookmarks_finish, user_data: manager);
243 break;
244
245 case G_FILE_MONITOR_EVENT_DELETED:
246 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
247 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
248 case G_FILE_MONITOR_EVENT_UNMOUNTED:
249 case G_FILE_MONITOR_EVENT_MOVED:
250 case G_FILE_MONITOR_EVENT_RENAMED:
251 case G_FILE_MONITOR_EVENT_MOVED_IN:
252 case G_FILE_MONITOR_EVENT_MOVED_OUT:
253 default:
254 /* ignore at the moment */
255 break;
256 }
257}
258
259GtkBookmarksManager *
260_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func, gpointer changed_func_data)
261{
262 GtkBookmarksManager *manager;
263 GFile *bookmarks_file;
264 GError *error;
265
266 manager = g_new0 (GtkBookmarksManager, 1);
267
268 manager->changed_func = changed_func;
269 manager->changed_func_data = changed_func_data;
270
271 manager->cancellable = g_cancellable_new ();
272
273 bookmarks_file = get_bookmarks_file ();
274 if (!g_file_query_exists (file: bookmarks_file, NULL))
275 {
276 GFile *legacy_bookmarks_file;
277
278 /* Read the legacy one and write it to the new one */
279 legacy_bookmarks_file = get_legacy_bookmarks_file ();
280 manager->bookmarks = read_bookmarks (file: legacy_bookmarks_file);
281 if (manager->bookmarks)
282 save_bookmarks (bookmarks_file, bookmarks: manager->bookmarks);
283
284 g_object_unref (object: legacy_bookmarks_file);
285 }
286 else
287 g_file_load_contents_async (file: bookmarks_file, cancellable: manager->cancellable, callback: read_bookmarks_finish, user_data: manager);
288
289 error = NULL;
290 manager->bookmarks_monitor = g_file_monitor_file (file: bookmarks_file,
291 flags: G_FILE_MONITOR_NONE,
292 NULL, error: &error);
293 if (error)
294 {
295 g_warning ("%s", error->message);
296 g_error_free (error);
297 }
298 else
299 manager->bookmarks_monitor_changed_id = g_signal_connect (manager->bookmarks_monitor, "changed",
300 G_CALLBACK (bookmarks_file_changed), manager);
301
302
303 g_object_unref (object: bookmarks_file);
304
305 return manager;
306}
307
308void
309_gtk_bookmarks_manager_free (GtkBookmarksManager *manager)
310{
311 g_return_if_fail (manager != NULL);
312
313 g_cancellable_cancel (cancellable: manager->cancellable);
314 g_object_unref (object: manager->cancellable);
315
316 if (manager->bookmarks_monitor)
317 {
318 g_file_monitor_cancel (monitor: manager->bookmarks_monitor);
319 g_signal_handler_disconnect (instance: manager->bookmarks_monitor, handler_id: manager->bookmarks_monitor_changed_id);
320 manager->bookmarks_monitor_changed_id = 0;
321 g_object_unref (object: manager->bookmarks_monitor);
322 }
323
324 g_slist_free_full (list: manager->bookmarks, free_func: _gtk_bookmark_free);
325
326 g_free (mem: manager);
327}
328
329GSList *
330_gtk_bookmarks_manager_list_bookmarks (GtkBookmarksManager *manager)
331{
332 GSList *bookmarks, *files = NULL;
333
334 g_return_val_if_fail (manager != NULL, NULL);
335
336 bookmarks = manager->bookmarks;
337
338 while (bookmarks)
339 {
340 GtkBookmark *bookmark;
341
342 bookmark = bookmarks->data;
343 bookmarks = bookmarks->next;
344
345 files = g_slist_prepend (list: files, g_object_ref (bookmark->file));
346 }
347
348 return g_slist_reverse (list: files);
349}
350
351static GSList *
352find_bookmark_link_for_file (GSList *bookmarks, GFile *file, int *position_ret)
353{
354 int pos;
355
356 pos = 0;
357 for (; bookmarks; bookmarks = bookmarks->next)
358 {
359 GtkBookmark *bookmark = bookmarks->data;
360
361 if (g_file_equal (file1: file, file2: bookmark->file))
362 {
363 if (position_ret)
364 *position_ret = pos;
365
366 return bookmarks;
367 }
368
369 pos++;
370 }
371
372 if (position_ret)
373 *position_ret = -1;
374
375 return NULL;
376}
377
378gboolean
379_gtk_bookmarks_manager_has_bookmark (GtkBookmarksManager *manager,
380 GFile *file)
381{
382 GSList *link;
383
384 link = find_bookmark_link_for_file (bookmarks: manager->bookmarks, file, NULL);
385 return (link != NULL);
386}
387
388gboolean
389_gtk_bookmarks_manager_insert_bookmark (GtkBookmarksManager *manager,
390 GFile *file,
391 int position,
392 GError **error)
393{
394 GSList *link;
395 GtkBookmark *bookmark;
396 GFile *bookmarks_file;
397
398 g_return_val_if_fail (manager != NULL, FALSE);
399 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
400
401 link = find_bookmark_link_for_file (bookmarks: manager->bookmarks, file, NULL);
402
403 if (link)
404 {
405 char *uri;
406 bookmark = link->data;
407 uri = g_file_get_uri (file: bookmark->file);
408
409 g_set_error (err: error,
410 GTK_FILE_CHOOSER_ERROR,
411 code: GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS,
412 _("%s already exists in the bookmarks list"),
413 uri);
414
415 g_free (mem: uri);
416
417 return FALSE;
418 }
419
420 bookmark = g_slice_new0 (GtkBookmark);
421 bookmark->file = g_object_ref (file);
422
423 manager->bookmarks = g_slist_insert (list: manager->bookmarks, data: bookmark, position);
424
425 bookmarks_file = get_bookmarks_file ();
426 save_bookmarks (bookmarks_file, bookmarks: manager->bookmarks);
427 g_object_unref (object: bookmarks_file);
428
429 notify_changed (manager);
430
431 return TRUE;
432}
433
434gboolean
435_gtk_bookmarks_manager_remove_bookmark (GtkBookmarksManager *manager,
436 GFile *file,
437 GError **error)
438{
439 GSList *link;
440 GFile *bookmarks_file;
441
442 g_return_val_if_fail (manager != NULL, FALSE);
443 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
444
445 if (!manager->bookmarks)
446 return FALSE;
447
448 link = find_bookmark_link_for_file (bookmarks: manager->bookmarks, file, NULL);
449 if (link)
450 {
451 GtkBookmark *bookmark = link->data;
452
453 manager->bookmarks = g_slist_remove_link (list: manager->bookmarks, link_: link);
454 _gtk_bookmark_free (data: bookmark);
455 g_slist_free_1 (list: link);
456 }
457 else
458 {
459 set_error_bookmark_doesnt_exist (file, error);
460 return FALSE;
461 }
462
463 bookmarks_file = get_bookmarks_file ();
464 save_bookmarks (bookmarks_file, bookmarks: manager->bookmarks);
465 g_object_unref (object: bookmarks_file);
466
467 notify_changed (manager);
468
469 return TRUE;
470}
471
472gboolean
473_gtk_bookmarks_manager_reorder_bookmark (GtkBookmarksManager *manager,
474 GFile *file,
475 int new_position,
476 GError **error)
477{
478 GSList *link;
479 GFile *bookmarks_file;
480 int old_position;
481
482 g_return_val_if_fail (manager != NULL, FALSE);
483 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
484 g_return_val_if_fail (new_position >= 0, FALSE);
485
486 if (!manager->bookmarks)
487 return FALSE;
488
489 link = find_bookmark_link_for_file (bookmarks: manager->bookmarks, file, position_ret: &old_position);
490 if (new_position == old_position)
491 return TRUE;
492
493 if (link)
494 {
495 GtkBookmark *bookmark = link->data;
496
497 manager->bookmarks = g_slist_remove_link (list: manager->bookmarks, link_: link);
498 g_slist_free_1 (list: link);
499
500 if (new_position > old_position)
501 new_position--;
502
503 manager->bookmarks = g_slist_insert (list: manager->bookmarks, data: bookmark, position: new_position);
504 }
505 else
506 {
507 set_error_bookmark_doesnt_exist (file, error);
508 return FALSE;
509 }
510
511 bookmarks_file = get_bookmarks_file ();
512 save_bookmarks (bookmarks_file, bookmarks: manager->bookmarks);
513 g_object_unref (object: bookmarks_file);
514
515 notify_changed (manager);
516
517 return TRUE;
518}
519
520char *
521_gtk_bookmarks_manager_get_bookmark_label (GtkBookmarksManager *manager,
522 GFile *file)
523{
524 GSList *bookmarks;
525 char *label = NULL;
526
527 g_return_val_if_fail (manager != NULL, NULL);
528 g_return_val_if_fail (file != NULL, NULL);
529
530 bookmarks = manager->bookmarks;
531
532 while (bookmarks)
533 {
534 GtkBookmark *bookmark;
535
536 bookmark = bookmarks->data;
537 bookmarks = bookmarks->next;
538
539 if (g_file_equal (file1: file, file2: bookmark->file))
540 {
541 label = g_strdup (str: bookmark->label);
542 break;
543 }
544 }
545
546 return label;
547}
548
549gboolean
550_gtk_bookmarks_manager_set_bookmark_label (GtkBookmarksManager *manager,
551 GFile *file,
552 const char *label,
553 GError **error)
554{
555 GFile *bookmarks_file;
556 GSList *link;
557
558 g_return_val_if_fail (manager != NULL, FALSE);
559 g_return_val_if_fail (file != NULL, FALSE);
560
561 link = find_bookmark_link_for_file (bookmarks: manager->bookmarks, file, NULL);
562 if (link)
563 {
564 GtkBookmark *bookmark = link->data;
565
566 g_free (mem: bookmark->label);
567 bookmark->label = g_strdup (str: label);
568 }
569 else
570 {
571 set_error_bookmark_doesnt_exist (file, error);
572 return FALSE;
573 }
574
575 bookmarks_file = get_bookmarks_file ();
576 save_bookmarks (bookmarks_file, bookmarks: manager->bookmarks);
577 g_object_unref (object: bookmarks_file);
578
579 notify_changed (manager);
580
581 return TRUE;
582}
583
584static gboolean
585_gtk_bookmarks_manager_get_xdg_type (GtkBookmarksManager *manager,
586 GFile *file,
587 GUserDirectory *directory)
588{
589 GSList *link;
590 gboolean match;
591 GFile *location;
592 const char *path;
593 GUserDirectory dir;
594 GtkBookmark *bookmark;
595
596 link = find_bookmark_link_for_file (bookmarks: manager->bookmarks, file, NULL);
597 if (!link)
598 return FALSE;
599
600 match = FALSE;
601 bookmark = link->data;
602
603 for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++)
604 {
605 path = g_get_user_special_dir (directory: dir);
606 if (!path)
607 continue;
608
609 location = g_file_new_for_path (path);
610 match = g_file_equal (file1: location, file2: bookmark->file);
611 g_object_unref (object: location);
612
613 if (match)
614 break;
615 }
616
617 if (match && directory != NULL)
618 *directory = dir;
619
620 return match;
621}
622
623gboolean
624_gtk_bookmarks_manager_get_is_builtin (GtkBookmarksManager *manager,
625 GFile *file)
626{
627 GUserDirectory xdg_type;
628
629 /* if this is not an XDG dir, it's never builtin */
630 if (!_gtk_bookmarks_manager_get_xdg_type (manager, file, directory: &xdg_type))
631 return FALSE;
632
633 /* exclude XDG locations we don't display by default */
634 return _gtk_bookmarks_manager_get_is_xdg_dir_builtin (xdg_type);
635}
636
637gboolean
638_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type)
639{
640 return (xdg_type != G_USER_DIRECTORY_DESKTOP) &&
641 (xdg_type != G_USER_DIRECTORY_TEMPLATES) &&
642 (xdg_type != G_USER_DIRECTORY_PUBLIC_SHARE);
643}
644

source code of gtk/gtk/gtkbookmarksmanager.c