Gimp/plug-ins/common/file-compressor.c
Jehan c548d5d342 Bug 792657 - A useless error message when cancelling opening a .svgz.
Use the new gimp_get_pdb_status() to forward the error returned by
gimp_file_load(). Previous code was always returning
GIMP_PDB_EXECUTION_ERROR when the file load was failing, but this was
not granular enough. In particular when the file load is actually
interactively cancelled through Esc or the "Cancel" button, we don't
want to display an error message on screen. Therefore we forward the
actual error raised by the underlining plug-in.
2018-01-19 14:20:40 +01:00

1017 lines
25 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
* Copyright (C) 1997 Daniel Risacher, magnus@alum.mit.edu
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Minor changes to support file magic */
/* 4 Oct 1997 -- Risacher */
/* compressor plug-in for GIMP */
/* based on gz.c which in turn is */
/* loosley based on url.c by */
/* Josh MacDonald, jmacd@cs.berkeley.edu */
/* and, very loosely on hrz.c by */
/* Albert Cahalan <acahalan at cs.uml.edu> */
/* LZMA compression code is based on code by Lasse Collin which was
* placed in the public-domain. */
/* This is reads and writes compressed image files for GIMP
*
* It should work with file names of the form
* filename.foo.[gz|bz2] where foo is some already-recognized extension
*
* and it also works for names of the form
* filename.xcf[gz|bz2] - which is equivalent to
* filename.xcf.[gz|bz2]
*
* I added the xcfgz bit because having a default extension of xcf.gz
* can confuse the file selection dialog box somewhat, forcing the
* user to type sometimes when he/she otherwise wouldn't need to.
*
* I later decided I didn't like it because I don't like to bloat
* the file-extension namespace. But I left in the recognition
* feature/bug so if people want to use files named foo.xcfgz by
* default, they can just hack their pluginrc file.
*
* to do this hack, change :
* "xcf.gz,gz,xcfgz"
* to
* "xcfgz,gz,xcf.gz"
*
*
* -Dan Risacher, 0430 CDT, 26 May 1997
*/
#include "config.h"
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <glib/gstdio.h>
#ifndef _O_BINARY
#define _O_BINARY 0
#endif
#include <libgimp/gimp.h>
#include "libgimp/stdplugins-intl.h"
#include <zlib.h>
#include <bzlib.h>
#include <lzma.h>
/* Author 1: Josh MacDonald (url.c) */
/* Author 2: Daniel Risacher (gz.c) */
/* Author 3: Michael Natterer (compressor.c) */
/* According to USAF Lt Steve Werhle, US DoD software development
* contracts average about $25 USD per source line of code (SLOC). By
* that metric, I figure this plug-in is worth about $10,000 USD */
/* But you got it free. Magic of Gnu. */
typedef gboolean (*LoadFn) (const char *infile,
const char *outfile);
typedef gboolean (*SaveFn) (const char *infile,
const char *outfile);
typedef struct _Compressor Compressor;
struct _Compressor
{
const gchar *file_type;
const gchar *mime_type;
const gchar *extensions;
const gchar *magic;
const gchar *xcf_extension;
const gchar *generic_extension;
const gchar *load_proc;
const gchar *load_blurb;
const gchar *load_help;
LoadFn load_fn;
const gchar *save_proc;
const gchar *save_blurb;
const gchar *save_help;
SaveFn save_fn;
};
static void query (void);
static void run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals);
static GimpPDBStatusType save_image (const Compressor *compressor,
const gchar *filename,
gint32 image_ID,
gint32 drawable_ID,
gint32 run_mode,
GError **error);
static gint32 load_image (const Compressor *compressor,
const gchar *filename,
gint32 run_mode,
GimpPDBStatusType *status,
GError **error);
static gboolean valid_file (const gchar *filename);
static const gchar * find_extension (const Compressor *compressor,
const gchar *filename);
static gboolean gzip_load (const char *infile,
const char *outfile);
static gboolean gzip_save (const char *infile,
const char *outfile);
static gboolean bzip2_load (const char *infile,
const char *outfile);
static gboolean bzip2_save (const char *infile,
const char *outfile);
static gboolean xz_load (const char *infile,
const char *outfile);
static gboolean xz_save (const char *infile,
const char *outfile);
static goffset get_file_info (const gchar *filename);
static const Compressor compressors[] =
{
{
N_("gzip archive"),
"application/x-gzip",
"xcf.gz,xcfgz", /* FIXME "xcf.gz,gz,xcfgz" */
"0,string,\037\213",
".xcfgz",
".gz",
"file-gz-load",
"loads files compressed with gzip",
"This procedure loads files in the gzip compressed format.",
gzip_load,
"file-gz-save",
"saves files compressed with gzip",
"This procedure saves files in the gzip compressed format.",
gzip_save
},
{
N_("bzip archive"),
"application/x-bzip",
"xcf.bz2,xcfbz2", /* FIXME "xcf.bz2,bz2,xcfbz2" */
"0,string,BZh",
".xcfbz2",
".bz2",
"file-bz2-load",
"loads files compressed with bzip2",
"This procedure loads files in the bzip2 compressed format.",
bzip2_load,
"file-bz2-save",
"saves files compressed with bzip2",
"This procedure saves files in the bzip2 compressed format.",
bzip2_save
},
{
N_("xz archive"),
"application/x-xz",
"xcf.xz,xcfxz", /* FIXME "xcf.xz,xz,xcfxz" */
"0,string,\3757zXZ\x00",
".xcfxz",
".xz",
"file-xz-load",
"loads files compressed with xz",
"This procedure loads files in the xz compressed format.",
xz_load,
"file-xz-save",
"saves files compressed with xz",
"This procedure saves files in the xz compressed format.",
xz_save
}
};
const GimpPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
MAIN ()
static void
query (void)
{
static const GimpParamDef load_args[] =
{
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
{ GIMP_PDB_STRING, "filename", "The name of the file to load" },
{ GIMP_PDB_STRING, "raw-filename", "The name entered" }
};
static const GimpParamDef load_return_vals[] =
{
{ GIMP_PDB_IMAGE, "image", "Output image" },
};
static const GimpParamDef save_args[] =
{
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
{ GIMP_PDB_IMAGE, "image", "Input image" },
{ GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" },
{ GIMP_PDB_STRING, "filename", "The name of the file to "
"save the image in" },
{ GIMP_PDB_STRING, "raw-filename", "The name entered" },
};
gint i;
for (i = 0; i < G_N_ELEMENTS (compressors); i++)
{
const Compressor *compressor = &compressors[i];
gimp_install_procedure (compressor->load_proc,
compressor->load_blurb,
compressor->load_help,
"Daniel Risacher",
"Daniel Risacher, Spencer Kimball and Peter Mattis",
"1995-1997",
compressor->file_type,
NULL,
GIMP_PLUGIN,
G_N_ELEMENTS (load_args),
G_N_ELEMENTS (load_return_vals),
load_args, load_return_vals);
gimp_register_file_handler_mime (compressor->load_proc,
compressor->mime_type);
gimp_register_magic_load_handler (compressor->load_proc,
compressor->extensions,
"",
compressor->magic);
gimp_install_procedure (compressor->save_proc,
compressor->save_blurb,
compressor->save_help,
"Daniel Risacher",
"Daniel Risacher, Spencer Kimball and Peter Mattis",
"1995-1997",
compressor->file_type,
"RGB*, GRAY*, INDEXED*",
GIMP_PLUGIN,
G_N_ELEMENTS (save_args), 0,
save_args, NULL);
gimp_register_file_handler_mime (compressor->save_proc,
compressor->mime_type);
gimp_register_save_handler (compressor->save_proc,
compressor->extensions, "");
}
}
static void
run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals)
{
static GimpParam values[2];
GimpRunMode run_mode;
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
GError *error = NULL;
gint32 image_ID;
gint i;
run_mode = param[0].data.d_int32;
INIT_I18N();
*nreturn_vals = 1;
*return_vals = values;
values[0].type = GIMP_PDB_STATUS;
values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
/* We handle PDB errors by forwarding them to the caller in
* our return values.
*/
gimp_plugin_set_pdb_error_handler (GIMP_PDB_ERROR_HANDLER_PLUGIN);
for (i = 0; i < G_N_ELEMENTS (compressors); i++)
{
const Compressor *compressor = &compressors[i];
if (! strcmp (name, compressor->load_proc))
{
image_ID = load_image (compressor,
param[1].data.d_string,
param[0].data.d_int32,
&status, &error);
if (image_ID != -1 && status == GIMP_PDB_SUCCESS)
{
*nreturn_vals = 2;
values[1].type = GIMP_PDB_IMAGE;
values[1].data.d_image = image_ID;
}
break;
}
else if (! strcmp (name, compressor->save_proc))
{
switch (run_mode)
{
case GIMP_RUN_INTERACTIVE:
break;
case GIMP_RUN_NONINTERACTIVE:
/* Make sure all the arguments are there! */
if (nparams != 5)
status = GIMP_PDB_CALLING_ERROR;
break;
case GIMP_RUN_WITH_LAST_VALS:
break;
default:
break;
}
if (status == GIMP_PDB_SUCCESS)
status = save_image (compressor,
param[3].data.d_string,
param[1].data.d_int32,
param[2].data.d_int32,
param[0].data.d_int32,
&error);
break;
}
}
if (i == G_N_ELEMENTS (compressors))
status = GIMP_PDB_CALLING_ERROR;
if (status != GIMP_PDB_SUCCESS && error)
{
*nreturn_vals = 2;
values[1].type = GIMP_PDB_STRING;
values[1].data.d_string = error->message;
}
values[0].data.d_status = status;
}
static GimpPDBStatusType
save_image (const Compressor *compressor,
const gchar *filename,
gint32 image_ID,
gint32 drawable_ID,
gint32 run_mode,
GError **error)
{
const gchar *ext;
gchar *tmpname;
ext = find_extension (compressor, filename);
if (! ext)
{
g_message (_("No sensible file extension, saving as compressed XCF."));
ext = ".xcf";
}
/* get a temp name with the right extension and save into it. */
tmpname = gimp_temp_name (ext + 1);
if (! (gimp_file_save (run_mode,
image_ID,
drawable_ID,
tmpname,
tmpname) && valid_file (tmpname)))
{
g_unlink (tmpname);
g_free (tmpname);
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"%s", gimp_get_pdb_error ());
return GIMP_PDB_EXECUTION_ERROR;
}
gimp_progress_init_printf (_("Compressing '%s'"),
gimp_filename_to_utf8 (filename));
if (!compressor->save_fn (tmpname, filename))
{
g_unlink (tmpname);
g_free (tmpname);
return GIMP_PDB_EXECUTION_ERROR;
}
g_unlink (tmpname);
gimp_progress_update (1.0);
g_free (tmpname);
/* ask the core to save a thumbnail for compressed XCF files */
if (strcmp (ext, ".xcf") == 0)
gimp_file_save_thumbnail (image_ID, filename);
return GIMP_PDB_SUCCESS;
}
static gint32
load_image (const Compressor *compressor,
const gchar *filename,
gint32 run_mode,
GimpPDBStatusType *status,
GError **error)
{
gint32 image_ID;
const gchar *ext;
gchar *tmpname;
ext = find_extension (compressor, filename);
if (! ext)
{
g_message (_("No sensible file extension, "
"attempting to load with file magic."));
ext = ".foo";
}
/* find a temp name */
tmpname = gimp_temp_name (ext + 1);
if (!compressor->load_fn (filename, tmpname))
{
g_free (tmpname);
*status = GIMP_PDB_EXECUTION_ERROR;
return -1;
}
/* now that we uncompressed it, load the temp file */
image_ID = gimp_file_load (run_mode, tmpname, tmpname);
g_unlink (tmpname);
g_free (tmpname);
if (image_ID != -1)
{
*status = GIMP_PDB_SUCCESS;
gimp_image_set_filename (image_ID, filename);
}
else
{
/* Forward the return status of the underlining plug-in for the
* given format.
*/
*status = gimp_get_pdb_status ();
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"%s", gimp_get_pdb_error ());
}
return image_ID;
}
static gboolean
valid_file (const gchar *filename)
{
struct stat buf;
return g_stat (filename, &buf) == 0 && buf.st_size > 0;
}
static const gchar *
find_extension (const Compressor *compressor,
const gchar *filename)
{
gchar *filename_copy;
gchar *ext;
/* we never free this copy - aren't we evil! */
filename_copy = g_strdup (filename);
/* find the extension, boy! */
ext = strrchr (filename_copy, '.');
while (TRUE)
{
if (!ext || ext[1] == '\0' || strchr (ext, G_DIR_SEPARATOR))
{
return NULL;
}
if (0 == g_ascii_strcasecmp (ext, compressor->xcf_extension))
{
return ".xcf"; /* we've found it */
}
if (0 != g_ascii_strcasecmp (ext, compressor->generic_extension))
{
return ext;
}
else
{
/* we found ".gz" so strip it, loop back, and look again */
*ext = '\0';
ext = strrchr (filename_copy, '.');
}
}
}
static gboolean
gzip_load (const char *infile,
const char *outfile)
{
gboolean ret;
int fd;
gzFile in;
FILE *out;
char buf[16384];
int len;
ret = FALSE;
in = NULL;
out = NULL;
fd = g_open (infile, O_RDONLY | _O_BINARY, 0);
if (fd == -1)
goto out;
in = gzdopen (fd, "rb");
if (!in)
{
close (fd);
goto out;
}
out = g_fopen (outfile, "wb");
if (!out)
goto out;
while (TRUE)
{
len = gzread (in, buf, sizeof buf);
if (len < 0)
break;
else if (len == 0)
{
ret = TRUE;
break;
}
if (fwrite(buf, 1, len, out) != len)
break;
}
out:
/* There is no need to close(fd) as it is closed by gzclose(). */
if (in)
if (gzclose (in) != Z_OK)
ret = FALSE;
if (out)
fclose (out);
return ret;
}
static gboolean
gzip_save (const char *infile,
const char *outfile)
{
gboolean ret;
FILE *in;
int fd;
gzFile out;
char buf[16384];
int len;
goffset tot = 0, file_size;
ret = FALSE;
in = NULL;
out = NULL;
in = g_fopen (infile, "rb");
if (!in)
goto out;
fd = g_open (outfile, O_CREAT | O_WRONLY | O_TRUNC | _O_BINARY, 0664);
if (fd == -1)
goto out;
out = gzdopen (fd, "wb");
if (!out)
{
close (fd);
goto out;
}
file_size = get_file_info (infile);
while (TRUE)
{
len = fread (buf, 1, sizeof buf, in);
if (ferror (in))
break;
if (len < 0)
break;
else if (len == 0)
{
ret = TRUE;
break;
}
if (gzwrite (out, buf, len) != len)
break;
gimp_progress_update ((tot += len) * 1.0 / file_size);
}
out:
if (in)
fclose (in);
/* There is no need to close(fd) as it is closed by gzclose(). */
if (out)
if (gzclose (out) != Z_OK)
ret = FALSE;
return ret;
}
static gboolean
bzip2_load (const char *infile,
const char *outfile)
{
gboolean ret;
int fd;
BZFILE *in;
FILE *out;
char buf[16384];
int len;
ret = FALSE;
in = NULL;
out = NULL;
fd = g_open (infile, O_RDONLY | _O_BINARY, 0);
if (fd == -1)
goto out;
in = BZ2_bzdopen (fd, "rb");
if (!in)
{
close (fd);
goto out;
}
out = g_fopen (outfile, "wb");
if (!out)
goto out;
while (TRUE)
{
len = BZ2_bzread (in, buf, sizeof buf);
if (len < 0)
break;
else if (len == 0)
{
ret = TRUE;
break;
}
if (fwrite(buf, 1, len, out) != len)
break;
}
out:
/* TODO: Check this in the case of BZ2_bzclose(): */
/* There is no need to close(fd) as it is closed by BZ2_bzclose(). */
if (in)
BZ2_bzclose (in);
if (out)
fclose (out);
return ret;
}
static gboolean
bzip2_save (const char *infile,
const char *outfile)
{
gboolean ret;
FILE *in;
int fd;
BZFILE *out;
char buf[16384];
int len;
goffset tot = 0, file_size;
ret = FALSE;
in = NULL;
out = NULL;
in = g_fopen (infile, "rb");
if (!in)
goto out;
fd = g_open (outfile, O_CREAT | O_WRONLY | O_TRUNC | _O_BINARY, 0664);
if (fd == -1)
goto out;
out = BZ2_bzdopen (fd, "wb");
if (!out)
{
close (fd);
goto out;
}
file_size = get_file_info (infile);
while (TRUE)
{
len = fread (buf, 1, sizeof buf, in);
if (ferror (in))
break;
if (len < 0)
break;
else if (len == 0)
{
ret = TRUE;
break;
}
if (BZ2_bzwrite (out, buf, len) != len)
break;
gimp_progress_update ((tot += len) * 1.0 / file_size);
}
out:
if (in)
fclose (in);
/* TODO: Check this in the case of BZ2_bzclose(): */
/* There is no need to close(fd) as it is closed by BZ2_bzclose(). */
if (out)
BZ2_bzclose (out);
return ret;
}
static gboolean
xz_load (const char *infile,
const char *outfile)
{
gboolean ret;
FILE *in;
FILE *out;
lzma_stream strm = LZMA_STREAM_INIT;
lzma_action action;
guint8 inbuf[BUFSIZ];
guint8 outbuf[BUFSIZ];
lzma_ret status;
ret = FALSE;
in = NULL;
out = NULL;
in = g_fopen (infile, "rb");
if (!in)
goto out;
out = g_fopen (outfile, "wb");
if (!out)
goto out;
if (lzma_stream_decoder (&strm, UINT64_MAX, 0) != LZMA_OK)
goto out;
strm.next_in = NULL;
strm.avail_in = 0;
strm.next_out = outbuf;
strm.avail_out = sizeof outbuf;
action = LZMA_RUN;
status = LZMA_OK;
while (status == LZMA_OK)
{
/* Fill the input buffer if it is empty. */
if ((strm.avail_in == 0) && (!feof(in)))
{
strm.next_in = inbuf;
strm.avail_in = fread (inbuf, 1, sizeof inbuf, in);
if (ferror (in))
goto out;
/* Once the end of the input file has been reached, we need to
tell lzma_code() that no more input will be coming and that
it should finish the encoding. */
if (feof (in))
action = LZMA_FINISH;
}
status = lzma_code (&strm, action);
if ((strm.avail_out == 0) || (status == LZMA_STREAM_END))
{
/* When lzma_code() has returned LZMA_STREAM_END, the output
buffer is likely to be only partially full. Calculate how
much new data there is to be written to the output file. */
size_t write_size = sizeof outbuf - strm.avail_out;
if (fwrite (outbuf, 1, write_size, out) != write_size)
goto out;
/* Reset next_out and avail_out. */
strm.next_out = outbuf;
strm.avail_out = sizeof outbuf;
}
}
if (status != LZMA_STREAM_END)
goto out;
lzma_end (&strm);
ret = TRUE;
out:
if (in)
fclose (in);
if (out)
fclose (out);
return ret;
}
static gboolean
xz_save (const char *infile,
const char *outfile)
{
gboolean ret;
FILE *in;
FILE *out;
lzma_stream strm = LZMA_STREAM_INIT;
lzma_action action;
guint8 inbuf[BUFSIZ];
guint8 outbuf[BUFSIZ];
lzma_ret status;
goffset tot = 0, file_size;
ret = FALSE;
in = NULL;
out = NULL;
in = g_fopen (infile, "rb");
if (!in)
goto out;
file_size = get_file_info (infile);
out = g_fopen (outfile, "wb");
if (!out)
goto out;
if (lzma_easy_encoder (&strm,
LZMA_PRESET_DEFAULT,
LZMA_CHECK_CRC64) != LZMA_OK)
goto out;
strm.next_in = NULL;
strm.avail_in = 0;
strm.next_out = outbuf;
strm.avail_out = sizeof outbuf;
action = LZMA_RUN;
status = LZMA_OK;
while (status == LZMA_OK)
{
/* Fill the input buffer if it is empty. */
if ((strm.avail_in == 0) && (!feof(in)))
{
strm.next_in = inbuf;
strm.avail_in = fread (inbuf, 1, sizeof inbuf, in);
if (ferror (in))
goto out;
/* Once the end of the input file has been reached, we need to
tell lzma_code() that no more input will be coming and that
it should finish the encoding. */
if (feof (in))
action = LZMA_FINISH;
gimp_progress_update ((tot += strm.avail_in) * 1.0 / file_size);
}
status = lzma_code (&strm, action);
if ((strm.avail_out == 0) || (status == LZMA_STREAM_END))
{
/* When lzma_code() has returned LZMA_STREAM_END, the output
buffer is likely to be only partially full. Calculate how
much new data there is to be written to the output file. */
size_t write_size = sizeof outbuf - strm.avail_out;
if (fwrite (outbuf, 1, write_size, out) != write_size)
goto out;
/* Reset next_out and avail_out. */
strm.next_out = outbuf;
strm.avail_out = sizeof outbuf;
}
}
if (status != LZMA_STREAM_END)
goto out;
lzma_end (&strm);
ret = TRUE;
out:
if (in)
fclose (in);
if (out)
fclose (out);
return ret;
}
/* get file size from a filename */
static goffset
get_file_info (const gchar *filename)
{
GFile *file = g_file_new_for_path (filename);
GFileInfo *info;
goffset size = 1;
info = g_file_query_info (file,
G_FILE_ATTRIBUTE_STANDARD_SIZE,
G_FILE_QUERY_INFO_NONE,
NULL, NULL);
if (info)
{
size = g_file_info_get_size (info);
g_object_unref (info);
}
g_object_unref (file);
return size;
}