/* * 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 #include "katachi-image-cache.h" typedef struct { GCancellable *cancellable; GtkWidget *widget; gboolean preview; CachePixbufCallback callback; gpointer user_data; } LoaderData; static LoaderData *current_loader; /* Hashes from GFile to GdkPixbuf. Reference the file and pixbuf on insertion. */ static GHashTable *preview_hash; static GHashTable *full_hash; static void loader_size_prepared_cb (GdkPixbufLoader *loader, int width, int height, gpointer user_data) { LoaderData *data = user_data; int dest_w, dest_h; double scale; g_assert (data); /* If not in preview mode, we don't scale */ if (!data->preview) return; /* Load the preview based on the size of the screen so that fullscreen view is fast and accurate */ dest_w = gdk_screen_get_width (gtk_widget_get_screen (data->widget)); dest_h = gdk_screen_get_height (gtk_widget_get_screen (data->widget)); /* Don't scale if original size is smaller than screen size */ if (width < dest_w || height < dest_h) return; scale = (double)width/height; if (width > height) { gdk_pixbuf_loader_set_size (loader, dest_w, dest_w / scale); } else { gdk_pixbuf_loader_set_size (loader, dest_h * scale, dest_h); } } static void load_contents_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GFile *file = G_FILE (source); LoaderData *data = user_data; gchar *contents = NULL; gsize length; GError *error = NULL; GdkPixbufLoader *loader = NULL; GdkPixbuf *pixbuf = NULL; if (!g_file_load_contents_finish (file, res, &contents, &length, NULL, &error)) { /* Don't display a warning and clear the preview if the operation was cancelled */ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) { goto cleanup; } else { g_warning ("Cannot open file: %s", error->message); data->callback (file, NULL, data->user_data); goto done; } } /* If this is a stale loader that didn't get cancelled in time, don't do anything */ if (data != current_loader) { goto cleanup; } loader = gdk_pixbuf_loader_new (); g_signal_connect (loader, "size-prepared", G_CALLBACK (loader_size_prepared_cb), data); if (!gdk_pixbuf_loader_write (loader, (guchar*)contents, length, &error)) { g_warning ("Cannot write to pixbuf loader: %s", error->message); goto done; } if (!gdk_pixbuf_loader_close (loader, &error)) { g_warning ("Cannot close pixbuf loader: %s", error->message); goto done; } pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); /* TODO: if the cache if over a certain size, remove old items */ /* Stash it in the cache */ g_hash_table_insert (data->preview ? preview_hash : full_hash, g_object_ref (file), g_object_ref (pixbuf)); /* Need to ref it because the loader owns the reference */ data->callback (file, g_object_ref (pixbuf), data->user_data); done: current_loader = NULL; cleanup: g_slice_free (LoaderData, data); if (error) g_error_free (error); if (loader) g_object_unref (loader); if (contents) g_free (contents); } void katachi_image_cache_init (void) { preview_hash = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, g_object_unref); full_hash = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, g_object_unref); } void katachi_image_cache_get_pixbuf (GtkWidget *widget, GFile *file, gboolean preview, CachePixbufCallback callback, gpointer user_data) { GdkPixbuf *pixbuf; g_return_if_fail (G_IS_FILE (file)); g_return_if_fail (callback != NULL); g_return_if_fail (preview && GTK_IS_WIDGET (widget)); if (current_loader) { g_cancellable_cancel (current_loader->cancellable); } /* Lookup this file in the cache first */ pixbuf = g_hash_table_lookup (preview ? preview_hash : full_hash, file); if (pixbuf) { callback (file, g_object_ref (pixbuf), user_data); return; } current_loader = g_slice_new0 (LoaderData); current_loader->cancellable = g_cancellable_new (); current_loader->widget = widget; current_loader->preview = preview; current_loader->callback = callback; current_loader->user_data = user_data; g_file_load_contents_async (file, current_loader->cancellable, load_contents_cb, current_loader); } /** * katachi_image_cache_evict_file: * * Forcibly remove any cached data for the file @file. Typically this is used * when files are deleted. */ void katachi_image_cache_evict_file (GFile *file) { g_return_if_fail (G_IS_FILE (file)); g_hash_table_remove (preview_hash, file); g_hash_table_remove (full_hash, file); }