gnome-control-center/panels/background/bg-pictures-source.c
Debarshi Ray 57c83a1d59 background: Add a placeholder icon before creating thumbnails
We do not wait until the first thumbnail is created to start filling up
the store. This avoids the "No Pictures Found" message from showing up
on slow machines were I/O and decoding the data may take a while.

However, placeholders are not shown for PNGs since they might be
screenshots.

https://bugzilla.gnome.org/show_bug.cgi?id=708943
2014-02-03 15:56:27 +01:00

791 lines
22 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-item.h"
#include <string.h>
#include <gio/gio.h>
#include <libgnome-desktop/gnome-desktop-thumbnail.h>
#include <gdesktop-enums.h>
G_DEFINE_TYPE (BgPicturesSource, bg_pictures_source, BG_TYPE_SOURCE)
#define PICTURES_SOURCE_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), BG_TYPE_PICTURES_SOURCE, BgPicturesSourcePrivate))
#define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
G_FILE_ATTRIBUTE_TIME_MODIFIED
struct _BgPicturesSourcePrivate
{
GCancellable *cancellable;
GnomeDesktopThumbnailFactory *thumb_factory;
GFileMonitor *picture_dir_monitor;
GFileMonitor *cache_dir_monitor;
GHashTable *known_items;
};
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
bg_pictures_source_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
bg_pictures_source_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
bg_pictures_source_dispose (GObject *object)
{
BgPicturesSourcePrivate *priv = BG_PICTURES_SOURCE (object)->priv;
if (priv->cancellable)
{
g_cancellable_cancel (priv->cancellable);
g_clear_object (&priv->cancellable);
}
g_clear_object (&priv->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->priv->thumb_factory);
g_clear_pointer (&bg_source->priv->known_items, g_hash_table_destroy);
g_clear_object (&bg_source->priv->picture_dir_monitor);
g_clear_object (&bg_source->priv->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);
g_type_class_add_private (klass, sizeof (BgPicturesSourcePrivate));
object_class->get_property = bg_pictures_source_get_property;
object_class->set_property = bg_pictures_source_set_property;
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 void
picture_scaled (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
BgPicturesSource *bg_source;
CcBackgroundItem *item;
GError *error = NULL;
GdkPixbuf *pixbuf = NULL;
const char *software;
const char *uri;
GtkTreeIter iter;
GtkTreePath *path;
GtkTreeRowReference *row_ref;
GtkListStore *store;
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);
}
g_error_free (error);
goto out;
}
/* 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);
/* 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",
cc_background_item_get_uri (item));
goto out;
}
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, pixbuf,
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, pixbuf,
-1);
}
}
g_hash_table_insert (bg_source->priv->known_items,
bg_pictures_source_get_unique_filename (uri),
GINT_TO_POINTER (TRUE));
out:
g_clear_object (&pixbuf);
}
static void
picture_opened_for_read (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
BgPicturesSource *bg_source;
CcBackgroundItem *item;
GFileInputStream *stream;
GError *error = NULL;
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))
{
char *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);
g_free (filename);
}
g_error_free (error);
return;
}
/* since we were not cancelled, we can now cast user_data
* back to BgPicturesSource.
*/
bg_source = BG_PICTURES_SOURCE (user_data);
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->priv->cancellable,
picture_scaled, bg_source);
g_object_unref (stream);
}
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 gboolean
add_single_file (BgPicturesSource *bg_source,
GFile *file,
GFileInfo *info,
const char *source_uri)
{
const gchar *content_type;
CcBackgroundItem *item = NULL;
GError *error = NULL;
GdkPixbuf *pixbuf = NULL;
GtkIconInfo *icon_info = NULL;
GtkIconTheme *theme;
GtkListStore *store;
GtkTreeIter iter;
GtkTreePath *path = NULL;
GtkTreeRowReference *row_ref;
char *uri = NULL;
gboolean retval = FALSE;
guint64 mtime;
/* find png and jpeg files */
content_type = g_file_info_get_content_type (info);
if (!content_type)
goto out;
if (!in_content_types (content_type))
goto out;
/* create a new CcBackgroundItem */
uri = g_file_get_uri (file);
mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
item = cc_background_item_new (uri);
g_object_set (G_OBJECT (item),
"flags", CC_BACKGROUND_ITEM_HAS_URI | CC_BACKGROUND_ITEM_HAS_SHADING,
"shading", G_DESKTOP_BACKGROUND_SHADING_SOLID,
"placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM,
"modified", mtime,
NULL);
if (source_uri != NULL)
g_object_set (G_OBJECT (item), "source-url", source_uri, NULL);
if (in_screenshot_types (content_type))
goto read_file;
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");
goto read_file;
}
pixbuf = gtk_icon_info_load_icon (icon_info, &error);
if (pixbuf == NULL)
{
g_warning ("Failed to load placeholder icon: %s", error->message);
g_clear_error (&error);
goto read_file;
}
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, pixbuf,
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:
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->priv->cancellable,
picture_opened_for_read, bg_source);
retval = TRUE;
out:
gtk_tree_path_free (path);
g_clear_object (&pixbuf);
g_clear_object (&icon_info);
g_clear_object (&item);
g_object_unref (file);
g_free (uri);
return retval;
}
gboolean
bg_pictures_source_add (BgPicturesSource *bg_source,
const char *uri)
{
GFile *file;
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 (bg_source, file, info, uri);
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)
{
CcBackgroundItem *tmp_item;
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->priv->known_items,
uuid, NULL);
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
retval = TRUE;
break;
}
g_object_unref (tmp_item);
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;
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_error_free (err);
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;
GFile *file;
file = g_file_get_child (parent, g_file_info_get_name (info));
add_single_file (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 *source,
GAsyncResult *res,
gpointer user_data)
{
BgPicturesSourcePrivate *priv;
GFileEnumerator *enumerator;
GError *err = NULL;
enumerator = g_file_enumerate_children_finish (G_FILE (source), 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);
g_error_free (err);
return;
}
priv = BG_PICTURES_SOURCE (user_data)->priv;
/* get the files */
g_file_enumerator_next_files_async (enumerator,
G_MAXINT,
G_PRIORITY_LOW,
priv->cancellable,
file_info_async_ready,
user_data);
g_object_unref (enumerator);
}
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)
{
GChecksum *csum;
char *ret;
csum = g_checksum_new (G_CHECKSUM_SHA256);
g_checksum_update (csum, (guchar *) uri, -1);
ret = g_strdup (g_checksum_get_string (csum));
g_checksum_free (csum);
return ret;
}
char *
bg_pictures_source_get_unique_path (const char *uri)
{
GFile *parent, *file;
char *cache_path;
char *filename;
char *ret;
cache_path = bg_pictures_source_get_cache_path ();
parent = g_file_new_for_path (cache_path);
g_free (cache_path);
filename = bg_pictures_source_get_unique_filename (uri);
file = g_file_get_child (parent, filename);
g_free (filename);
ret = g_file_get_path (file);
g_object_unref (file);
return ret;
}
gboolean
bg_pictures_source_is_known (BgPicturesSource *bg_source,
const char *uri)
{
gboolean retval;
char *uuid;
uuid = bg_pictures_source_get_unique_filename (uri);
retval = (GPOINTER_TO_INT (g_hash_table_lookup (bg_source->priv->known_items, uuid)));
g_free (uuid);
return retval;
}
static int
sort_func (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
BgPicturesSource *bg_source)
{
CcBackgroundItem *item_a;
CcBackgroundItem *item_b;
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;
g_object_unref (item_a);
g_object_unref (item_b);
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;
}
/* Up the ref count so we can re-use the add_single_item code path which
* reduces the ref count.
*/
g_object_ref (file);
add_single_file (BG_PICTURES_SOURCE (user_data), file, info, NULL);
}
static void
file_added (GFile *file,
BgPicturesSource *self)
{
char *uri;
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);
}
g_free (uri);
}
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);
char *uri;
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);
g_free (uri);
break;
default:
return;
}
}
static void
bg_pictures_source_init (BgPicturesSource *self)
{
const gchar *pictures_path;
BgPicturesSourcePrivate *priv;
GFile *dir;
char *cache_path;
GtkListStore *store;
priv = self->priv = PICTURES_SOURCE_PRIVATE (self);
priv->cancellable = g_cancellable_new ();
priv->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);
g_mkdir_with_parents (pictures_path, USER_DIR_MODE);
dir = g_file_new_for_path (pictures_path);
g_file_enumerate_children_async (dir,
ATTRIBUTES,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_LOW, priv->cancellable,
dir_enum_async_ready, self);
priv->picture_dir_monitor = g_file_monitor_directory (dir,
G_FILE_MONITOR_NONE,
priv->cancellable,
NULL);
if (priv->picture_dir_monitor)
g_signal_connect (priv->picture_dir_monitor,
"changed",
G_CALLBACK (files_changed_cb),
self);
g_object_unref (dir);
cache_path = bg_pictures_source_get_cache_path ();
g_mkdir_with_parents (cache_path, USER_DIR_MODE);
dir = g_file_new_for_path (cache_path);
g_file_enumerate_children_async (dir,
ATTRIBUTES,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_LOW, priv->cancellable,
dir_enum_async_ready, self);
priv->cache_dir_monitor = g_file_monitor_directory (dir,
G_FILE_MONITOR_NONE,
priv->cancellable,
NULL);
if (priv->cache_dir_monitor)
g_signal_connect (priv->cache_dir_monitor,
"changed",
G_CALLBACK (files_changed_cb),
self);
g_object_unref (dir);
priv->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 (void)
{
return g_object_new (BG_TYPE_PICTURES_SOURCE, NULL);
}
const char * const *
bg_pictures_get_support_content_types (void)
{
return content_types;
}