1 /* bg-recent-source.c
2 *
3 * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #undef G_LOG_DOMAIN
22 #define G_LOG_DOMAIN "bg-recent-source"
23
24 #include "bg-recent-source.h"
25 #include "cc-background-item.h"
26
27 #define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \
28 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
29 G_FILE_ATTRIBUTE_TIME_MODIFIED
30
31 struct _BgRecentSource
32 {
33 BgSource parent;
34
35 GFile *backgrounds_folder;
36 GFileMonitor *monitor;
37
38 GCancellable *cancellable;
39 GHashTable *items;
40 };
41
42 G_DEFINE_TYPE (BgRecentSource, bg_recent_source, BG_TYPE_SOURCE)
43
44
45 static const gchar * const content_types[] = {
46 "image/png",
47 "image/jp2",
48 "image/jpeg",
49 "image/bmp",
50 "image/svg+xml",
51 "image/x-portable-anymap",
52 NULL
53 };
54
55 static int
sort_func(gconstpointer a,gconstpointer b,gpointer user_data)56 sort_func (gconstpointer a,
57 gconstpointer b,
58 gpointer user_data)
59 {
60 CcBackgroundItem *item_a;
61 CcBackgroundItem *item_b;
62 guint64 modified_a;
63 guint64 modified_b;
64 int retval;
65
66 item_a = (CcBackgroundItem *) a;
67 item_b = (CcBackgroundItem *) b;
68 modified_a = cc_background_item_get_modified (item_a);
69 modified_b = cc_background_item_get_modified (item_b);
70
71 retval = modified_b - modified_a;
72
73 return retval;
74 }
75
76 static void
add_file_from_info(BgRecentSource * self,GFile * file,GFileInfo * info)77 add_file_from_info (BgRecentSource *self,
78 GFile *file,
79 GFileInfo *info)
80 {
81 g_autoptr(CcBackgroundItem) item = NULL;
82 CcBackgroundItemFlags flags = 0;
83 g_autofree gchar *source_uri = NULL;
84 g_autofree gchar *uri = NULL;
85 GListStore *store;
86 const gchar *content_type;
87 guint64 mtime;
88
89 content_type = g_file_info_get_content_type (info);
90 mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
91
92 if (!content_type || !g_strv_contains (content_types, content_type))
93 return;
94
95 uri = g_file_get_uri (file);
96 item = cc_background_item_new (uri);
97 flags |= CC_BACKGROUND_ITEM_HAS_SHADING | CC_BACKGROUND_ITEM_HAS_PLACEMENT;
98 g_object_set (G_OBJECT (item),
99 "flags", flags,
100 "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID,
101 "placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM,
102 "modified", mtime,
103 "needs-download", FALSE,
104 "source-url", source_uri,
105 NULL);
106
107 store = bg_source_get_liststore (BG_SOURCE (self));
108 g_list_store_insert_sorted (store, item, sort_func, self);
109
110 g_hash_table_insert (self->items, g_strdup (uri), g_object_ref (item));
111 }
112
113 static void
remove_item(BgRecentSource * self,CcBackgroundItem * item)114 remove_item (BgRecentSource *self,
115 CcBackgroundItem *item)
116 {
117 GListStore *store;
118 const gchar *uri;
119 guint i;
120
121 g_return_if_fail (BG_IS_RECENT_SOURCE (self));
122 g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
123
124 uri = cc_background_item_get_uri (item);
125 store = bg_source_get_liststore (BG_SOURCE (self));
126
127 g_debug ("Removing wallpaper %s", uri);
128
129 for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
130 {
131 g_autoptr(CcBackgroundItem) tmp = NULL;
132
133 tmp = g_list_model_get_item (G_LIST_MODEL (store), i);
134
135 if (tmp == item)
136 {
137 g_list_store_remove (store, i);
138 break;
139 }
140 }
141
142 g_hash_table_remove (self->items, cc_background_item_get_uri (item));
143 }
144
145 static void
query_info_finished_cb(GObject * source,GAsyncResult * result,gpointer user_data)146 query_info_finished_cb (GObject *source,
147 GAsyncResult *result,
148 gpointer user_data)
149 {
150 BgRecentSource *self;
151 g_autoptr(GFileInfo) file_info = NULL;
152 g_autoptr(GError) error = NULL;
153 GFile *file = NULL;
154
155 file = G_FILE (source);
156 file_info = g_file_query_info_finish (file, result, &error);
157 if (error)
158 {
159 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
160 g_warning ("Could not get pictures file information: %s", error->message);
161 return;
162 }
163
164 self = BG_RECENT_SOURCE (user_data);
165
166 g_debug ("Adding wallpaper %s (%d)",
167 g_file_info_get_name (file_info),
168 G_IS_FILE (self->backgrounds_folder));
169
170 add_file_from_info (self, file, file_info);
171 }
172
173 static void
on_file_changed_cb(BgRecentSource * self,GFile * file,GFile * other_file,GFileMonitorEvent event_type)174 on_file_changed_cb (BgRecentSource *self,
175 GFile *file,
176 GFile *other_file,
177 GFileMonitorEvent event_type)
178 {
179 g_autofree gchar *uri = NULL;
180
181 switch (event_type)
182 {
183 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
184 g_file_query_info_async (file,
185 ATTRIBUTES,
186 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
187 G_PRIORITY_DEFAULT,
188 self->cancellable,
189 query_info_finished_cb,
190 self);
191 break;
192
193 case G_FILE_MONITOR_EVENT_DELETED:
194 uri = g_file_get_uri (file);
195 remove_item (self, g_hash_table_lookup (self->items, uri));
196 break;
197
198 default:
199 return;
200 }
201 }
202
203 static int
file_sort_func(gconstpointer a,gconstpointer b)204 file_sort_func (gconstpointer a,
205 gconstpointer b)
206 {
207 GFileInfo *file_a = G_FILE_INFO (a);
208 GFileInfo *file_b = G_FILE_INFO (b);
209 guint64 modified_a, modified_b;
210
211 modified_a = g_file_info_get_attribute_uint64 (file_a, G_FILE_ATTRIBUTE_TIME_MODIFIED);
212 modified_b = g_file_info_get_attribute_uint64 (file_b, G_FILE_ATTRIBUTE_TIME_MODIFIED);
213
214 return modified_b - modified_a;
215 }
216
217 static void
file_info_async_ready_cb(GObject * source,GAsyncResult * result,gpointer user_data)218 file_info_async_ready_cb (GObject *source,
219 GAsyncResult *result,
220 gpointer user_data)
221 {
222 BgRecentSource *self;
223 g_autolist(GFileInfo) file_infos = NULL;
224 g_autoptr(GError) error = NULL;
225 GFile *parent = NULL;
226 GList *l;
227
228 file_infos = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source),
229 result,
230 &error);
231 if (error)
232 {
233 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
234 g_warning ("Could not get pictures file information: %s", error->message);
235 return;
236 }
237
238 self = BG_RECENT_SOURCE (user_data);
239 parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source));
240
241 file_infos = g_list_sort (file_infos, file_sort_func);
242
243 for (l = file_infos; l; l = l->next)
244 {
245 g_autoptr(GFile) file = NULL;
246 GFileInfo *info;
247
248 info = l->data;
249 file = g_file_get_child (parent, g_file_info_get_name (info));
250
251 g_debug ("Found recent wallpaper %s", g_file_info_get_name (info));
252
253 add_file_from_info (self, file, info);
254 }
255
256 g_file_enumerator_close (G_FILE_ENUMERATOR (source), self->cancellable, &error);
257
258 if (error)
259 g_warning ("Error closing file enumerator: %s", error->message);
260 }
261
262 static void
enumerate_children_finished_cb(GObject * source,GAsyncResult * result,gpointer user_data)263 enumerate_children_finished_cb (GObject *source,
264 GAsyncResult *result,
265 gpointer user_data)
266 {
267 BgRecentSource *self;
268 g_autoptr(GFileEnumerator) enumerator = NULL;
269 g_autoptr(GError) error = NULL;
270
271 enumerator = g_file_enumerate_children_finish (G_FILE (source), result, &error);
272
273 if (error)
274 {
275 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
276 g_warning ("Could not fill pictures source: %s", error->message);
277 return;
278 }
279
280 self = BG_RECENT_SOURCE (user_data);
281 g_file_enumerator_next_files_async (enumerator,
282 G_MAXINT,
283 G_PRIORITY_DEFAULT,
284 self->cancellable,
285 file_info_async_ready_cb,
286 self);
287 }
288
289 static void
load_backgrounds(BgRecentSource * self)290 load_backgrounds (BgRecentSource *self)
291 {
292 g_autofree gchar *backgrounds_path = NULL;
293 g_autoptr(GError) error = NULL;
294
295 if (!g_file_make_directory_with_parents (self->backgrounds_folder, self->cancellable, &error) &&
296 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
297 {
298 g_critical ("Failed to create local background directory: %s", error->message);
299 return;
300 }
301
302 backgrounds_path = g_file_get_path (self->backgrounds_folder);
303 g_debug ("Enumerating wallpapers under %s", backgrounds_path);
304
305 g_file_enumerate_children_async (self->backgrounds_folder,
306 ATTRIBUTES,
307 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
308 G_PRIORITY_DEFAULT,
309 self->cancellable,
310 enumerate_children_finished_cb,
311 self);
312
313 self->monitor = g_file_monitor_directory (self->backgrounds_folder,
314 G_FILE_MONITOR_WATCH_MOVES,
315 self->cancellable,
316 &error);
317
318 if (!self->monitor)
319 {
320 g_critical ("Failed to monitor background directory: %s", error->message);
321 return;
322 }
323
324 g_signal_connect_object (self->monitor, "changed", G_CALLBACK (on_file_changed_cb), self, G_CONNECT_SWAPPED);
325 }
326
327 /* Callbacks */
328
329 static void
on_file_copied_cb(GObject * source,GAsyncResult * result,gpointer user_data)330 on_file_copied_cb (GObject *source,
331 GAsyncResult *result,
332 gpointer user_data)
333 {
334 g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data);
335 g_autofree gchar *original_file = NULL;
336 g_autoptr(GError) error = NULL;
337
338 g_file_copy_finish (G_FILE (source), result, &error);
339
340 if (error)
341 {
342 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
343 g_critical ("Failed to copy file: %s", error->message);
344 return;
345 }
346
347 original_file = g_file_get_path (G_FILE (source));
348 g_debug ("Successfully copied wallpaper: %s", original_file);
349 }
350
351 static void
on_file_deleted_cb(GObject * source,GAsyncResult * result,gpointer user_data)352 on_file_deleted_cb (GObject *source,
353 GAsyncResult *result,
354 gpointer user_data)
355 {
356 g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data);
357 g_autofree gchar *original_file = NULL;
358 g_autoptr(GError) error = NULL;
359
360 g_file_delete_finish (G_FILE (source), result, &error);
361
362 if (error)
363 {
364 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
365 g_critical ("Failed to delete wallpaper: %s", error->message);
366 return;
367 }
368
369 original_file = g_file_get_path (G_FILE (source));
370 g_debug ("Successfully deleted wallpaper: %s", original_file);
371 }
372
373 /* GObject overrides */
374
375 static void
bg_recent_source_finalize(GObject * object)376 bg_recent_source_finalize (GObject *object)
377 {
378 BgRecentSource *self = (BgRecentSource *)object;
379
380 g_cancellable_cancel (self->cancellable);
381 g_clear_object (&self->cancellable);
382 g_clear_object (&self->monitor);
383
384 G_OBJECT_CLASS (bg_recent_source_parent_class)->finalize (object);
385 }
386
387 static void
bg_recent_source_class_init(BgRecentSourceClass * klass)388 bg_recent_source_class_init (BgRecentSourceClass *klass)
389 {
390 GObjectClass *object_class = G_OBJECT_CLASS (klass);
391
392 object_class->finalize = bg_recent_source_finalize;
393 }
394
395 static void
bg_recent_source_init(BgRecentSource * self)396 bg_recent_source_init (BgRecentSource *self)
397 {
398 g_autofree gchar *backgrounds_path = NULL;
399
400 backgrounds_path = g_build_filename (g_get_user_data_dir (), "backgrounds", NULL);
401
402 self->items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
403 self->cancellable = g_cancellable_new ();
404 self->backgrounds_folder = g_file_new_for_path (backgrounds_path);
405
406 load_backgrounds (self);
407 }
408
409 BgRecentSource*
bg_recent_source_new(GtkWidget * widget)410 bg_recent_source_new (GtkWidget *widget)
411 {
412 return g_object_new (BG_TYPE_RECENT_SOURCE,
413 "widget", widget,
414 NULL);
415 }
416
417 void
bg_recent_source_add_file(BgRecentSource * self,const gchar * path)418 bg_recent_source_add_file (BgRecentSource *self,
419 const gchar *path)
420 {
421 g_autoptr(GDateTime) now = NULL;
422 g_autofree gchar *destination_name = NULL;
423 g_autofree gchar *formatted_now = NULL;
424 g_autofree gchar *basename = NULL;
425 g_autoptr(GFile) destination = NULL;
426 g_autoptr(GFile) file = NULL;
427
428 g_return_if_fail (BG_IS_RECENT_SOURCE (self));
429 g_return_if_fail (path && *path);
430
431 g_debug ("Importing wallpaper %s", path);
432
433 now = g_date_time_new_now_local ();
434 formatted_now = g_date_time_format (now, "%Y-%m-%d-%H-%M-%S");
435
436 file = g_file_new_for_path (path);
437
438 basename = g_file_get_basename (file);
439 destination_name = g_strdup_printf ("%s-%s", formatted_now, basename);
440 destination = g_file_get_child (self->backgrounds_folder, destination_name);
441
442 g_file_copy_async (file,
443 destination,
444 G_FILE_COPY_NONE,
445 G_PRIORITY_DEFAULT,
446 self->cancellable,
447 NULL, NULL,
448 on_file_copied_cb,
449 g_object_ref (self));
450 }
451
452 void
bg_recent_source_remove_item(BgRecentSource * self,CcBackgroundItem * item)453 bg_recent_source_remove_item (BgRecentSource *self,
454 CcBackgroundItem *item)
455 {
456 g_autoptr(GFile) file = NULL;
457 const gchar *uri;
458
459 g_return_if_fail (BG_IS_RECENT_SOURCE (self));
460 g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
461
462 uri = cc_background_item_get_uri (item);
463 file = g_file_new_for_uri (uri);
464
465 g_file_delete_async (file,
466 G_PRIORITY_DEFAULT,
467 self->cancellable,
468 on_file_deleted_cb,
469 g_object_ref (self));
470 }
471