diff options
author | Richard Hughes <richard@hughsie.com> | 2013-07-03 14:06:13 +0100 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2013-07-03 17:08:35 +0100 |
commit | 5ed2f646d70d3c5164dbd22bd03862ae660cff9a (patch) | |
tree | be67efb3a7c7d3bee0ffe765c5f15b4d6f36b0f8 | |
parent | 65e1294c8c424482f96e46e7ffaa260385713de7 (diff) | |
download | colord-CdIccStore.tar.gz |
Add CdIccStore to monitor directories of ICC profilesCdIccStore
This allows clients to watch special directories and be notified when profiles
are deleted or created.
-rw-r--r-- | lib/colord/Makefile.am | 3 | ||||
-rw-r--r-- | lib/colord/cd-icc-store.c | 731 | ||||
-rw-r--r-- | lib/colord/cd-icc-store.h | 99 | ||||
-rw-r--r-- | lib/colord/cd-self-test.c | 142 | ||||
-rw-r--r-- | lib/colord/colord-private.h | 1 |
5 files changed, 976 insertions, 0 deletions
diff --git a/lib/colord/Makefile.am b/lib/colord/Makefile.am index ceb49ea..dc7855b 100644 --- a/lib/colord/Makefile.am +++ b/lib/colord/Makefile.am @@ -13,6 +13,7 @@ AM_CPPFLAGS = \ -DCD_COMPILATION \ -DG_LOG_DOMAIN=\"libcolord\" \ -DTESTDATADIR=\""$(top_srcdir)/data/tests"\" \ + -DCD_SYSTEM_PROFILES_DIR="\"$(CD_SYSTEM_PROFILES_DIR)"\" \ -DPACKAGE_DATA_DIR=\""$(datadir)"\" lib_LTLIBRARIES = \ @@ -35,6 +36,7 @@ libcolordbase_include_HEADERS = \ cd-dom.h \ cd-enum.h \ cd-icc.h \ + cd-icc-store.h \ cd-interp-akima.h \ cd-interp-linear.h \ cd-interp.h \ @@ -55,6 +57,7 @@ libcolordprivate_la_SOURCES = \ cd-dom.c \ cd-enum.c \ cd-icc.c \ + cd-icc-store.c \ cd-interp-akima.c \ cd-interp-linear.c \ cd-interp.c \ diff --git a/lib/colord/cd-icc-store.c b/lib/colord/cd-icc-store.c new file mode 100644 index 0000000..d393240 --- /dev/null +++ b/lib/colord/cd-icc-store.c @@ -0,0 +1,731 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2009-2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:cd-icc-store + * @short_description: An object to monitor a directory full of ICC profiles + */ + +#include "config.h" + +#include <glib-object.h> +#include <gio/gio.h> + +#include "cd-icc-store.h" + +static void cd_icc_store_finalize (GObject *object); + +#define CD_ICC_STORE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CD_TYPE_ICC_STORE, CdIccStorePrivate)) + +struct _CdIccStorePrivate +{ + CdIccLoadFlags load_flags; + GPtrArray *directory_array; + GPtrArray *icc_array; + GResource *cache; +}; + +enum { + SIGNAL_ADDED, + SIGNAL_REMOVED, + SIGNAL_LAST +}; + +static guint signals[SIGNAL_LAST] = { 0 }; + +G_DEFINE_TYPE (CdIccStore, cd_icc_store, G_TYPE_OBJECT) + +#define CD_ICC_STORE_MAX_RECURSION_LEVELS 2 + +static gboolean +cd_icc_store_search_path (CdIccStore *store, + const gchar *path, + guint depth, + GCancellable *cancellable, + GError **error); +static gboolean +cd_icc_store_search_path_child (CdIccStore *store, + const gchar *path, + GFileInfo *info, + guint depth, + GCancellable *cancellable, + GError **error); + +typedef struct { + gchar *path; + GFileMonitor *monitor; + guint depth; +} CdIccStoreDirHelper; + +/** + * cd_icc_store_helper_free: + **/ +static void +cd_icc_store_helper_free (CdIccStoreDirHelper *helper) +{ + g_free (helper->path); + if (helper->monitor != NULL) + g_object_unref (helper->monitor); + g_free (helper); +} + +/** + * cd_icc_store_find_filename: + **/ +static CdIcc * +cd_icc_store_find_filename (CdIccStore *store, const gchar *filename) +{ + CdIcc *tmp; + guint i; + GPtrArray *array = store->priv->icc_array; + + for (i = 0; i < array->len; i++) { + tmp = g_ptr_array_index (array, i); + if (g_strcmp0 (filename, cd_icc_get_filename (tmp)) == 0) + return tmp; + } + return NULL; +} + +/** + * cd_icc_store_find_directory: + **/ +static CdIccStoreDirHelper * +cd_icc_store_find_directory (CdIccStore *store, const gchar *path) +{ + CdIccStoreDirHelper *tmp; + guint i; + GPtrArray *array = store->priv->directory_array; + + for (i = 0; i < array->len; i++) { + tmp = g_ptr_array_index (array, i); + if (g_strcmp0 (path, tmp->path) == 0) + return tmp; + } + return NULL; +} + +/** + * cd_icc_store_remove_icc: + **/ +static gboolean +cd_icc_store_remove_icc (CdIccStore *store, const gchar *filename) +{ + CdIcc *icc = NULL; + gboolean ret = FALSE; + + /* find exact pointer */ + icc = cd_icc_store_find_filename (store, filename); + if (icc == NULL) + goto out; + + /* ref so we can emit the signal */ + g_object_ref (icc); + ret = g_ptr_array_remove (store->priv->icc_array, icc); + if (!ret) { + g_warning ("failed to remove %s", filename); + goto out; + } + + /* emit a signal */ + g_signal_emit (store, signals[SIGNAL_REMOVED], 0, icc); +out: + if (icc != NULL) + g_object_unref (icc); + return ret; +} + +/** + * cd_icc_store_add_icc: + **/ +static gboolean +cd_icc_store_add_icc (CdIccStore *store, GFile *file, GError **error) +{ + CdIcc *icc; + CdIccStorePrivate *priv = store->priv; + gboolean ret; + gchar *cache_key = NULL; + gchar *filename = NULL; + gchar *basename = NULL; + GBytes *data = NULL; + + /* use the GResource cache if available */ + icc = cd_icc_new (); + if (store->priv->cache != NULL) { + filename = g_file_get_path (file); + if (g_str_has_prefix (filename, "/usr/share/color/icc/colord/")) { + cache_key = g_build_filename ("/org/freedesktop/colord", + "profiles", + filename + 28, + NULL); + data = g_resource_lookup_data (store->priv->cache, + cache_key, + G_RESOURCE_LOOKUP_FLAGS_NONE, + NULL); + } + } + + /* parse new icc object */ + if (data != NULL) { + g_debug ("Using built-in %s", basename); + ret = cd_icc_load_data (icc, + g_bytes_get_data (data, NULL), + g_bytes_get_size (data), + CD_ICC_LOAD_FLAGS_METADATA, + error); + if (!ret) + goto out; + } else { + ret = cd_icc_load_file (icc, + file, + store->priv->load_flags, + NULL, + error); + if (!ret) + goto out; + } + + /* add to list */ + g_ptr_array_add (priv->icc_array, g_object_ref (icc)); + + /* emit a signal */ + g_signal_emit (store, signals[SIGNAL_ADDED], 0, icc); +out: + if (data != NULL) + g_bytes_unref (data); + g_object_unref (icc); + g_free (filename); + g_free (cache_key); + return ret; +} + +/** + * cd_icc_store_created_query_info_cb: + **/ +static void +cd_icc_store_created_query_info_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileInfo *info; + GError *error = NULL; + gchar *path; + GFile *file = G_FILE (source_object); + GFile *parent; + gboolean ret; + CdIccStore *store = CD_ICC_STORE (user_data); + + info = g_file_query_info_finish (file, res, NULL); + if (info == NULL) + return; + parent = g_file_get_parent (file); + path = g_file_get_path (parent); + ret = cd_icc_store_search_path_child (store, + path, + info, + 0, + NULL, + &error); + if (!ret) { + g_warning ("failed to search file: %s", + error->message); + g_error_free (error); + return; + } + g_free (path); + g_object_unref (info); + g_object_unref (parent); +} + +/** + * cd_icc_store_remove_from_prefix: + **/ +static void +cd_icc_store_remove_from_prefix (CdIccStore *store, const gchar *prefix) +{ + CdIccStorePrivate *priv = store->priv; + CdIcc *tmp; + const gchar *filename; + guint i; + + for (i = 0; i < priv->icc_array->len; i++) { + tmp = g_ptr_array_index (priv->icc_array, i); + filename = cd_icc_get_filename (tmp); + if (g_str_has_prefix (filename, prefix)) { + g_debug ("auto-removed %s as path removed", prefix); + cd_icc_store_remove_icc (store, filename); + } + } +} + +/** + * cd_icc_store_file_monitor_changed_cb: + **/ +static void +cd_icc_store_file_monitor_changed_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + CdIccStore *store) +{ + gchar *path = NULL; + gchar *parent_path = NULL; + CdIcc *tmp; + CdIccStoreDirHelper *helper; + + /* icc was deleted */ + if (event_type == G_FILE_MONITOR_EVENT_DELETED) { + + /* we can either have two things here, a directory or a + * file. We can't call g_file_query_info_async() as the + * inode doesn't exist anymore */ + path = g_file_get_path (file); + tmp = cd_icc_store_find_filename (store, path); + if (tmp != NULL) { + /* is a file */ + cd_icc_store_remove_icc (store, path); + goto out; + } + + /* is a directory, urgh. Remove all ICCs there. */ + cd_icc_store_remove_from_prefix (store, path); + helper = cd_icc_store_find_directory (store, path); + if (helper != NULL) { + g_ptr_array_remove (store->priv->directory_array, + helper); + } + goto out; + } + + /* ignore temp files */ + path = g_file_get_path (file); + if (g_strrstr (path, ".goutputstream") != NULL) { + g_debug ("ignoring gvfs temporary file"); + goto out; + } + + /* only care about created objects */ + if (event_type == G_FILE_MONITOR_EVENT_CREATED) { + g_file_query_info_async (file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + G_PRIORITY_LOW, + NULL, + cd_icc_store_created_query_info_cb, + store); + goto out; + } +out: + g_free (path); + g_free (parent_path); +} + +/** + * cd_icc_store_search_path_child: + **/ +static gboolean +cd_icc_store_search_path_child (CdIccStore *store, + const gchar *path, + GFileInfo *info, + guint depth, + GCancellable *cancellable, + GError **error) +{ + const gchar *name; + const gchar *type; + gboolean ret = TRUE; + gchar *full_path; + GFile *file = NULL; + + /* further down the worm-hole */ + name = g_file_info_get_name (info); + full_path = g_build_filename (path, name, NULL); + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + ret = cd_icc_store_search_path (store, + full_path, + depth + 1, + cancellable, + error); + if (!ret) + goto out; + goto out; + } + + /* ignore temp files */ + if (g_strrstr (full_path, ".goutputstream") != NULL) { + g_debug ("ignoring gvfs temporary file"); + goto out; + } + + /* check type */ + type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); + if (g_strcmp0 (type, "application/vnd.iccprofile") != 0) { + g_debug ("Incorrect content type for %s, got %s", full_path, type); + goto out; + } + + /* is a file */ + file = g_file_new_for_path (full_path); + ret = cd_icc_store_add_icc (store, file, error); + if (!ret) + goto out; +out: + if (file != NULL) + g_object_unref (file); + g_free (full_path); + return ret; +} + +/** + * cd_icc_store_search_path: + **/ +static gboolean +cd_icc_store_search_path (CdIccStore *store, + const gchar *path, + guint depth, + GCancellable *cancellable, + GError **error) +{ + CdIccStoreDirHelper *helper; + GFileEnumerator *enumerator = NULL; + GFile *file = NULL; + gboolean ret = TRUE; + GFileInfo *info; + GError *error_local = NULL; + + /* check sanity */ + if (depth > CD_ICC_STORE_MAX_RECURSION_LEVELS) { + ret = FALSE; + g_set_error (error, + CD_ICC_ERROR, + CD_ICC_ERROR_FAILED_TO_OPEN, + "cannot recurse more than %i levels deep", + CD_ICC_STORE_MAX_RECURSION_LEVELS); + goto out; + } + + /* add an inotify watch if not already added */ + file = g_file_new_for_path (path); + helper = cd_icc_store_find_directory (store, path); + if (helper == NULL) { + helper = g_new0 (CdIccStoreDirHelper, 1); + helper->depth = depth; + helper->path = g_strdup (path); + helper->monitor = g_file_monitor_directory (file, + G_FILE_MONITOR_NONE, + NULL, + error); + if (helper->monitor == NULL) { + ret = FALSE; + cd_icc_store_helper_free (helper); + goto out; + } + g_signal_connect (helper->monitor, "changed", + G_CALLBACK(cd_icc_store_file_monitor_changed_cb), + store); + g_ptr_array_add (store->priv->directory_array, helper); + } + + /* get contents of directory */ + enumerator = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (enumerator == NULL) { + ret = FALSE; + helper = cd_icc_store_find_directory (store, path); + if (helper != NULL) + g_ptr_array_remove (store->priv->directory_array, helper); + goto out; + } + + /* get all the files */ + while (TRUE) { + info = g_file_enumerator_next_file (enumerator, + cancellable, + &error_local); + if (info == NULL && error_local != NULL) { + ret = FALSE; + g_propagate_error (error, error_local); + goto out; + } + + /* special value, meaning "no more files to process" */ + if (info == NULL) + break; + + /* process this child */ + ret = cd_icc_store_search_path_child (store, + path, + info, + depth, + cancellable, + error); + if (!ret) + goto out; + } +out: + if (enumerator != NULL) + g_object_unref (enumerator); + g_object_unref (file); + return ret; +} + +/** + * cd_icc_store_set_load_flags: + * @store: a #CdIccStore instance. + * @load_flags: #CdIccLoadFlags, e.g. %CD_ICC_LOAD_FLAGS_TRANSLATIONS + * + * Sets the load flags to use when loading newly added profiles + * + * Since: 1.1.1 + **/ +void +cd_icc_store_set_load_flags (CdIccStore *store, CdIccLoadFlags load_flags) +{ + g_return_if_fail (CD_IS_ICC_STORE (store)); + store->priv->load_flags = load_flags; +} + +/** + * cd_icc_store_set_cache: + * @store: a #CdIccStore instance. + * @cache: a #GResource + * + * Sets an optional cache to use when reading profiles. This is probably + * only useful to the colord daemon. This function can only be called once. + * + * Since: 1.1.1 + **/ +void +cd_icc_store_set_cache (CdIccStore *store, GResource *cache) +{ + g_return_if_fail (CD_IS_ICC_STORE (store)); + g_return_if_fail (store->priv->cache == NULL); + store->priv->cache = g_object_ref (cache); +} + +/** + * cd_icc_store_get_array: + * @store: a #CdIccStore instance. + * + * Gets the list of #CdIcc objects in the store + * + * Return value: (transfer container) (element-type CdIcc): ICC profile objects + * + * Since: 1.1.1 + **/ +GPtrArray * +cd_icc_store_get_array (CdIccStore *store) +{ + g_return_val_if_fail (CD_IS_ICC_STORE (store), NULL); + return g_ptr_array_ref (store->priv->icc_array); +} + +/** + * cd_icc_store_search_location: + * @store: a #CdIccStore instance. + * @search_kind: a #CdIccStoreSearchKind, e.g. %CD_ICC_STORE_SEARCH_KIND_USER + * @watch_flags: a #CdIccStoreWatchFlags, e.g. %CD_ICC_STORE_WATCH_FLAGS_CREATE_LOCATION + * @error: A #GError or %NULL + * + * Adds a location to be watched for ICC profiles + * + * Return value: %TRUE for success + * + * Since: 1.1.1 + **/ +gboolean +cd_icc_store_search_kind (CdIccStore *store, + CdIccStoreSearchKind search_kind, + CdIccStoreWatchFlags watch_flags, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = TRUE; + gchar *tmp; + GPtrArray *locations; + guint i; + + /* get the locations for each kind */ + locations = g_ptr_array_new_with_free_func (g_free); + switch (search_kind) { + case CD_ICC_STORE_SEARCH_KIND_USER: + tmp = g_build_filename (g_get_user_data_dir (), "icc", NULL); + g_ptr_array_add (locations, tmp); + break; + case CD_ICC_STORE_SEARCH_KIND_USER_DEPRECATED: + tmp = g_build_filename (g_get_home_dir (), ".color", "icc", NULL); + g_ptr_array_add (locations, tmp); + break; + case CD_ICC_STORE_SEARCH_KIND_MACHINE: + g_ptr_array_add (locations, g_strdup (CD_SYSTEM_PROFILES_DIR)); + g_ptr_array_add (locations, g_strdup ("/var/lib/color/icc")); + break; + case CD_ICC_STORE_SEARCH_KIND_SYSTEM: + g_ptr_array_add (locations, g_strdup ("/usr/share/color/icc")); + g_ptr_array_add (locations, g_strdup ("/usr/local/share/color/icc")); + g_ptr_array_add (locations, g_strdup ("/Library/ColorSync/Profiles/Displays")); + break; + default: + break; + } + + /* add any found locations */ + for (i = 0; i < locations->len; i++) { + tmp = g_ptr_array_index (locations, i); + ret = cd_icc_store_search_location (store, + tmp, + watch_flags, + cancellable, + error); + if (!ret) + goto out; + } +out: + g_ptr_array_unref (locations); + return ret; +} + +/** + * cd_icc_store_search_location: + * @store: a #CdIccStore instance. + * @location: a fully qualified path + * @watch_flags: #CdIccStoreWatchFlags, e.g. %CD_ICC_STORE_WATCH_FLAGS_CREATE_LOCATION + * @error: A #GError or %NULL + * + * Adds a location to be watched for ICC profiles + * + * Return value: %TRUE for success + * + * Since: 1.1.1 + **/ +gboolean +cd_icc_store_search_location (CdIccStore *store, + const gchar *location, + CdIccStoreWatchFlags watch_flags, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = TRUE; + gboolean exists; + GFile *file; + + g_return_val_if_fail (CD_IS_ICC_STORE (store), FALSE); + g_return_val_if_fail (location != NULL, FALSE); + + /* does folder exist? */ + file = g_file_new_for_path (location); + exists = g_file_query_exists (file, cancellable); + if (!exists) { + if ((watch_flags & CD_ICC_STORE_WATCH_FLAGS_CREATE_LOCATION) > 0) { + ret = g_file_make_directory_with_parents (file, cancellable, error); + if (!ret) + goto out; + } else { + /* the directory does not exist */ + goto out; + } + } + + /* search all */ + ret = cd_icc_store_search_path (store, location, 0, cancellable, error); + if (!ret) + goto out; +out: + g_object_unref (file); + return ret; +} + +/** + * cd_icc_store_class_init: + **/ +static void +cd_icc_store_class_init (CdIccStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = cd_icc_store_finalize; + + signals[SIGNAL_ADDED] = + g_signal_new ("added", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CdIccStoreClass, added), + NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 1, CD_TYPE_ICC); + signals[SIGNAL_REMOVED] = + g_signal_new ("removed", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CdIccStoreClass, removed), + NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 1, CD_TYPE_ICC); + + g_type_class_add_private (klass, sizeof (CdIccStorePrivate)); +} + +/** + * cd_icc_store_init: + **/ +static void +cd_icc_store_init (CdIccStore *store) +{ + store->priv = CD_ICC_STORE_GET_PRIVATE (store); + store->priv->icc_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + store->priv->directory_array = g_ptr_array_new_with_free_func ((GDestroyNotify) cd_icc_store_helper_free); +} + +/** + * cd_icc_store_finalize: + **/ +static void +cd_icc_store_finalize (GObject *object) +{ + CdIccStore *store = CD_ICC_STORE (object); + CdIccStorePrivate *priv = store->priv; + + g_ptr_array_unref (priv->icc_array); + g_ptr_array_unref (priv->directory_array); + if (priv->cache != NULL) + g_object_unref (priv->cache); + + G_OBJECT_CLASS (cd_icc_store_parent_class)->finalize (object); +} + +/** + * cd_icc_store_new: + * + * Creates a new #CdIccStore object. + * + * Return value: a new CdIccStore object. + * + * Since: 1.1.1 + **/ +CdIccStore * +cd_icc_store_new (void) +{ + CdIccStore *store; + store = g_object_new (CD_TYPE_ICC_STORE, NULL); + return CD_ICC_STORE (store); +} diff --git a/lib/colord/cd-icc-store.h b/lib/colord/cd-icc-store.h new file mode 100644 index 0000000..ec8f89d --- /dev/null +++ b/lib/colord/cd-icc-store.h @@ -0,0 +1,99 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2009-2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __CD_ICC_STORE_H +#define __CD_ICC_STORE_H + +#include <glib-object.h> + +#include "cd-icc.h" + +G_BEGIN_DECLS + +#define CD_TYPE_ICC_STORE (cd_icc_store_get_type ()) +#define CD_ICC_STORE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CD_TYPE_ICC_STORE, CdIccStore)) +#define CD_ICC_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CD_TYPE_ICC_STORE, CdIccStoreClass)) +#define CD_IS_ICC_STORE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CD_TYPE_ICC_STORE)) +#define CD_IS_ICC_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CD_TYPE_ICC_STORE)) +#define CD_ICC_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CD_TYPE_ICC_STORE, CdIccStoreClass)) + +typedef struct _CdIccStorePrivate CdIccStorePrivate; +typedef struct _CdIccStore CdIccStore; +typedef struct _CdIccStoreClass CdIccStoreClass; + +struct _CdIccStore +{ + GObject parent; + CdIccStorePrivate *priv; +}; + +struct _CdIccStoreClass +{ + GObjectClass parent_class; + void (* added) (CdIcc *icc); + void (* removed) (CdIcc *icc); +}; + +/** + * CdIccStoreWatchFlags: + * @CD_ICC_STORE_WATCH_FLAGS_NONE: No flags set. + * @CD_ICC_STORE_WATCH_FLAGS_CREATE_LOCATION: Create the location if it does not exist + * + * Flags used when adding scan locations. + * + * Since: 1.1.1 + **/ +typedef enum { + CD_ICC_STORE_WATCH_FLAGS_NONE = 0, + CD_ICC_STORE_WATCH_FLAGS_CREATE_LOCATION = 1, + CD_ICC_STORE_WATCH_FLAGS_LAST +} CdIccStoreWatchFlags; + +typedef enum { + CD_ICC_STORE_SEARCH_KIND_SYSTEM, + CD_ICC_STORE_SEARCH_KIND_VOLUMES, /* TODO: not implemented */ + CD_ICC_STORE_SEARCH_KIND_MACHINE, + CD_ICC_STORE_SEARCH_KIND_USER, + CD_ICC_STORE_SEARCH_KIND_USER_DEPRECATED, + CD_ICC_STORE_SEARCH_KIND_LAST +} CdIccStoreSearchKind; + +GType cd_icc_store_get_type (void); +CdIccStore *cd_icc_store_new (void); +gboolean cd_icc_store_search_location (CdIccStore *store, + const gchar *location, + CdIccStoreWatchFlags watch_flags, + GCancellable *cancellable, + GError **error); +gboolean cd_icc_store_search_kind (CdIccStore *store, + CdIccStoreSearchKind search_kind, + CdIccStoreWatchFlags watch_flags, + GCancellable *cancellable, + GError **error); +void cd_icc_store_set_load_flags (CdIccStore *store, + CdIccLoadFlags load_flags); +void cd_icc_store_set_cache (CdIccStore *store, + GResource *cache); +GPtrArray *cd_icc_store_get_array (CdIccStore *store); + +G_END_DECLS + +#endif /* __CD_ICC_STORE_H */ diff --git a/lib/colord/cd-self-test.c b/lib/colord/cd-self-test.c index 4a3dfce..eb6911d 100644 --- a/lib/colord/cd-self-test.c +++ b/lib/colord/cd-self-test.c @@ -42,6 +42,7 @@ #include "cd-device-sync.h" #include "cd-dom.h" #include "cd-icc.h" +#include "cd-icc-store.h" #include "cd-interp-akima.h" #include "cd-interp-linear.h" #include "cd-interp.h" @@ -3787,6 +3788,146 @@ colord_transform_func (void) g_object_unref (transform); } +#include <glib/gstdio.h> + +static void +_copy_files (const gchar *src, const gchar *dest) +{ + gboolean ret; + gchar *data; + GError *error = NULL; + gsize len; + + ret = g_file_get_contents (src, &data, &len, &error); + g_assert (ret); + g_assert_no_error (error); + ret = g_file_set_contents (dest, data, len, &error); + g_assert (ret); + g_assert_no_error (error); + g_free (data); +} + +static void +colord_icc_store_added_cb (CdIccStore *store, CdIcc *icc, guint *cnt) +{ + g_debug ("Got ::added(%s)", cd_icc_get_checksum (icc)); + (*cnt)++; + _g_test_loop_quit (); +} + +static void +colord_icc_store_removed_cb (CdIccStore *store, CdIcc *icc, guint *cnt) +{ + g_debug ("Got ::removed(%s)", cd_icc_get_checksum (icc)); + (*cnt)++; + _g_test_loop_quit (); +} + +static void +colord_icc_store_func (void) +{ + CdIccStore *store; + gboolean ret; + gchar *file1; + gchar *file2; + gchar *filename; + gchar *newroot; + gchar *root; + GError *error = NULL; + GPtrArray *array; + guint added = 0; + guint removed = 0; + + store = cd_icc_store_new (); + g_signal_connect (store, "added", + G_CALLBACK (colord_icc_store_added_cb), + &added); + g_signal_connect (store, "removed", + G_CALLBACK (colord_icc_store_removed_cb), + &removed); + cd_icc_store_set_load_flags (store, CD_ICC_LOAD_FLAGS_NONE); + + filename = _g_test_realpath (TESTDATADIR "/ibm-t61.icc"); + + /* create test directory */ + root = g_strdup_printf ("/tmp/colord-%c%c%c%c", + g_random_int_range ('a', 'z'), + g_random_int_range ('a', 'z'), + g_random_int_range ('a', 'z'), + g_random_int_range ('a', 'z')); + g_mkdir (root, 0777); + + file1 = g_build_filename (root, "already-exists.icc", NULL); + _copy_files (filename, file1); + + g_assert_cmpint (added, ==, 0); + g_assert_cmpint (removed, ==, 0); + + /* this is done sync */ + ret = cd_icc_store_search_location (store, root, + CD_ICC_STORE_WATCH_FLAGS_CREATE_LOCATION, + NULL, &error); + g_assert (ret); + g_assert_no_error (error); + + g_assert_cmpint (added, ==, 1); + g_assert_cmpint (removed, ==, 0); + + /* create /tmp/colord-foo/new-root/new-icc.icc which should cause a + * new directory notifier to be added and the new file to be + * discovered */ + newroot = g_build_filename (root, "new-root", NULL); + g_mkdir (newroot, 0777); + file2 = g_build_filename (newroot, "new-icc.icc", NULL); + _copy_files (filename, file2); + + /* wait for file notifier */ + _g_test_loop_run_with_timeout (5000); + _g_test_loop_quit (); + + g_assert_cmpint (added, ==, 2); + g_assert_cmpint (removed, ==, 0); + + /* check store size */ + array = cd_icc_store_get_array (store); + g_assert_cmpint (array->len, ==, 2); + g_ptr_array_unref (array); + + g_unlink (file2); + + /* wait for file notifier */ + _g_test_loop_run_with_timeout (5000); + _g_test_loop_quit (); + + g_assert_cmpint (added, ==, 2); + g_assert_cmpint (removed, ==, 1); + + /* remove already-exists.icc */ + g_unlink (file1); + + /* wait for file notifier */ + _g_test_loop_run_with_timeout (5000); + _g_test_loop_quit (); + + g_assert_cmpint (added, ==, 2); + g_assert_cmpint (removed, ==, 2); + + g_remove (newroot); + g_remove (root); + + /* check store size */ + array = cd_icc_store_get_array (store); + g_assert_cmpint (array->len, ==, 0); + g_ptr_array_unref (array); + + g_free (file1); + g_free (file2); + g_free (filename); + g_free (newroot); + g_free (root); + g_object_unref (store); +} + int main (int argc, char **argv) { @@ -3801,6 +3942,7 @@ main (int argc, char **argv) g_test_add_func ("/colord/icc{localized}", colord_icc_localized_func); g_test_add_func ("/colord/icc{edid}", colord_icc_edid_func); g_test_add_func ("/colord/icc{save}", colord_icc_save_func); + g_test_add_func ("/colord/icc-store", colord_icc_store_func); g_test_add_func ("/colord/buffer", colord_buffer_func); g_test_add_func ("/colord/enum", colord_enum_func); g_test_add_func ("/colord/dom", colord_dom_func); diff --git a/lib/colord/colord-private.h b/lib/colord/colord-private.h index a54a85b..f06cc10 100644 --- a/lib/colord/colord-private.h +++ b/lib/colord/colord-private.h @@ -39,6 +39,7 @@ #include <colord/cd-dom.h> #include <colord/cd-enum.h> #include <colord/cd-icc.h> +#include <colord/cd-icc-store.h> #include <colord/cd-interp-akima.h> #include <colord/cd-interp-linear.h> #include <colord/cd-interp.h> |