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 | |
33 | static 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 | |
43 | static void |
44 | set_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 | |
57 | static GFile * |
58 | get_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 | |
70 | static GFile * |
71 | get_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 | |
87 | static GSList * |
88 | parse_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 | |
124 | static GSList * |
125 | read_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 | |
141 | static void |
142 | notify_changed (GtkBookmarksManager *manager) |
143 | { |
144 | if (manager->changed_func) |
145 | manager->changed_func (manager->changed_func_data); |
146 | } |
147 | |
148 | static void |
149 | read_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 | |
174 | static void |
175 | save_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 | |
228 | static void |
229 | bookmarks_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 | |
259 | GtkBookmarksManager * |
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 | |
308 | void |
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 | |
329 | GSList * |
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 | |
351 | static GSList * |
352 | find_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 | |
378 | gboolean |
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 | |
388 | gboolean |
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 | |
434 | gboolean |
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 | |
472 | gboolean |
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 | |
520 | char * |
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 | |
549 | gboolean |
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 | |
584 | static 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 | |
623 | gboolean |
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 | |
637 | gboolean |
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 | |