ev-file-helpers.c 18.7 KB
Newer Older
1 2
/*
 *  Copyright (C) 2002 Jorn Baayen
3
 *  Copyright © 2009 Christian Persch
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 *  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, 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
17
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 19
 */

20
#include <config.h>
21

22
#include <stdlib.h>
23
#include <sys/stat.h>
24
#include <sys/types.h>
25
#include <unistd.h>
26
#include <string.h>
27
#include <errno.h>
28
#include <fcntl.h>
29

30
#include <glib.h>
31
#include <glib/gstdio.h>
32
#include <glib/gi18n-lib.h>
33

34 35
#include "ev-file-helpers.h"

36
static gchar *tmp_dir = NULL;
37

38 39 40 41
#ifndef O_BINARY
#define O_BINARY 0
#endif

42
/*
43 44 45 46 47 48 49 50 51
 * ev_dir_ensure_exists:
 * @dir: the directory name
 * @mode: permissions to use when creating the directory
 * @error: a location to store a #GError
 *
 * Create @dir recursively with permissions @mode.
 *
 * Returns: %TRUE on success, or %FALSE on error with @error filled in
 */
52 53 54 55
static gboolean
_ev_dir_ensure_exists (const gchar *dir,
                       int          mode,
                       GError     **error)
56
{
57 58 59 60 61 62
        int errsv;
        char *display_name;

        g_return_val_if_fail (dir != NULL, FALSE);

        errno = 0;
63
	if (g_mkdir_with_parents (dir, mode) == 0)
64
		return TRUE;
65

66 67 68 69 70 71 72 73 74 75
        errsv = errno;
	if (errsv == EEXIST && g_file_test (dir, G_FILE_TEST_IS_DIR))
                return TRUE;

        display_name = g_filename_display_name (dir);
        g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
                     "Failed to create directory '%s': %s",
                     display_name, g_strerror (errsv));
        g_free (display_name);

76
	return FALSE;
77 78
}

79 80 81 82 83 84 85 86 87
/*
 * _ev_tmp_dir:
 * @error: a location to store a #GError
 *
 * Returns the tmp directory.
 *
 * Returns: the tmp directory, or %NULL with @error filled in if the
 *   directory could not be created
 */
88
static const char *
89
_ev_tmp_dir (GError **error)
90
{
91 92

        if (tmp_dir == NULL) {
93 94
                gchar *dirname;
                const gchar *prgname;
95

96
                prgname = g_get_prgname ();
97 98 99 100 101
                dirname = g_strdup_printf ("%s-%u", prgname ? prgname : "unknown", getpid ());
                tmp_dir = g_build_filename (g_get_tmp_dir (), dirname, NULL);
                g_free (dirname);
        }

102
        if (!_ev_dir_ensure_exists (tmp_dir, 0700, error))
103
                return NULL;
104 105 106 107

	return tmp_dir;
}

108
void
109
_ev_file_helpers_init (void)
110 111 112 113
{
}

void
114
_ev_file_helpers_shutdown (void)
115 116
{	
	if (tmp_dir != NULL)	
117
		g_rmdir (tmp_dir);
118 119 120 121 122

	g_free (tmp_dir);
	tmp_dir = NULL;
}

123 124
/**
 * ev_mkstemp:
125
 * @tmpl: a template string; must contain 'XXXXXX', but not necessarily as a suffix
126 127 128 129 130 131 132 133 134
 * @file_name: a location to store the filename of the temp file
 * @error: a location to store a #GError
 *
 * Creates a temp file in the evince temp directory.
 *
 * Returns: a file descriptor to the newly created temp file name, or %-1
 *   on error with @error filled in
 */
int
135
ev_mkstemp (const char  *tmpl,
136 137
            char       **file_name,
            GError     **error)
138
{
139 140 141
        const char *tmp;
        char *name;
        int fd;
142

143 144 145
        if ((tmp = _ev_tmp_dir (error)) == NULL)
              return -1;

146
        name = g_build_filename (tmp, tmpl, NULL);
147
        fd = g_mkstemp_full (name, O_RDWR | O_BINARY | O_CLOEXEC, 0600);
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

        if (fd == -1) {
		int errsv = errno;

                g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
                             _("Failed to create a temporary file: %s"),
                             g_strerror (errsv));

                g_free (name);
                return -1;
        }

        if (file_name)
                *file_name = name;

        return fd;
164 165
}

166 167
static void
close_fd_cb (gpointer fdptr)
168
{
169 170 171 172 173 174 175
        int fd = GPOINTER_TO_INT (fdptr);

        close (fd);
}

/**
 * ev_mkstemp_file:
176
 * @tmpl: a template string; must contain 'XXXXXX', but not necessarily as a suffix
177 178 179 180
 * @error: a location to store a #GError
 *
 * Creates a temp #GFile in the evince temp directory. See ev_mkstemp() for more information.
 *
181
 * Returns: (transfer full): a newly allocated #GFile for the newly created temp file name, or %NULL
182 183 184
 *   on error with @error filled in
 */
GFile *
185
ev_mkstemp_file (const char        *tmpl,
186 187 188 189 190 191
                 GError           **error)
{
        char *file_name;
        int fd;
        GFile *file;

192
        fd = ev_mkstemp (tmpl, &file_name, error);
193 194 195 196 197 198 199 200 201 202
        if (fd == -1)
                return NULL;

        file = g_file_new_for_path (file_name);
        g_free (file_name);

        g_object_set_data_full (G_OBJECT (file), "ev-mkstemp-fd",
                                GINT_TO_POINTER (fd), (GDestroyNotify) close_fd_cb);

        return file;
203
}
204

205
/*
206 207 208 209 210 211
 * This function is copied from
 * http://bugzilla.gnome.org/show_bug.cgi?id=524831
 * and renamed from g_mkdtemp to _ev_g_mkdtemp.
 *
 * If/when this function gets added to glib, it can be removed from
 * evince' sources.
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
 * g_mkdtemp:
 * @tmpl: template directory name
 *
 * Creates a temporary directory. See the mkdtemp() documentation
 * on most UNIX-like systems.
 *
 * The parameter is a string that should follow the rules for
 * mkdtemp() templates, i.e. contain the string "XXXXXX".  g_mkdtemp()
 * is slightly more flexible than mkdtemp() in that the sequence does
 * not have to occur at the very end of the template. The X string
 * will be modified to form the name of a directory that didn't
 * already exist.  The string should be in the GLib file name
 * encoding. Most importantly, on Windows it should be in UTF-8.
 *
 * Return value: If a temporary directory was successfully created,
 * @tmpl will be returned with the XXXXXX string modified in such a
 * way as to make the path unique.  In case of errors, %NULL is
 * returned.
 */
static gchar *
_ev_g_mkdtemp (gchar *tmpl)
{
  static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  static const int NLETTERS = sizeof (letters) - 1;
  static int counter = 0;
  char *XXXXXX;
  GTimeVal tv;
  glong value;
  int count;

  /* find the last occurrence of "XXXXXX" */
  XXXXXX = g_strrstr (tmpl, "XXXXXX");

  if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6))
    {
      errno = EINVAL;
      return NULL;
    }

  /* Get some more or less random data.  */
  g_get_current_time (&tv);
  value = (tv.tv_usec ^ tv.tv_sec) + counter++;

  for (count = 0; count < 100; value += 7777, ++count)
    {
      glong v = value;

      /* Fill in the random bits.  */
      XXXXXX[0] = letters[v % NLETTERS];
      v /= NLETTERS;
      XXXXXX[1] = letters[v % NLETTERS];
      v /= NLETTERS;
      XXXXXX[2] = letters[v % NLETTERS];
      v /= NLETTERS;
      XXXXXX[3] = letters[v % NLETTERS];
      v /= NLETTERS;
      XXXXXX[4] = letters[v % NLETTERS];
      v /= NLETTERS;
      XXXXXX[5] = letters[v % NLETTERS];

      /* tmpl is in UTF-8 on Windows, thus use g_mkdir() */
      if (g_mkdir (tmpl, 0700) == 0)
        return tmpl;

      if (errno != EEXIST)
         /* Any other error will apply also to other names we might
         *  try, and there are 2^32 or so of them, so give up now.
         */
         return NULL;
    }

  /* We got out of the loop because we ran out of combinations to try.  */
  errno = EEXIST;
  return NULL;
}

290 291
/**
 * ev_mkdtemp:
292
 * @tmpl: a template string; must end in 'XXXXXX'
293 294 295 296 297 298 299 300
 * @error: a location to store a #GError
 *
 * Creates a temp directory in the evince temp directory.
 *
 * Returns: a newly allocated string with the temp directory name, or %NULL
 *   on error with @error filled in
 */
gchar *
301
ev_mkdtemp (const char        *tmpl,
302
            GError           **error)
303
{
304 305 306 307 308 309
        const char *tmp;
        char *name;

        if ((tmp = _ev_tmp_dir (error)) == NULL)
              return NULL;

310
        name = g_build_filename (tmp, tmpl, NULL);
311
        if (_ev_g_mkdtemp (name) == NULL) {
312 313 314 315 316 317 318 319 320 321 322
		int errsv = errno;

                g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
                             _("Failed to create a temporary directory: %s"),
                             g_strerror (errsv));

                g_free (name);
                return NULL;
        }

        return name;
323 324
}

325 326 327 328 329 330 331
/* Remove a local temp file created by evince */
void
ev_tmp_filename_unlink (const gchar *filename)
{
	if (!filename)
		return;

332 333 334 335
	if (!tmp_dir)
		return;

	if (g_str_has_prefix (filename, tmp_dir)) {
336 337 338 339
		g_unlink (filename);
	}
}

340 341 342 343
void
ev_tmp_file_unlink (GFile *file)
{
	gboolean res;
344
	GError  *error = NULL;
345 346 347 348

	if (!file)
		return;
	
349
	res = g_file_delete (file, NULL, &error);
350 351 352 353
	if (!res) {
		char *uri;
		
		uri = g_file_get_uri (file);
354
		g_warning ("Unable to delete temp file %s: %s\n", uri, error->message);
355
		g_free (uri);
356
		g_error_free (error);
357 358 359
	}
}

360 361 362
void
ev_tmp_uri_unlink (const gchar *uri)
{
363
	GFile *file;
364 365 366 367
	
	if (!uri)
		return;
	
368 369 370 371
	file = g_file_new_for_uri (uri);
	if (!g_file_is_native (file)) {
		g_warning ("Attempting to delete non native uri: %s\n", uri);
		g_object_unref (file);
372 373
		return;
	}
374 375 376
	
	ev_tmp_file_unlink (file);
	g_object_unref (file);
377 378
}

379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
gboolean
ev_file_is_temp (GFile *file)
{
	gchar   *path;
	gboolean retval;

	if (!g_file_is_native (file))
		return FALSE;

	path = g_file_get_path (file);
	if (!path)
		return FALSE;

	retval = g_str_has_prefix (path, g_get_tmp_dir ());
	g_free (path);

	return retval;
}

Christian Persch's avatar
Christian Persch committed
398 399 400 401 402 403 404 405 406 407
/**
 * ev_xfer_uri_simple:
 * @from: the source URI
 * @to: the target URI
 * @error: a #GError location to store an error, or %NULL
 *
 * Performs a g_file_copy() from @from to @to.
 *
 * Returns: %TRUE on success, or %FALSE on error with @error filled in
 */
408 409 410 411 412
gboolean
ev_xfer_uri_simple (const char *from,
		    const char *to,
		    GError     **error)
{
413 414 415
	GFile *source_file;
	GFile *target_file;
	gboolean result;
416 417
	
	if (!from)
Christian Persch's avatar
Christian Persch committed
418 419 420 421
		return TRUE;

        g_return_val_if_fail (to != NULL, TRUE);

422 423 424 425
	source_file = g_file_new_for_uri (from);
	target_file = g_file_new_for_uri (to);
	
	result = g_file_copy (source_file, target_file,
426
			      G_FILE_COPY_TARGET_DEFAULT_PERMS |
427
			      G_FILE_COPY_OVERWRITE,
Christian Persch's avatar
Christian Persch committed
428
			      NULL, NULL, NULL, error);
429 430 431

	g_object_unref (target_file);
	g_object_unref (source_file);
432
    
433
	return result;
434
}
435

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
/**
 * ev_file_copy_metadata:
 * @from: the source URI
 * @to: the target URI
 * @error: a #GError location to store an error, or %NULL
 *
 * Performs a g_file_copy_attributes() with %G_FILE_COPY_ALL_METADATA
 * from @from to @to.
 *
 * Returns: %TRUE if the attributes were copied successfully, %FALSE otherwise.
 *
 * Since: 3.4
 */
gboolean
ev_file_copy_metadata (const char *from,
                       const char *to,
                       GError     **error)
{
        GFile *source_file;
        GFile *target_file;
        gboolean result;

        g_return_val_if_fail (from != NULL, FALSE);
        g_return_val_if_fail (to != NULL, FALSE);

        source_file = g_file_new_for_uri (from);
        target_file = g_file_new_for_uri (to);

        result = g_file_copy_attributes (source_file, target_file,
465 466
                                         G_FILE_COPY_ALL_METADATA |
                                         G_FILE_COPY_TARGET_DEFAULT_PERMS,
467 468 469 470 471 472 473 474
                                         NULL, error);

        g_object_unref (target_file);
        g_object_unref (source_file);

        return result;
}

475 476 477
static gchar *
get_mime_type_from_uri (const gchar *uri, GError **error)
{
478 479 480
	GFile       *file;
	GFileInfo   *file_info;
	const gchar *content_type;
Christian Persch's avatar
Christian Persch committed
481
        gchar       *mime_type = NULL;
482 483 484 485 486 487 488 489 490 491

	file = g_file_new_for_uri (uri);
	file_info = g_file_query_info (file,
				       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
				       0, NULL, error);
	g_object_unref (file);

	if (file_info == NULL)
		return NULL;

492
	content_type = g_file_info_get_content_type (file_info);
493
	if (content_type != NULL) {
Christian Persch's avatar
Christian Persch committed
494 495
                mime_type = g_content_type_get_mime_type (content_type);
        }
496 497 498 499
        if (mime_type == NULL) {
                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                                     _("Unknown MIME Type"));
        }
500

Christian Persch's avatar
Christian Persch committed
501 502
	g_object_unref (file_info);
	return mime_type;
503 504 505 506 507 508 509 510 511
}

static gchar *
get_mime_type_from_data (const gchar *uri, GError **error)
{
	GFile            *file;
	GFileInputStream *input_stream;
	gssize            size_read;
	guchar            buffer[1024];
512
	gboolean          retval;
513 514
	gchar            *content_type;
        gchar            *mime_type = NULL;
515 516 517 518 519 520 521 522 523 524

	file = g_file_new_for_uri (uri);
	
	input_stream = g_file_read (file, NULL, error);
	if (!input_stream) {
		g_object_unref (file);
		return NULL;
	}

	size_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
525 526
					 buffer, sizeof (buffer), NULL, error);
	if (size_read == -1) {
527
		g_object_unref (input_stream);
528 529 530
		g_object_unref (file);
		return NULL;
	}
531

532
	retval = g_input_stream_close (G_INPUT_STREAM (input_stream), NULL, error);
533 534

	g_object_unref (input_stream);
535
	g_object_unref (file);
536
	if (!retval)
537 538
		return NULL;

539 540 541
	content_type = g_content_type_guess (NULL, /* no filename */
					     buffer, size_read,
					     NULL);
542 543 544 545 546
        if (content_type == NULL) {
                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                                     _("Unknown MIME Type"));
                return NULL;
        }
547

548 549 550 551 552 553 554 555
#ifndef G_OS_WIN32
       /* On Windows, the implementation of g_content_type_guess() is
        * sometimes too limited, so we do use get_mime_type_from_uri()
        * as a fallback */
       if (strcmp (content_type, "*") == 0) {
               g_free (content_type);
               return get_mime_type_from_uri (uri, error);
       }
556 557
#endif /* G_OS_WIN32 */

558 559 560 561 562 563 564 565
        mime_type = g_content_type_get_mime_type (content_type);
        g_free (content_type);

        if (mime_type == NULL) {
                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                                     _("Unknown MIME Type"));
        }

566
	return mime_type;
567 568
}

Christian Persch's avatar
Christian Persch committed
569 570 571 572 573 574 575 576 577
/**
 * ev_file_get_mime_type:
 * @uri: the URI
 * @fast: whether to use fast MIME type detection
 * @error: a #GError location to store an error, or %NULL
 *
 * Returns: a newly allocated string with the MIME type of the file at
 *   @uri, or %NULL on error or if the MIME type could not be determined
 */
578 579 580 581 582 583 584 585
gchar *
ev_file_get_mime_type (const gchar *uri,
		       gboolean     fast,
		       GError     **error)
{
	return fast ? get_mime_type_from_uri (uri, error) : get_mime_type_from_data (uri, error);
}

586
/* Compressed files support */
587 588 589 590 591 592 593 594

static const char *compressor_cmds[] = {
  NULL,
  "bzip2",
  "gzip",
  "xz"
};

595
#define N_ARGS      4
596 597
#define BUFFER_SIZE 1024

598 599 600 601 602 603 604 605 606 607 608 609 610
static void
compression_child_setup_cb (gpointer fd_ptr)
{
        int fd = GPOINTER_TO_INT (fd_ptr);
        int flags;

        flags = fcntl (fd, F_GETFD);
        if (flags >= 0 && (flags & FD_CLOEXEC)) {
                flags &= ~FD_CLOEXEC;
                fcntl (fd, F_SETFD, flags);
        }
}

611 612 613 614 615
static gchar *
compression_run (const gchar       *uri,
		 EvCompressionType  type,
		 gboolean           compress, 
		 GError           **error)
616 617
{
	gchar *argv[N_ARGS];
618
	gchar *uri_dst = NULL;
619
	gchar *filename, *filename_dst = NULL;
620 621
	gchar *cmd;
	gint   fd, pout;
622
	GError *err = NULL;
623 624 625 626

	if (type == EV_COMPRESSION_NONE)
		return NULL;

627
	cmd = g_find_program_in_path (compressor_cmds[type]);
628 629 630 631 632
	if (!cmd) {
		/* FIXME: better error codes! */
		/* FIXME: i18n later */
		g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
			     "Failed to find the \"%s\" command in the search path.",
633
                             compressor_cmds[type]);
634
		return NULL;
635
	}
636

637
	filename = g_filename_from_uri (uri, NULL, error);
638 639 640 641
	if (!filename) {
		g_free (cmd);
		return NULL;
	}
642

643 644
        fd = ev_mkstemp ("comp.XXXXXX", &filename_dst, error);
	if (fd == -1) {
645 646
		g_free (cmd);
		g_free (filename);
647

648 649
		return NULL;
	}
650

651
	argv[0] = cmd;
652
	argv[1] = compress ? (char *) "-c" : (char *) "-cd";
653
	argv[2] = filename;
654
	argv[3] = NULL;
655 656 657

	if (g_spawn_async_with_pipes (NULL, argv, NULL,
				      G_SPAWN_STDERR_TO_DEV_NULL,
658 659
                                      compression_child_setup_cb, GINT_TO_POINTER (fd),
                                      NULL,
660
				      NULL, &pout, NULL, &err)) {
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
		GIOChannel *in, *out;
		gchar buf[BUFFER_SIZE];
		GIOStatus read_st, write_st;
		gsize bytes_read, bytes_written;

		in = g_io_channel_unix_new (pout);
		g_io_channel_set_encoding (in, NULL, NULL);
		out = g_io_channel_unix_new (fd);
		g_io_channel_set_encoding (out, NULL, NULL);

		do {
			read_st = g_io_channel_read_chars (in, buf,
							   BUFFER_SIZE,
							   &bytes_read,
							   error);
			if (read_st == G_IO_STATUS_NORMAL) {
				write_st = g_io_channel_write_chars (out, buf,
								     bytes_read,
								     &bytes_written,
								     error);
				if (write_st == G_IO_STATUS_ERROR)
					break;
			} else if (read_st == G_IO_STATUS_ERROR) {
				break;
			}
		} while (bytes_read > 0);
687

688 689 690 691 692 693
		g_io_channel_unref (in);
		g_io_channel_unref (out);
	}

	close (fd);

694 695 696 697
	if (err) {
		g_propagate_error (error, err);
	} else {
		uri_dst = g_filename_to_uri (filename_dst, NULL, error);
698 699 700 701
	}

	g_free (cmd);
	g_free (filename);
702 703 704 705 706
	g_free (filename_dst);

	return uri_dst;
}

Christian Persch's avatar
Christian Persch committed
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
/**
 * ev_file_uncompress:
 * @uri: a file URI
 * @type: the compression type
 * @error: a #GError location to store an error, or %NULL
 *
 * Uncompresses the file at @uri.
 *
 * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
 *
 * Otherwise, it returns the filename of a
 * temporary file containing the decompressed data from the file at @uri.
 * On error it returns %NULL and fills in @error.
 *
 * It is the caller's responsibility to unlink the temp file after use.
 *
 * Returns: a newly allocated string URI, or %NULL on error
 */
725 726 727 728 729 730 731 732 733 734
gchar *
ev_file_uncompress (const gchar       *uri,
		    EvCompressionType  type,
		    GError           **error)
{
	g_return_val_if_fail (uri != NULL, NULL);

	return compression_run (uri, type, FALSE, error);
}

Christian Persch's avatar
Christian Persch committed
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
/**
 * ev_file_compress:
 * @uri: a file URI
 * @type: the compression type
 * @error: a #GError location to store an error, or %NULL
 *
 * Compresses the file at @uri.
 
 * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
 *
 * Otherwise, it returns the filename of a
 * temporary file containing the compressed data from the file at @uri.
 *
 * On error it returns %NULL and fills in @error.
 *
 * It is the caller's responsibility to unlink the temp file after use.
 *
 * Returns: a newly allocated string URI, or %NULL on error
 */
754 755 756 757 758 759
gchar *
ev_file_compress (const gchar       *uri,
		  EvCompressionType  type,
		  GError           **error)
{
	g_return_val_if_fail (uri != NULL, NULL);
760

761
	return compression_run (uri, type, TRUE, error);
762
}