summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2013-07-03 14:06:13 +0100
committerRichard Hughes <richard@hughsie.com>2013-07-03 17:08:35 +0100
commit5ed2f646d70d3c5164dbd22bd03862ae660cff9a (patch)
treebe67efb3a7c7d3bee0ffe765c5f15b4d6f36b0f8
parent65e1294c8c424482f96e46e7ffaa260385713de7 (diff)
downloadcolord-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.am3
-rw-r--r--lib/colord/cd-icc-store.c731
-rw-r--r--lib/colord/cd-icc-store.h99
-rw-r--r--lib/colord/cd-self-test.c142
-rw-r--r--lib/colord/colord-private.h1
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>