/* bg-recent-source.c * * Copyright 2019 Georges Basile Stavracas Neto * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "bg-recent-source" #include "bg-recent-source.h" #include "cc-background-item.h" #define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \ G_FILE_ATTRIBUTE_TIME_MODIFIED struct _BgRecentSource { BgSource parent; GFile *backgrounds_folder; GFileMonitor *monitor; GCancellable *cancellable; GHashTable *items; }; G_DEFINE_TYPE (BgRecentSource, bg_recent_source, BG_TYPE_SOURCE) static int sort_func (gconstpointer a, gconstpointer b, gpointer user_data) { CcBackgroundItem *item_a; CcBackgroundItem *item_b; guint64 modified_a; guint64 modified_b; int retval; item_a = (CcBackgroundItem *) a; item_b = (CcBackgroundItem *) b; modified_a = cc_background_item_get_modified (item_a); modified_b = cc_background_item_get_modified (item_b); retval = modified_b - modified_a; return retval; } static void add_file_from_info (BgRecentSource *self, GFile *file, GFileInfo *info) { g_autoptr(CcBackgroundItem) item = NULL; CcBackgroundItemFlags flags = 0; g_autofree gchar *source_uri = NULL; g_autofree gchar *uri = NULL; GListStore *store; const gchar *content_type; guint64 mtime; content_type = g_file_info_get_content_type (info); mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); if (!content_type || !g_content_type_is_a (content_type, "image/*")) return; uri = g_file_get_uri (file); item = cc_background_item_new (uri); flags |= CC_BACKGROUND_ITEM_HAS_SHADING | CC_BACKGROUND_ITEM_HAS_PLACEMENT; g_object_set (G_OBJECT (item), "flags", flags, "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID, "placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM, "modified", mtime, "needs-download", FALSE, "source-url", source_uri, NULL); store = bg_source_get_liststore (BG_SOURCE (self)); g_list_store_insert_sorted (store, item, sort_func, self); g_hash_table_insert (self->items, g_strdup (uri), g_object_ref (item)); } static void remove_item (BgRecentSource *self, CcBackgroundItem *item) { GListStore *store; const gchar *uri; guint i; g_return_if_fail (BG_IS_RECENT_SOURCE (self)); g_return_if_fail (CC_IS_BACKGROUND_ITEM (item)); uri = cc_background_item_get_uri (item); store = bg_source_get_liststore (BG_SOURCE (self)); g_debug ("Removing wallpaper %s", uri); for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++) { g_autoptr(CcBackgroundItem) tmp = NULL; tmp = g_list_model_get_item (G_LIST_MODEL (store), i); if (tmp == item) { g_list_store_remove (store, i); break; } } g_hash_table_remove (self->items, cc_background_item_get_uri (item)); } static void query_info_finished_cb (GObject *source, GAsyncResult *result, gpointer user_data) { BgRecentSource *self; g_autoptr(GFileInfo) file_info = NULL; g_autoptr(GError) error = NULL; GFile *file = NULL; file = G_FILE (source); file_info = g_file_query_info_finish (file, result, &error); if (error) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Could not get pictures file information: %s", error->message); return; } self = BG_RECENT_SOURCE (user_data); g_debug ("Adding wallpaper %s (%d)", g_file_info_get_name (file_info), G_IS_FILE (self->backgrounds_folder)); add_file_from_info (self, file, file_info); } static void on_file_changed_cb (BgRecentSource *self, GFile *file, GFile *other_file, GFileMonitorEvent event_type) { g_autofree gchar *uri = NULL; switch (event_type) { case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: g_file_query_info_async (file, ATTRIBUTES, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, G_PRIORITY_DEFAULT, self->cancellable, query_info_finished_cb, self); break; case G_FILE_MONITOR_EVENT_DELETED: uri = g_file_get_uri (file); remove_item (self, g_hash_table_lookup (self->items, uri)); break; default: return; } } static int file_sort_func (gconstpointer a, gconstpointer b) { GFileInfo *file_a = G_FILE_INFO (a); GFileInfo *file_b = G_FILE_INFO (b); guint64 modified_a, modified_b; modified_a = g_file_info_get_attribute_uint64 (file_a, G_FILE_ATTRIBUTE_TIME_MODIFIED); modified_b = g_file_info_get_attribute_uint64 (file_b, G_FILE_ATTRIBUTE_TIME_MODIFIED); return modified_b - modified_a; } static void file_info_async_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data) { BgRecentSource *self; g_autolist(GFileInfo) file_infos = NULL; g_autoptr(GError) error = NULL; GFile *parent = NULL; GList *l; file_infos = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source), result, &error); if (error) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Could not get pictures file information: %s", error->message); return; } self = BG_RECENT_SOURCE (user_data); parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source)); file_infos = g_list_sort (file_infos, file_sort_func); for (l = file_infos; l; l = l->next) { g_autoptr(GFile) file = NULL; GFileInfo *info; info = l->data; file = g_file_get_child (parent, g_file_info_get_name (info)); g_debug ("Found recent wallpaper %s", g_file_info_get_name (info)); add_file_from_info (self, file, info); } g_file_enumerator_close (G_FILE_ENUMERATOR (source), self->cancellable, &error); if (error) g_warning ("Error closing file enumerator: %s", error->message); } static void enumerate_children_finished_cb (GObject *source, GAsyncResult *result, gpointer user_data) { BgRecentSource *self; g_autoptr(GFileEnumerator) enumerator = NULL; g_autoptr(GError) error = NULL; enumerator = g_file_enumerate_children_finish (G_FILE (source), result, &error); if (error) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Could not fill pictures source: %s", error->message); return; } self = BG_RECENT_SOURCE (user_data); g_file_enumerator_next_files_async (enumerator, G_MAXINT, G_PRIORITY_DEFAULT, self->cancellable, file_info_async_ready_cb, self); } static void load_backgrounds (BgRecentSource *self) { g_autofree gchar *backgrounds_path = NULL; g_autoptr(GError) error = NULL; if (!g_file_make_directory_with_parents (self->backgrounds_folder, self->cancellable, &error) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_critical ("Failed to create local background directory: %s", error->message); return; } backgrounds_path = g_file_get_path (self->backgrounds_folder); g_debug ("Enumerating wallpapers under %s", backgrounds_path); g_file_enumerate_children_async (self->backgrounds_folder, ATTRIBUTES, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, G_PRIORITY_DEFAULT, self->cancellable, enumerate_children_finished_cb, self); self->monitor = g_file_monitor_directory (self->backgrounds_folder, G_FILE_MONITOR_WATCH_MOVES, self->cancellable, &error); if (!self->monitor) { g_critical ("Failed to monitor background directory: %s", error->message); return; } g_signal_connect_object (self->monitor, "changed", G_CALLBACK (on_file_changed_cb), self, G_CONNECT_SWAPPED); } /* Callbacks */ static void on_file_copied_cb (GObject *source, GAsyncResult *result, gpointer user_data) { g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data); g_autofree gchar *original_file = NULL; g_autoptr(GError) error = NULL; g_file_copy_finish (G_FILE (source), result, &error); if (error) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_critical ("Failed to copy file: %s", error->message); return; } original_file = g_file_get_path (G_FILE (source)); g_debug ("Successfully copied wallpaper: %s", original_file); } static void on_file_deleted_cb (GObject *source, GAsyncResult *result, gpointer user_data) { g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data); g_autofree gchar *original_file = NULL; g_autoptr(GError) error = NULL; g_file_delete_finish (G_FILE (source), result, &error); if (error) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_critical ("Failed to delete wallpaper: %s", error->message); return; } original_file = g_file_get_path (G_FILE (source)); g_debug ("Successfully deleted wallpaper: %s", original_file); } /* GObject overrides */ static void bg_recent_source_finalize (GObject *object) { BgRecentSource *self = (BgRecentSource *)object; g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); g_clear_object (&self->monitor); G_OBJECT_CLASS (bg_recent_source_parent_class)->finalize (object); } static void bg_recent_source_class_init (BgRecentSourceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = bg_recent_source_finalize; } static void bg_recent_source_init (BgRecentSource *self) { g_autofree gchar *backgrounds_path = NULL; backgrounds_path = g_build_filename (g_get_user_data_dir (), "backgrounds", NULL); self->items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); self->cancellable = g_cancellable_new (); self->backgrounds_folder = g_file_new_for_path (backgrounds_path); load_backgrounds (self); } BgRecentSource* bg_recent_source_new (GtkWidget *widget) { return g_object_new (BG_TYPE_RECENT_SOURCE, "widget", widget, NULL); } void bg_recent_source_add_file (BgRecentSource *self, const gchar *path) { g_autoptr(GDateTime) now = NULL; g_autofree gchar *destination_name = NULL; g_autofree gchar *formatted_now = NULL; g_autofree gchar *basename = NULL; g_autoptr(GFile) destination = NULL; g_autoptr(GFile) file = NULL; g_return_if_fail (BG_IS_RECENT_SOURCE (self)); g_return_if_fail (path && *path); g_debug ("Importing wallpaper %s", path); now = g_date_time_new_now_local (); formatted_now = g_date_time_format (now, "%Y-%m-%d-%H-%M-%S"); file = g_file_new_for_path (path); basename = g_file_get_basename (file); destination_name = g_strdup_printf ("%s-%s", formatted_now, basename); destination = g_file_get_child (self->backgrounds_folder, destination_name); g_file_copy_async (file, destination, G_FILE_COPY_NONE, G_PRIORITY_DEFAULT, self->cancellable, NULL, NULL, on_file_copied_cb, g_object_ref (self)); } void bg_recent_source_remove_item (BgRecentSource *self, CcBackgroundItem *item) { g_autoptr(GFile) file = NULL; const gchar *uri; g_return_if_fail (BG_IS_RECENT_SOURCE (self)); g_return_if_fail (CC_IS_BACKGROUND_ITEM (item)); uri = cc_background_item_get_uri (item); file = g_file_new_for_uri (uri); g_file_delete_async (file, G_PRIORITY_DEFAULT, self->cancellable, on_file_deleted_cb, g_object_ref (self)); }