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