[gnome-control-center/gbsneto/background: 6/7] background: Introduce BgRecentSource



commit fcad5f7147c08d2e0394a1c0135e909387026948
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri May 24 13:57:17 2019 -0300

    background: Introduce BgRecentSource
    
    BgRecentSource is a background source implementation that
    keeps track of recently added backgrounds.
    
    The backgrounds are copied to $(local data dir)/backgrounds,
    which is monitored. When copying files to this folder, the
    current time is added as prefix to avoid filename collisions.

 panels/background/bg-recent-source.c | 484 +++++++++++++++++++++++++++++++++++
 panels/background/bg-recent-source.h |  39 +++
 panels/background/meson.build        |   1 +
 3 files changed, 524 insertions(+)
---
diff --git a/panels/background/bg-recent-source.c b/panels/background/bg-recent-source.c
new file mode 100644
index 000000000..7853c2755
--- /dev/null
+++ b/panels/background/bg-recent-source.c
@@ -0,0 +1,484 @@
+/* bg-recent-source.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#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 const gchar * const content_types[] = {
+       "image/png",
+       "image/jp2",
+       "image/jpeg",
+       "image/bmp",
+       "image/svg+xml",
+       "image/x-portable-anymap",
+       NULL
+};
+
+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;
+  gboolean needs_download;
+  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_strv_contains (content_types, content_type))
+    return;
+
+  uri = g_file_get_uri (file);
+  needs_download = !g_file_has_parent (file, self->backgrounds_folder);
+
+  if (!needs_download)
+    {
+      source_uri = g_strdup (uri);
+      flags |= CC_BACKGROUND_ITEM_HAS_URI;
+    }
+  else
+    {
+      source_uri = g_steal_pointer (&uri);
+    }
+
+  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", needs_download,
+                "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 %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 file %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 (GFileMonitor      *monitor,
+                    GFile             *file,
+                    GFile             *other_file,
+                    GFileMonitorEvent  event_type,
+                    BgRecentSource    *self)
+{
+  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 file %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 = BG_RECENT_SOURCE (user_data);
+  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;
+    }
+
+  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 files 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 (self->monitor, "changed", G_CALLBACK (on_file_changed_cb), self);
+}
+
+/* Callbacks */
+
+static void
+on_file_copied_cb (GObject      *source,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  BgRecentSource *self = BG_RECENT_SOURCE (user_data);
+  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);
+    }
+  else
+    {
+      g_autofree gchar *original_file = g_file_get_path (G_FILE (source));
+      g_debug ("Successfully copied background: %s", original_file);
+    }
+
+  g_object_unref (self);
+}
+
+static void
+on_file_deleted_cb (GObject      *source,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+  BgRecentSource *self = BG_RECENT_SOURCE (user_data);
+  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 file: %s", error->message);
+    }
+  else
+    {
+      g_autofree gchar *original_file = g_file_get_path (G_FILE (source));
+      g_debug ("Successfully deleted background: %s", original_file);
+    }
+
+  g_object_unref (self);
+}
+
+/* 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 file %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));
+}
diff --git a/panels/background/bg-recent-source.h b/panels/background/bg-recent-source.h
new file mode 100644
index 000000000..58d4c04a0
--- /dev/null
+++ b/panels/background/bg-recent-source.h
@@ -0,0 +1,39 @@
+/* bg-recent-source.h
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "bg-source.h"
+#include "cc-background-item.h"
+
+G_BEGIN_DECLS
+
+#define BG_TYPE_RECENT_SOURCE (bg_recent_source_get_type())
+G_DECLARE_FINAL_TYPE (BgRecentSource, bg_recent_source, BG, RECENT_SOURCE, BgSource)
+
+BgRecentSource* bg_recent_source_new         (GtkWidget        *widget);
+
+void            bg_recent_source_add_file    (BgRecentSource   *self,
+                                              const gchar      *path);
+
+void            bg_recent_source_remove_item (BgRecentSource   *self,
+                                              CcBackgroundItem *item);
+
+G_END_DECLS
diff --git a/panels/background/meson.build b/panels/background/meson.build
index 7482431f3..4058ba808 100644
--- a/panels/background/meson.build
+++ b/panels/background/meson.build
@@ -77,6 +77,7 @@ common_sources += gnome.compile_resources(
 sources = common_sources + files(
   'bg-colors-source.c',
   'bg-pictures-source.c',
+  'bg-recent-source.c',
   'bg-source.c',
   'bg-wallpapers-source.c',
   'cc-background-chooser.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]