/* * Copyright (C) 2007 Ross Burton * * Author: Ross Burton * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ #include #include #include "katachi-image-store.h" G_DEFINE_TYPE (KatachiImageStore, katachi_image_store, GTK_TYPE_LIST_STORE); #define GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), KATACHI_TYPE_IMAGE_STORE, KatachiImageStorePrivate)) typedef struct { /* A hash of GFile* to GtkTreeIter*. The has takes ownership of a reference on insertion, and the GFile will be unreffed on removal. */ GHashTable *file_hash; GdkPixbuf *generic_thumb; GFileMonitor *monitor; } KatachiImageStorePrivate; /* * Private methods */ static void populate_store (KatachiImageStore *store, GFile *path); static void handle_file (KatachiImageStore *store, GFile *file, GFileInfo *info) { KatachiImageStorePrivate *priv; GtkTreeIter iter; char *path, *key; g_return_if_fail (KATACHI_IS_IMAGE_STORE (store)); g_return_if_fail (G_IS_FILE (file)); g_return_if_fail (G_IS_FILE_INFO (info)); priv = GET_PRIVATE (store); if (g_content_type_is_a (g_file_info_get_content_type (info), "image/*") && !g_content_type_is_a (g_file_info_get_content_type (info), "image/x-dcraw")) { /* Generate the collation key */ path = g_file_get_uri (file); key = g_utf8_collate_key_for_filename (path, -1); gtk_list_store_insert_with_values (GTK_LIST_STORE (store), &iter, 0, COL_KEY, key, COL_FILE, file, COL_THUMB, priv->generic_thumb, -1); g_hash_table_insert (GET_PRIVATE (store)->file_hash, g_object_ref (file), gtk_tree_iter_copy (&iter)); g_free (key); g_free (path); } g_object_unref (file); } static void add_file (KatachiImageStore *store, GFile *file) { GError *error = NULL; GFileInfo *info; info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, NULL, &error); if (!info) { g_warning (G_STRLOC ": %s", error->message); g_error_free (error); return; } if (g_file_info_get_file_type (info) & G_FILE_TYPE_DIRECTORY) { /* populate_store will unref the file object when it has finished with it */ populate_store (store, file); } else { handle_file (store, file, info); } g_object_unref (info); } static void on_monitor_changed (GFileMonitor *monitor, GFile *child, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { KatachiImageStore *store; KatachiImageStorePrivate *priv; GtkTreeIter *iter; store = KATACHI_IMAGE_STORE (user_data); priv = GET_PRIVATE (store); g_assert (priv); switch (event_type) { case G_FILE_MONITOR_EVENT_CREATED: add_file (store, child); break; case G_FILE_MONITOR_EVENT_DELETED: /* Remove from the store */ iter = g_hash_table_lookup (priv->file_hash, child); if (iter) { gtk_list_store_remove (GTK_LIST_STORE (store), iter); g_hash_table_remove (priv->file_hash, child); } /* Remove from the cache */ katachi_image_cache_evict_file (child); break; case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: /* TODO: update store */ case G_FILE_MONITOR_EVENT_UNMOUNTED: /* TODO: not sure what to do here */ break; default: g_warning ("Unhandled event type %d", event_type); break; } } static void files_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source); GError *error = NULL; GList *files, *l; GFile *file; KatachiImageStore *store = user_data; files = g_file_enumerator_next_files_finish (enumerator, res, &error); if (error) { /* Error */ g_printerr (G_STRLOC ": %s", error->message); g_error_free (error); g_object_unref (enumerator); return; } else if (files == NULL) { /* Done */ GFileMonitor* monitor; /* Register the directory monitor */ GET_PRIVATE (store)->monitor = g_file_monitor_directory (g_file_enumerator_get_container (enumerator), G_FILE_MONITOR_NONE, NULL, NULL); g_signal_connect (GET_PRIVATE (store)->monitor, "changed", G_CALLBACK (on_monitor_changed), store); /* Now cleanup */ g_object_unref (enumerator); return; } else { /* Got files */ for (l = files; l ; l = l->next) { GFileInfo *info = l->data; file = g_file_get_child (g_file_enumerator_get_container (enumerator), g_file_info_get_name (info)); if (g_file_info_get_file_type (info) & G_FILE_TYPE_DIRECTORY) { populate_store (store, file); } else { handle_file (store, file, info); } /* file is unreffed for us */ g_object_unref (info); } g_file_enumerator_next_files_async (enumerator, 16, G_PRIORITY_LOW, NULL, files_cb, store); } g_list_free (files); } static void populate_store (KatachiImageStore *store, GFile *path) { GFileEnumerator *enumerator; GError *error = NULL; enumerator = g_file_enumerate_children (path, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, NULL, &error); if (!enumerator) { g_warning (G_STRLOC ": %s", error->message); g_error_free (error); } else { g_file_enumerator_next_files_async (enumerator, 16, G_PRIORITY_LOW, NULL, files_cb, store); } } static void katachi_image_store_dispose (GObject *object) { KatachiImageStorePrivate *priv = GET_PRIVATE (object); if (priv->generic_thumb) { g_object_unref (priv->generic_thumb); priv->generic_thumb = NULL; } if (G_OBJECT_CLASS (katachi_image_store_parent_class)->dispose) G_OBJECT_CLASS (katachi_image_store_parent_class)->dispose (object); } static void katachi_image_store_finalize (GObject *object) { KatachiImageStorePrivate *priv = GET_PRIVATE (object); g_hash_table_destroy (priv->file_hash); G_OBJECT_CLASS (katachi_image_store_parent_class)->finalize (object); } static void katachi_image_store_class_init (KatachiImageStoreClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (KatachiImageStorePrivate)); object_class->dispose = katachi_image_store_dispose; object_class->finalize = katachi_image_store_finalize; } static void katachi_image_store_init (KatachiImageStore *self) { KatachiImageStorePrivate *priv = GET_PRIVATE (self); const GType column_types[] = { G_TYPE_STRING, /* Sort key */ G_TYPE_FILE, /* Filename */ GDK_TYPE_PIXBUF, /* Thumbnail */ }; gtk_list_store_set_column_types (GTK_LIST_STORE (self), G_N_ELEMENTS (column_types), (GType *) column_types); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self), COL_KEY, GTK_SORT_ASCENDING); priv->file_hash = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, (GDestroyNotify)gtk_tree_iter_free); /* TODO: move to style-set */ priv->generic_thumb = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), "image-x-generic", 64, 0, NULL); } /* * Public methods */ GtkTreeModel* katachi_image_store_new (void) { return g_object_new (KATACHI_TYPE_IMAGE_STORE, NULL); } /* When the search is complete, path will be unreffed for you */ void katachi_image_store_populate (KatachiImageStore *store, GFile *parent) { KatachiImageStorePrivate *priv; g_return_if_fail (KATACHI_IS_IMAGE_STORE (store)); g_return_if_fail (G_IS_FILE (parent)); /* TODO: sanity check that parent is a directory */ priv = GET_PRIVATE (store); g_hash_table_remove_all (priv->file_hash); gtk_list_store_clear (GTK_LIST_STORE (store)); if (priv->monitor) { g_file_monitor_cancel (priv->monitor); priv->monitor = NULL; } populate_store (store, parent); }