/*
 *  $Id: gwyzip-libzip.h 29403 2026-01-29 17:37:40Z yeti-dn $
 *  Copyright (C) 2015-2024 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  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 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, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Do not include this file directly, let gwyzip.h include it as needed! */

#ifndef __GWY_FILE_LIBZIP_H__
#define __GWY_FILE_LIBZIP_H__

#include <zip.h>

#define GWYZIP_HAS_MEMORY_FILES 1

/* This is not defined in 0.11 yet (1.0 is required) but we can live without it. */
#ifndef ZIP_RDONLY
#define ZIP_RDONLY 0
#endif

struct _GwyZipFile {
    struct zip *archive;
    zip_source_t *source;
    guint index;
    guint nentries;
};

G_GNUC_UNUSED
static GwyZipFile
gwyzip_open_memory(const guchar *data, gsize size, GError **error)
{
    struct _GwyZipFile *zipfile;
    struct zip *archive;
    zip_source_t *source;
    zip_error_t ziperror;

    if (!(source = zip_source_buffer_create(data, size, 0, &ziperror))) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_SPECIFIC,
                    _("%s cannot open the file as a ZIP file."), "Libzip");
        return NULL;
    }

    if (!(archive = zip_open_from_source(source, ZIP_RDONLY, &ziperror))) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_SPECIFIC,
                    _("%s cannot open the file as a ZIP file."), "Libzip");
        /* zip_source_free() is only for freeing free-standing sources we failed to open. */
        zip_source_free(source);
        return NULL;
    }

    zipfile = g_new0(struct _GwyZipFile, 1);
    zipfile->archive = archive;
    zipfile->source = source;
    zipfile->nentries = zip_get_num_entries(archive, 0);
    return zipfile;
}

G_GNUC_UNUSED
static GwyZipFile
gwyzip_open(const char *path, GError **error)
{
    struct _GwyZipFile *zipfile;
    struct zip *archive;

    if (!(archive = zip_open(path, ZIP_RDONLY, NULL))) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_SPECIFIC,
                    _("%s cannot open the file as a ZIP file."), "Libzip");
        return NULL;
    }

    zipfile = g_new0(struct _GwyZipFile, 1);
    zipfile->archive = archive;
    zipfile->nentries = zip_get_num_entries(archive, 0);
    return zipfile;
}

G_GNUC_UNUSED
static void
gwyzip_close(GwyZipFile zipfile)
{
    if (zipfile->source)
        zip_source_close(zipfile->source);
    zip_close(zipfile->archive);
    g_free(zipfile);
}

G_GNUC_UNUSED
static gboolean
err_ZIP_NOFILE(GwyZipFile zipfile, GError **error)
{
    if (zipfile->index >= zipfile->nentries) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                    _("%s error while reading the zip file: %s."), "Libzip", _("End of list of files"));
        return TRUE;
    }
    return FALSE;
}

G_GNUC_UNUSED
static void
err_ZIP(GwyZipFile zipfile, GError **error)
{
    g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                _("%s error while reading the zip file: %s."), "Libzip", zip_strerror(zipfile->archive));
}

G_GNUC_UNUSED
static gboolean
gwyzip_next_file(GwyZipFile zipfile, GError **error)
{
    if (err_ZIP_NOFILE(zipfile, error))
        return FALSE;
    zipfile->index++;
    return !err_ZIP_NOFILE(zipfile, error);
}

G_GNUC_UNUSED
static gboolean
gwyzip_first_file(GwyZipFile zipfile, GError **error)
{
    zipfile->index = 0;
    return !err_ZIP_NOFILE(zipfile, error);
}

G_GNUC_UNUSED
static gboolean
gwyzip_get_current_filename(GwyZipFile zipfile, gchar **filename, GError **error)
{
    const char *filename_buf;

    if (err_ZIP_NOFILE(zipfile, error)) {
        *filename = NULL;
        return FALSE;
    }

    filename_buf = zip_get_name(zipfile->archive, zipfile->index, ZIP_FL_ENC_GUESS);
    if (filename_buf) {
        *filename = g_strdup(filename_buf);
        return TRUE;
    }

    err_ZIP(zipfile, error);
    *filename = NULL;
    return FALSE;
}

G_GNUC_UNUSED
static gboolean
gwyzip_locate_file(GwyZipFile zipfile, const gchar *filename, gint casesens, GError **error)
{
    zip_int64_t i;

    i = zip_name_locate(zipfile->archive, filename, ZIP_FL_ENC_GUESS | (casesens ? 0 : ZIP_FL_NOCASE));
    if (i == -1) {
        err_ZIP(zipfile, error);
        return FALSE;
    }
    zipfile->index = i;
    return TRUE;
}

G_GNUC_UNUSED
static guchar*
gwyzip_get_file_content(GwyZipFile zipfile, gsize *contentsize, GError **error)
{
    struct zip_file *file;
    struct zip_stat zst;
    guchar *buffer;

    if (err_ZIP_NOFILE(zipfile, error))
        return NULL;

    zip_stat_init(&zst);
    if (zip_stat_index(zipfile->archive, zipfile->index, 0, &zst) == -1) {
        err_ZIP(zipfile, error);
        return NULL;
    }

    if (!(zst.valid & ZIP_STAT_SIZE)) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                    _("Cannot obtain the uncompressed file size."));
        return NULL;
    }

    gwy_debug("uncompressed_size: %lu", (gulong)zst.size);
    file = zip_fopen_index(zipfile->archive, zipfile->index, 0);
    if (!file) {
        err_ZIP(zipfile, error);
        return NULL;
    }

    buffer = g_new(guchar, zst.size + 1);
    if (zip_fread(file, buffer, zst.size) != zst.size) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO, _("Cannot read file contents."));
        zip_fclose(file);
        g_free(buffer);
        return NULL;
    }
    zip_fclose(file);

    buffer[zst.size] = '\0';
    if (contentsize)
        *contentsize = zst.size;
    return buffer;
}

#endif

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
