997 lines
29 KiB
C
997 lines
29 KiB
C
/* bg-pictures-source.c */
|
|
/*
|
|
* Copyright (C) 2010 Intel, Inc
|
|
*
|
|
* 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 2 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/>.
|
|
*
|
|
* Author: Thomas Wood <thomas.wood@intel.com>
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "bg-pictures-source.h"
|
|
|
|
#include "cc-background-grilo-miner.h"
|
|
#include "cc-background-item.h"
|
|
|
|
#include <string.h>
|
|
#include <cairo-gobject.h>
|
|
#include <gio/gio.h>
|
|
#include <grilo.h>
|
|
#include <libgnome-desktop/gnome-desktop-thumbnail.h>
|
|
#include <gdesktop-enums.h>
|
|
|
|
#define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \
|
|
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
|
|
G_FILE_ATTRIBUTE_TIME_MODIFIED
|
|
|
|
struct _BgPicturesSource
|
|
{
|
|
BgSource parent_instance;
|
|
|
|
GCancellable *cancellable;
|
|
|
|
CcBackgroundGriloMiner *grl_miner;
|
|
|
|
GnomeDesktopThumbnailFactory *thumb_factory;
|
|
|
|
GFileMonitor *picture_dir_monitor;
|
|
GFileMonitor *cache_dir_monitor;
|
|
|
|
GHashTable *known_items;
|
|
};
|
|
|
|
G_DEFINE_TYPE (BgPicturesSource, bg_pictures_source, BG_TYPE_SOURCE)
|
|
|
|
const char * const content_types[] = {
|
|
"image/png",
|
|
"image/jp2",
|
|
"image/jpeg",
|
|
"image/bmp",
|
|
"image/svg+xml",
|
|
"image/x-portable-anymap",
|
|
NULL
|
|
};
|
|
|
|
const char * const screenshot_types[] = {
|
|
"image/png",
|
|
NULL
|
|
};
|
|
|
|
static char *bg_pictures_source_get_unique_filename (const char *uri);
|
|
|
|
static void picture_opened_for_read (GObject *source_object, GAsyncResult *res, gpointer user_data);
|
|
|
|
static void
|
|
bg_pictures_source_dispose (GObject *object)
|
|
{
|
|
BgPicturesSource *source = BG_PICTURES_SOURCE (object);
|
|
|
|
if (source->cancellable)
|
|
{
|
|
g_cancellable_cancel (source->cancellable);
|
|
g_clear_object (&source->cancellable);
|
|
}
|
|
|
|
g_clear_object (&source->grl_miner);
|
|
g_clear_object (&source->thumb_factory);
|
|
|
|
G_OBJECT_CLASS (bg_pictures_source_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
bg_pictures_source_finalize (GObject *object)
|
|
{
|
|
BgPicturesSource *bg_source = BG_PICTURES_SOURCE (object);
|
|
|
|
g_clear_object (&bg_source->thumb_factory);
|
|
|
|
g_clear_pointer (&bg_source->known_items, g_hash_table_destroy);
|
|
|
|
g_clear_object (&bg_source->picture_dir_monitor);
|
|
g_clear_object (&bg_source->cache_dir_monitor);
|
|
|
|
G_OBJECT_CLASS (bg_pictures_source_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
bg_pictures_source_class_init (BgPicturesSourceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = bg_pictures_source_dispose;
|
|
object_class->finalize = bg_pictures_source_finalize;
|
|
}
|
|
|
|
static void
|
|
remove_placeholder (BgPicturesSource *bg_source, CcBackgroundItem *item)
|
|
{
|
|
GtkListStore *store;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
GtkTreeRowReference *row_ref;
|
|
|
|
store = bg_source_get_liststore (BG_SOURCE (bg_source));
|
|
row_ref = g_object_get_data (G_OBJECT (item), "row-ref");
|
|
if (row_ref == NULL)
|
|
return;
|
|
|
|
path = gtk_tree_row_reference_get_path (row_ref);
|
|
if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
|
|
return;
|
|
|
|
gtk_list_store_remove (store, &iter);
|
|
}
|
|
|
|
static gboolean
|
|
picture_needs_rotation (GdkPixbuf *pixbuf)
|
|
{
|
|
const gchar *str;
|
|
|
|
str = gdk_pixbuf_get_option (pixbuf, "orientation");
|
|
if (str == NULL)
|
|
return FALSE;
|
|
|
|
if (*str == '5' || *str == '6' || *str == '7' || *str == '8')
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GdkPixbuf *
|
|
swap_rotated_pixbuf (GdkPixbuf *pixbuf)
|
|
{
|
|
GdkPixbuf *tmp_pixbuf;
|
|
|
|
tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
|
|
if (tmp_pixbuf == NULL)
|
|
return pixbuf;
|
|
|
|
g_object_unref (pixbuf);
|
|
return tmp_pixbuf;
|
|
}
|
|
|
|
static void
|
|
picture_scaled (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
BgPicturesSource *bg_source;
|
|
CcBackgroundItem *item;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(GdkPixbuf) pixbuf = NULL;
|
|
const char *software;
|
|
const char *uri;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
GtkTreeRowReference *row_ref;
|
|
GtkListStore *store;
|
|
cairo_surface_t *surface = NULL;
|
|
int scale_factor;
|
|
gboolean rotation_applied;
|
|
|
|
item = g_object_get_data (source_object, "item");
|
|
pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error);
|
|
if (pixbuf == NULL)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
{
|
|
g_warning ("Failed to load image: %s", error->message);
|
|
remove_placeholder (BG_PICTURES_SOURCE (user_data), item);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* since we were not cancelled, we can now cast user_data
|
|
* back to BgPicturesSource.
|
|
*/
|
|
bg_source = BG_PICTURES_SOURCE (user_data);
|
|
store = bg_source_get_liststore (BG_SOURCE (bg_source));
|
|
uri = cc_background_item_get_uri (item);
|
|
if (uri == NULL)
|
|
uri = cc_background_item_get_source_url (item);
|
|
|
|
/* Ignore screenshots */
|
|
software = gdk_pixbuf_get_option (pixbuf, "tEXt::Software");
|
|
if (software != NULL &&
|
|
g_str_equal (software, "gnome-screenshot"))
|
|
{
|
|
g_debug ("Ignored URL '%s' as it's a screenshot from gnome-screenshot", uri);
|
|
remove_placeholder (BG_PICTURES_SOURCE (user_data), item);
|
|
return;
|
|
}
|
|
|
|
/* Process embedded orientation */
|
|
rotation_applied = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "rotation-applied"));
|
|
|
|
if (!rotation_applied && picture_needs_rotation (pixbuf))
|
|
{
|
|
/* the width and height of pixbuf we requested are wrong for EXIF
|
|
* orientations 5, 6, 7 and 8. the file has to be reloaded. */
|
|
g_autoptr(GFile) file = NULL;
|
|
|
|
file = g_file_new_for_uri (uri);
|
|
g_object_set_data (G_OBJECT (item), "needs-rotation", GINT_TO_POINTER (TRUE));
|
|
g_object_set_data_full (G_OBJECT (file), "item", g_object_ref (item), g_object_unref);
|
|
g_file_read_async (G_FILE (file), G_PRIORITY_DEFAULT,
|
|
bg_source->cancellable,
|
|
picture_opened_for_read, bg_source);
|
|
return;
|
|
}
|
|
|
|
pixbuf = swap_rotated_pixbuf (pixbuf);
|
|
|
|
scale_factor = bg_source_get_scale_factor (BG_SOURCE (bg_source));
|
|
surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, NULL);
|
|
cc_background_item_load (item, NULL);
|
|
|
|
row_ref = g_object_get_data (G_OBJECT (item), "row-ref");
|
|
if (row_ref == NULL)
|
|
{
|
|
/* insert the item into the liststore if it did not exist */
|
|
gtk_list_store_insert_with_values (store, NULL, -1,
|
|
0, surface,
|
|
1, item,
|
|
-1);
|
|
}
|
|
else
|
|
{
|
|
path = gtk_tree_row_reference_get_path (row_ref);
|
|
if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
|
|
{
|
|
/* otherwise update the thumbnail */
|
|
gtk_list_store_set (store, &iter,
|
|
0, surface,
|
|
-1);
|
|
}
|
|
}
|
|
|
|
g_hash_table_insert (bg_source->known_items,
|
|
bg_pictures_source_get_unique_filename (uri),
|
|
GINT_TO_POINTER (TRUE));
|
|
|
|
g_clear_pointer (&surface, (GDestroyNotify) cairo_surface_destroy);
|
|
}
|
|
|
|
static void
|
|
picture_opened_for_read (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
BgPicturesSource *bg_source;
|
|
CcBackgroundItem *item;
|
|
g_autoptr(GFileInputStream) stream = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
gint thumbnail_height;
|
|
gint thumbnail_width;
|
|
gboolean needs_rotation;
|
|
|
|
item = g_object_get_data (source_object, "item");
|
|
stream = g_file_read_finish (G_FILE (source_object), res, &error);
|
|
if (stream == NULL)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
{
|
|
g_autofree gchar *filename = g_file_get_path (G_FILE (source_object));
|
|
g_warning ("Failed to load picture '%s': %s", filename, error->message);
|
|
remove_placeholder (BG_PICTURES_SOURCE (user_data), item);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* since we were not cancelled, we can now cast user_data
|
|
* back to BgPicturesSource.
|
|
*/
|
|
bg_source = BG_PICTURES_SOURCE (user_data);
|
|
|
|
needs_rotation = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "needs-rotation"));
|
|
if (needs_rotation)
|
|
{
|
|
/* swap width and height for EXIF orientations that need it */
|
|
thumbnail_width = bg_source_get_thumbnail_height (BG_SOURCE (bg_source));
|
|
thumbnail_height = bg_source_get_thumbnail_width (BG_SOURCE (bg_source));
|
|
g_object_set_data (G_OBJECT (item), "rotation-applied", GINT_TO_POINTER (TRUE));
|
|
}
|
|
else
|
|
{
|
|
thumbnail_width = bg_source_get_thumbnail_width (BG_SOURCE (bg_source));
|
|
thumbnail_height = bg_source_get_thumbnail_height (BG_SOURCE (bg_source));
|
|
}
|
|
|
|
g_object_set_data_full (G_OBJECT (stream), "item", g_object_ref (item), g_object_unref);
|
|
gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (stream),
|
|
thumbnail_width, thumbnail_height,
|
|
TRUE,
|
|
bg_source->cancellable,
|
|
picture_scaled, bg_source);
|
|
}
|
|
|
|
static void
|
|
picture_copied_for_read (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
BgPicturesSource *bg_source;
|
|
CcBackgroundItem *item;
|
|
g_autoptr(GError) error = NULL;
|
|
GFile *thumbnail_file = G_FILE (source_object);
|
|
GFile *native_file;
|
|
|
|
if (!g_file_copy_finish (thumbnail_file, res, &error))
|
|
{
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
return;
|
|
else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
|
|
{
|
|
g_autofree gchar *uri = NULL;
|
|
|
|
uri = g_file_get_uri (thumbnail_file);
|
|
g_warning ("Failed to download '%s': %s", uri, error->message);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bg_source = BG_PICTURES_SOURCE (user_data);
|
|
|
|
native_file = g_object_get_data (G_OBJECT (thumbnail_file), "native-file");
|
|
item = g_object_get_data (G_OBJECT (thumbnail_file), "item");
|
|
g_object_set_data_full (G_OBJECT (native_file), "item", g_object_ref (item), g_object_unref);
|
|
g_file_read_async (native_file,
|
|
G_PRIORITY_DEFAULT,
|
|
bg_source->cancellable,
|
|
picture_opened_for_read,
|
|
bg_source);
|
|
}
|
|
|
|
static gboolean
|
|
in_content_types (const char *content_type)
|
|
{
|
|
guint i;
|
|
for (i = 0; content_types[i]; i++)
|
|
if (g_str_equal (content_types[i], content_type))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
in_screenshot_types (const char *content_type)
|
|
{
|
|
guint i;
|
|
for (i = 0; screenshot_types[i]; i++)
|
|
if (g_str_equal (screenshot_types[i], content_type))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
get_content_loading_icon (BgSource *source)
|
|
{
|
|
GtkIconTheme *theme;
|
|
g_autoptr(GtkIconInfo) icon_info = NULL;
|
|
g_autoptr(GdkPixbuf) pixbuf = NULL;
|
|
g_autoptr(GdkPixbuf) ret = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
int scale_factor;
|
|
cairo_surface_t *surface;
|
|
int thumbnail_height;
|
|
int thumbnail_width;
|
|
|
|
theme = gtk_icon_theme_get_default ();
|
|
icon_info = gtk_icon_theme_lookup_icon (theme,
|
|
"content-loading-symbolic",
|
|
16,
|
|
GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_GENERIC_FALLBACK);
|
|
if (icon_info == NULL)
|
|
{
|
|
g_warning ("Failed to find placeholder icon");
|
|
return NULL;
|
|
}
|
|
|
|
pixbuf = gtk_icon_info_load_icon (icon_info, &error);
|
|
if (pixbuf == NULL)
|
|
{
|
|
g_warning ("Failed to load placeholder icon: %s", error->message);
|
|
return NULL;
|
|
}
|
|
|
|
thumbnail_height = bg_source_get_thumbnail_height (source);
|
|
thumbnail_width = bg_source_get_thumbnail_width (source);
|
|
ret = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
TRUE,
|
|
8, thumbnail_width, thumbnail_height);
|
|
gdk_pixbuf_fill (ret, 0x00000000);
|
|
|
|
/* Put the icon in the middle */
|
|
gdk_pixbuf_copy_area (pixbuf, 0, 0,
|
|
gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf),
|
|
ret,
|
|
(thumbnail_width - gdk_pixbuf_get_width (pixbuf)) / 2,
|
|
(thumbnail_height - gdk_pixbuf_get_height (pixbuf)) / 2);
|
|
|
|
scale_factor = bg_source_get_scale_factor (source);
|
|
surface = gdk_cairo_surface_create_from_pixbuf (ret, scale_factor, NULL);
|
|
|
|
return surface;
|
|
}
|
|
|
|
static GFile *
|
|
bg_pictures_source_get_cache_file (void)
|
|
{
|
|
g_autofree gchar *path = NULL;
|
|
GFile *file;
|
|
|
|
path = bg_pictures_source_get_cache_path ();
|
|
file = g_file_new_for_path (path);
|
|
|
|
return file;
|
|
}
|
|
|
|
static gboolean
|
|
add_single_file (BgPicturesSource *bg_source,
|
|
GFile *file,
|
|
const gchar *content_type,
|
|
guint64 mtime,
|
|
GtkTreeRowReference **ret_row_ref)
|
|
{
|
|
g_autoptr(CcBackgroundItem) item = NULL;
|
|
CcBackgroundItemFlags flags = 0;
|
|
GtkListStore *store;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path = NULL;
|
|
GtkTreeRowReference *row_ref = NULL;
|
|
cairo_surface_t *surface = NULL;
|
|
g_autofree gchar *source_uri = NULL;
|
|
g_autofree gchar *uri = NULL;
|
|
gboolean needs_download;
|
|
gboolean retval = FALSE;
|
|
g_autoptr(GFile) pictures_dir = NULL;
|
|
g_autoptr(GFile) cache_dir = NULL;
|
|
GrlMedia *media;
|
|
|
|
/* find png and jpeg files */
|
|
if (!content_type)
|
|
goto out;
|
|
if (!in_content_types (content_type))
|
|
goto out;
|
|
|
|
/* create a new CcBackgroundItem */
|
|
uri = g_file_get_uri (file);
|
|
|
|
pictures_dir = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
|
|
cache_dir = bg_pictures_source_get_cache_file ();
|
|
needs_download = !g_file_has_parent (file, pictures_dir) &&
|
|
!g_file_has_parent (file, cache_dir);
|
|
|
|
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);
|
|
|
|
if (!ret_row_ref && in_screenshot_types (content_type))
|
|
goto read_file;
|
|
|
|
surface = get_content_loading_icon (BG_SOURCE (bg_source));
|
|
store = bg_source_get_liststore (BG_SOURCE (bg_source));
|
|
|
|
/* insert the item into the liststore */
|
|
gtk_list_store_insert_with_values (store, &iter, -1,
|
|
0, surface,
|
|
1, item,
|
|
-1);
|
|
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
|
row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path);
|
|
g_object_set_data_full (G_OBJECT (item), "row-ref", row_ref, (GDestroyNotify) gtk_tree_row_reference_free);
|
|
|
|
|
|
read_file:
|
|
|
|
media = g_object_get_data (G_OBJECT (file), "grl-media");
|
|
if (media == NULL)
|
|
{
|
|
g_object_set_data_full (G_OBJECT (file), "item", g_object_ref (item), g_object_unref);
|
|
g_file_read_async (file, G_PRIORITY_DEFAULT,
|
|
bg_source->cancellable,
|
|
picture_opened_for_read, bg_source);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr(GFile) native_file = NULL;
|
|
g_autoptr(GFile) thumbnail_file = NULL;
|
|
g_autofree gchar *native_dir = NULL;
|
|
g_autofree gchar *native_path = NULL;
|
|
const gchar *title;
|
|
const gchar *thumbnail_uri;
|
|
|
|
title = grl_media_get_title (media);
|
|
g_object_set (G_OBJECT (item), "name", title, NULL);
|
|
|
|
thumbnail_uri = grl_media_get_thumbnail (media);
|
|
thumbnail_file = g_file_new_for_uri (thumbnail_uri);
|
|
|
|
native_path = gnome_desktop_thumbnail_path_for_uri (source_uri, GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
|
|
native_file = g_file_new_for_path (native_path);
|
|
|
|
native_dir = g_path_get_dirname (native_path);
|
|
g_mkdir_with_parents (native_dir, USER_DIR_MODE);
|
|
|
|
g_object_set_data_full (G_OBJECT (thumbnail_file), "item", g_object_ref (item), g_object_unref);
|
|
g_object_set_data_full (G_OBJECT (thumbnail_file),
|
|
"native-file",
|
|
g_object_ref (native_file),
|
|
g_object_unref);
|
|
g_file_copy_async (thumbnail_file,
|
|
native_file,
|
|
G_FILE_COPY_ALL_METADATA,
|
|
G_PRIORITY_DEFAULT,
|
|
bg_source->cancellable,
|
|
NULL,
|
|
NULL,
|
|
picture_copied_for_read,
|
|
bg_source);
|
|
}
|
|
|
|
retval = TRUE;
|
|
|
|
out:
|
|
if (ret_row_ref)
|
|
{
|
|
if (row_ref && retval != FALSE)
|
|
*ret_row_ref = gtk_tree_row_reference_copy (row_ref);
|
|
else
|
|
*ret_row_ref = NULL;
|
|
}
|
|
gtk_tree_path_free (path);
|
|
g_clear_pointer (&surface, (GDestroyNotify) cairo_surface_destroy);
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
add_single_file_from_info (BgPicturesSource *bg_source,
|
|
GFile *file,
|
|
GFileInfo *info,
|
|
GtkTreeRowReference **ret_row_ref)
|
|
{
|
|
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);
|
|
return add_single_file (bg_source, file, content_type, mtime, ret_row_ref);
|
|
}
|
|
|
|
static gboolean
|
|
add_single_file_from_media (BgPicturesSource *bg_source,
|
|
GFile *file,
|
|
GrlMedia *media)
|
|
{
|
|
GDateTime *mtime;
|
|
const gchar *content_type;
|
|
gint64 mtime_unix;
|
|
|
|
content_type = grl_media_get_mime (media);
|
|
|
|
/* only GRL_METADATA_KEY_CREATION_DATE is implemented in the Flickr
|
|
* plugin, GRL_METADATA_KEY_MODIFICATION_DATE is not
|
|
*/
|
|
mtime = grl_media_get_creation_date (media);
|
|
if (!mtime)
|
|
mtime = grl_media_get_modification_date (media);
|
|
if (mtime)
|
|
mtime_unix = g_date_time_to_unix (mtime);
|
|
else
|
|
mtime_unix = g_get_real_time () / G_USEC_PER_SEC;
|
|
|
|
return add_single_file (bg_source, file, content_type, (guint64) mtime_unix, NULL);
|
|
}
|
|
|
|
gboolean
|
|
bg_pictures_source_add (BgPicturesSource *bg_source,
|
|
const char *uri,
|
|
GtkTreeRowReference **ret_row_ref)
|
|
{
|
|
g_autoptr(GFile) file = NULL;
|
|
GFileInfo *info;
|
|
gboolean retval;
|
|
|
|
file = g_file_new_for_uri (uri);
|
|
info = g_file_query_info (file, ATTRIBUTES, G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
|
if (info == NULL)
|
|
return FALSE;
|
|
|
|
retval = add_single_file_from_info (bg_source, file, info, ret_row_ref);
|
|
|
|
return retval;
|
|
}
|
|
|
|
gboolean
|
|
bg_pictures_source_remove (BgPicturesSource *bg_source,
|
|
const char *uri)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gboolean cont;
|
|
gboolean retval;
|
|
|
|
retval = FALSE;
|
|
model = GTK_TREE_MODEL (bg_source_get_liststore (BG_SOURCE (bg_source)));
|
|
|
|
cont = gtk_tree_model_get_iter_first (model, &iter);
|
|
while (cont)
|
|
{
|
|
g_autoptr(CcBackgroundItem) tmp_item = NULL;
|
|
const char *tmp_uri;
|
|
|
|
gtk_tree_model_get (model, &iter, 1, &tmp_item, -1);
|
|
tmp_uri = cc_background_item_get_uri (tmp_item);
|
|
if (g_str_equal (tmp_uri, uri))
|
|
{
|
|
char *uuid;
|
|
uuid = bg_pictures_source_get_unique_filename (uri);
|
|
g_hash_table_insert (bg_source->known_items,
|
|
uuid, NULL);
|
|
|
|
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
|
|
retval = TRUE;
|
|
break;
|
|
}
|
|
cont = gtk_tree_model_iter_next (model, &iter);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
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 (GObject *source,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
BgPicturesSource *bg_source;
|
|
GList *files, *l;
|
|
g_autoptr(GError) err = NULL;
|
|
GFile *parent;
|
|
|
|
files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source),
|
|
res,
|
|
&err);
|
|
if (err)
|
|
{
|
|
if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
g_warning ("Could not get pictures file information: %s", err->message);
|
|
|
|
g_list_foreach (files, (GFunc) g_object_unref, NULL);
|
|
g_list_free (files);
|
|
return;
|
|
}
|
|
|
|
bg_source = BG_PICTURES_SOURCE (user_data);
|
|
|
|
parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source));
|
|
|
|
files = g_list_sort (files, file_sort_func);
|
|
|
|
/* iterate over the available files */
|
|
for (l = files; l; l = g_list_next (l))
|
|
{
|
|
GFileInfo *info = l->data;
|
|
g_autoptr(GFile) file = NULL;
|
|
|
|
file = g_file_get_child (parent, g_file_info_get_name (info));
|
|
|
|
add_single_file_from_info (bg_source, file, info, NULL);
|
|
}
|
|
|
|
g_list_foreach (files, (GFunc) g_object_unref, NULL);
|
|
g_list_free (files);
|
|
}
|
|
|
|
static void
|
|
dir_enum_async_ready (GObject *s,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
BgPicturesSource *source = (BgPicturesSource *) user_data;
|
|
g_autoptr(GFileEnumerator) enumerator = NULL;
|
|
g_autoptr(GError) err = NULL;
|
|
|
|
enumerator = g_file_enumerate_children_finish (G_FILE (s), res, &err);
|
|
|
|
if (err)
|
|
{
|
|
if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
g_warning ("Could not fill pictures source: %s", err->message);
|
|
return;
|
|
}
|
|
|
|
/* get the files */
|
|
g_file_enumerator_next_files_async (enumerator,
|
|
G_MAXINT,
|
|
G_PRIORITY_LOW,
|
|
source->cancellable,
|
|
file_info_async_ready,
|
|
user_data);
|
|
}
|
|
|
|
char *
|
|
bg_pictures_source_get_cache_path (void)
|
|
{
|
|
return g_build_filename (g_get_user_cache_dir (),
|
|
"gnome-control-center",
|
|
"backgrounds",
|
|
NULL);
|
|
}
|
|
|
|
static char *
|
|
bg_pictures_source_get_unique_filename (const char *uri)
|
|
{
|
|
g_autoptr(GChecksum) csum = NULL;
|
|
char *ret;
|
|
|
|
csum = g_checksum_new (G_CHECKSUM_SHA256);
|
|
g_checksum_update (csum, (guchar *) uri, -1);
|
|
ret = g_strdup (g_checksum_get_string (csum));
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
bg_pictures_source_get_unique_path (const char *uri)
|
|
{
|
|
g_autoptr(GFile) parent = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
g_autofree gchar *cache_path = NULL;
|
|
g_autofree gchar *filename = NULL;
|
|
|
|
cache_path = bg_pictures_source_get_cache_path ();
|
|
parent = g_file_new_for_path (cache_path);
|
|
|
|
filename = bg_pictures_source_get_unique_filename (uri);
|
|
file = g_file_get_child (parent, filename);
|
|
|
|
return g_file_get_path (file);
|
|
}
|
|
|
|
gboolean
|
|
bg_pictures_source_is_known (BgPicturesSource *bg_source,
|
|
const char *uri)
|
|
{
|
|
g_autofree gchar *uuid = NULL;
|
|
|
|
uuid = bg_pictures_source_get_unique_filename (uri);
|
|
|
|
return GPOINTER_TO_INT (g_hash_table_lookup (bg_source->known_items, uuid));
|
|
}
|
|
|
|
static int
|
|
sort_func (GtkTreeModel *model,
|
|
GtkTreeIter *a,
|
|
GtkTreeIter *b,
|
|
BgPicturesSource *bg_source)
|
|
{
|
|
g_autoptr(CcBackgroundItem) item_a = NULL;
|
|
g_autoptr(CcBackgroundItem) item_b = NULL;
|
|
guint64 modified_a;
|
|
guint64 modified_b;
|
|
int retval;
|
|
|
|
gtk_tree_model_get (model, a,
|
|
1, &item_a,
|
|
-1);
|
|
gtk_tree_model_get (model, b,
|
|
1, &item_b,
|
|
-1);
|
|
|
|
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
|
|
file_info_ready (GObject *object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
GFileInfo *info;
|
|
GError *error = NULL;
|
|
GFile *file = G_FILE (object);
|
|
|
|
info = g_file_query_info_finish (file, res, &error);
|
|
|
|
if (!info)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
g_warning ("Problem looking up file info: %s", error->message);
|
|
g_clear_error (&error);
|
|
return;
|
|
}
|
|
|
|
add_single_file_from_info (BG_PICTURES_SOURCE (user_data), file, info, NULL);
|
|
}
|
|
|
|
static void
|
|
file_added (GFile *file,
|
|
BgPicturesSource *self)
|
|
{
|
|
g_autofree gchar *uri = NULL;
|
|
uri = g_file_get_uri (file);
|
|
|
|
if (!bg_pictures_source_is_known (self, uri))
|
|
{
|
|
g_file_query_info_async (file,
|
|
ATTRIBUTES,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_LOW,
|
|
NULL,
|
|
file_info_ready,
|
|
self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
files_changed_cb (GFileMonitor *monitor,
|
|
GFile *file,
|
|
GFile *other_file,
|
|
GFileMonitorEvent event_type,
|
|
gpointer user_data)
|
|
{
|
|
BgPicturesSource *self = BG_PICTURES_SOURCE (user_data);
|
|
g_autofree gchar *uri = NULL;
|
|
|
|
switch (event_type)
|
|
{
|
|
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
|
|
file_added (file, self);
|
|
break;
|
|
|
|
case G_FILE_MONITOR_EVENT_DELETED:
|
|
uri = g_file_get_uri (file);
|
|
bg_pictures_source_remove (self, uri);
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static GFileMonitor *
|
|
monitor_path (BgPicturesSource *self,
|
|
const char *path)
|
|
{
|
|
GFileMonitor *monitor;
|
|
g_autoptr(GFile) dir = NULL;
|
|
|
|
g_mkdir_with_parents (path, USER_DIR_MODE);
|
|
|
|
dir = g_file_new_for_path (path);
|
|
g_file_enumerate_children_async (dir,
|
|
ATTRIBUTES,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_LOW, self->cancellable,
|
|
dir_enum_async_ready, self);
|
|
|
|
monitor = g_file_monitor_directory (dir,
|
|
G_FILE_MONITOR_NONE,
|
|
self->cancellable,
|
|
NULL);
|
|
|
|
if (monitor)
|
|
g_signal_connect (monitor,
|
|
"changed",
|
|
G_CALLBACK (files_changed_cb),
|
|
self);
|
|
|
|
return monitor;
|
|
}
|
|
|
|
static void
|
|
media_found_cb (BgPicturesSource *self, GrlMedia *media)
|
|
{
|
|
g_autoptr(GFile) file = NULL;
|
|
const gchar *uri;
|
|
|
|
uri = grl_media_get_url (media);
|
|
file = g_file_new_for_uri (uri);
|
|
g_object_set_data_full (G_OBJECT (file), "grl-media", g_object_ref (media), g_object_unref);
|
|
add_single_file_from_media (self, file, media);
|
|
}
|
|
|
|
static void
|
|
bg_pictures_source_init (BgPicturesSource *self)
|
|
{
|
|
const gchar *pictures_path;
|
|
g_autofree gchar *cache_path = NULL;
|
|
GtkListStore *store;
|
|
|
|
self->cancellable = g_cancellable_new ();
|
|
self->known_items = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
(GDestroyNotify) g_free,
|
|
NULL);
|
|
|
|
pictures_path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
|
|
if (pictures_path == NULL)
|
|
pictures_path = g_get_home_dir ();
|
|
|
|
self->picture_dir_monitor = monitor_path (self, pictures_path);
|
|
|
|
cache_path = bg_pictures_source_get_cache_path ();
|
|
self->cache_dir_monitor = monitor_path (self, cache_path);
|
|
|
|
self->grl_miner = cc_background_grilo_miner_new ();
|
|
g_signal_connect_swapped (self->grl_miner, "media-found", G_CALLBACK (media_found_cb), self);
|
|
cc_background_grilo_miner_start (self->grl_miner);
|
|
|
|
self->thumb_factory =
|
|
gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
|
|
|
|
store = bg_source_get_liststore (BG_SOURCE (self));
|
|
|
|
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
|
|
1,
|
|
(GtkTreeIterCompareFunc)sort_func,
|
|
self,
|
|
NULL);
|
|
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
|
|
1,
|
|
GTK_SORT_ASCENDING);
|
|
}
|
|
|
|
BgPicturesSource *
|
|
bg_pictures_source_new (GtkWindow *window)
|
|
{
|
|
return g_object_new (BG_TYPE_PICTURES_SOURCE, "window", window, NULL);
|
|
}
|
|
|
|
const char * const *
|
|
bg_pictures_get_support_content_types (void)
|
|
{
|
|
return content_types;
|
|
}
|