[gnome-settings-daemon] color: Register icc profile files with colord



commit cbed3c745faf29e490b4a58adef55dabaa3a228c
Author: Richard Hughes <richard hughsie com>
Date:   Sat May 21 14:26:41 2011 +0100

    color: Register icc profile files with colord
    
    Watch the user directories where programs may install ICC files and register
    them with colord when they appear and unregister them from colord when they
    are deleted.
    
    This is required as colord is unable to scan the users home directory, for
    three principal reasons:
    
    1. Layering design violation
    2. SELinux won't let a daemon running as root grub around in the users $HOME.
    3. The home directory may be encrypted until after login.
    
    This code is simply moved from the old gcm-session process. An optional lcms2
    dependancy is added, which allows us to get the profile embedded checksum
    without reading all the bytes and running it through an MD5 transform.
    
    For a moderate number of large CMYK profiles (~5Mb) it's actually quite slow to
    do this, but most users will just have the tiny EDID profiles which are really
    quick to parse. If you've got GIMP or G-C-M installed, you already have lcms2
    installed, and as a library it's _tiny_. So it's recommended.
    
    As an added safety measure, the plugin will not recurse more than 2 levels deep
    when searching for profiles.

 configure.ac                      |    9 +
 plugins/color/Makefile.am         |    4 +
 plugins/color/gcm-profile-store.c |  502 +++++++++++++++++++++++++++++++++++++
 plugins/color/gcm-profile-store.h |   59 +++++
 plugins/color/gsd-color-manager.c |  213 ++++++++++++++++
 5 files changed, 787 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 9439234..75b4582 100644
--- a/configure.ac
+++ b/configure.ac
@@ -112,6 +112,14 @@ PKG_CHECK_MODULES(LIBCANBERRA, libcanberra-gtk3,
 AM_CONDITIONAL(HAVE_LIBCANBERRA, test "x$have_libcanberra" = "xtrue")
 
 dnl ---------------------------------------------------------------------------
+dnl - Check for LCMS2
+dnl ---------------------------------------------------------------------------
+PKG_CHECK_MODULES(LCMS, lcms2, HAVE_LCMS="yes", HAVE_LCMS="no")
+if test "x$HAVE_LCMS" = "xyes"; then
+        AC_DEFINE(HAVE_LCMS, 1, [define if LCMS is available])
+fi
+
+dnl ---------------------------------------------------------------------------
 dnl - Check for libnotify
 dnl ---------------------------------------------------------------------------
 
@@ -601,6 +609,7 @@ echo "
 
         dbus-1 system.d dir:      ${DBUS_SYS_DIR}
         PolicyKit support:        ${HAVE_POLKIT}
+        LCMS support:             ${HAVE_LCMS}
 
         Libnotify support:        ${have_libnotify}
         Libcanberra support:      ${have_libcanberra}
diff --git a/plugins/color/Makefile.am b/plugins/color/Makefile.am
index a2f6aea..5d6174e 100644
--- a/plugins/color/Makefile.am
+++ b/plugins/color/Makefile.am
@@ -4,6 +4,8 @@ plugin_LTLIBRARIES = \
 	libcolor.la
 
 libcolor_la_SOURCES = 		\
+	gcm-profile-store.c	\
+	gcm-profile-store.h	\
 	gsd-color-manager.c	\
 	gsd-color-manager.h	\
 	gsd-color-plugin.c	\
@@ -19,6 +21,7 @@ libcolor_la_CFLAGS = \
 	$(PLUGIN_CFLAGS)		\
 	$(COLORD_CFLAGS)		\
 	$(LIBCANBERRA_CFLAGS)		\
+	$(LCMS_CFLAGS)			\
 	$(SETTINGS_PLUGIN_CFLAGS)	\
 	$(AM_CFLAGS)
 
@@ -28,6 +31,7 @@ libcolor_la_LDFLAGS = 		\
 libcolor_la_LIBADD  = 		\
 	$(COLORD_LIBS)		\
 	$(LIBCANBERRA_LIBS)	\
+	$(LCMS_LIBS)		\
 	$(SETTINGS_PLUGIN_LIBS)
 
 plugin_in_files = 		\
diff --git a/plugins/color/gcm-profile-store.c b/plugins/color/gcm-profile-store.c
new file mode 100644
index 0000000..eddc7ba
--- /dev/null
+++ b/plugins/color/gcm-profile-store.c
@@ -0,0 +1,502 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2011 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "gcm-profile-store.h"
+
+static void     gcm_profile_store_finalize      (GObject     *object);
+
+#define GCM_PROFILE_STORE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCM_TYPE_PROFILE_STORE, GcmProfileStorePrivate))
+
+struct _GcmProfileStorePrivate
+{
+        GPtrArray                       *filename_array;
+        GPtrArray                       *directory_array;
+};
+
+enum {
+        SIGNAL_ADDED,
+        SIGNAL_REMOVED,
+        SIGNAL_CHANGED,
+        SIGNAL_LAST
+};
+
+static guint signals[SIGNAL_LAST] = { 0 };
+
+G_DEFINE_TYPE (GcmProfileStore, gcm_profile_store, G_TYPE_OBJECT)
+
+static void gcm_profile_store_search_path (GcmProfileStore *profile_store, const gchar *path, guint depth);
+static void gcm_profile_store_process_child (GcmProfileStore *profile_store, const gchar *path, GFileInfo *info);
+
+#define GCM_PROFILE_STORE_MAX_RECURSION_LEVELS          2
+
+typedef struct {
+        gchar           *path;
+        GFileMonitor    *monitor;
+        guint            depth;
+} GcmProfileStoreDirHelper;
+
+static void
+gcm_profile_store_helper_free (GcmProfileStoreDirHelper *helper)
+{
+        g_free (helper->path);
+        if (helper->monitor != NULL)
+                g_object_unref (helper->monitor);
+        g_free (helper);
+}
+
+static const gchar *
+gcm_profile_store_find_filename (GcmProfileStore *profile_store, const gchar *filename)
+{
+        const gchar *tmp;
+        guint i;
+        GPtrArray *array = profile_store->priv->filename_array;
+
+        for (i=0; i<array->len; i++) {
+                tmp = g_ptr_array_index (array, i);
+                if (g_strcmp0 (filename, tmp) == 0)
+                        return tmp;
+        }
+        return NULL;
+}
+
+static GcmProfileStoreDirHelper *
+gcm_profile_store_find_directory (GcmProfileStore *profile_store, const gchar *path)
+{
+        GcmProfileStoreDirHelper *tmp;
+        guint i;
+        GPtrArray *array = profile_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;
+}
+
+static gboolean
+gcm_profile_store_remove_profile (GcmProfileStore *profile_store,
+                                  const gchar *filename)
+{
+        gboolean ret = FALSE;
+        const gchar *tmp;
+        gchar *filename_dup = NULL;
+
+        GcmProfileStorePrivate *priv = profile_store->priv;
+
+        /* find exact pointer */
+        tmp = gcm_profile_store_find_filename (profile_store, filename);
+        if (tmp == NULL)
+                goto out;
+
+        /* dup so we can emit the signal */
+        filename_dup = g_strdup (tmp);
+        ret = g_ptr_array_remove (priv->filename_array, (gpointer)tmp);
+        if (!ret) {
+                g_warning ("failed to remove %s", filename);
+                goto out;
+        }
+
+        /* emit a signal */
+        g_debug ("emit removed: %s", filename_dup);
+        g_signal_emit (profile_store, signals[SIGNAL_REMOVED], 0, filename_dup);
+out:
+        g_free (filename_dup);
+        return ret;
+}
+
+static void
+gcm_profile_store_add_profile (GcmProfileStore *profile_store, const gchar *filename)
+{
+        GcmProfileStorePrivate *priv = profile_store->priv;
+
+        /* add to list */
+        g_ptr_array_add (priv->filename_array, g_strdup (filename));
+
+        /* emit a signal */
+        g_debug ("emit add: %s", filename);
+        g_signal_emit (profile_store, signals[SIGNAL_ADDED], 0, filename);
+}
+
+static void
+gcm_profile_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;
+        GcmProfileStore *profile_store = GCM_PROFILE_STORE (user_data);
+
+        info = g_file_query_info_finish (file, res, &error);
+        if (info == NULL) {
+                g_warning ("failed to get info about deleted file: %s",
+                           error->message);
+                g_error_free (error);
+                return;
+        }
+        parent = g_file_get_parent (file);
+        path = g_file_get_path (parent);
+        gcm_profile_store_process_child (profile_store,
+                                         path,
+                                         info);
+        g_free (path);
+        g_object_unref (info);
+        g_object_unref (parent);
+}
+
+static void
+gcm_profile_store_remove_from_prefix (GcmProfileStore *profile_store,
+                                      const gchar *prefix)
+{
+        guint i;
+        const gchar *path;
+        GcmProfileStorePrivate *priv = profile_store->priv;
+
+        for (i = 0; i < priv->filename_array->len; i++) {
+                path = g_ptr_array_index (priv->filename_array, i);
+                if (g_str_has_prefix (path, prefix)) {
+                        g_debug ("auto-removed %s as path removed", path);
+                        gcm_profile_store_remove_profile (profile_store, path);
+                }
+        }
+}
+
+static void
+gcm_profile_store_file_monitor_changed_cb (GFileMonitor *monitor,
+                                           GFile *file,
+                                           GFile *other_file,
+                                           GFileMonitorEvent event_type,
+                                           GcmProfileStore *profile_store)
+{
+        gchar *path = NULL;
+        gchar *parent_path = NULL;
+        GFile *parent = NULL;
+        const gchar *tmp;
+        GcmProfileStoreDirHelper *helper;
+
+        /* profile 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 = gcm_profile_store_find_filename (profile_store, path);
+                if (tmp != NULL) {
+                        /* is a file */
+                        gcm_profile_store_remove_profile (profile_store, path);
+                        goto out;
+                }
+
+                /* is a directory, urgh. Remove all profiles there. */
+                gcm_profile_store_remove_from_prefix (profile_store, path);
+                helper = gcm_profile_store_find_directory (profile_store, path);
+                if (helper != NULL) {
+                        g_ptr_array_remove (profile_store->priv->directory_array,
+                                            helper);
+                }
+                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_TYPE,
+                                         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                         G_PRIORITY_LOW,
+                                         NULL,
+                                         gcm_profile_store_created_query_info_cb,
+                                         profile_store);
+                goto out;
+        }
+out:
+        if (parent != NULL)
+                g_object_unref (parent);
+        g_free (path);
+        g_free (parent_path);
+}
+
+static void
+gcm_profile_store_process_child (GcmProfileStore *profile_store,
+                                 const gchar *path,
+                                 GFileInfo *info)
+{
+        gchar *full_path = NULL;
+        const gchar *name;
+        GcmProfileStoreDirHelper *helper;
+
+        /* check we're not in a loop */
+        helper = gcm_profile_store_find_directory (profile_store, path);
+        if (helper->depth > GCM_PROFILE_STORE_MAX_RECURSION_LEVELS) {
+                g_warning ("recursing more than %i levels deep is insane",
+                           GCM_PROFILE_STORE_MAX_RECURSION_LEVELS);
+                goto out;
+        }
+
+        /* make the compete path */
+        name = g_file_info_get_name (info);
+        full_path = g_build_filename (path, name, NULL);
+
+        /* if a directory */
+        if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+                gcm_profile_store_search_path (profile_store,
+                                               full_path,
+                                               helper->depth + 1);
+                goto out;
+        }
+
+        /* ignore temp files */
+        if (g_strrstr (full_path, ".goutputstream") != NULL) {
+                g_debug ("ignoring gvfs temporary file");
+                goto out;
+        }
+
+        /* is a file */
+        gcm_profile_store_add_profile (profile_store, full_path);
+out:
+        g_free (full_path);
+}
+
+static void
+gcm_profile_store_next_files_cb (GObject *source_object,
+                                 GAsyncResult *res,
+                                 gpointer user_data)
+{
+        GList *files;
+        GList *f;
+        GError *error = NULL;
+        GFileInfo *info;
+        GFile *file;
+        gchar *path;
+        GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source_object);
+        GcmProfileStore *profile_store = GCM_PROFILE_STORE (user_data);
+
+        files = g_file_enumerator_next_files_finish (enumerator,
+                                                     res,
+                                                     &error);
+        if (files == NULL) {
+                /* special value, meaning "no more files to process" */
+                return;
+        }
+        if (error != NULL) {
+                g_warning ("failed to get data about enumerated directory: %s",
+                           error->message);
+                g_error_free (error);
+                return;
+        }
+
+        /* get each file */
+        file = g_file_enumerator_get_container (enumerator);
+        path = g_file_get_path (file);
+        for (f = files; f != NULL; f = f->next) {
+                info = G_FILE_INFO (f->data);
+                gcm_profile_store_process_child (profile_store, path, info);
+        }
+
+        /* continue to get the rest of the data in chunks */
+        g_file_enumerator_next_files_async  (enumerator,
+                                             5,
+                                             G_PRIORITY_LOW,
+                                             NULL,
+                                             gcm_profile_store_next_files_cb,
+                                             user_data);
+
+        g_free (path);
+        g_list_foreach (files, (GFunc) g_object_unref, NULL);
+        g_list_free (files);
+}
+
+static void
+gcm_profile_store_enumerate_children_cb (GObject *source_object,
+                                         GAsyncResult *res,
+                                         gpointer user_data)
+{
+        gchar *path = NULL;
+        GError *error = NULL;
+        GFileEnumerator *enumerator;
+
+        enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
+                                                       res,
+                                                       &error);
+        if (enumerator == NULL) {
+                path = g_file_get_path (G_FILE (source_object));
+                g_warning ("failed to enumerate directory %s: %s",
+                           path, error->message);
+                g_error_free (error);
+                g_free (path);
+                return;
+        }
+
+        /* get the first chunk of data */
+        g_file_enumerator_next_files_async (enumerator,
+                                            5,
+                                            G_PRIORITY_LOW,
+                                            NULL,
+                                            gcm_profile_store_next_files_cb,
+                                            user_data);
+        g_object_unref (enumerator);
+}
+
+static void
+gcm_profile_store_search_path (GcmProfileStore *profile_store, const gchar *path, guint depth)
+{
+        GFile *file = NULL;
+        GError *error = NULL;
+        GcmProfileStoreDirHelper *helper;
+
+        /* add an inotify watch if not already added */
+        file = g_file_new_for_path (path);
+        helper = gcm_profile_store_find_directory (profile_store, path);
+        if (helper == NULL) {
+                helper = g_new0 (GcmProfileStoreDirHelper, 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) {
+                        g_debug ("failed to monitor path: %s", error->message);
+                        g_error_free (error);
+                        gcm_profile_store_helper_free (helper);
+                        goto out;
+                }
+                g_signal_connect (helper->monitor, "changed",
+                                  G_CALLBACK(gcm_profile_store_file_monitor_changed_cb),
+                                  profile_store);
+                g_ptr_array_add (profile_store->priv->directory_array, helper);
+        }
+
+        /* get contents of directory */
+        g_file_enumerate_children_async (file,
+                                         G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                         G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                         G_PRIORITY_LOW,
+                                         NULL,
+                                         gcm_profile_store_enumerate_children_cb,
+                                         profile_store);
+out:
+        if (file != NULL)
+                g_object_unref (file);
+}
+
+static gboolean
+gcm_profile_store_mkdir_with_parents (const gchar *filename, GError **error)
+{
+        gboolean ret;
+        GFile *file = NULL;
+
+        /* ensure destination exists */
+        ret = g_file_test (filename, G_FILE_TEST_EXISTS);
+        if (!ret) {
+                file = g_file_new_for_path (filename);
+                ret = g_file_make_directory_with_parents (file, NULL, error);
+                if (!ret)
+                        goto out;
+        }
+out:
+        if (file != NULL)
+                g_object_unref (file);
+        return ret;
+}
+
+gboolean
+gcm_profile_store_search (GcmProfileStore *profile_store)
+{
+        gchar *path;
+        gboolean ret;
+        GError *error;
+
+        /* get Linux per-user profiles */
+        path = g_build_filename (g_get_user_data_dir (), "icc", NULL);
+        ret = gcm_profile_store_mkdir_with_parents (path, &error);
+        if (!ret) {
+                g_warning ("failed to create directory on startup: %s", error->message);
+                g_error_free (error);
+        } else {
+                gcm_profile_store_search_path (profile_store, path, 0);
+        }
+        g_free (path);
+
+        /* get per-user profiles from obsolete location */
+        path = g_build_filename (g_get_home_dir (), ".color", "icc", NULL);
+        gcm_profile_store_search_path (profile_store, path, 0);
+        g_free (path);
+        return TRUE;
+}
+
+static void
+gcm_profile_store_class_init (GcmProfileStoreClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        object_class->finalize = gcm_profile_store_finalize;
+
+        signals[SIGNAL_ADDED] =
+                g_signal_new ("added",
+                              G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GcmProfileStoreClass, added),
+                              NULL, NULL, g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE, 1, G_TYPE_STRING);
+        signals[SIGNAL_REMOVED] =
+                g_signal_new ("removed",
+                              G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GcmProfileStoreClass, removed),
+                              NULL, NULL, g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE, 1, G_TYPE_STRING);
+
+        g_type_class_add_private (klass, sizeof (GcmProfileStorePrivate));
+}
+
+static void
+gcm_profile_store_init (GcmProfileStore *profile_store)
+{
+        profile_store->priv = GCM_PROFILE_STORE_GET_PRIVATE (profile_store);
+        profile_store->priv->filename_array = g_ptr_array_new_with_free_func (g_free);
+        profile_store->priv->directory_array = g_ptr_array_new_with_free_func ((GDestroyNotify) gcm_profile_store_helper_free);
+}
+
+static void
+gcm_profile_store_finalize (GObject *object)
+{
+        GcmProfileStore *profile_store = GCM_PROFILE_STORE (object);
+        GcmProfileStorePrivate *priv = profile_store->priv;
+
+        g_ptr_array_unref (priv->filename_array);
+        g_ptr_array_unref (priv->directory_array);
+
+        G_OBJECT_CLASS (gcm_profile_store_parent_class)->finalize (object);
+}
+
+GcmProfileStore *
+gcm_profile_store_new (void)
+{
+        GcmProfileStore *profile_store;
+        profile_store = g_object_new (GCM_TYPE_PROFILE_STORE, NULL);
+        return GCM_PROFILE_STORE (profile_store);
+}
+
diff --git a/plugins/color/gcm-profile-store.h b/plugins/color/gcm-profile-store.h
new file mode 100644
index 0000000..095bcf0
--- /dev/null
+++ b/plugins/color/gcm-profile-store.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2011 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GCM_PROFILE_STORE_H
+#define __GCM_PROFILE_STORE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_PROFILE_STORE          (gcm_profile_store_get_type ())
+#define GCM_PROFILE_STORE(o)            (G_TYPE_CHECK_INSTANCE_CAST ((o), GCM_TYPE_PROFILE_STORE, GcmProfileStore))
+#define GCM_PROFILE_STORE_CLASS(k)      (G_TYPE_CHECK_CLASS_CAST((k), GCM_TYPE_PROFILE_STORE, GcmProfileStoreClass))
+#define GCM_IS_PROFILE_STORE(o)         (G_TYPE_CHECK_INSTANCE_TYPE ((o), GCM_TYPE_PROFILE_STORE))
+#define GCM_IS_PROFILE_STORE_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE ((k), GCM_TYPE_PROFILE_STORE))
+#define GCM_PROFILE_STORE_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS ((o), GCM_TYPE_PROFILE_STORE, GcmProfileStoreClass))
+
+typedef struct _GcmProfileStorePrivate  GcmProfileStorePrivate;
+typedef struct _GcmProfileStore         GcmProfileStore;
+typedef struct _GcmProfileStoreClass    GcmProfileStoreClass;
+
+struct _GcmProfileStore
+{
+         GObject                         parent;
+         GcmProfileStorePrivate         *priv;
+};
+
+struct _GcmProfileStoreClass
+{
+        GObjectClass    parent_class;
+        void            (* added)                       (const gchar            *filename);
+        void            (* removed)                     (const gchar            *filename);
+};
+
+GType            gcm_profile_store_get_type             (void);
+GcmProfileStore *gcm_profile_store_new                  (void);
+gboolean         gcm_profile_store_search               (GcmProfileStore        *profile_store);
+
+G_END_DECLS
+
+#endif /* __GCM_PROFILE_STORE_H */
diff --git a/plugins/color/gsd-color-manager.c b/plugins/color/gsd-color-manager.c
index e5f5e10..c691dd6 100644
--- a/plugins/color/gsd-color-manager.c
+++ b/plugins/color/gsd-color-manager.c
@@ -29,8 +29,13 @@
 #include <canberra-gtk.h>
 #endif
 
+#ifdef HAVE_LCMS
+  #include <lcms2.h>
+#endif
+
 #include "gnome-settings-profile.h"
 #include "gsd-color-manager.h"
+#include "gcm-profile-store.h"
 
 #define GSD_COLOR_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_COLOR_MANAGER, GsdColorManagerPrivate))
 
@@ -43,6 +48,7 @@ struct GsdColorManagerPrivate
 {
         CdClient        *client;
         GSettings       *settings;
+        GcmProfileStore *profile_store;
 };
 
 enum {
@@ -65,6 +71,7 @@ gcm_session_client_connect_cb (GObject *source_object,
         gboolean ret;
         GError *error = NULL;
         GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+        GsdColorManagerPrivate *priv = manager->priv;
 
         /* connected */
         g_debug ("connected to colord");
@@ -72,7 +79,11 @@ gcm_session_client_connect_cb (GObject *source_object,
         if (!ret) {
                 g_warning ("failed to connect to colord: %s", error->message);
                 g_error_free (error);
+                return;
         }
+
+        /* add profiles */
+        gcm_profile_store_search (priv->profile_store);
 }
 
 gboolean
@@ -305,6 +316,198 @@ out:
         g_free (basename);
 }
 
+#ifdef HAVE_LCMS
+static gchar *
+gcm_session_get_precooked_md5 (cmsHPROFILE lcms_profile)
+{
+        cmsUInt8Number profile_id[16];
+        gboolean md5_precooked = FALSE;
+        guint i;
+        gchar *md5 = NULL;
+
+        /* check to see if we have a pre-cooked MD5 */
+        cmsGetHeaderProfileID (lcms_profile, profile_id);
+        for (i=0; i<16; i++) {
+                if (profile_id[i] != 0) {
+                        md5_precooked = TRUE;
+                        break;
+                }
+        }
+        if (!md5_precooked)
+                goto out;
+
+        /* convert to a hex string */
+        md5 = g_new0 (gchar, 32 + 1);
+        for (i=0; i<16; i++)
+                g_snprintf (md5 + i*2, 3, "%02x", profile_id[i]);
+out:
+        return md5;
+}
+#endif
+
+static gchar *
+gcm_session_get_md5_for_filename (const gchar *filename,
+                                  GError **error)
+{
+        gboolean ret;
+        gchar *checksum = NULL;
+        gchar *data = NULL;
+        gsize length;
+
+#ifdef HAVE_LCMS
+        cmsHPROFILE lcms_profile = NULL;
+
+        /* get the internal profile id, if it exists */
+        lcms_profile = cmsOpenProfileFromFile (filename, "r");
+        if (lcms_profile == NULL) {
+                g_set_error_literal (error, 1, 0,
+                                     "failed to load: not an ICC profile");
+                goto out;
+        }
+        checksum = gcm_session_get_precooked_md5 (lcms_profile);
+        if (checksum != NULL)
+                goto out;
+#endif
+
+        /* generate checksum */
+        ret = g_file_get_contents (filename, &data, &length, error);
+        if (!ret)
+                goto out;
+        checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
+                                                (const guchar *) data,
+                                                length);
+out:
+        g_free (data);
+#ifdef HAVE_LCMS
+        if (lcms_profile != NULL)
+                cmsCloseProfile (lcms_profile);
+#endif
+        return checksum;
+}
+
+static void
+gcm_session_create_profile_cb (GObject *object,
+                               GAsyncResult *res,
+                               gpointer user_data)
+{
+        CdProfile *profile;
+        GError *error = NULL;
+        CdClient *client = CD_CLIENT (object);
+
+        profile = cd_client_create_profile_finish (client, res, &error);
+        if (profile == NULL) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return;
+        }
+        g_object_unref (profile);
+}
+
+static void
+gcm_session_profile_store_added_cb (GcmProfileStore *profile_store,
+                                    const gchar *filename,
+                                    GsdColorManager *manager)
+{
+        CdProfile *profile = NULL;
+        gchar *checksum = NULL;
+        gchar *profile_id = NULL;
+        GError *error = NULL;
+        GHashTable *profile_props = NULL;
+        GsdColorManagerPrivate *priv = manager->priv;
+
+        g_debug ("profile %s added", filename);
+
+        /* generate ID */
+        checksum = gcm_session_get_md5_for_filename (filename, &error);
+        if (checksum == NULL) {
+                g_warning ("failed to get profile checksum: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+        profile_id = g_strdup_printf ("icc-%s", checksum);
+        profile_props = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                               NULL, NULL);
+        g_hash_table_insert (profile_props,
+                             CD_PROFILE_PROPERTY_FILENAME,
+                             (gpointer) filename);
+        g_hash_table_insert (profile_props,
+                             CD_PROFILE_METADATA_FILE_CHECKSUM,
+                             (gpointer) checksum);
+        cd_client_create_profile (priv->client,
+                                  profile_id,
+                                  CD_OBJECT_SCOPE_TEMP,
+                                  profile_props,
+                                  NULL,
+                                  gcm_session_create_profile_cb,
+                                  manager);
+out:
+        g_free (checksum);
+        g_free (profile_id);
+        if (profile_props != NULL)
+                g_hash_table_unref (profile_props);
+        if (profile != NULL)
+                g_object_unref (profile);
+}
+
+static void
+gcm_session_delete_profile_cb (GObject *object,
+                               GAsyncResult *res,
+                               gpointer user_data)
+{
+        gboolean ret;
+        GError *error = NULL;
+        CdClient *client = CD_CLIENT (object);
+
+        ret = cd_client_delete_profile_finish (client, res, &error);
+        if (!ret) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+gcm_session_find_profile_by_filename_cb (GObject *object,
+                                         GAsyncResult *res,
+                                         gpointer user_data)
+{
+        GError *error = NULL;
+        CdProfile *profile;
+        CdClient *client = CD_CLIENT (object);
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+        profile = cd_client_find_profile_by_filename_finish (client, res, &error);
+        if (profile == NULL) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* remove it from colord */
+        g_debug ("profile %s removed", cd_profile_get_id (profile));
+        cd_client_delete_profile (manager->priv->client,
+                                  cd_profile_get_id (profile),
+                                  NULL,
+                                  gcm_session_delete_profile_cb,
+                                  manager);
+out:
+        if (profile != NULL)
+                g_object_unref (profile);
+}
+
+static void
+gcm_session_profile_store_removed_cb (GcmProfileStore *profile_store,
+                                      const gchar *filename,
+                                      GsdColorManager *manager)
+{
+        /* find the ID for the filename */
+        g_debug ("filename %s removed", filename);
+        cd_client_find_profile_by_filename (manager->priv->client,
+                                            filename,
+                                            NULL,
+                                            gcm_session_find_profile_by_filename_cb,
+                                            manager);
+}
 
 static void
 gcm_session_sensor_added_cb (CdClient *client,
@@ -401,6 +604,15 @@ gsd_color_manager_init (GsdColorManager *manager)
         g_signal_connect (priv->client, "sensor-removed",
                           G_CALLBACK (gcm_session_sensor_removed_cb),
                           manager);
+
+        /* have access to all user profiles */
+        priv->profile_store = gcm_profile_store_new ();
+        g_signal_connect (priv->profile_store, "added",
+                          G_CALLBACK (gcm_session_profile_store_added_cb),
+                          manager);
+        g_signal_connect (priv->profile_store, "removed",
+                          G_CALLBACK (gcm_session_profile_store_removed_cb),
+                          manager);
 }
 
 static void
@@ -417,6 +629,7 @@ gsd_color_manager_finalize (GObject *object)
 
         g_object_unref (manager->priv->settings);
         g_object_unref (manager->priv->client);
+        g_object_unref (manager->priv->profile_store);
 
         G_OBJECT_CLASS (gsd_color_manager_parent_class)->finalize (object);
 }



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