1 /*
2  *      fm-bookmarks.c
3  *
4  *      Copyright 2009 PCMan <pcman.tw@gmail.com>
5  *      Copyright 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
6  *
7  *      This file is a part of the Libfm library.
8  *
9  *      This library is free software; you can redistribute it and/or
10  *      modify it under the terms of the GNU Lesser General Public
11  *      License as published by the Free Software Foundation; either
12  *      version 2.1 of the License, or (at your option) any later version.
13  *
14  *      This library is distributed in the hope that it will be useful,
15  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  *      Lesser General Public License for more details.
18  *
19  *      You should have received a copy of the GNU Lesser General Public
20  *      License along with this library; if not, write to the Free Software
21  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  */
23 
24 /**
25  * SECTION:fm-bookmarks
26  * @short_description: Bookmarks support for libfm.
27  * @title: FmBookmarks
28  *
29  * @include: libfm/fm.h
30  *
31  * The application that uses libfm can use user-wide bookmark list via
32  * class FmBookmarks.
33  */
34 
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
38 
39 #define FM_DISABLE_SEAL
40 
41 #include "fm-bookmarks.h"
42 #include <stdio.h>
43 #include <string.h>
44 #include "fm-utils.h"
45 
46 enum
47 {
48     CHANGED,
49     N_SIGNALS
50 };
51 
52 static FmBookmarks* fm_bookmarks_new (void);
53 
54 static void fm_bookmarks_finalize           (GObject *object);
55 static GList* load_bookmarks(GFile *file);
56 
57 G_DEFINE_TYPE(FmBookmarks, fm_bookmarks, G_TYPE_OBJECT);
58 
59 G_LOCK_DEFINE_STATIC(bookmarks);
60 
61 static FmBookmarks* singleton = NULL;
62 static guint signals[N_SIGNALS];
63 
64 static guint idle_handler = 0;
65 
fm_bookmarks_class_init(FmBookmarksClass * klass)66 static void fm_bookmarks_class_init(FmBookmarksClass *klass)
67 {
68     GObjectClass *g_object_class;
69     g_object_class = G_OBJECT_CLASS(klass);
70     g_object_class->finalize = fm_bookmarks_finalize;
71 
72     /**
73      * FmBookmarks::changed:
74      * @bookmarks: pointer to bookmarks list singleton descriptor
75      *
76      * The "changed" signal is emitted when some bookmark item is
77      * changed, added, or removed.
78      *
79      * Since: 0.1.0
80      */
81     signals[CHANGED] =
82         g_signal_new("changed",
83                      G_TYPE_FROM_CLASS(klass),
84                      G_SIGNAL_RUN_FIRST,
85                      G_STRUCT_OFFSET(FmBookmarksClass, changed),
86                      NULL, NULL,
87                      g_cclosure_marshal_VOID__VOID,
88                      G_TYPE_NONE, 0 );
89 }
90 
fm_bookmarks_finalize(GObject * object)91 static void fm_bookmarks_finalize(GObject *object)
92 {
93     FmBookmarks *self;
94 
95     g_return_if_fail(object != NULL);
96     g_return_if_fail(FM_IS_BOOKMARKS(object));
97 
98     self = FM_BOOKMARKS(object);
99 
100     if(idle_handler)
101     {
102         g_source_remove(idle_handler);
103         idle_handler = 0;
104     }
105 
106     g_list_foreach(self->items, (GFunc)fm_bookmark_item_unref, NULL);
107     g_list_free(self->items);
108 
109     g_object_unref(self->mon);
110     g_object_unref(self->file);
111 
112     G_OBJECT_CLASS(fm_bookmarks_parent_class)->finalize(object);
113 }
114 
115 
get_legacy_bookmarks_file(void)116 static inline char *get_legacy_bookmarks_file(void)
117 {
118     return g_build_filename(fm_get_home_dir(), ".gtk-bookmarks", NULL);
119 }
120 
get_new_bookmarks_file(void)121 static inline char *get_new_bookmarks_file(void)
122 {
123     return g_build_filename(g_get_user_config_dir(), "gtk-3.0", "bookmarks", NULL);
124 }
125 
on_changed(GFileMonitor * mon,GFile * gf,GFile * other,GFileMonitorEvent evt,FmBookmarks * bookmarks)126 static void on_changed( GFileMonitor* mon, GFile* gf, GFile* other,
127                     GFileMonitorEvent evt, FmBookmarks* bookmarks )
128 {
129     G_LOCK(bookmarks);
130     /* reload bookmarks */
131     g_list_foreach(bookmarks->items, (GFunc)fm_bookmark_item_unref, NULL);
132     g_list_free(bookmarks->items);
133 
134     bookmarks->items = load_bookmarks(bookmarks->file);
135     G_UNLOCK(bookmarks);
136     g_signal_emit(bookmarks, signals[CHANGED], 0);
137 }
138 
new_item(char * line)139 static FmBookmarkItem* new_item(char* line)
140 {
141     FmBookmarkItem* item = g_slice_new0(FmBookmarkItem);
142     char* sep;
143     sep = strchr(line, '\n');
144     if(sep)
145         *sep = '\0';
146     sep = strchr(line, ' ');
147     if(sep)
148         *sep = '\0';
149 
150     item->path = fm_path_new_for_uri(line);
151     if(sep)
152         item->name = g_strdup(sep+1);
153     else
154         item->name = g_filename_display_name(fm_path_get_basename(item->path));
155 
156     item->n_ref = 1;
157     return item;
158 }
159 
load_bookmarks(GFile * file)160 static GList* load_bookmarks(GFile *file)
161 {
162     char *fpath = g_file_get_path(file);
163     FILE* f;
164     char buf[1024];
165     FmBookmarkItem* item;
166     GList* items = NULL;
167 
168     /* load the file */
169     f = fopen(fpath, "r");
170     g_free(fpath);
171     if(f)
172     {
173         while(fgets(buf, 1024, f))
174         {
175             item = new_item(buf);
176             items = g_list_prepend(items, item);
177         }
178         fclose(f);
179     }
180     items = g_list_reverse(items);
181     return items;
182 }
183 
fm_bookmarks_init(FmBookmarks * self)184 static void fm_bookmarks_init(FmBookmarks *self)
185 {
186     /* trying the XDG-3.0 first and use it if it exists */
187     char* fpath = get_new_bookmarks_file();
188 
189     self->file = g_file_new_for_path(fpath);
190     g_free(fpath);
191     self->items = load_bookmarks(self->file);
192     if (!self->items) /* not found, use legacy file */
193     {
194         g_object_unref(self->file);
195         fpath = get_legacy_bookmarks_file();
196         self->file = g_file_new_for_path(fpath);
197         g_free(fpath);
198         self->items = load_bookmarks(self->file);
199     }
200     self->mon = g_file_monitor_file(self->file, 0, NULL, NULL);
201     if (self->mon)
202         g_signal_connect(self->mon, "changed", G_CALLBACK(on_changed), self);
203 }
204 
fm_bookmarks_new(void)205 static FmBookmarks *fm_bookmarks_new(void)
206 {
207     return g_object_new(FM_BOOKMARKS_TYPE, NULL);
208 }
209 
210 /**
211  * fm_bookmarks_dup
212  *
213  * Returns reference to bookmarks list singleton descriptor.
214  *
215  * This API is not thread-safe and should be used only in default context.
216  *
217  * Return value: (transfer full): a reference to bookmarks list
218  *
219  * Since: 0.1.99
220  */
fm_bookmarks_dup(void)221 FmBookmarks* fm_bookmarks_dup(void)
222 {
223     G_LOCK(bookmarks);
224     if( G_LIKELY(singleton) )
225         g_object_ref(singleton);
226     else
227     {
228         singleton = fm_bookmarks_new();
229         g_object_add_weak_pointer(G_OBJECT(singleton), (gpointer*)&singleton);
230     }
231     G_UNLOCK(bookmarks);
232     return singleton;
233 }
234 
235 /**
236  * fm_bookmarks_list_all
237  * @bookmarks: bookmarks list
238  *
239  * Returns list of FmBookmarkItem retrieved from bookmarks list. Returned
240  * list is owned by bookmarks list and should not be freed by caller.
241  *
242  * Return value: (transfer none) (element-type FmBookmarkItem): list of bookmark items
243  *
244  * Since: 0.1.0
245  *
246  * Deprecated: 1.0.2: Use fm_bookmarks_get_all() instead.
247  */
fm_bookmarks_list_all(FmBookmarks * bookmarks)248 const GList* fm_bookmarks_list_all(FmBookmarks* bookmarks)
249 {
250     return bookmarks->items;
251 }
252 
253 /**
254  * fm_bookmark_item_ref
255  * @item: an item
256  *
257  * Increases reference counter on @item.
258  *
259  * Returns: @item.
260  *
261  * Since: 1.0.2
262  */
fm_bookmark_item_ref(FmBookmarkItem * item)263 FmBookmarkItem* fm_bookmark_item_ref(FmBookmarkItem* item)
264 {
265     g_return_val_if_fail(item != NULL, NULL);
266     g_atomic_int_inc(&item->n_ref);
267     return item;
268 }
269 
270 /**
271  * fm_bookmark_item_unref
272  * @item: item to be freed
273  *
274  * Decreases reference counter on @item and frees data when it reaches 0.
275  *
276  * Since: 1.0.2
277  */
fm_bookmark_item_unref(FmBookmarkItem * item)278 void fm_bookmark_item_unref(FmBookmarkItem *item)
279 {
280     g_return_if_fail(item != NULL);
281     if(g_atomic_int_dec_and_test(&item->n_ref))
282     {
283         g_free(item->name);
284         fm_path_unref(item->path);
285         g_slice_free(FmBookmarkItem, item);
286     }
287 }
288 
289 /**
290  * fm_bookmarks_get_all
291  * @bookmarks: bookmarks list
292  *
293  * Returns list of FmBookmarkItem retrieved from bookmarks list. Returned
294  * list should be freed with g_list_free_full(list, fm_bookmark_item_unref).
295  *
296  * Return value: (transfer full) (element-type FmBookmarkItem): list of bookmark items
297  *
298  * Since: 1.0.2
299  */
fm_bookmarks_get_all(FmBookmarks * bookmarks)300 GList* fm_bookmarks_get_all(FmBookmarks* bookmarks)
301 {
302     GList *copy = NULL, *l;
303 
304     G_LOCK(bookmarks);
305     for(l = bookmarks->items; l; l = l->next)
306     {
307         fm_bookmark_item_ref(l->data);
308         copy = g_list_prepend(copy, l->data);
309     }
310     copy = g_list_reverse(copy);
311     G_UNLOCK(bookmarks);
312     return copy;
313 }
314 
save_bookmarks(FmBookmarks * bookmarks)315 static gboolean save_bookmarks(FmBookmarks* bookmarks)
316 {
317     FmBookmarkItem* item;
318     GList* l;
319     GString* buf;
320     GError *err = NULL;
321 
322     if(g_source_is_destroyed(g_main_current_source()))
323         return FALSE;
324 
325     buf = g_string_sized_new(1024);
326     G_LOCK(bookmarks);
327     for( l=bookmarks->items; l; l=l->next )
328     {
329         char* uri;
330         item = (FmBookmarkItem*)l->data;
331         uri = fm_path_to_uri(item->path);
332         g_string_append(buf, uri);
333         g_free(uri);
334         g_string_append_c(buf, ' ');
335         g_string_append(buf, item->name);
336         g_string_append_c(buf, '\n');
337     }
338     idle_handler = 0;
339     G_UNLOCK(bookmarks);
340 
341     if (!g_file_replace_contents(bookmarks->file, buf->str, buf->len, NULL,
342                                  FALSE, 0, NULL, NULL, &err))
343     {
344         g_critical("%s", err->message);
345         g_error_free(err);
346     }
347 
348     g_string_free(buf, TRUE);
349     /* we changed bookmarks list, let inform who interested in that */
350     g_signal_emit(bookmarks, signals[CHANGED], 0);
351     return FALSE;
352 }
353 
queue_save_bookmarks(FmBookmarks * bookmarks)354 static void queue_save_bookmarks(FmBookmarks* bookmarks)
355 {
356     if(!idle_handler)
357         idle_handler = g_idle_add((GSourceFunc)save_bookmarks, bookmarks);
358 }
359 
360 /**
361  * fm_bookmarks_insert
362  * @bookmarks: bookmarks list
363  * @path: path requested to add to bookmarks
364  * @name: name new bookmark will be seen in list with
365  * @pos: where to insert a bookmark into list
366  *
367  * Adds a bookmark into bookmark list. Returned structure is managed by
368  * bookmarks list and should not be freed by caller. If you want to save
369  * returned data then call fm_bookmark_item_ref() on it.
370  *
371  * Return value: (transfer none): new created bookmark item
372  *
373  * Since: 0.1.0
374  */
fm_bookmarks_insert(FmBookmarks * bookmarks,FmPath * path,const char * name,int pos)375 FmBookmarkItem* fm_bookmarks_insert(FmBookmarks* bookmarks, FmPath* path, const char* name, int pos)
376 {
377     FmBookmarkItem* item = g_slice_new0(FmBookmarkItem);
378     item->path = fm_path_ref(path);
379     item->name = g_strdup(name);
380     item->n_ref = 1;
381     G_LOCK(bookmarks);
382     /* FIXME: disable creation of duplicate bookmark if set in config */
383     bookmarks->items = g_list_insert(bookmarks->items, item, pos);
384     /* g_debug("insert %s at %d", name, pos); */
385     queue_save_bookmarks(bookmarks);
386     G_UNLOCK(bookmarks);
387     return item;
388 }
389 
390 /**
391  * fm_bookmarks_remove
392  * @bookmarks: bookmarks list
393  * @item: bookmark item for deletion
394  *
395  * Removes a bookmark from bookmark list.
396  *
397  * Since: 0.1.0
398  */
fm_bookmarks_remove(FmBookmarks * bookmarks,FmBookmarkItem * item)399 void fm_bookmarks_remove(FmBookmarks* bookmarks, FmBookmarkItem* item)
400 {
401     G_LOCK(bookmarks);
402     bookmarks->items = g_list_remove(bookmarks->items, item);
403     fm_bookmark_item_unref(item);
404     queue_save_bookmarks(bookmarks);
405     G_UNLOCK(bookmarks);
406 }
407 
408 /**
409  * fm_bookmarks_rename
410  * @bookmarks: bookmarks list
411  * @item: bookmark item which will be changed
412  * @new_name: new name for bookmark item to be seen in list
413  *
414  * Changes name of existing bookmark item.
415  *
416  * Since: 0.1.0
417  */
fm_bookmarks_rename(FmBookmarks * bookmarks,FmBookmarkItem * item,const char * new_name)418 void fm_bookmarks_rename(FmBookmarks* bookmarks, FmBookmarkItem* item, const char* new_name)
419 {
420     G_LOCK(bookmarks);
421     g_free(item->name);
422     item->name = g_strdup(new_name);
423     queue_save_bookmarks(bookmarks);
424     G_UNLOCK(bookmarks);
425 }
426 
427 /**
428  * fm_bookmarks_reorder
429  * @bookmarks: bookmarks list
430  * @item: bookmark item which will be changed
431  * @pos: new position for bookmark item in list
432  *
433  * Changes position of existing bookmark item.
434  *
435  * Since: 0.1.0
436  */
fm_bookmarks_reorder(FmBookmarks * bookmarks,FmBookmarkItem * item,int pos)437 void fm_bookmarks_reorder(FmBookmarks* bookmarks, FmBookmarkItem* item, int pos)
438 {
439     G_LOCK(bookmarks);
440     bookmarks->items = g_list_remove(bookmarks->items, item);
441     bookmarks->items = g_list_insert(bookmarks->items, item, pos);
442     queue_save_bookmarks(bookmarks);
443     G_UNLOCK(bookmarks);
444 }
445