mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@12096 57a11ea4-9604-0410-9ed3-97b8803252fd
402 lines
9.4 KiB
C
402 lines
9.4 KiB
C
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/*
|
|
* gsf-output-gzip.c: wrapper to compress to gzipped output. See rfc1952.
|
|
*
|
|
* Copyright (C) 2002-2004 Jon K Hellan (hellan@acm.org)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2.1 of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation.
|
|
*
|
|
* 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 Lesser General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
|
|
* USA
|
|
*/
|
|
|
|
#include <gsf-config.h>
|
|
#include <gsf/gsf-output-gzip.h>
|
|
#include <gsf/gsf-output-impl.h>
|
|
#include <gsf/gsf-impl-utils.h>
|
|
#include <gsf/gsf-utils.h>
|
|
|
|
#include <zlib.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
|
|
static GObjectClass *parent_class;
|
|
|
|
struct _GsfOutputGZip {
|
|
GsfOutput output;
|
|
|
|
GsfOutput *sink; /* compressed data */
|
|
gboolean raw; /* No header and no trailer. */
|
|
|
|
z_stream stream;
|
|
uLong crc; /* crc32 of uncompressed data */
|
|
size_t isize;
|
|
|
|
guint8 *buf;
|
|
size_t buf_size;
|
|
};
|
|
|
|
typedef struct {
|
|
GsfOutputClass output_class;
|
|
} GsfOutputGZipClass;
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_RAW,
|
|
PROP_SINK
|
|
};
|
|
|
|
|
|
/* gzip flag byte */
|
|
#define GZIP_ORIGINAL_NAME 0x08 /* the original is stored */
|
|
|
|
static gboolean
|
|
init_gzip (GsfOutputGZip *gzip)
|
|
{
|
|
int ret;
|
|
|
|
ret = deflateInit2 (&gzip->stream, Z_DEFAULT_COMPRESSION,
|
|
Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL,
|
|
Z_DEFAULT_STRATEGY);
|
|
if (ret != Z_OK)
|
|
return FALSE;
|
|
|
|
if (!gzip->buf) {
|
|
gzip->buf_size = 0x100;
|
|
gzip->buf = g_new (guint8, gzip->buf_size);
|
|
}
|
|
gzip->stream.next_out = gzip->buf;
|
|
gzip->stream.avail_out = gzip->buf_size;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gzip_output_header (GsfOutputGZip *gzip)
|
|
{
|
|
guint8 buf[3 + 1 + 4 + 2];
|
|
static guint8 const gzip_signature[] = { 0x1f, 0x8b, 0x08 } ;
|
|
time_t mtime = time (NULL);
|
|
char const *name = gsf_output_name (gzip->sink);
|
|
/* FIXME: What to do about gz extension ... ? */
|
|
int nlen = 0; /* name ? strlen (name) : 0; */
|
|
gboolean ret;
|
|
|
|
memset (buf, 0, sizeof buf);
|
|
memcpy (buf, gzip_signature, 3);
|
|
if (nlen > 0)
|
|
buf[3] = GZIP_ORIGINAL_NAME;
|
|
GSF_LE_SET_GUINT32 (buf + 4, (guint32) mtime);
|
|
buf[9] = 3; /* UNIX */
|
|
ret = gsf_output_write (gzip->sink, sizeof buf, buf);
|
|
if (ret && name && nlen > 0)
|
|
ret = gsf_output_write (gzip->sink, nlen, name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gsf_output_gzip_new :
|
|
* @sink : The underlying data source.
|
|
* @err : optionally NULL.
|
|
*
|
|
* Adds a reference to @sink.
|
|
*
|
|
* Returns a new file or NULL.
|
|
**/
|
|
GsfOutput *
|
|
gsf_output_gzip_new (GsfOutput *sink, GError **err)
|
|
{
|
|
GsfOutput *output;
|
|
const GError *con_err;
|
|
|
|
g_return_val_if_fail (GSF_IS_OUTPUT (sink), NULL);
|
|
|
|
output = g_object_new (GSF_OUTPUT_GZIP_TYPE, "sink", sink, NULL);
|
|
con_err = gsf_output_error (output);
|
|
|
|
if (con_err) {
|
|
if (err)
|
|
*err = g_error_copy (con_err);
|
|
g_object_unref (output);
|
|
return NULL;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
static void
|
|
gsf_output_gzip_finalize (GObject *obj)
|
|
{
|
|
GsfOutputGZip *gzip = (GsfOutputGZip *)obj;
|
|
|
|
if (gzip->sink != NULL) {
|
|
g_object_unref (G_OBJECT (gzip->sink));
|
|
gzip->sink = NULL;
|
|
}
|
|
|
|
g_free (gzip->buf);
|
|
|
|
/* FIXME: check for error? */
|
|
deflateEnd (&gzip->stream);
|
|
|
|
parent_class->finalize (obj);
|
|
}
|
|
|
|
static gboolean
|
|
gzip_output_block (GsfOutputGZip *gzip)
|
|
{
|
|
size_t num_bytes = gzip->buf_size - gzip->stream.avail_out;
|
|
|
|
if (!gsf_output_write (gzip->sink, num_bytes, gzip->buf)) {
|
|
gsf_output_set_error (GSF_OUTPUT (gzip), 0,
|
|
"Failed to write");
|
|
return FALSE;
|
|
}
|
|
gzip->stream.next_out = gzip->buf;
|
|
gzip->stream.avail_out = gzip->buf_size;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gzip_flush (GsfOutputGZip *gzip)
|
|
{
|
|
int zret;
|
|
|
|
do {
|
|
zret = deflate (&gzip->stream, Z_FINISH);
|
|
if (zret == Z_OK) {
|
|
/* In this case Z_OK means more buffer space
|
|
needed */
|
|
if (!gzip_output_block (gzip))
|
|
return FALSE;
|
|
}
|
|
} while (zret == Z_OK);
|
|
if (zret != Z_STREAM_END) {
|
|
gsf_output_set_error (GSF_OUTPUT (gzip), 0,
|
|
"Unexpected compression failure");
|
|
g_warning ("Unexpected error code %d from zlib during compression.",
|
|
zret);
|
|
return FALSE;
|
|
}
|
|
if (!gzip_output_block (gzip))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gsf_output_gzip_write (GsfOutput *output,
|
|
size_t num_bytes, guint8 const *data)
|
|
{
|
|
GsfOutputGZip *gzip = GSF_OUTPUT_GZIP (output);
|
|
|
|
g_return_val_if_fail (data, FALSE);
|
|
|
|
gzip->stream.next_in = (unsigned char *) data;
|
|
gzip->stream.avail_in = num_bytes;
|
|
|
|
while (gzip->stream.avail_in > 0) {
|
|
int zret;
|
|
if (gzip->stream.avail_out == 0) {
|
|
if (!gzip_output_block (gzip))
|
|
return FALSE;
|
|
}
|
|
|
|
zret = deflate (&gzip->stream, Z_NO_FLUSH);
|
|
if (zret != Z_OK) {
|
|
gsf_output_set_error (output, 0,
|
|
"Unexpected compression failure");
|
|
g_warning ("Unexpected error code %d from zlib during compression.",
|
|
zret);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
gzip->crc = crc32 (gzip->crc, data, num_bytes);
|
|
gzip->isize += num_bytes;
|
|
|
|
if (gzip->stream.avail_out == 0) {
|
|
if (!gzip_output_block (gzip))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gsf_output_gzip_seek (G_GNUC_UNUSED GsfOutput *output,
|
|
G_GNUC_UNUSED gsf_off_t offset,
|
|
G_GNUC_UNUSED GSeekType whence)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gsf_output_gzip_close (GsfOutput *output)
|
|
{
|
|
if (!gsf_output_error (output)) {
|
|
GsfOutputGZip *gzip = GSF_OUTPUT_GZIP (output);
|
|
|
|
if (!gzip_flush (gzip))
|
|
return FALSE;
|
|
|
|
if (!gzip->raw) {
|
|
guint8 buf[8];
|
|
|
|
GSF_LE_SET_GUINT32 (buf, gzip->crc);
|
|
GSF_LE_SET_GUINT32 (buf + 4, gzip->isize);
|
|
if (!gsf_output_write (gzip->sink, 8, buf))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsf_output_gzip_init (GObject *obj)
|
|
{
|
|
GsfOutputGZip *gzip = GSF_OUTPUT_GZIP (obj);
|
|
|
|
gzip->sink = NULL;
|
|
gzip->stream.zalloc = (alloc_func)0;
|
|
gzip->stream.zfree = (free_func)0;
|
|
gzip->stream.opaque = (voidpf)0;
|
|
gzip->stream.next_in = Z_NULL;
|
|
gzip->stream.next_out = Z_NULL;
|
|
gzip->stream.avail_in = gzip->stream.avail_out = 0;
|
|
gzip->crc = crc32 (0L, Z_NULL, 0);
|
|
gzip->isize = 0;
|
|
gzip->buf = NULL;
|
|
gzip->buf_size = 0;
|
|
}
|
|
|
|
static void
|
|
gsf_output_gzip_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GsfOutputGZip *gzip = (GsfOutputGZip *)object;
|
|
|
|
switch (property_id) {
|
|
case PROP_RAW:
|
|
g_value_set_boolean (value, gzip->raw);
|
|
break;
|
|
case PROP_SINK:
|
|
g_value_set_object (value, gzip->sink);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsf_output_gzip_set_sink (GsfOutputGZip *gzip, GsfOutput *sink)
|
|
{
|
|
if (sink)
|
|
g_object_ref (GSF_OUTPUT (sink));
|
|
if (gzip->sink)
|
|
g_object_unref (gzip->sink);
|
|
gzip->sink = sink;
|
|
}
|
|
|
|
static void
|
|
gsf_output_gzip_set_property (GObject *object,
|
|
guint property_id,
|
|
GValue const *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GsfOutputGZip *gzip = (GsfOutputGZip *)object;
|
|
|
|
switch (property_id) {
|
|
case PROP_RAW:
|
|
gzip->raw = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_SINK:
|
|
gsf_output_gzip_set_sink (gzip, g_value_get_object (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GObject*
|
|
gsf_output_gzip_constructor (GType type,
|
|
guint n_construct_properties,
|
|
GObjectConstructParam *construct_params)
|
|
{
|
|
GsfOutputGZip *gzip;
|
|
|
|
gzip = (GsfOutputGZip *)(parent_class->constructor (type,
|
|
n_construct_properties,
|
|
construct_params));
|
|
|
|
if (!gzip->sink)
|
|
gsf_output_set_error (GSF_OUTPUT (gzip),
|
|
0,
|
|
"NULL sink");
|
|
else if (!init_gzip (gzip))
|
|
gsf_output_set_error (GSF_OUTPUT (gzip),
|
|
0,
|
|
"Failed to initialize zlib structure");
|
|
else if (!gzip->raw && !gzip_output_header (gzip))
|
|
gsf_output_set_error (GSF_OUTPUT (gzip),
|
|
0,
|
|
"Failed to write gzip header");
|
|
|
|
return (GObject *)gzip;
|
|
}
|
|
|
|
static void
|
|
gsf_output_gzip_class_init (GObjectClass *gobject_class)
|
|
{
|
|
GsfOutputClass *output_class = GSF_OUTPUT_CLASS (gobject_class);
|
|
|
|
gobject_class->constructor = gsf_output_gzip_constructor;
|
|
gobject_class->finalize = gsf_output_gzip_finalize;
|
|
gobject_class->set_property = gsf_output_gzip_set_property;
|
|
gobject_class->get_property = gsf_output_gzip_get_property;
|
|
output_class->Write = gsf_output_gzip_write;
|
|
output_class->Seek = gsf_output_gzip_seek;
|
|
output_class->Close = gsf_output_gzip_close;
|
|
|
|
g_object_class_install_property
|
|
(gobject_class,
|
|
PROP_RAW,
|
|
g_param_spec_boolean ("raw", "Raw",
|
|
"Whether to write compressed data with no header/tailer.",
|
|
FALSE,
|
|
GSF_PARAM_STATIC |
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
g_object_class_install_property
|
|
(gobject_class,
|
|
PROP_SINK,
|
|
g_param_spec_object ("sink", "Sink",
|
|
"Where the compressed data is written.",
|
|
GSF_OUTPUT_TYPE,
|
|
GSF_PARAM_STATIC |
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
parent_class = g_type_class_peek_parent (gobject_class);
|
|
}
|
|
|
|
GSF_CLASS (GsfOutputGZip, gsf_output_gzip,
|
|
gsf_output_gzip_class_init, gsf_output_gzip_init, GSF_OUTPUT_TYPE)
|