nautilus/libnautilus-private/nautilus-thumbnails.c
Pavel Cisler 647c86239e Fix a crash that was blocking Eli -- a close was being called without
2001-01-18  Pavel Cisler  <pavel@eazel.com>

	* libnautilus-extensions/nautilus-thumbnails.c:
	(check_for_thumbnails):
	Fix a crash that was blocking Eli -- a close was being called
	without checking the result of a previous create.
	This will need more work, just wanted to do a quick fix to
	unblock Eli.
2001-01-18 22:30:30 +00:00

651 lines
21 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-thumbnails.h: Thumbnail code for icon factory.
Copyright (C) 2000 Eazel, Inc.
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.
Author: Andy Hertzfeld <andy@eazel.com>
*/
#include <config.h>
#include "nautilus-thumbnails.h"
#include <libgnomevfs/gnome-vfs-file-info.h>
#include <string.h>
#include "nautilus-file-utilities.h"
#include "nautilus-string.h"
#include <gtk/gtkmain.h>
#include "nautilus-icon-factory-private.h"
#include "nautilus-directory-notify.h"
#include "nautilus-theme.h"
#include <stdio.h>
#include "nautilus-gdk-pixbuf-extensions.h"
#include <unistd.h>
#include <sys/wait.h>
#include <librsvg/rsvg.h>
#include "nautilus-graphic-effects.h"
/* permissions for thumbnail directory */
#define THUMBNAIL_DIR_PERMISSIONS (GNOME_VFS_PERM_USER_ALL \
| GNOME_VFS_PERM_GROUP_ALL \
| GNOME_VFS_PERM_OTHER_ALL)
#define THUMBNAIL_PLACEHOLDER_PERMISSIONS (GNOME_VFS_PERM_USER_READ | \
GNOME_VFS_PERM_USER_WRITE | \
GNOME_VFS_PERM_GROUP_READ | \
GNOME_VFS_PERM_GROUP_WRITE | \
GNOME_VFS_PERM_OTHER_READ)
/* thumbnail task state */
static GList *thumbnails;
static char *new_thumbnail_path;
static gboolean thumbnail_in_progress;
/* id of timeout task for making thumbnails */
static int thumbnail_timeout_id;
static int make_thumbnails (gpointer data);
/* utility to test whether a file exists using vfs */
static gboolean
vfs_file_exists (const char *file_uri)
{
gboolean result;
GnomeVFSURI *uri;
uri = gnome_vfs_uri_new (file_uri);
if (uri == NULL) {
return FALSE;
}
/* FIXME bugzilla.eazel.com 3137: the synchronous I/O here means this call is
unsuitable for use on anything that might be remote. */
result = gnome_vfs_uri_exists (uri);
gnome_vfs_uri_unref (uri);
return result;
}
/* utility routine that, given the uri of an image, constructs the uri to the corresponding thumbnail */
static char *
make_thumbnail_path (const char *image_uri, gboolean directory_only, gboolean use_local_directory,
gboolean anti_aliased, gboolean create_parents_if_needed)
{
GnomeVFSURI *vfs_uri;
char *thumbnail_uri, *thumbnail_path;
char *directory_name = g_strdup (image_uri);
char *last_slash = strrchr (directory_name, '/');
char *dot_pos;
gboolean is_local;
*last_slash = '\0';
vfs_uri = gnome_vfs_uri_new (image_uri);
is_local = gnome_vfs_uri_is_local (vfs_uri);
gnome_vfs_uri_unref (vfs_uri);
/* either use the local directory or one in the user's home directory, as selected by the passed in flag */
if (use_local_directory && is_local) {
thumbnail_uri = g_strdup_printf ("%s/.thumbnails", directory_name);
} else {
GnomeVFSResult result;
GnomeVFSURI *thumbnail_directory_uri;
char *escaped_uri = gnome_vfs_escape_slashes (directory_name);
thumbnail_path = g_strdup_printf ("%s/.nautilus/thumbnails/%s", g_get_home_dir(), escaped_uri);
thumbnail_uri = gnome_vfs_get_uri_from_local_path (thumbnail_path);
g_free (thumbnail_path);
g_free(escaped_uri);
/* we must create the directory if it doesn't exist */
thumbnail_directory_uri = gnome_vfs_uri_new (thumbnail_uri);
if (!create_parents_if_needed) {
if (!gnome_vfs_uri_exists (thumbnail_directory_uri)) {
gnome_vfs_uri_unref (thumbnail_directory_uri);
return NULL;
}
}
/* FIXME bugzilla.eazel.com 3137: synchronous I/O - it
looks like the URI will be local-only, but best to
make sure. */
result = nautilus_make_directory_and_parents (thumbnail_directory_uri, THUMBNAIL_DIR_PERMISSIONS);
gnome_vfs_uri_unref (thumbnail_directory_uri);
}
/* append the file name if necessary */
if (!directory_only) {
char* old_uri = thumbnail_uri;
thumbnail_uri = g_strdup_printf ("%s/%s", thumbnail_uri, last_slash + 1);
g_free(old_uri);
/* append the anti-aliased suffix if necessary */
if (anti_aliased) {
char *old_uri = thumbnail_uri;
dot_pos = strrchr (thumbnail_uri, '.');
if (dot_pos) {
*dot_pos = '\0';
thumbnail_uri = g_strdup_printf ("%s.aa.%s", old_uri, dot_pos + 1);
} else {
thumbnail_uri = g_strconcat (old_uri, ".aa", NULL);
}
g_free (old_uri);
}
/* append an image suffix if the correct one isn't already present */
if (!nautilus_istr_has_suffix (image_uri, ".png")) {
char *old_uri = thumbnail_uri;
thumbnail_uri = g_strdup_printf ("%s.png", thumbnail_uri);
g_free(old_uri);
}
}
g_free (directory_name);
return thumbnail_uri;
}
/* utility routine that takes two uris and returns true if the first file has been modified later than the second */
/* FIXME bugzilla.eazel.com 2565: it makes synchronous file info calls, so for now, it returns FALSE if either of the uri's are non-local */
static gboolean
first_file_more_recent(const char* file_uri, const char* other_file_uri)
{
GnomeVFSURI *vfs_uri, *other_vfs_uri;
gboolean more_recent, is_local;
GnomeVFSFileInfo file_info, other_file_info;
/* if either file is remote, return FALSE. Eventually we'll make this async to fix this */
vfs_uri = gnome_vfs_uri_new(file_uri);
other_vfs_uri = gnome_vfs_uri_new(other_file_uri);
is_local = gnome_vfs_uri_is_local (vfs_uri) && gnome_vfs_uri_is_local (other_vfs_uri);
gnome_vfs_uri_unref(vfs_uri);
gnome_vfs_uri_unref(other_vfs_uri);
if (!is_local) {
return FALSE;
}
/* gather the info and then compare modification times */
gnome_vfs_file_info_init (&file_info);
gnome_vfs_get_file_info (file_uri, &file_info, GNOME_VFS_FILE_INFO_DEFAULT);
gnome_vfs_file_info_init (&other_file_info);
gnome_vfs_get_file_info (other_file_uri, &other_file_info, GNOME_VFS_FILE_INFO_DEFAULT);
more_recent = file_info.mtime > other_file_info.mtime;
gnome_vfs_file_info_clear (&file_info);
gnome_vfs_file_info_clear (&other_file_info);
return more_recent;
}
/* structure used for making thumbnails, associating a uri with where the thumbnail is to be stored */
typedef struct {
char *thumbnail_uri;
gboolean is_local;
gboolean anti_aliased;
pid_t thumbnail_task;
} NautilusThumbnailInfo;
/* GCompareFunc-style function for comparing NautilusThumbnailInfos.
* Returns 0 if they refer to the same uri.
*/
static int
compare_thumbnail_info (gconstpointer a, gconstpointer b)
{
NautilusThumbnailInfo *info_a;
NautilusThumbnailInfo *info_b;
info_a = (NautilusThumbnailInfo *)a;
info_b = (NautilusThumbnailInfo *)b;
return strcmp (info_a->thumbnail_uri, info_b->thumbnail_uri) != 0;
}
/* utility to create a placeholder thumbnail uri (which indicates that a
* previous thumbnailing attempt has failed)
*/
static char *
make_invalid_thumbnail_uri (const char *thumbnail_uri)
{
return g_strconcat (thumbnail_uri, ".x", NULL);
}
/* return true if there's a placeholder thumbnail present for the passed in
* file, which indicates that a previous thumbnailing attempt failed and
* we should use the mime-type icon instead
*/
gboolean nautilus_thumbnail_has_invalid_thumbnail (NautilusFile *file,
gboolean anti_aliased)
{
char *file_uri, *thumbnail_uri, *invalid_thumbnail_uri;
GnomeVFSURI *temp_uri;
gboolean uri_is_local, is_invalid;
file_uri = nautilus_file_get_uri (file);
/* compose the uri for the thumbnail locally */
temp_uri = gnome_vfs_uri_new (file_uri);
uri_is_local = gnome_vfs_uri_is_local (temp_uri);
gnome_vfs_uri_unref (temp_uri);
thumbnail_uri = make_thumbnail_path (file_uri, FALSE, uri_is_local, anti_aliased, TRUE);
invalid_thumbnail_uri = make_invalid_thumbnail_uri (thumbnail_uri);
is_invalid = vfs_file_exists (invalid_thumbnail_uri);
g_free (file_uri);
g_free (thumbnail_uri);
g_free (invalid_thumbnail_uri);
return is_invalid;
}
/* routine that takes a uri of a large image file and returns the uri of its corresponding thumbnail.
If no thumbnail is available, put the image on the thumbnail queue so one is eventually made. */
/* FIXME bugzilla.eazel.com 642:
* Most of this thumbnail machinery belongs in NautilusFile, not here.
*/
char *
nautilus_get_thumbnail_uri (NautilusFile *file, gboolean anti_aliased)
{
GnomeVFSResult result;
GnomeVFSURI *temp_uri;
char *thumbnail_uri;
char *file_uri;
gboolean can_write, uri_is_local;
NautilusFile *destination_file;
gboolean local_flag = TRUE;
gboolean remake_thumbnail = FALSE;
file_uri = nautilus_file_get_uri (file);
/* compose the uri for the thumbnail locally */
temp_uri = gnome_vfs_uri_new (file_uri);
uri_is_local = gnome_vfs_uri_is_local (temp_uri);
gnome_vfs_uri_unref (temp_uri);
thumbnail_uri = make_thumbnail_path (file_uri, FALSE, uri_is_local, anti_aliased, TRUE);
/* if the thumbnail file already exists locally, simply return the uri */
/* note: thumbnail_uri is always local here, so it's not a disaster that we make a synchronous call below.
Eventually, we'll want to do everything asynchronously, when we have time to restructure the thumbnail engine */
if (vfs_file_exists (thumbnail_uri)) {
/* see if the file changed since it was thumbnailed by comparing the modification time */
remake_thumbnail = first_file_more_recent (file_uri, thumbnail_uri);
/* if the file hasn't changed, return the thumbnail uri */
if (!remake_thumbnail) {
g_free (file_uri);
return thumbnail_uri;
} else {
nautilus_icon_factory_remove_by_uri (thumbnail_uri);
/* the thumbnail uri is always local, so we can do synchronous I/O to delete it */
gnome_vfs_unlink (thumbnail_uri);
}
}
/* now try it globally */
if (!remake_thumbnail) {
g_free (thumbnail_uri);
thumbnail_uri = make_thumbnail_path (file_uri, FALSE, FALSE, anti_aliased, TRUE);
/* if the thumbnail file already exists in the common area, return that uri, */
/* the uri is guaranteed to be local */
if (vfs_file_exists (thumbnail_uri)) {
/* see if the file changed since it was thumbnailed by comparing the modification time */
remake_thumbnail = first_file_more_recent(file_uri, thumbnail_uri);
/* if the file hasn't changed, return the thumbnail uri */
if (!remake_thumbnail) {
g_free (file_uri);
return thumbnail_uri;
} else {
nautilus_icon_factory_remove_by_uri (thumbnail_uri);
/* the uri is guaranteed to be local */
gnome_vfs_unlink (thumbnail_uri);
}
}
}
/* make the thumbnail directory if necessary, at first try it locally */
g_free (thumbnail_uri);
local_flag = TRUE;
thumbnail_uri = make_thumbnail_path (file_uri, TRUE, local_flag, anti_aliased, TRUE);
/* FIXME bugzilla.eazel.com 3137: more potentially losing
synch I/O - this could be remote */
result = gnome_vfs_make_directory (thumbnail_uri, THUMBNAIL_DIR_PERMISSIONS);
/* the directory could already exist, but we better make sure we can write to it */
destination_file = nautilus_file_get (thumbnail_uri);
can_write = FALSE;
if (destination_file != NULL) {
can_write = nautilus_file_can_write (destination_file);
nautilus_file_unref (destination_file);
}
/* if we can't make if locally, try it in the global place */
if (!can_write || (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_FILE_EXISTS)) {
g_free (thumbnail_uri);
local_flag = FALSE;
thumbnail_uri = make_thumbnail_path (file_uri, TRUE, local_flag, anti_aliased, TRUE);
/* this is guaranteed to be local, so synch I/O can be tolerated here */
result = gnome_vfs_make_directory (thumbnail_uri, THUMBNAIL_DIR_PERMISSIONS);
}
/* the thumbnail needs to be created (or recreated), so add an entry to the thumbnail list */
if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_FILE_EXISTS) {
g_warning ("error when making thumbnail directory %d, for %s", result, thumbnail_uri);
} else {
NautilusThumbnailInfo *info = g_new0 (NautilusThumbnailInfo, 1);
info->thumbnail_uri = file_uri;
info->is_local = local_flag;
info->anti_aliased = anti_aliased;
if (thumbnails != NULL) {
if (g_list_find_custom (thumbnails, info, compare_thumbnail_info) == NULL) {
thumbnails = g_list_append (thumbnails, info);
}
} else {
thumbnails = g_list_alloc ();
thumbnails->data = info;
}
if (thumbnail_timeout_id == 0) {
thumbnail_timeout_id = gtk_timeout_add
(400, make_thumbnails, NULL);
}
}
g_free (thumbnail_uri);
/* Return NULL to indicate the thumbnail is loading. */
return NULL;
}
static void
nautilus_update_thumbnail_file_renamed_one (const char *old_file_name, const char *new_file_name,
gboolean anti_aliased)
{
GnomeVFSURI *uri;
gboolean is_local;
char *old_thumbnail_uri, *new_thumbnail_uri;
uri = gnome_vfs_uri_new (old_file_name);
is_local = gnome_vfs_uri_is_local (uri);
gnome_vfs_uri_unref (uri);
old_thumbnail_uri = make_thumbnail_path (old_file_name, FALSE, is_local, anti_aliased, FALSE);
if (old_thumbnail_uri != NULL && vfs_file_exists (old_thumbnail_uri)) {
new_thumbnail_uri = make_thumbnail_path (new_file_name, FALSE, is_local, anti_aliased, FALSE);
g_assert (new_thumbnail_uri != NULL);
gnome_vfs_move (old_thumbnail_uri, new_thumbnail_uri, FALSE);
g_free (new_thumbnail_uri);
}
g_free (old_thumbnail_uri);
}
/* update the thumbnail after the thumbnailed file got renamed */
void
nautilus_update_thumbnail_file_renamed (const char *old_file_name, const char *new_file_name)
{
/* rename both the AA and non-AA thumbnails, if they exist */
nautilus_update_thumbnail_file_renamed_one (old_file_name, new_file_name, FALSE);
nautilus_update_thumbnail_file_renamed_one (old_file_name, new_file_name, TRUE);
}
static void
nautilus_remove_thumbnail_for_file_one (const char *old_file_name, gboolean anti_aliased)
{
GnomeVFSURI *uri;
gboolean is_local;
char *thumbnail_uri;
uri = gnome_vfs_uri_new (old_file_name);
is_local = gnome_vfs_uri_is_local (uri);
gnome_vfs_uri_unref (uri);
thumbnail_uri = make_thumbnail_path (old_file_name, FALSE, is_local, anti_aliased, FALSE);
if (thumbnail_uri != NULL && vfs_file_exists (thumbnail_uri)) {
gnome_vfs_unlink (thumbnail_uri);
}
g_free (thumbnail_uri);
}
/* remove the thumbnail after the thumbnailed file got deleted */
void
nautilus_remove_thumbnail_for_file (const char *old_file_name)
{
/* remove both the AA and non-AA thumbnails, if they exist */
nautilus_remove_thumbnail_for_file_one (old_file_name, FALSE);
nautilus_remove_thumbnail_for_file_one (old_file_name, TRUE);
}
/* check_for_thumbnails is a utility that checks to see if the current thumbnail task has terminated.
If it has, remove the thumbnail info from the queue and return TRUE; if it's still in progress, return FALSE.
*/
static gboolean
check_for_thumbnails (void)
{
NautilusThumbnailInfo *info;
GList *head;
NautilusFile *file;
int status;
char *current_thumbnail, *invalid_uri;
gboolean task_terminated;
gboolean need_update;
GnomeVFSResult result;
GnomeVFSHandle *handle;
info = (NautilusThumbnailInfo*) thumbnails->data;
task_terminated = waitpid (info->thumbnail_task, &status, WNOHANG) != 0;
if (task_terminated) {
/* the thumbnail task has completed, so update the current entry from the list */
file = nautilus_file_get (info->thumbnail_uri);
current_thumbnail = make_thumbnail_path (info->thumbnail_uri, FALSE, info->is_local,
info->anti_aliased, TRUE);
/* if a thumbnail wasn't successfully made, create a placeholder to flag that we tried */
need_update = TRUE;
if (!vfs_file_exists (current_thumbnail)) {
invalid_uri = make_invalid_thumbnail_uri (current_thumbnail);
result = gnome_vfs_create (&handle, invalid_uri, GNOME_VFS_OPEN_WRITE,
FALSE, THUMBNAIL_PLACEHOLDER_PERMISSIONS);
if (result == GNOME_VFS_OK) {
gnome_vfs_close (handle);
}
g_free (invalid_uri);
}
/* update the file's icon */
if (file != NULL && need_update) {
nautilus_file_changed (file);
}
if (file != NULL) {
nautilus_file_unref (file);
}
g_free (current_thumbnail);
g_free (info->thumbnail_uri);
g_free (info);
/* remove it from the queue */
head = thumbnails;
thumbnails = g_list_remove_link (thumbnails, head);
g_list_free_1 (head);
return TRUE;
}
return FALSE;
}
/* make_thumbnails is invoked periodically as a timer task to launch a task to make thumbnails */
static GdkPixbuf*
load_thumbnail_frame (gboolean anti_aliased)
{
char *image_path;
GdkPixbuf *frame_image;
/* load the thumbnail frame */
image_path = nautilus_theme_get_image_path (anti_aliased ? "thumbnail_frame.aa.png" : "thumbnail_frame.png");
frame_image = gdk_pixbuf_new_from_file (image_path);
g_free (image_path);
return frame_image;
}
static int
make_thumbnails (gpointer data)
{
NautilusThumbnailInfo *info;
GList *next_thumbnail = thumbnails;
GdkPixbuf *scaled_image, *framed_image, *thumbnail_image_frame;
char *frame_offset_str;
int left_offset, top_offset, right_offset, bottom_offset;
/* if the queue is empty, there's nothing more to do */
if (next_thumbnail == NULL) {
gtk_timeout_remove (thumbnail_timeout_id);
thumbnail_timeout_id = 0;
return FALSE;
}
info = (NautilusThumbnailInfo *) next_thumbnail->data;
/* see which state we're in. If a thumbnail isn't in progress, start one up. Otherwise,
check if the pending one is completed. */
if (thumbnail_in_progress) {
if (check_for_thumbnails ()) {
thumbnail_in_progress = FALSE;
}
} else {
/* start up a task to make the thumbnail corresponding to the queue element. */
/* First, compute the path name of the target thumbnail */
g_free (new_thumbnail_path);
new_thumbnail_path = make_thumbnail_path (info->thumbnail_uri, FALSE, info->is_local,
info->anti_aliased, TRUE);
/* fork a task to make the thumbnail, using gdk-pixbuf to do the scaling */
if (!(info->thumbnail_task = fork())) {
GdkPixbuf* full_size_image;
NautilusFile *file;
GnomeVFSFileSize file_size;
char *thumbnail_path;
file = nautilus_file_get (info->thumbnail_uri);
file_size = nautilus_file_get_size (file);
full_size_image = NULL;
if (nautilus_file_is_mime_type (file, "image/svg")) {
thumbnail_path = gnome_vfs_get_local_path_from_uri (info->thumbnail_uri);
if (thumbnail_path != NULL) {
FILE *f = fopen (thumbnail_path, "rb");
if (f != NULL) {
full_size_image = rsvg_render_file (f, 1.0);
fclose (f);
}
}
} else {
if (info->thumbnail_uri != NULL)
full_size_image = nautilus_gdk_pixbuf_load (info->thumbnail_uri);
}
nautilus_file_unref (file);
if (full_size_image != NULL) {
thumbnail_image_frame = load_thumbnail_frame (info->anti_aliased);
/* scale the content image as necessary */
scaled_image = nautilus_gdk_pixbuf_scale_down_to_fit(full_size_image, 96, 96);
gdk_pixbuf_unref (full_size_image);
/* embed the content image in the frame, if necessary */
if (file_size > SELF_THUMBNAIL_SIZE_THRESHOLD) {
frame_offset_str = nautilus_theme_get_theme_data ("thumbnails", "FRAME_OFFSETS");
if (frame_offset_str != NULL) {
sscanf (frame_offset_str," %d , %d , %d , %d %*s", &left_offset, &top_offset, &right_offset, &bottom_offset);
} else {
/* use nominal values since the info in the theme couldn't be found */
left_offset = 3; top_offset = 3;
right_offset = 6; bottom_offset = 6;
}
framed_image = nautilus_embed_image_in_frame (scaled_image, thumbnail_image_frame,
left_offset, top_offset, right_offset, bottom_offset);
g_free (frame_offset_str);
gdk_pixbuf_unref (scaled_image);
gdk_pixbuf_unref (thumbnail_image_frame);
} else {
framed_image = scaled_image;
}
thumbnail_path = gnome_vfs_get_local_path_from_uri (new_thumbnail_path);
if (!nautilus_gdk_pixbuf_save_to_file (framed_image, thumbnail_path)) {
g_warning ("error saving thumbnail %s", thumbnail_path);
}
g_free (thumbnail_path);
gdk_pixbuf_unref (framed_image);
} else {
/* gdk-pixbuf couldn't load the image, so trying using ImageMagick */
char *temp_str;
thumbnail_path = gnome_vfs_get_local_path_from_uri (new_thumbnail_path);
temp_str = g_strdup_printf ("png:%s", thumbnail_path);
g_free (thumbnail_path);
thumbnail_path = gnome_vfs_get_local_path_from_uri (info->thumbnail_uri);
/* scale the image */
execlp ("convert", "convert", "-geometry", "96x96", thumbnail_path, temp_str, NULL);
/* we don't come back from this call, so no point in freeing anything up */
}
_exit(0);
}
thumbnail_in_progress = TRUE;
}
return TRUE; /* we're not done yet */
}