gimpthumbnail.c 36.1 KB
Newer Older
1 2 3 4 5 6
/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * Thumbnail handling according to the Thumbnail Managing Standard.
 * http://triq.net/~pearl/thumbnail-spec/
 *
7
 * Copyright (C) 2001-2004  Sven Neumann <sven@gimp.org>
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 *                          Michael Natterer <mitch@gimp.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>
#include <stdio.h>
30 31 32
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
33 34 35

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

36
#ifdef G_OS_WIN32
37
#include "libgimpbase/gimpwin32-io.h"
38 39
#endif

40 41 42 43
#include "gimpthumb-types.h"
#include "gimpthumb-utils.h"
#include "gimpthumbnail.h"

44 45
#include "libgimp/libgimp-intl.h"

46

47
#define GIMP_THUMB_DEBUG
Sven Neumann's avatar
Sven Neumann committed
48

49 50 51 52 53 54 55 56

#if defined (GIMP_THUMB_DEBUG) && defined (__GNUC__)
#define GIMP_THUMB_DEBUG_CALL(t) \
        g_printerr ("%s: %s\n", \
                     __FUNCTION__, t->image_uri ? t->image_uri : "(null)")
#else
#define GIMP_THUMB_DEBUG_CALL(t) ((void)(0))
#endif
57 58


Sven Neumann's avatar
Sven Neumann committed
59 60 61 62 63 64 65 66 67 68
#define TAG_DESCRIPTION           "tEXt::Description"
#define TAG_SOFTWARE              "tEXt::Software"
#define TAG_THUMB_URI             "tEXt::Thumb::URI"
#define TAG_THUMB_MTIME           "tEXt::Thumb::MTime"
#define TAG_THUMB_FILESIZE        "tEXt::Thumb::Size"
#define TAG_THUMB_IMAGE_WIDTH     "tEXt::Thumb::Image::Width"
#define TAG_THUMB_IMAGE_HEIGHT    "tEXt::Thumb::Image::Height"
#define TAG_THUMB_IMAGE_MIMETYPE  "tEXt::Thumb::Image::Mimetype"
#define TAG_THUMB_GIMP_TYPE       "tEXt::Thumb::X-GIMP::Type"
#define TAG_THUMB_GIMP_LAYERS     "tEXt::Thumb::X-GIMP::Layers"
69 70 71 72 73


enum
{
  PROP_0,
74 75 76 77 78 79
  PROP_IMAGE_STATE,
  PROP_IMAGE_URI,
  PROP_IMAGE_MTIME,
  PROP_IMAGE_FILESIZE,
  PROP_IMAGE_WIDTH,
  PROP_IMAGE_HEIGHT,
Sven Neumann's avatar
Sven Neumann committed
80
  PROP_IMAGE_MIMETYPE,
81 82 83
  PROP_IMAGE_TYPE,
  PROP_IMAGE_NUM_LAYERS,
  PROP_THUMB_STATE
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
static void      gimp_thumbnail_class_init   (GimpThumbnailClass *klass);
static void      gimp_thumbnail_init         (GimpThumbnail      *thumbnail);
static void      gimp_thumbnail_finalize     (GObject            *object);
static void      gimp_thumbnail_set_property (GObject            *object,
                                              guint               property_id,
                                              const GValue       *value,
                                              GParamSpec         *pspec);
static void      gimp_thumbnail_get_property (GObject            *object,
                                              guint               property_id,
                                              GValue             *value,
                                              GParamSpec         *pspec);
static void      gimp_thumbnail_reset_info   (GimpThumbnail      *thumbnail);

static void      gimp_thumbnail_update_image (GimpThumbnail      *thumbnail);
static void      gimp_thumbnail_update_thumb (GimpThumbnail      *thumbnail,
                                              GimpThumbSize       size);

static gboolean  gimp_thumbnail_save         (GimpThumbnail      *thumbnail,
                                              GimpThumbSize       size,
                                              const gchar        *filename,
                                              GdkPixbuf          *pixbuf,
                                              const gchar        *software,
                                              GError            **error);
110
#ifdef GIMP_THUMB_DEBUG
111 112
static void      gimp_thumbnail_debug_notify (GObject            *object,
                                              GParamSpec         *pspec);
113 114
#endif

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160

static GObjectClass *parent_class = NULL;


GType
gimp_thumbnail_get_type (void)
{
  static GType thumbnail_type = 0;

  if (!thumbnail_type)
    {
      static const GTypeInfo thumbnail_info =
      {
        sizeof (GimpThumbnailClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gimp_thumbnail_class_init,
	NULL,           /* class_finalize */
	NULL,		/* class_data     */
	sizeof (GimpThumbnail),
	0,              /* n_preallocs    */
	(GInstanceInitFunc) gimp_thumbnail_init,
      };

      thumbnail_type = g_type_register_static (G_TYPE_OBJECT,
					       "GimpThumbnail",
                                               &thumbnail_info, 0);
    }

  return thumbnail_type;
}

static void
gimp_thumbnail_class_init (GimpThumbnailClass *klass)
{
  GObjectClass *object_class;

  parent_class = g_type_class_peek_parent (klass);

  object_class = G_OBJECT_CLASS (klass);

  object_class->finalize     = gimp_thumbnail_finalize;
  object_class->set_property = gimp_thumbnail_set_property;
  object_class->get_property = gimp_thumbnail_get_property;

  g_object_class_install_property (object_class,
161 162
				   PROP_IMAGE_STATE,
				   g_param_spec_enum ("image-state", NULL,
Sven Neumann's avatar
Sven Neumann committed
163
                                                      "State of the image associated to the thumbnail object",
164 165 166 167
                                                      GIMP_TYPE_THUMB_STATE,
                                                      GIMP_THUMB_STATE_UNKNOWN,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
168 169
				   PROP_IMAGE_URI,
				   g_param_spec_string ("image-uri", NULL,
Sven Neumann's avatar
Sven Neumann committed
170
                                                       "URI of the image file",
171 172 173 174
							NULL,
							G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
				   PROP_IMAGE_MTIME,
175
				   g_param_spec_int64 ("image-mtime", NULL,
Sven Neumann's avatar
Sven Neumann committed
176
                                                       "Modification time of the image file in seconds since the Epoch",
177 178 179
                                                       0, G_MAXINT64, 0,
                                                       G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
180
				   PROP_IMAGE_FILESIZE,
181
				   g_param_spec_int64 ("image-filesize", NULL,
Sven Neumann's avatar
Sven Neumann committed
182
                                                       "Size of the image file in bytes",
183 184 185
                                                       0, G_MAXINT64, 0,
                                                       G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
186
				   PROP_IMAGE_WIDTH,
187
				   g_param_spec_int ("image-width", NULL,
Sven Neumann's avatar
Sven Neumann committed
188
                                                     "Width of the image in pixels",
189 190 191
                                                     0, G_MAXINT, 0,
                                                     G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
192
				   PROP_IMAGE_HEIGHT,
193
				   g_param_spec_int ("image-height", NULL,
Sven Neumann's avatar
Sven Neumann committed
194
                                                     "Height of the image in pixels",
195 196
                                                     0, G_MAXINT, 0,
                                                     G_PARAM_READWRITE));
Sven Neumann's avatar
Sven Neumann committed
197 198 199 200 201 202 203 204 205 206 207
  /**
   * GimpThumbnail::image-mimetype:
   *
   * Since: GIMP 2.2
   **/
  g_object_class_install_property (object_class,
				   PROP_IMAGE_MIMETYPE,
				   g_param_spec_string ("image-mimetype", NULL,
                                                        "Image mimetype",
                                                        NULL,
                                                        G_PARAM_READWRITE));
208
  g_object_class_install_property (object_class,
209
                                   PROP_IMAGE_TYPE,
210
                                   g_param_spec_string ("image-type", NULL,
Sven Neumann's avatar
Sven Neumann committed
211
                                                        "String describing the type of the image format",
212 213 214
                                                        NULL,
                                                        G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
215
                                   PROP_IMAGE_NUM_LAYERS,
216
                                   g_param_spec_int ("image-num-layers", NULL,
Sven Neumann's avatar
Sven Neumann committed
217
                                                     "The number of layers in the image",
218 219
                                                     0, G_MAXINT, 0,
                                                     G_PARAM_READWRITE));
220 221 222
  g_object_class_install_property (object_class,
				   PROP_THUMB_STATE,
				   g_param_spec_enum ("thumb-state", NULL,
Sven Neumann's avatar
Sven Neumann committed
223
                                                      "State of the thumbnail file",
224 225 226
                                                      GIMP_TYPE_THUMB_STATE,
                                                      GIMP_THUMB_STATE_UNKNOWN,
                                                      G_PARAM_READWRITE));
227 228 229 230 231
}

static void
gimp_thumbnail_init (GimpThumbnail *thumbnail)
{
232 233
  thumbnail->image_state      = GIMP_THUMB_STATE_UNKNOWN;
  thumbnail->image_uri        = NULL;
234 235 236 237 238
  thumbnail->image_filename   = NULL;
  thumbnail->image_mtime      = 0;
  thumbnail->image_filesize   = 0;
  thumbnail->image_width      = 0;
  thumbnail->image_height     = 0;
Sven Neumann's avatar
Sven Neumann committed
239
  thumbnail->image_mimetype   = NULL;
240 241
  thumbnail->image_type       = 0;
  thumbnail->image_num_layers = 0;
242 243

  thumbnail->thumb_state      = GIMP_THUMB_STATE_UNKNOWN;
244
  thumbnail->thumb_size       = -1;
245 246 247
  thumbnail->thumb_filename   = NULL;
  thumbnail->thumb_mtime      = 0;
  thumbnail->thumb_filesize   = 0;
248

249
#ifdef GIMP_THUMB_DEBUG
250 251 252 253
  g_signal_connect (thumbnail, "notify",
                    G_CALLBACK (gimp_thumbnail_debug_notify),
                    NULL);
#endif
254 255 256 257 258 259 260
}

static void
gimp_thumbnail_finalize (GObject *object)
{
  GimpThumbnail *thumbnail = GIMP_THUMBNAIL (object);

261
  if (thumbnail->image_uri)
262
    {
263 264
      g_free (thumbnail->image_uri);
      thumbnail->image_uri = NULL;
265 266 267 268 269 270
    }
  if (thumbnail->image_filename)
    {
      g_free (thumbnail->image_filename);
      thumbnail->image_filename = NULL;
    }
Sven Neumann's avatar
Sven Neumann committed
271 272 273 274 275
  if (thumbnail->image_mimetype)
    {
      g_free (thumbnail->image_mimetype);
      thumbnail->image_mimetype = NULL;
    }
276 277 278 279 280
  if (thumbnail->image_type)
    {
      g_free (thumbnail->image_type);
      thumbnail->image_type = NULL;
    }
281 282 283 284 285
  if (thumbnail->thumb_filename)
    {
      g_free (thumbnail->thumb_filename);
      thumbnail->thumb_filename = NULL;
    }
286 287 288 289 290 291 292 293 294 295 296 297 298 299

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

static void
gimp_thumbnail_set_property (GObject      *object,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GimpThumbnail *thumbnail = GIMP_THUMBNAIL (object);

  switch (property_id)
    {
300 301 302 303
    case PROP_IMAGE_STATE:
      thumbnail->image_state = g_value_get_enum (value);
      break;
    case PROP_IMAGE_URI:
304 305 306
      gimp_thumbnail_set_uri (GIMP_THUMBNAIL (object),
                              g_value_get_string (value));
      break;
307
    case PROP_IMAGE_MTIME:
308
      thumbnail->image_mtime = g_value_get_int64 (value);
309
      break;
310
    case PROP_IMAGE_FILESIZE:
311
      thumbnail->image_filesize = g_value_get_int64 (value);
312
      break;
313
    case PROP_IMAGE_WIDTH:
314 315
      thumbnail->image_width = g_value_get_int (value);
      break;
316
    case PROP_IMAGE_HEIGHT:
317 318
      thumbnail->image_height = g_value_get_int (value);
      break;
Sven Neumann's avatar
Sven Neumann committed
319 320 321 322
    case PROP_IMAGE_MIMETYPE:
      g_free (thumbnail->image_mimetype);
      thumbnail->image_mimetype = g_value_dup_string (value);
      break;
323
    case PROP_IMAGE_TYPE:
324 325 326
      g_free (thumbnail->image_type);
      thumbnail->image_type = g_value_dup_string (value);
      break;
327
    case PROP_IMAGE_NUM_LAYERS:
328 329
      thumbnail->image_num_layers = g_value_get_int (value);
      break;
330 331 332
    case PROP_THUMB_STATE:
      thumbnail->thumb_state = g_value_get_enum (value);
      break;
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_thumbnail_get_property (GObject    *object,
                             guint       property_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  GimpThumbnail *thumbnail = GIMP_THUMBNAIL (object);

  switch (property_id)
    {
350 351
    case PROP_IMAGE_STATE:
      g_value_set_enum (value, thumbnail->image_state);
352
      break;
353 354
    case PROP_IMAGE_URI:
      g_value_set_string (value, thumbnail->image_uri);
355
      break;
356
    case PROP_IMAGE_MTIME:
357 358
      g_value_set_int64 (value, thumbnail->image_mtime);
      break;
359
    case PROP_IMAGE_FILESIZE:
360 361
      g_value_set_int64 (value, thumbnail->image_filesize);
      break;
362
    case PROP_IMAGE_WIDTH:
363 364
      g_value_set_int (value, thumbnail->image_width);
      break;
365
    case PROP_IMAGE_HEIGHT:
366 367
      g_value_set_int (value, thumbnail->image_height);
      break;
Sven Neumann's avatar
Sven Neumann committed
368 369 370
    case PROP_IMAGE_MIMETYPE:
      g_value_set_string (value, thumbnail->image_mimetype);
      break;
371
    case PROP_IMAGE_TYPE:
372 373
      g_value_set_string (value, thumbnail->image_type);
      break;
374
    case PROP_IMAGE_NUM_LAYERS:
375
      g_value_set_int (value, thumbnail->image_num_layers);
376
      break;
377 378 379
    case PROP_THUMB_STATE:
      g_value_set_enum (value, thumbnail->thumb_state);
      break;
380 381 382 383 384 385 386

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

Sven Neumann's avatar
Sven Neumann committed
387 388 389 390 391 392 393
/**
 * gimp_thumbnail_new:
 *
 * Creates a new #GimpThumbnail object.
 *
 * Return value: a newly allocated GimpThumbnail object
 **/
394 395 396 397 398 399
GimpThumbnail *
gimp_thumbnail_new (void)
{
  return g_object_new (GIMP_TYPE_THUMBNAIL, NULL);
}

Sven Neumann's avatar
Sven Neumann committed
400 401 402
/**
 * gimp_thumbnail_set_uri:
 * @thumbnail: a #GimpThumbnail object
403
 * @uri: an escaped URI
Sven Neumann's avatar
Sven Neumann committed
404 405 406 407 408
 *
 * Sets the location of the image file associated with the #thumbnail.
 *
 * All informations stored in the #GimpThumbnail are reset.
 **/
409 410 411 412 413 414
void
gimp_thumbnail_set_uri (GimpThumbnail *thumbnail,
                        const gchar   *uri)
{
  g_return_if_fail (GIMP_IS_THUMBNAIL (thumbnail));

415 416
  GIMP_THUMB_DEBUG_CALL (thumbnail);

Sven Neumann's avatar
Sven Neumann committed
417 418 419 420 421 422 423 424 425 426 427
  if (thumbnail->image_uri)
    g_free (thumbnail->image_uri);

  thumbnail->image_uri = g_strdup (uri);

  if (thumbnail->image_filename)
    {
      g_free (thumbnail->image_filename);
      thumbnail->image_filename = NULL;
    }

428 429 430 431 432 433 434 435 436 437
  if (thumbnail->thumb_filename)
    {
      g_free (thumbnail->thumb_filename);
      thumbnail->thumb_filename = NULL;
    }

  thumbnail->thumb_size     = -1;
  thumbnail->thumb_filesize = 0;
  thumbnail->thumb_mtime    = 0;

438 439
  g_object_set (thumbnail,
                "image-state",      GIMP_THUMB_STATE_UNKNOWN,
440 441
                "image-filesize",   (gint64) 0,
                "image-mtime",      (gint64) 0,
442 443
                "image-width",      0,
                "image-height",     0,
Sven Neumann's avatar
Sven Neumann committed
444
                "image-mimetype",   NULL,
445 446
                "image-type",       NULL,
                "image-num-layers", 0,
447
                "thumb-state",      GIMP_THUMB_STATE_UNKNOWN,
448 449 450
                NULL);
}

Sven Neumann's avatar
Sven Neumann committed
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
/**
 * gimp_thumbnail_set_filename:
 * @thumbnail: a #GimpThumbnail object
 * @filename: a local filename in the encoding of the filesystem
 * @error: return location for possible errors
 *
 * Sets the location of the image file associated with the #thumbnail.
 *
 * Return value: %TRUE if the filename was successfully set,
 *               %FALSE otherwise
 **/
gboolean
gimp_thumbnail_set_filename (GimpThumbnail  *thumbnail,
                             const gchar    *filename,
                             GError        **error)
{
  gchar *uri = NULL;

  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

472 473
  GIMP_THUMB_DEBUG_CALL (thumbnail);

Sven Neumann's avatar
Sven Neumann committed
474 475 476 477 478 479 480 481 482 483
  if (filename)
    uri = g_filename_to_uri (filename, NULL, error);

  gimp_thumbnail_set_uri (thumbnail, uri);

  g_free (uri);

  return (!filename || uri);
}

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
/**
 * gimp_thumbnail_set_from_thumb:
 * @thumbnail: a #GimpThumbnail object
 * @filename: filename of a local thumbnail file
 * @error: return location for possible errors
 *
 * This function tries to load the thumbnail file pointed to by
 * @filename and retrieves the URI of the original image file from
 * it. This allows you to find the image file associated with a
 * thumbnail file.
 *
 * Return value: %TRUE if the pixbuf could be loaded, %FALSE otherwise
 **/
gboolean
gimp_thumbnail_set_from_thumb (GimpThumbnail  *thumbnail,
                               const gchar    *filename,
                               GError        **error)
{
  GdkPixbuf *pixbuf;

  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
  g_return_val_if_fail (filename != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

508 509
  GIMP_THUMB_DEBUG_CALL (thumbnail);

510 511 512 513 514 515 516 517 518 519 520 521
  pixbuf = gdk_pixbuf_new_from_file (filename, error);
  if (! pixbuf)
    return FALSE;

  gimp_thumbnail_set_uri (thumbnail,
                          gdk_pixbuf_get_option (pixbuf, TAG_THUMB_URI));

  g_object_unref (pixbuf);

  return TRUE;
}

Sven Neumann's avatar
Sven Neumann committed
522 523 524 525 526 527 528 529 530
/**
 * gimp_thumbnail_peek_image:
 * @thumbnail: a #GimpThumbnail object
 *
 * Checks the image file associated with the @thumbnail and updates
 * information such as state, filesize and modification time.
 *
 * Return value: the image's #GimpThumbState after the update
 **/
531 532 533 534 535 536
GimpThumbState
gimp_thumbnail_peek_image (GimpThumbnail *thumbnail)
{
  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail),
                        GIMP_THUMB_STATE_UNKNOWN);

537 538
  GIMP_THUMB_DEBUG_CALL (thumbnail);

539 540 541 542 543 544 545 546 547
  g_object_freeze_notify (G_OBJECT (thumbnail));

  gimp_thumbnail_update_image (thumbnail);

  g_object_thaw_notify (G_OBJECT (thumbnail));

  return thumbnail->image_state;
}

Sven Neumann's avatar
Sven Neumann committed
548 549 550 551 552 553 554 555 556 557 558 559
/**
 * gimp_thumbnail_peek_thumb:
 * @thumbnail: a #GimpThumbnail object
 * @size: the preferred size of the thumbnail image
 *
 * Checks if a thumbnail file for the @thumbnail exists. It doesn't
 * load the thumbnail image and thus cannot check if the thumbnail is
 * valid and uptodate for the image file asosciated with the
 * @thumbnail.
 *
 * Return value: the thumbnail's #GimpThumbState after the update
 **/
560 561 562
GimpThumbState
gimp_thumbnail_peek_thumb (GimpThumbnail *thumbnail,
                           GimpThumbSize  size)
563
{
564 565 566
  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail),
                        GIMP_THUMB_STATE_UNKNOWN);

567 568
  GIMP_THUMB_DEBUG_CALL (thumbnail);

569 570 571
  g_object_freeze_notify (G_OBJECT (thumbnail));

  gimp_thumbnail_update_image (thumbnail);
572
  gimp_thumbnail_update_thumb (thumbnail, size);
573 574

  g_object_thaw_notify (G_OBJECT (thumbnail));
575 576

  return thumbnail->thumb_state;
577 578 579 580
}

static void
gimp_thumbnail_update_image (GimpThumbnail *thumbnail)
581
{
582 583
  GimpThumbState  state;
  gint64          mtime    = 0;
584
  gint64          filesize = 0;
585

Sven Neumann's avatar
Sven Neumann committed
586 587
  if (! thumbnail->image_uri)
    return;
588

589
  state = thumbnail->image_state;
590 591 592 593 594 595

  switch (state)
    {
    case GIMP_THUMB_STATE_UNKNOWN:
      g_return_if_fail (thumbnail->image_filename == NULL);

596
      thumbnail->image_filename = g_filename_from_uri (thumbnail->image_uri,
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
                                                       NULL, NULL);

      if (! thumbnail->image_filename)
        state = GIMP_THUMB_STATE_REMOTE;

      break;

    case GIMP_THUMB_STATE_REMOTE:
      break;

    default:
      g_return_if_fail (thumbnail->image_filename != NULL);
      break;
    }

  switch (state)
    {
    case GIMP_THUMB_STATE_REMOTE:
      break;

    default:
618
      switch (gimp_thumb_file_test (thumbnail->image_filename,
619 620
                                    &mtime, &filesize,
                                    &thumbnail->image_not_found_errno))
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
        {
        case GIMP_THUMB_FILE_TYPE_REGULAR:
          state = GIMP_THUMB_STATE_EXISTS;
          break;

        case GIMP_THUMB_FILE_TYPE_FOLDER:
          state = GIMP_THUMB_STATE_FOLDER;
          break;

        case GIMP_THUMB_FILE_TYPE_SPECIAL:
          state = GIMP_THUMB_STATE_SPECIAL;
          break;

        default:
          state = GIMP_THUMB_STATE_NOT_FOUND;
          break;
        }
638 639 640
      break;
    }

641
  if (state != thumbnail->image_state)
642
    {
643
      g_object_set (thumbnail,
644 645
                    "image-state", state,
                    NULL);
646
    }
647

648 649 650 651 652 653 654 655
  if (mtime != thumbnail->image_mtime || filesize != thumbnail->image_filesize)
    {
      g_object_set (thumbnail,
                    "image-mtime",    mtime,
                    "image-filesize", filesize,
                    NULL);
    }
}
656

657
static void
658 659
gimp_thumbnail_update_thumb (GimpThumbnail *thumbnail,
                             GimpThumbSize  size)
660
{
661
  gchar          *filename;
662
  GimpThumbState  state;
663 664
  gint64          filesize = 0;
  gint64          mtime    = 0;
665

666 667 668
  if (! thumbnail->image_uri)
    return;

669
  state = thumbnail->thumb_state;
670

671 672 673 674 675
  filename = gimp_thumb_find_thumb (thumbnail->image_uri, &size);

  if (! filename)
    state = GIMP_THUMB_STATE_NOT_FOUND;

676 677
  switch (state)
    {
678 679
    case GIMP_THUMB_STATE_EXISTS:
    case GIMP_THUMB_STATE_OLD:
Sven Neumann's avatar
Sven Neumann committed
680
    case GIMP_THUMB_STATE_FAILED:
681 682
    case GIMP_THUMB_STATE_OK:
      g_return_if_fail (thumbnail->thumb_filename != NULL);
683

684 685 686 687 688 689 690
      if (thumbnail->thumb_size     == size     &&
          thumbnail->thumb_filesize == filesize &&
          thumbnail->thumb_mtime    == mtime)
        {
          g_free (filename);
          return;
        }
691 692
      break;
    default:
693 694
      break;
    }
695

696 697
  if (thumbnail->thumb_filename)
    g_free (thumbnail->thumb_filename);
698

699 700 701
  thumbnail->thumb_filename = filename;

  if (filename)
Sven Neumann's avatar
Sven Neumann committed
702 703 704
    state = (size > GIMP_THUMB_SIZE_FAIL ?
             GIMP_THUMB_STATE_EXISTS :
             GIMP_THUMB_STATE_FAILED);
705

706
  thumbnail->thumb_size     = size;
707 708
  thumbnail->thumb_filesize = filesize;
  thumbnail->thumb_mtime    = mtime;
709 710 711

  if (state != thumbnail->thumb_state)
    g_object_set (thumbnail, "thumb-state", state, NULL);
712 713
}

Sven Neumann's avatar
Sven Neumann committed
714 715 716 717 718 719
static void
gimp_thumbnail_reset_info (GimpThumbnail *thumbnail)
{
  g_object_set (thumbnail,
                "image-width",      0,
                "image-height",     0,
Sven Neumann's avatar
Sven Neumann committed
720
                "image-mimetype",   NULL,
Sven Neumann's avatar
Sven Neumann committed
721 722 723
                "image-type",       NULL,
                "image-num-layers", 0,
                NULL);
724 725 726 727 728 729 730
}

static void
gimp_thumbnail_set_info_from_pixbuf (GimpThumbnail *thumbnail,
                                     GdkPixbuf     *pixbuf)
{
  const gchar  *option;
731
  gint          num;
732 733 734 735 736 737

  g_object_freeze_notify (G_OBJECT (thumbnail));

  gimp_thumbnail_reset_info (thumbnail);

  option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_IMAGE_WIDTH);
738 739
  if (option && sscanf (option, "%d", &num) == 1)
    thumbnail->image_width = num;
740 741

  option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_IMAGE_HEIGHT);
742 743
  if (option && sscanf (option, "%d", &num) == 1)
    thumbnail->image_height = num;
744

Sven Neumann's avatar
Sven Neumann committed
745 746 747
  thumbnail->image_mimetype =
    g_strdup (gdk_pixbuf_get_option (pixbuf, TAG_THUMB_IMAGE_MIMETYPE));

748 749 750 751
  thumbnail->image_type =
    g_strdup (gdk_pixbuf_get_option (pixbuf, TAG_THUMB_GIMP_TYPE));

  option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_GIMP_LAYERS);
752 753
  if (option && sscanf (option, "%d", &num) == 1)
    thumbnail->image_num_layers = num;
754 755

  g_object_thaw_notify (G_OBJECT (thumbnail));
756 757
}

758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 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 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 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 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
static gboolean
gimp_thumbnail_save (GimpThumbnail  *thumbnail,
                     GimpThumbSize   size,
                     const gchar    *filename,
                     GdkPixbuf      *pixbuf,
                     const gchar    *software,
                     GError        **error)
{
  const gchar  *keys[12];
  gchar        *values[12];
  gboolean      success;
  gint          i = 0;

  keys[i]   = TAG_DESCRIPTION;
  values[i] = g_strdup_printf ("Thumbnail of %s",   thumbnail->image_uri);
  i++;

  keys[i]   = TAG_SOFTWARE;
  values[i] = g_strdup (software);
  i++;

  keys[i]   = TAG_THUMB_URI;
  values[i] = g_strdup (thumbnail->image_uri);
  i++;

  keys[i]   = TAG_THUMB_MTIME;
  values[i] = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_mtime);
  i++;

  keys[i]   = TAG_THUMB_FILESIZE;
  values[i] = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_filesize);
  i++;

  if (thumbnail->image_width > 0)
    {
      keys[i]   = TAG_THUMB_IMAGE_WIDTH;
      values[i] = g_strdup_printf ("%d", thumbnail->image_width);
      i++;
    }

  if (thumbnail->image_height > 0)
    {
      keys[i]   = TAG_THUMB_IMAGE_HEIGHT;
      values[i] = g_strdup_printf ("%d", thumbnail->image_height);
      i++;
    }

  if (thumbnail->image_mimetype)
    {
      keys[i]   = TAG_THUMB_IMAGE_MIMETYPE;
      values[i] = g_strdup_printf (thumbnail->image_mimetype);
      i++;
    }

  if (thumbnail->image_type)
    {
      keys[i]   = TAG_THUMB_GIMP_TYPE;
      values[i] = g_strdup (thumbnail->image_type);
      i++;
    }

  if (thumbnail->image_num_layers > 0)
    {
      keys[i]   = TAG_THUMB_GIMP_LAYERS;
      values[i] = g_strdup_printf ("%d", thumbnail->image_num_layers);
      i++;
    }

  keys[i]   = NULL;
  values[i] = NULL;

  success = gdk_pixbuf_savev (pixbuf, filename, "png",
                              (gchar **) keys, values,
                              error);

  for (i = 0; keys[i]; i++)
    g_free (values[i]);

  if (success)
    {
#ifdef GIMP_THUMB_DEBUG
      g_printerr ("thumbnail saved to file %s\n", filename);
#endif

      success = (chmod (filename, 0600) == 0);

      if (success)
        gimp_thumbnail_update_thumb (thumbnail, size);
      else
        g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                     "Could not set permissions of thumbnail for %s: %s",
                     thumbnail->image_uri, g_strerror (errno));
    }

  return success;
}

#ifdef GIMP_THUMB_DEBUG
static void
gimp_thumbnail_debug_notify (GObject    *object,
                             GParamSpec *pspec)
{
  GValue       value = { 0, };
  gchar       *str   = NULL;
  const gchar *name;

  g_value_init (&value, pspec->value_type);
  g_object_get_property (object, pspec->name, &value);

  if (G_VALUE_HOLDS_STRING (&value))
    {
      str = g_value_dup_string (&value);
    }
  else if (g_value_type_transformable (pspec->value_type, G_TYPE_STRING))
    {
      GValue  tmp = { 0, };

      g_value_init (&tmp, G_TYPE_STRING);
      g_value_transform (&value, &tmp);

      str = g_value_dup_string (&tmp);

      g_value_unset (&tmp);
    }

  g_value_unset (&value);

  name = GIMP_THUMBNAIL (object)->image_uri;

  g_printerr (" GimpThumb (%s) %s: %s\n",
              name ? name : "(null)", pspec->name, str);

  g_free (str);
}
#endif


Sven Neumann's avatar
Sven Neumann committed
895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
/**
 * gimp_thumbnail_load_thumb:
 * @thumbnail: a #GimpThumbnail object
 * @size: the preferred #GimpThumbSize for the preview
 * @error: return location for possible errors
 *
 * Attempts to load a thumbnail preview for the image associated with
 * @thumbnail. Before you use this function you need need to set an
 * image location using gimp_thumbnail_set_uri() or
 * gimp_thumbnail_set_filename(). You can also peek at the thumb
 * before loading it using gimp_thumbnail_peek_thumb.
 *
 * This function will return the best matching pixbuf for the
 * specified @size. It returns the pixbuf as loaded from disk. It is
 * left to the caller to scale it to the desired size. The returned
 * pixbuf may also represent an outdated preview of the image file.
 * In order to verify if the preview is uptodate, you should check the
 * "thumb_state" property after calling this function.
 *
 * Return value: a preview pixbuf or %NULL if no thumbnail was found
 **/
916
GdkPixbuf *
917
gimp_thumbnail_load_thumb (GimpThumbnail  *thumbnail,
918 919 920
                           GimpThumbSize   size,
                           GError        **error)
{
921
  GimpThumbState  state;
922
  GdkPixbuf      *pixbuf;
923 924 925
  const gchar    *option;
  gint64          image_mtime;
  gint64          image_size;
926

927 928
  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), NULL);

929 930
  GIMP_THUMB_DEBUG_CALL (thumbnail);

931
  if (! thumbnail->image_uri)
932 933
    return NULL;

934
  state = gimp_thumbnail_peek_thumb (thumbnail, size);
935

936
  if (state < GIMP_THUMB_STATE_EXISTS || state == GIMP_THUMB_STATE_FAILED)
937
    return NULL;
938

939
  pixbuf = gdk_pixbuf_new_from_file (thumbnail->thumb_filename, NULL);
940
  if (! pixbuf)
941
    return NULL;
942

943 944 945 946
#ifdef GIMP_THUMB_DEBUG
  g_printerr ("thumbnail loaded from %s\n", thumbnail->thumb_filename);
#endif

947 948
  g_object_freeze_notify (G_OBJECT (thumbnail));

949 950
  /* URI and mtime from the thumbnail need to match our file */
  option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_URI);
951
  if (!option)
952
    goto finish;
953

954 955 956 957 958 959 960 961 962
  if (strcmp (option, thumbnail->image_uri))
    {
      /*  might be a local thumbnail, try if the local part matches  */
      const gchar *baseuri = strrchr (thumbnail->image_uri, '/');

      if (!baseuri || strcmp (option, baseuri))
        goto finish;
    }

963 964 965 966
  state = GIMP_THUMB_STATE_OLD;

  option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_MTIME);
  if (!option || sscanf (option, "%" G_GINT64_FORMAT, &image_mtime) != 1)
967
    goto finish;
968 969 970

  option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_FILESIZE);
  if (option && sscanf (option, "%" G_GINT64_FORMAT, &image_size) != 1)
971
    goto finish;
972 973 974 975

  /* TAG_THUMB_FILESIZE is optional but must match if present */
  if (image_mtime == thumbnail->image_mtime &&
      (option == NULL || image_size == thumbnail->image_filesize))
976
    {
977
      if (thumbnail->thumb_size == GIMP_THUMB_SIZE_FAIL)
978 979 980 981
        state = GIMP_THUMB_STATE_FAILED;
      else
        state = GIMP_THUMB_STATE_OK;
    }
982

983
  if (state == GIMP_THUMB_STATE_FAILED)
984 985 986
    gimp_thumbnail_reset_info (thumbnail);
  else
    gimp_thumbnail_set_info_from_pixbuf (thumbnail, pixbuf);
987

988
 finish:
989 990
  if (thumbnail->thumb_size == GIMP_THUMB_SIZE_FAIL ||
      (state != GIMP_THUMB_STATE_OLD && state != GIMP_THUMB_STATE_OK))
991 992
    {
      g_object_unref (pixbuf);
993
      pixbuf = NULL;
994 995
    }

996 997 998 999
  g_object_set (thumbnail,
                "thumb-state", state,
                NULL);

1000 1001
  g_object_thaw_notify (G_OBJECT (thumbnail));

1002 1003 1004
  return pixbuf;
}

Sven Neumann's avatar
Sven Neumann committed
1005 1006 1007 1008 1009 1010 1011 1012
/**
 * gimp_thumbnail_save_thumb:
 * @thumbnail: a #GimpThumbnail object
 * @pixbuf: a #GdkPixbuf representing the preview thumbnail
 * @software: a string describing the software saving the thumbnail
 * @error: return location for possible errors
 *
 * Saves a preview thumbnail for the image associated with @thumbnail.
1013
 * to the global thumbnail repository.
Sven Neumann's avatar
Sven Neumann committed
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
 *
 * The caller is responsible for setting the image file location, it's
 * filesize, modification time. One way to set this info is to is to
 * call gimp_thumbnail_set_uri() followed by gimp_thumbnail_peek_image().
 * Since this won't work for remote images, it is left to the user of
 * gimp_thumbnail_save_thumb() to do this or to set the information
 * using the @thumbnail object properties.
 *
 * The image format type and the number of layers can optionally be
 * set in order to be stored with the preview image.
 *
 * Return value: %TRUE if a thumbnail was successfully written,
 *               %FALSE otherwise
 **/
1028
gboolean
1029 1030 1031 1032
gimp_thumbnail_save_thumb (GimpThumbnail  *thumbnail,
                           GdkPixbuf      *pixbuf,
                           const gchar    *software,
                           GError        **error)
1033 1034 1035 1036 1037 1038
{
  GimpThumbSize  size;
  gchar         *name;
  gboolean       success;

  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
1039
  g_return_val_if_fail (thumbnail->image_uri != NULL, FALSE);
1040 1041 1042 1043
  g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);
  g_return_val_if_fail (software != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

1044 1045
  GIMP_THUMB_DEBUG_CALL (thumbnail);

1046
  size = MAX (gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
1047 1048 1049
  if (size < 1)
    return TRUE;

1050
  name = gimp_thumb_name_from_uri (thumbnail->image_uri, size);
1051 1052 1053
  if (! name)
    return TRUE;

1054 1055 1056 1057 1058 1059
  if (! gimp_thumb_ensure_thumb_dir (size, error))
    {
      g_free (name);
      return FALSE;
    }

1060 1061 1062 1063
  success = gimp_thumbnail_save (thumbnail,
                                 size, name, pixbuf, software,
                                 error);
  g_free (name);
Sven Neumann's avatar
Sven Neumann committed
1064

1065 1066
  return success;
}
Sven Neumann's avatar
Sven Neumann committed
1067

1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
/**
 * gimp_thumbnail_save_thumb_local:
 * @thumbnail: a #GimpThumbnail object
 * @pixbuf: a #GdkPixbuf representing the preview thumbnail
 * @software: a string describing the software saving the thumbnail
 * @error: return location for possible errors
 *
 * Saves a preview thumbnail for the image associated with @thumbnail
 * to the local thumbnail repository. Local thumbnails have been added
 * with version 0.7 of the spec.
 *
 * Please see also gimp_thumbnail_save_thumb(). The notes made there
 * apply here as well.
 *
 * Return value: %TRUE if a thumbnail was successfully written,
 *               %FALSE otherwise
 *
 * Since: GIMP 2.2
 **/
gboolean
gimp_thumbnail_save_thumb_local (GimpThumbnail  *thumbnail,
                                 GdkPixbuf      *pixbuf,
                                 const gchar    *software,
                                 GError        **error)
{
  GimpThumbSize  size;
  gchar         *name;
  gchar         *filename;
  gchar         *dirname;
  gboolean       success;
Sven Neumann's avatar
Sven Neumann committed
1098

1099 1100 1101 1102 1103
  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
  g_return_val_if_fail (thumbnail->image_uri != NULL, FALSE);
  g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);
  g_return_val_if_fail (software != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
Sven Neumann's avatar
Sven Neumann committed
1104

1105
  GIMP_THUMB_DEBUG_CALL (thumbnail);
Sven Neumann's avatar
Sven Neumann committed
1106

1107 1108 1109
  size = MAX (gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
  if (size < 1)
    return TRUE;
Sven Neumann's avatar
Sven Neumann committed
1110

1111 1112 1113
  filename = g_filename_from_uri (thumbnail->image_uri, NULL, NULL);
  if (! filename)
    return TRUE;
Sven Neumann's avatar
Sven Neumann committed
1114

1115 1116
  dirname = g_path_get_dirname (filename);
  g_free (filename);
1117

1118 1119
  name = gimp_thumb_name_from_uri_local (thumbnail->image_uri, size);
  if (! name)
Sven Neumann's avatar
Sven Neumann committed
1120
    {
1121 1122
      g_free (dirname);
      return TRUE;
Sven Neumann's avatar
Sven Neumann committed
1123
    }
1124

1125
  if (! gimp_thumb_ensure_thumb_dir_local (dirname, size, error))
Sven Neumann's avatar
Sven Neumann committed
1126
    {
1127 1128 1129
      g_free (name);
      g_free (dirname);
      return FALSE;
Sven Neumann's avatar
Sven Neumann committed
1130
    }
1131

1132
  g_free (dirname);
1133

1134 1135 1136
  success = gimp_thumbnail_save (thumbnail,
                                 size, name, pixbuf, software,
                                 error);
1137 1138 1139 1140 1141
  g_free (name);

  return success;
}

Sven Neumann's avatar
Sven Neumann committed
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
/**
 * gimp_thumbnail_save_failure:
 * @thumbnail: a #GimpThumbnail object
 * @software: a string describing the software saving the thumbnail
 * @error: return location for possible errors
 *
 * Saves a failure thumbnail for the image associated with
 * @thumbnail. This is an empty pixbuf that indicates that an attempt
 * to create a preview for the image file failed. It should be used to
 * prevent the software from further attempts to create this thumbnail.
 *
 * Return value: %TRUE if a failure thumbnail was successfully written,
 *               %FALSE otherwise
 **/
1156 1157 1158 1159 1160
gboolean
gimp_thumbnail_save_failure (GimpThumbnail  *thumbnail,
                             const gchar    *software,
                             GError        **error)
{
1161 1162 1163 1164 1165 1166
  GdkPixbuf *pixbuf;
  gchar     *name;
  gchar     *desc;
  gchar     *time_str;
  gchar     *size_str;
  gboolean   success;
1167 1168

  g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
1169
  g_return_val_if_fail (thumbnail->image_uri != NULL, FALSE);
1170 1171
  g_return_val_if_fail (software != NULL, FALSE);

1172 1173
  GIMP_THUMB_DEBUG_CALL (thumbnail);

1174
  name = gimp_thumb_name_from_uri (thumbnail->image_uri, GIMP_THUMB_SIZE_FAIL);
1175 1176 1177
  if (! name)
    return TRUE;

1178
  if (! gimp_thumb_ensure_thumb_dir (GIMP_THUMB_SIZE_FAIL, error))
1179 1180 1181 1182 1183
    {
      g_free (name);
      return FALSE;
    }

1184 1185
  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);

1186
  desc = g_strdup_printf ("Thumbnail failure for %s", thumbnail->image_uri);
1187 1188 1189 1190 1191 1192
  time_str = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_mtime);
  size_str = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_filesize);

  success = gdk_pixbuf_save (pixbuf, name, "png", error,
                             TAG_DESCRIPTION,    desc,
                             TAG_SOFTWARE,       software,
1193
                             TAG_THUMB_URI,      thumbnail->image_uri,
1194 1195 1196 1197 1198
                             TAG_THUMB_MTIME,    time_str,
                             TAG_THUMB_FILESIZE, size_str,
                             NULL);
  if (success)
    {
1199
      success = (chmod (name, 0600) == 0);
1200

1201
      if (success)
Sven Neumann's avatar
Sven Neumann committed
1202
        gimp_thumbnail_update_thumb (thumbnail, GIMP_THUMB_SIZE_NORMAL);
1203
      else
1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217
        g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                     "Could not set permissions of thumbnail '%s': %s",
                     name, g_strerror (errno));
    }

  g_object_unref (pixbuf);

  g_free (size_str);
  g_free (time_str);
  g_free (desc);
  g_free (name);

  return success;
}