It's 3.30.2 release day, upload a tarball and fix that hidden pesky bug that you missed in .1 🦛

gnome-desktop-thumbnail.c 38.6 KB
Newer Older
1 2 3 4
/*
 * gnome-thumbnail.c: Utilities for handling thumbnails
 *
 * Copyright (C) 2002 Red Hat, Inc.
5
 * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This file is part of the Gnome Library.
 *
 * The Gnome Library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * The Gnome 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
21 22
 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
23 24 25 26
 *
 * Author: Alexander Larsson <alexl@redhat.com>
 */

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
/**
 * SECTION:gnome-desktop-thumbnail
 * @short_description: Generates and looks up thumbnails of files and
 * directories
 * @stability: Unstable
 * @include: libgnome-desktop/gnome-desktop-thumbnail.h
 *
 * #GnomeDesktopThumbnailFactory allows generation and loading of thumbnails for
 * local and remote files and directories. It uses a collection of programs
 * called <firstterm>thumbnailers</firstterm>, each one generating thumbnails
 * for a specific set of content-types of files. For example,
 * <application>totem-video-thumbnailer</application> generates thumbnails for
 * video files using GStreamer; <application>evince-thumbnailer</application>
 * generates thumbnails for PDFs and other document files. If no specific
 * thumbnailer exists for a file, or if the thumbnailer fails, gdk-pixbuf is
 * used as a fallback.
 *
 * To generate a thumbnail, an appropriate thumbnailer program is selected then
 * executed, passing it the URI of the file to thumbnail, plus a path to write
 * the thumbnail image to. If thumbnailing succeeds, the thumbnailer should have
 * written the image to disk before terminating; but if thumbnailing fails, no
 * image should be written, and the thumbnailer should return a non-zero exit
 * status. #GnomeDesktopThumbnailFactory will then fall back to using gdk-pixbuf
 * to generate a thumbnail, if possible.
 *
 * Thumbnailers are chosen by examining a series of
 * <filename>.thumbnailer</filename> files in
 * <filename><replaceable>$PREFIX</replaceable>/share/thumbnailers</filename>.
 * Each is in a simple key-file format:
 * <informalexample><programlisting>
 * [Thumbnailer Entry]
 * TryExec=evince-thumbnailer
 * Exec=evince-thumbnailer -s %s %u %o
 * MimeType=application/pdf;application/x-bzpdf;application/x-gzpdf;
 * </programlisting></informalexample>
 *
 * The <filename>.thumbnailer</filename> format supports three keys:
 * <variablelist>
 * <varlistentry><term><code>TryExec</code></term><listitem><para>
 * Optional. The name of the the thumbnailer program in the current
 * <envar>$PATH</envar>. This allows the calling code to quickly check whether
 * the thumbnailer exists before trying to execute it.
 * </para></listitem></varlistentry>
 * <varlistentry><term><code>Exec</code></term><listitem><para>
 * Required. The command to execute the thumbnailer. It supports a few different
 * parameters which are replaced before calling the thumbnailer:
 * <replaceable>%u</replaceable> is the URI of the file being thumbnailed;
 * <replaceable>%i</replaceable> is its path; <replaceable>%o</replaceable>
 * is the path of the image file to be written to;
 * <replaceable>%s</replaceable> is the maximum desired size of the thumbnail
 * image (the maximum width or height, in pixels); and
 * <replaceable>%%</replaceable> is a literal percent character.
 * </para></listitem></varlistentry>
 * <varlistentry><term><code>MimeType</code></term><listitem><para>
 * Required. A semicolon-separated list of MIME types which the thumbnailer
 * supports generating thumbnails for.
 * </para></listitem></varlistentry>
 * </variablelist>
 *
 * So in the example <filename>.thumbnailer</filename> file above, the command
 * passes the requested thumbnail size, then the input file’s URI, then the
 * path for the output image file to
 * <application>evince-thumbnailer</application>.
 *
 * The code to examine and call a thumbnailer is contained in
 * #GnomeDesktopThumbnailFactory, which handles looking up the right thumbnailer
 * script, building and executing the command for it, and loading the resulting
 * thumbnail image into a #GdkPixbuf.
 *
 * Thumbnail caching is also supported by #GnomeDesktopThumbnailFactory. When
 * calling a thumbnailer, the path passed for the output image file is in
 * <filename><envar>$XDG_CACHE_HOME</envar>/thumbnails/
 * <replaceable>$SIZE</replaceable>/</filename>. The cached image file is given
 * a (probably) unique filename, generated by hashing the original file’s URI,
 * so the thumbnail can be looked up in future. #GnomeDesktopThumbnailFactory
 * supports two sizes of thumbnails: %GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL and
 * %GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE. Normal thumbnails are up to 128×128
 * pixels, whereas large thumbnails are up to 256×256 pixels. Thumbnails which
 * are larger than this are scaled down before being cached, and non-square
 * thumbnails are scaled so their largest dimension is at most 128 or 256
 * pixels.
 *
 * #GnomeDesktopThumbnailFactory also handles failed thumbnails. If a
 * thumbnailer can’t generate a thumbnail for a file (e.g. because the file is
 * corrupt or because the right video codecs aren’t available), it returns a
 * non-zero exit status. The thumbnail factory then writes an entry to
 * <filename><envar>$XDG_CACHE_HOME</envar>/thumbnails/fail/
 * gnome-thumbnail-factory/</filename> which is named after the hash of the
 * input file URI (just like a successful cached thumbnail). For future queries
 * for thumbnails for that file, #GnomeDesktopThumbnailFactory can immediately
 * return an error after looking up the fail entry.
 *
 * If a file changes content, #GnomeDesktopThumbnailFactory will generate a new
 * thumbnail because each cached image has associated metadata (stored as PNG
 * tEXt keys) storing the full URI of the thumbnailed file (to check for hash
 * collisions) and its last modification time at the point of thumbnailing. If
 * the stored modification time doesn’t match the file’s current one, a new
 * thumbnail is generated.
 *
 * Since: 2.2
 */

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <math.h>
#include <string.h>
#include <glib.h>
#include <stdio.h>

#define GDK_PIXBUF_ENABLE_BACKEND
#include <gdk-pixbuf/gdk-pixbuf.h>

#define GNOME_DESKTOP_USE_UNSTABLE_API
146
#include "gnome-desktop-thumbnail.h"
147 148 149 150
#include <glib/gstdio.h>

#define SECONDS_BETWEEN_STATS 10

151 152 153 154 155 156 157
static void
thumbnailers_directory_changed (GFileMonitor                 *monitor,
                                GFile                        *file,
                                GFile                        *other_file,
                                GFileMonitorEvent             event_type,
                                GnomeDesktopThumbnailFactory *factory);

158 159 160
struct _GnomeDesktopThumbnailFactoryPrivate {
  GnomeDesktopThumbnailSize size;

Colin Walters's avatar
Colin Walters committed
161
  GMutex lock;
162

163 164 165 166 167 168 169 170
  GList *thumbnailers;
  GHashTable *mime_types_map;
  GList *monitors;

  GSettings *settings;
  gboolean loaded : 1;
  gboolean disabled : 1;
  gchar **disabled_types;
171 172
};

173 174
static const char *appname = "gnome-thumbnail-factory";

175 176 177 178 179
G_DEFINE_TYPE (GnomeDesktopThumbnailFactory,
	       gnome_desktop_thumbnail_factory,
	       G_TYPE_OBJECT)
#define parent_class gnome_desktop_thumbnail_factory_parent_class

180 181 182
#define GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryPrivate))

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
#define THUMBNAILER_EXTENSION   ".thumbnailer"

typedef struct {
    volatile gint ref_count;

    gchar *path;

    gchar  *try_exec;
    gchar  *command;
    gchar **mime_types;
} Thumbnailer;

static Thumbnailer *
thumbnailer_ref (Thumbnailer *thumb)
{
  g_return_val_if_fail (thumb != NULL, NULL);
  g_return_val_if_fail (thumb->ref_count > 0, NULL);

  g_atomic_int_inc (&thumb->ref_count);
  return thumb;
}

static void
thumbnailer_unref (Thumbnailer *thumb)
{
  g_return_if_fail (thumb != NULL);
  g_return_if_fail (thumb->ref_count > 0);

  if (g_atomic_int_dec_and_test (&thumb->ref_count))
    {
      g_free (thumb->path);
      g_free (thumb->try_exec);
      g_free (thumb->command);
      g_strfreev (thumb->mime_types);

      g_slice_free (Thumbnailer, thumb);
    }
}

static Thumbnailer *
thumbnailer_load (Thumbnailer *thumb)
{
  GKeyFile *key_file;
  GError *error = NULL;

  key_file = g_key_file_new ();
  if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error))
    {
      g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message);
      g_error_free (error);
      thumbnailer_unref (thumb);
      g_key_file_free (key_file);

      return NULL;
    }

  if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP))
    {
      g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP);
      thumbnailer_unref (thumb);
      g_key_file_free (key_file);

      return NULL;
    }

  thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL);
  if (!thumb->command)
    {
      g_warning ("Invalid thumbnailer: missing Exec key\n");
      thumbnailer_unref (thumb);
      g_key_file_free (key_file);

      return NULL;
    }

  thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
  if (!thumb->mime_types)
    {
      g_warning ("Invalid thumbnailer: missing MimeType key\n");
      thumbnailer_unref (thumb);
      g_key_file_free (key_file);

      return NULL;
    }

  thumb->try_exec = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "TryExec", NULL);

  g_key_file_free (key_file);

  return thumb;
}

static Thumbnailer *
thumbnailer_reload (Thumbnailer *thumb)
{
  g_return_val_if_fail (thumb != NULL, NULL);

  g_free (thumb->command);
  thumb->command = NULL;
  g_strfreev (thumb->mime_types);
  thumb->mime_types = NULL;
  g_free (thumb->try_exec);
  thumb->try_exec = NULL;

  return thumbnailer_load (thumb);
}

static Thumbnailer *
thumbnailer_new (const gchar *path)
{
  Thumbnailer *thumb;

  thumb = g_slice_new0 (Thumbnailer);
  thumb->ref_count = 1;
  thumb->path = g_strdup (path);

  return thumbnailer_load (thumb);
}

static gboolean
thumbnailer_try_exec (Thumbnailer *thumb)
{
  gchar *path;
  gboolean retval;

  if (G_UNLIKELY (!thumb))
    return FALSE;

  /* TryExec is optinal, but Exec isn't, so we assume
   * the thumbnailer can be run when TryExec is not present
   */
  if (!thumb->try_exec)
    return TRUE;

  path = g_find_program_in_path (thumb->try_exec);
  retval = path != NULL;
  g_free (path);

  return retval;
}

static gpointer
init_thumbnailers_dirs (gpointer data)
{
  const gchar * const *data_dirs;
  gchar **thumbs_dirs;
  guint i, length;

  data_dirs = g_get_system_data_dirs ();
  length = g_strv_length ((char **) data_dirs);

  thumbs_dirs = g_new (gchar *, length + 2);
  thumbs_dirs[0] = g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL);
337 338 339
  for (i = 0; i < length; i++)
    thumbs_dirs[i + 1] = g_build_filename (data_dirs[i], "thumbnailers", NULL);
  thumbs_dirs[length + 1] = NULL;
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

  return thumbs_dirs;
}

static const gchar * const *
get_thumbnailers_dirs (void)
{
  static GOnce once_init = G_ONCE_INIT;
  return g_once (&once_init, init_thumbnailers_dirs, NULL);
}

/* These should be called with the lock held */
static void
gnome_desktop_thumbnail_factory_register_mime_types (GnomeDesktopThumbnailFactory *factory,
                                                     Thumbnailer                  *thumb)
355
{
356 357
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
  gint i;
358

359
  for (i = 0; thumb->mime_types[i]; i++)
360
    {
361 362 363 364
      if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i]))
        g_hash_table_insert (priv->mime_types_map,
                             g_strdup (thumb->mime_types[i]),
                             thumbnailer_ref (thumb));
365
    }
366
}
367

368 369 370 371 372
static void
gnome_desktop_thumbnail_factory_add_thumbnailer (GnomeDesktopThumbnailFactory *factory,
                                                 Thumbnailer                  *thumb)
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
373

374 375 376
  gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
  priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb);
}
377

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
static gboolean
gnome_desktop_thumbnail_factory_is_disabled (GnomeDesktopThumbnailFactory *factory,
                                             const gchar                  *mime_type)
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
  guint i;

  if (priv->disabled)
    return TRUE;

  if (!priv->disabled_types)
    return FALSE;

  for (i = 0; priv->disabled_types[i]; i++)
    {
      if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0)
        return TRUE;
395 396
    }

397 398 399 400 401 402 403 404 405
  return FALSE;
}

static gboolean
remove_thumbnailer_from_mime_type_map (gchar       *key,
                                       Thumbnailer *value,
                                       gchar       *path)
{
  return (strcmp (value->path, path) == 0);
406 407 408
}

static void
409 410
update_or_create_thumbnailer (GnomeDesktopThumbnailFactory *factory,
                              const gchar                  *path)
411 412
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
413 414 415
  GList *l;
  Thumbnailer *thumb;
  gboolean found = FALSE;
416

Colin Walters's avatar
Colin Walters committed
417
  g_mutex_lock (&priv->lock);
418

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
  for (l = priv->thumbnailers; l && !found; l = g_list_next (l))
    {
      thumb = (Thumbnailer *)l->data;

      if (strcmp (thumb->path, path) == 0)
        {
          found = TRUE;

          /* First remove the mime_types associated to this thumbnailer */
          g_hash_table_foreach_remove (priv->mime_types_map,
                                       (GHRFunc)remove_thumbnailer_from_mime_type_map,
                                       (gpointer)path);
          if (!thumbnailer_reload (thumb))
              priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
          else
              gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
        }
    }

  if (!found)
    {
      thumb = thumbnailer_new (path);
      if (thumb)
        gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
    }

Colin Walters's avatar
Colin Walters committed
445
  g_mutex_unlock (&priv->lock);
446 447
}

448 449 450
static void
remove_thumbnailer (GnomeDesktopThumbnailFactory *factory,
                    const gchar                  *path)
451 452
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
453 454
  GList *l;
  Thumbnailer *thumb;
455

Colin Walters's avatar
Colin Walters committed
456
  g_mutex_lock (&priv->lock);
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473

  for (l = priv->thumbnailers; l; l = g_list_next (l))
    {
      thumb = (Thumbnailer *)l->data;

      if (strcmp (thumb->path, path) == 0)
        {
          priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
          g_hash_table_foreach_remove (priv->mime_types_map,
                                       (GHRFunc)remove_thumbnailer_from_mime_type_map,
                                       (gpointer)path);
          thumbnailer_unref (thumb);

          break;
        }
    }

Colin Walters's avatar
Colin Walters committed
474
  g_mutex_unlock (&priv->lock);
475 476
}

477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
static void
remove_thumbnailers_for_dir (GnomeDesktopThumbnailFactory *factory,
                             const gchar                  *thumbnailer_dir,
                             GFileMonitor                 *monitor)
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
  GList *l;
  Thumbnailer *thumb;

  g_mutex_lock (&priv->lock);

  /* Remove all the thumbnailers inside this @thumbnailer_dir. */
  for (l = priv->thumbnailers; l; l = g_list_next (l))
    {
      thumb = (Thumbnailer *)l->data;

      if (g_str_has_prefix (thumb->path, thumbnailer_dir) == TRUE)
        {
          priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
          g_hash_table_foreach_remove (priv->mime_types_map,
                                       (GHRFunc)remove_thumbnailer_from_mime_type_map,
                                       (gpointer)thumb->path);
          thumbnailer_unref (thumb);

          break;
        }
    }

  /* Remove the monitor for @thumbnailer_dir. */
  priv->monitors = g_list_remove (priv->monitors, monitor);
  g_signal_handlers_disconnect_by_func (monitor, thumbnailers_directory_changed, factory);

  g_mutex_unlock (&priv->lock);
}

static void
gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (GnomeDesktopThumbnailFactory *factory,
                                                           const gchar                  *path)
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
  GDir *dir;
  GFile *dir_file;
  GFileMonitor *monitor;
  const gchar *dirent;

  dir = g_dir_open (path, 0, NULL);
  if (!dir)
      return;

  /* Monitor dir */
  dir_file = g_file_new_for_path (path);
  monitor = g_file_monitor_directory (dir_file,
                                      G_FILE_MONITOR_NONE,
                                      NULL, NULL);
  if (monitor)
    {
      g_signal_connect (monitor, "changed",
                        G_CALLBACK (thumbnailers_directory_changed),
                        factory);
      priv->monitors = g_list_prepend (priv->monitors, monitor);
    }
  g_object_unref (dir_file);

  while ((dirent = g_dir_read_name (dir)))
    {
      Thumbnailer *thumb;
      gchar       *filename;

      if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION))
          continue;

      filename = g_build_filename (path, dirent, NULL);
      thumb = thumbnailer_new (filename);
      g_free (filename);

      if (thumb)
          gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
    }

  g_dir_close (dir);
}

559
static void
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
thumbnailers_directory_changed (GFileMonitor                 *monitor,
                                GFile                        *file,
                                GFile                        *other_file,
                                GFileMonitorEvent             event_type,
                                GnomeDesktopThumbnailFactory *factory)
{
  gchar *path;

  switch (event_type)
    {
    case G_FILE_MONITOR_EVENT_CREATED:
    case G_FILE_MONITOR_EVENT_CHANGED:
    case G_FILE_MONITOR_EVENT_DELETED:
      path = g_file_get_path (file);
      if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION))
        {
          g_free (path);
          return;
        }

      if (event_type == G_FILE_MONITOR_EVENT_DELETED)
        remove_thumbnailer (factory, path);
      else
        update_or_create_thumbnailer (factory, path);

      g_free (path);
      break;
587 588 589 590 591 592 593 594 595 596 597 598 599
    case G_FILE_MONITOR_EVENT_UNMOUNTED:
    case G_FILE_MONITOR_EVENT_MOVED:
      path = g_file_get_path (file);
      remove_thumbnailers_for_dir (factory, path, monitor);

      if (event_type == G_FILE_MONITOR_EVENT_MOVED)
          gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, path);

      g_free (path);
      break;
    case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
    case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
    case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
    default:
      break;
    }
}

static void
gnome_desktop_thumbnail_factory_load_thumbnailers (GnomeDesktopThumbnailFactory *factory)
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
  const gchar * const *dirs;
  guint i;

  if (priv->loaded)
    return;

  dirs = get_thumbnailers_dirs ();
  for (i = 0; dirs[i]; i++)
    {
618
      gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, dirs[i]);
619 620 621 622 623 624 625 626 627
    }

  priv->loaded = TRUE;
}

static void
external_thumbnailers_disabled_all_changed_cb (GSettings                    *settings,
                                               const gchar                  *key,
                                               GnomeDesktopThumbnailFactory *factory)
628 629 630
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;

Colin Walters's avatar
Colin Walters committed
631
  g_mutex_lock (&priv->lock);
632

633 634
  priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
  if (priv->disabled)
635
    {
636 637
      g_strfreev (priv->disabled_types);
      priv->disabled_types = NULL;
638
    }
639 640 641 642 643 644
  else
    {
      priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
      gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
    }

Colin Walters's avatar
Colin Walters committed
645
  g_mutex_unlock (&priv->lock);
646 647
}

648 649 650 651 652 653 654
static void
external_thumbnailers_disabled_changed_cb (GSettings                    *settings,
                                           const gchar                  *key,
                                           GnomeDesktopThumbnailFactory *factory)
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;

Colin Walters's avatar
Colin Walters committed
655
  g_mutex_lock (&priv->lock);
656

657 658 659 660 661
  if (!priv->disabled)
    {
      g_strfreev (priv->disabled_types);
      priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
    }
662

Colin Walters's avatar
Colin Walters committed
663
  g_mutex_unlock (&priv->lock);
664
}
665 666 667 668 669 670

static void
gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory)
{
  GnomeDesktopThumbnailFactoryPrivate *priv;
  
671
  factory->priv = GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE (factory);
672 673 674 675 676

  priv = factory->priv;

  priv->size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL;
  
677 678 679 680
  priv->mime_types_map = g_hash_table_new_full (g_str_hash,
                                                g_str_equal,
                                                (GDestroyNotify)g_free,
                                                (GDestroyNotify)thumbnailer_unref);
681
  
Colin Walters's avatar
Colin Walters committed
682
  g_mutex_init (&priv->lock);
683

684 685 686 687 688 689 690 691 692 693 694 695 696
  priv->settings = g_settings_new ("org.gnome.desktop.thumbnailers");
  priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
  if (!priv->disabled)
    priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
  g_signal_connect (priv->settings, "changed::disable-all",
                    G_CALLBACK (external_thumbnailers_disabled_all_changed_cb),
                    factory);
  g_signal_connect (priv->settings, "changed::disable",
                    G_CALLBACK (external_thumbnailers_disabled_changed_cb),
                    factory);

  if (!priv->disabled)
    gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
697 698
}

699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
static void
gnome_desktop_thumbnail_factory_finalize (GObject *object)
{
  GnomeDesktopThumbnailFactory *factory;
  GnomeDesktopThumbnailFactoryPrivate *priv;
  
  factory = GNOME_DESKTOP_THUMBNAIL_FACTORY (object);

  priv = factory->priv;

  if (priv->thumbnailers)
    {
      g_list_free_full (priv->thumbnailers, (GDestroyNotify)thumbnailer_unref);
      priv->thumbnailers = NULL;
    }

  g_clear_pointer (&priv->mime_types_map, g_hash_table_destroy);

  if (priv->monitors)
    {
      g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref);
      priv->monitors = NULL;
    }

  g_mutex_clear (&priv->lock);

  g_clear_pointer (&priv->disabled_types, g_strfreev);

  if (priv->settings)
    {
      g_signal_handlers_disconnect_by_func (priv->settings,
                                            external_thumbnailers_disabled_all_changed_cb,
                                            factory);
      g_signal_handlers_disconnect_by_func (priv->settings,
                                            external_thumbnailers_disabled_changed_cb,
                                            factory);
      g_clear_object (&priv->settings);
    }

  if (G_OBJECT_CLASS (parent_class)->finalize)
    (* G_OBJECT_CLASS (parent_class)->finalize) (object);
}

742 743 744 745 746 747 748 749
static void
gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (class);
	
  gobject_class->finalize = gnome_desktop_thumbnail_factory_finalize;
750 751

  g_type_class_add_private (class, sizeof (GnomeDesktopThumbnailFactoryPrivate));
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
}

/**
 * gnome_desktop_thumbnail_factory_new:
 * @size: The thumbnail size to use
 *
 * Creates a new #GnomeDesktopThumbnailFactory.
 *
 * This function must be called on the main thread.
 * 
 * Return value: a new #GnomeDesktopThumbnailFactory
 *
 * Since: 2.2
 **/
GnomeDesktopThumbnailFactory *
gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size)
{
  GnomeDesktopThumbnailFactory *factory;
  
771
  factory = g_object_new (GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
772 773 774 775 776 777
  
  factory->priv->size = size;
  
  return factory;
}

778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
static char *
thumbnail_filename (const char *uri)
{
  GChecksum *checksum;
  guint8 digest[16];
  gsize digest_len = sizeof (digest);
  char *file;

  checksum = g_checksum_new (G_CHECKSUM_MD5);
  g_checksum_update (checksum, (const guchar *) uri, strlen (uri));

  g_checksum_get_digest (checksum, digest, &digest_len);
  g_assert (digest_len == 16);

  file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);

  g_checksum_free (checksum);

  return file;
}

static char *
thumbnail_path (const char                *uri,
                GnomeDesktopThumbnailSize  size)
{
  char *path, *file;

  file = thumbnail_filename (uri);
  path = g_build_filename (g_get_user_cache_dir (),
                           "thumbnails",
                           size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE ? "large" : "normal",
809
                           file,
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
                           NULL);
  g_free (file);
  return path;
}

static char *
thumbnail_failed_path (const char *uri)
{
  char *path, *file;

  file = thumbnail_filename (uri);
  /* XXX: appname is only used for failed thumbnails. Is this a mistake? */
  path = g_build_filename (g_get_user_cache_dir (),
                           "thumbnails",
                           "fail",
                           appname,
                           file,
                           NULL);
  g_free (file);
  return path;
}

832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
static char *
validate_thumbnail_path (char                      *path,
                         const char                *uri,
                         time_t                     mtime,
                         GnomeDesktopThumbnailSize  size)
{
  GdkPixbuf *pixbuf;

  pixbuf = gdk_pixbuf_new_from_file (path, NULL);
  if (pixbuf == NULL ||
      !gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime)) {
      g_free (path);
      return NULL;
  }

  g_clear_object (&pixbuf);

  return path;
}

static char *
lookup_thumbnail_path (const char                *uri,
                       time_t                     mtime,
                       GnomeDesktopThumbnailSize  size)
{
  char *path = thumbnail_path (uri, size);
  return validate_thumbnail_path (path, uri, mtime, size);
}

static char *
lookup_failed_thumbnail_path (const char                *uri,
                              time_t                     mtime,
                              GnomeDesktopThumbnailSize  size)
{
  char *path = thumbnail_failed_path (uri);
  return validate_thumbnail_path (path, uri, mtime, size);
}

870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
/**
 * gnome_desktop_thumbnail_factory_lookup:
 * @factory: a #GnomeDesktopThumbnailFactory
 * @uri: the uri of a file
 * @mtime: the mtime of the file
 *
 * Tries to locate an existing thumbnail for the file specified.
 *
 * Usage of this function is threadsafe.
 *
 * Return value: The absolute path of the thumbnail, or %NULL if none exist.
 *
 * Since: 2.2
 **/
char *
gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
					const char            *uri,
					time_t                 mtime)
{
  GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;

  g_return_val_if_fail (uri != NULL, NULL);

893
  return lookup_thumbnail_path (uri, mtime, priv->size);
894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
}

/**
 * gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail:
 * @factory: a #GnomeDesktopThumbnailFactory
 * @uri: the uri of a file
 * @mtime: the mtime of the file
 *
 * Tries to locate an failed thumbnail for the file specified. Writing
 * and looking for failed thumbnails is important to avoid to try to
 * thumbnail e.g. broken images several times.
 *
 * Usage of this function is threadsafe.
 *
 * Return value: TRUE if there is a failed thumbnail for the file.
 *
 * Since: 2.2
 **/
gboolean
gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
							    const char            *uri,
							    time_t                 mtime)
{
917
  char *path;
918

919
  g_return_val_if_fail (uri != NULL, FALSE);
920

921 922 923
  path = lookup_failed_thumbnail_path (uri, mtime, factory->priv->size);
  if (path == NULL)
    return FALSE;
924 925 926

  g_free (path);

927
  return TRUE;
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
}

/**
 * gnome_desktop_thumbnail_factory_can_thumbnail:
 * @factory: a #GnomeDesktopThumbnailFactory
 * @uri: the uri of a file
 * @mime_type: the mime type of the file
 * @mtime: the mtime of the file
 *
 * Returns TRUE if this GnomeIconFactory can (at least try) to thumbnail
 * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
 *
 * Usage of this function is threadsafe.
 *
 * Return value: TRUE if the file can be thumbnailed.
 *
 * Since: 2.2
 **/
gboolean
gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
					       const char            *uri,
					       const char            *mime_type,
					       time_t                 mtime)
{
952
  gboolean have_script = FALSE;
953

954 955 956
  /* Don't thumbnail thumbnails */
  if (uri &&
      strncmp (uri, "file:/", 6) == 0 &&
957
      strstr (uri, "/thumbnails/") != NULL)
958 959
    return FALSE;
  
960 961 962
  if (!mime_type)
    return FALSE;

Colin Walters's avatar
Colin Walters committed
963
  g_mutex_lock (&factory->priv->lock);
964 965 966 967 968 969 970
  if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
    {
      Thumbnailer *thumb;

      thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
      have_script = thumbnailer_try_exec (thumb);
    }
Colin Walters's avatar
Colin Walters committed
971
  g_mutex_unlock (&factory->priv->lock);
972

973
  if (have_script)
974 975
    {
      return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
976 977
                                                                          uri,
                                                                          mtime);
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
    }
  
  return FALSE;
}

static char *
expand_thumbnailing_script (const char *script,
			    const int   size, 
			    const char *inuri,
			    const char *outfile)
{
  GString *str;
  const char *p, *last;
  char *localfile, *quoted;
  gboolean got_in;

  str = g_string_new (NULL);
  
  got_in = FALSE;
  last = script;
  while ((p = strchr (last, '%')) != NULL)
    {
      g_string_append_len (str, last, p - last);
      p++;

      switch (*p) {
      case 'u':
	quoted = g_shell_quote (inuri);
	g_string_append (str, quoted);
	g_free (quoted);
	got_in = TRUE;
	p++;
	break;
      case 'i':
	localfile = g_filename_from_uri (inuri, NULL, NULL);
	if (localfile)
	  {
	    quoted = g_shell_quote (localfile);
	    g_string_append (str, quoted);
	    got_in = TRUE;
	    g_free (quoted);
	    g_free (localfile);
	  }
	p++;
	break;
      case 'o':
	quoted = g_shell_quote (outfile);
	g_string_append (str, quoted);
	g_free (quoted);
	p++;
	break;
      case 's':
	g_string_append_printf (str, "%d", size);
	p++;
	break;
      case '%':
	g_string_append_c (str, '%');
	p++;
	break;
      case 0:
      default:
	break;
      }
      last = p;
    }
  g_string_append (str, last);

  if (got_in)
    return g_string_free (str, FALSE);

  g_string_free (str, TRUE);
  return NULL;
}

/**
 * gnome_desktop_thumbnail_factory_generate_thumbnail:
 * @factory: a #GnomeDesktopThumbnailFactory
 * @uri: the uri of a file
 * @mime_type: the mime type of the file
 *
 * Tries to generate a thumbnail for the specified file. If it succeeds
 * it returns a pixbuf that can be used as a thumbnail.
 *
 * Usage of this function is threadsafe.
 *
1063
 * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
1064 1065 1066 1067 1068 1069 1070 1071
 *
 * Since: 2.2
 **/
GdkPixbuf *
gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
						    const char            *uri,
						    const char            *mime_type)
{
1072
  GdkPixbuf *pixbuf;
1073
  char *script, *expanded_script;
1074
  int size;
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
  int exit_status;
  char *tmpname;

  g_return_val_if_fail (uri != NULL, NULL);
  g_return_val_if_fail (mime_type != NULL, NULL);

  /* Doesn't access any volatile fields in factory, so it's threadsafe */
  
  size = 128;
  if (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE)
    size = 256;

  pixbuf = NULL;

  script = NULL;
Colin Walters's avatar
Colin Walters committed
1090
  g_mutex_lock (&factory->priv->lock);
1091
  if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
1092
    {
1093 1094 1095 1096 1097
      Thumbnailer *thumb;

      thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
      if (thumb)
        script = g_strdup (thumb->command);
1098
    }
Colin Walters's avatar
Colin Walters committed
1099
  g_mutex_unlock (&factory->priv->lock);
1100 1101 1102 1103 1104
  
  if (script)
    {
      int fd;

1105
      fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, NULL);
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120

      if (fd != -1)
	{
	  close (fd);

	  expanded_script = expand_thumbnailing_script (script, size, uri, tmpname);
	  if (expanded_script != NULL &&
	      g_spawn_command_line_sync (expanded_script,
					 NULL, NULL, &exit_status, NULL) &&
	      exit_status == 0)
	    {
	      pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL);
	    }

	  g_free (expanded_script);
1121
	  g_unlink (tmpname);
1122
	  g_free (tmpname);
1123
	}
1124 1125

      g_free (script);
1126 1127 1128 1129 1130 1131
    }

  return pixbuf;
}

static gboolean
1132 1133 1134 1135
save_thumbnail (GdkPixbuf  *pixbuf,
                char       *path,
                const char *uri,
                time_t      mtime)
1136
{
1137 1138 1139 1140 1141 1142 1143
  char *dirname;
  char *tmp_path = NULL;
  int tmp_fd;
  char mtime_str[21];
  gboolean ret = FALSE;
  GError *error = NULL;
  const char *width, *height;
1144

1145 1146
  if (pixbuf == NULL)
    return FALSE;
1147

1148
  dirname = g_path_get_dirname (path);
1149

1150
  if (g_mkdir_with_parents (dirname, 0700) != 0)
1151
    goto out;
1152

1153 1154
  tmp_path = g_strconcat (path, ".XXXXXX", NULL);
  tmp_fd = g_mkstemp (tmp_path);
1155

1156 1157 1158
  if (tmp_fd == -1)
    goto out;
  close (tmp_fd);
1159

1160 1161 1162
  g_snprintf (mtime_str, 21, "%ld",  mtime);
  width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
  height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
1163

1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
  error = NULL;
  if (width != NULL && height != NULL)
    ret = gdk_pixbuf_save (pixbuf,
			   tmp_path,
			   "png", &error,
			   "tEXt::Thumb::Image::Width", width,
			   "tEXt::Thumb::Image::Height", height,
			   "tEXt::Thumb::URI", uri,
			   "tEXt::Thumb::MTime", mtime_str,
			   "tEXt::Software", "GNOME::ThumbnailFactory",
			   NULL);
  else
    ret = gdk_pixbuf_save (pixbuf,
			   tmp_path,
			   "png", &error,
			   "tEXt::Thumb::URI", uri,
			   "tEXt::Thumb::MTime", mtime_str,
			   "tEXt::Software", "GNOME::ThumbnailFactory",
			   NULL);
1183

1184 1185 1186 1187 1188
  if (!ret)
    goto out;

  g_chmod (tmp_path, 0600);
  g_rename (tmp_path, path);
1189

1190 1191
 out:
  if (error != NULL)
1192
    {
1193 1194
      g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message);
      g_error_free (error);
1195
    }
1196 1197 1198 1199
  g_unlink (tmp_path);
  g_free (tmp_path);
  g_free (dirname);
  return ret;
1200 1201
}

1202 1203 1204 1205 1206
static GdkPixbuf *
make_failed_thumbnail (void)
{
  return gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
}
1207 1208 1209 1210

/**
 * gnome_desktop_thumbnail_factory_save_thumbnail:
 * @factory: a #GnomeDesktopThumbnailFactory
1211
 * @thumbnail: the thumbnail as a pixbuf
1212
 * @uri: the uri of a file
1213
 * @original_mtime: the modification time of the original file
1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227
 *
 * Saves @thumbnail at the right place. If the save fails a
 * failed thumbnail is written.
 *
 * Usage of this function is threadsafe.
 *
 * Since: 2.2
 **/
void
gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
						GdkPixbuf             *thumbnail,
						const char            *uri,
						time_t                 original_mtime)
{
1228
  char *path;
1229

1230 1231
  path = thumbnail_path (uri, factory->priv->size);
  if (!save_thumbnail (thumbnail, path, uri, original_mtime))
1232
    {
1233
      thumbnail = make_failed_thumbnail ();
1234
      g_free (path);
1235 1236 1237
      path = thumbnail_failed_path (uri);
      save_thumbnail (thumbnail, path, uri, original_mtime);
      g_object_unref (thumbnail);
1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259
    }
  g_free (path);
}

/**
 * gnome_desktop_thumbnail_factory_create_failed_thumbnail:
 * @factory: a #GnomeDesktopThumbnailFactory
 * @uri: the uri of a file
 * @mtime: the modification time of the file
 *
 * Creates a failed thumbnail for the file so that we don't try
 * to re-thumbnail the file later.
 *
 * Usage of this function is threadsafe.
 *
 * Since: 2.2
 **/
void
gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
							 const char            *uri,
							 time_t                 mtime)
{
1260
  char *path;
1261 1262
  GdkPixbuf *pixbuf;

1263
  path = thumbnail_failed_path (uri);
1264 1265
  pixbuf = make_failed_thumbnail ();
  save_thumbnail (pixbuf, path, uri, mtime);
1266
  g_free (path);
1267
  g_object_unref (pixbuf);
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
}

/**
 * gnome_desktop_thumbnail_md5:
 * @uri: an uri
 *
 * Calculates the MD5 checksum of the uri. This can be useful
 * if you want to manually handle thumbnail files.
 *
 * Return value: A string with the MD5 digest of the uri string.
 *
 * Since: 2.2
1280
 * Deprecated: 2.22: Use #GChecksum instead
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
 **/
char *
gnome_desktop_thumbnail_md5 (const char *uri)
{
  return g_compute_checksum_for_data (G_CHECKSUM_MD5,
                                      (const guchar *) uri,
                                      strlen (uri));
}

/**
 * gnome_desktop_thumbnail_path_for_uri:
 * @uri: an uri
 * @size: a thumbnail size
 *
 * Returns the filename that a thumbnail of size @size for @uri would have.
 *
 * Return value: an absolute filename
 *
 * Since: 2.2
 **/
char *
gnome_desktop_thumbnail_path_for_uri (const char         *uri,
				      GnomeDesktopThumbnailSize  size)
{
1305
  return thumbnail_path (uri, size);
1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
}

/**
 * gnome_desktop_thumbnail_has_uri:
 * @pixbuf: an loaded thumbnail pixbuf
 * @uri: a uri
 *
 * Returns whether the thumbnail has the correct uri embedded in the
 * Thumb::URI option in the png.
 *
 * Return value: TRUE if the thumbnail is for @uri
 *
 * Since: 2.2
 **/
gboolean
gnome_desktop_thumbnail_has_uri (GdkPixbuf          *pixbuf,
				 const char         *uri)
{
  const char *thumb_uri;
  
  thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
  if (!thumb_uri)
    return FALSE;

  return strcmp (uri, thumb_uri) == 0;
}

/**
 * gnome_desktop_thumbnail_is_valid:
 * @pixbuf: an loaded thumbnail #GdkPixbuf
 * @uri: a uri
 * @mtime: the mtime
 *
 * Returns whether the thumbnail has the correct uri and mtime embedded in the
 * png options.
 *
 * Return value: TRUE if the thumbnail has the right @uri and @mtime
 *
 * Since: 2.2
 **/
gboolean
gnome_desktop_thumbnail_is_valid (GdkPixbuf          *pixbuf,
				  const char         *uri,
				  time_t              mtime)
{
  const char *thumb_uri, *thumb_mtime_str;
  time_t thumb_mtime;
  
  thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
  if (!thumb_uri)
    return FALSE;
  if (strcmp (uri, thumb_uri) != 0)
    return FALSE;
  
  thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
  if (!thumb_mtime_str)
    return FALSE;
  thumb_mtime = atol (thumb_mtime_str);
  if (mtime != thumb_mtime)
    return FALSE;
  
  return TRUE;
}