Skip to content

Commit

Permalink
Add support for Zstd for on-disk data compression
Browse files Browse the repository at this point in the history
In measurements, zstd was as fast or even faster than gzip to
decompress, and it provides a small but pretty nice reduction in tarball
size for the larger icon tarballs.
We can't make the switch to zstd for a while, but this change lies the
groundwork to make this possible in future.
  • Loading branch information
ximion committed Nov 7, 2023
1 parent 5e6df17 commit 1bde5bf
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 25 deletions.
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ yaml_dep = dependency('yaml-0.1')
xmlb_dep = dependency('xmlb', version: '>= 0.3.14',
fallback: ['libxmlb', 'libxmlb_dep'],
default_options: ['gtkdoc=false', 'introspection=false'])
zstd_dep = dependency('libzstd')
libsystemd_dep = dependency('libsystemd', required: get_option('systemd'))

if get_option ('gir')
Expand Down
66 changes: 48 additions & 18 deletions src/as-distro-extras.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,48 @@ as_get_yml_data_origin (const gchar *fname)
return origin;
}

/**
* as_apt_list_get_icon_tarball_path:
*/
static gchar *
as_apt_list_get_icon_tarball_path (const gchar *lists_dir,
const gchar *basename,
const gchar *size_str)
{
g_autofree gchar *escaped_size = NULL;
g_autofree gchar *icons_tarball = NULL;

escaped_size = g_uri_escape_string (size_str, NULL, FALSE);
icons_tarball = g_strdup_printf ("%s/%sicons-%s.tar.zst",
lists_dir,
basename,
escaped_size);
if (g_file_test (icons_tarball, G_FILE_TEST_EXISTS))
return g_steal_pointer (&icons_tarball);

g_free (icons_tarball);
icons_tarball = g_strdup_printf ("%s/%sicons-%s.tar.gz", lists_dir, basename, escaped_size);
if (g_file_test (icons_tarball, G_FILE_TEST_EXISTS))
return g_steal_pointer (&icons_tarball);

/* we couldn't find the file */
return NULL;
}

/**
* as_apt_list_icon_tarball_exists:
*/
static gboolean
as_apt_list_icon_tarball_exists (const gchar *lists_dir,
const gchar *basename,
const gchar *size_str)
{
g_autofree gchar *path = NULL;
path = as_apt_list_get_icon_tarball_path (lists_dir, basename, size_str);

return path != NULL;
}

/**
* as_extract_icon_cache_tarball:
*/
Expand All @@ -173,17 +215,12 @@ as_extract_icon_cache_tarball (const gchar *asicons_target,
const gchar *apt_basename,
const gchar *icons_size)
{
g_autofree gchar *escaped_size = NULL;
g_autofree gchar *icons_tarball = NULL;
g_autofree gchar *target_dir = NULL;
g_autoptr(GError) tmp_error = NULL;

escaped_size = g_uri_escape_string (icons_size, NULL, FALSE);
icons_tarball = g_strdup_printf ("%s/%sicons-%s.tar.gz",
apt_lists_dir,
apt_basename,
escaped_size);
if (!g_file_test (icons_tarball, G_FILE_TEST_EXISTS)) {
icons_tarball = as_apt_list_get_icon_tarball_path (apt_lists_dir, apt_basename, icons_size);
if (icons_tarball == NULL) {
/* no icons found, stop here */
return;
}
Expand Down Expand Up @@ -313,23 +350,16 @@ as_pool_scan_apt (AsPool *pool, gboolean force, GError **error)

if (!icons_available) {
g_autofree gchar *apt_basename = NULL;
guint j;

/* get base prefix for this file in the APT download cache */
apt_basename = g_strndup (fbasename,
strlen (fbasename) -
strlen (g_strrstr (fbasename, "_") + 1));

for (j = 0; default_icon_sizes[j] != NULL; j++) {
g_autofree gchar *icons_tarball = NULL;

/* NOTE: We would normally need to escape the "@" of HiDPI icons here, but since having only HiDPI icons is
* a case that never happens and the 64x64px icons are required to be present anyway, we ignore that fact. */
icons_tarball = g_strdup_printf ("%s/%sicons-%s.tar.gz",
apt_lists_dir,
apt_basename,
default_icon_sizes[j]);
if (g_file_test (icons_tarball, G_FILE_TEST_EXISTS)) {
for (guint j = 0; default_icon_sizes[j] != NULL; j++) {
if (as_apt_list_icon_tarball_exists (apt_lists_dir,
apt_basename,
default_icon_sizes[j])) {
icons_available = TRUE;
break;
}
Expand Down
10 changes: 8 additions & 2 deletions src/as-metadata.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "as-release-list-private.h"
#include "as-context-private.h"
#include "as-desktop-entry.h"
#include "as-zstd-decompressor.h"

#include "as-xml.h"
#include "as-yaml.h"
Expand Down Expand Up @@ -750,8 +751,13 @@ as_metadata_parse_file (AsMetadata *metad, GFile *file, AsFormatKind format, GEr
return FALSE;
}

if ((g_strcmp0 (content_type, "application/gzip") == 0) ||
(g_strcmp0 (content_type, "application/x-gzip") == 0)) {
if (as_str_equal0 (content_type, "application/zstd")) {
/* decompress the Zstd stream */
conv = G_CONVERTER (as_zstd_decompressor_new ());
stream_data = g_converter_input_stream_new (file_stream, conv);

} else if (as_str_equal0 (content_type, "application/gzip") ||
as_str_equal0 (content_type, "application/x-gzip")) {
/* decompress the GZip stream */
conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
stream_data = g_converter_input_stream_new (file_stream, conv);
Expand Down
1 change: 1 addition & 0 deletions src/as-utils-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ void as_ref_string_assign_safe (GRefString **rstr_ptr, const gchar *str);

void as_ref_string_assign_transfer (GRefString **rstr_ptr, GRefString *new_rstr);

AS_INTERNAL_VISIBLE
gboolean as_utils_extract_tarball (const gchar *filename, const gchar *target_dir, GError **error);

gboolean as_utils_is_platform_triplet_arch (const gchar *arch);
Expand Down
2 changes: 1 addition & 1 deletion src/as-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -2246,7 +2246,7 @@ as_utils_extract_tarball (const gchar *filename, const gchar *target_dir, GError
g_autofree gchar *wdir = NULL;
gboolean ret;
gint exit_status;
const gchar *argv[] = { "/bin/tar", "-xzf", filename, "-C", target_dir, NULL };
const gchar *argv[] = { "/bin/tar", "-xf", filename, "-C", target_dir, NULL };

g_return_val_if_fail (filename != NULL, FALSE);

Expand Down
11 changes: 9 additions & 2 deletions src/as-validator.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "as-yaml.h"
#include "as-desktop-entry.h"
#include "as-content-rating-private.h"
#include "as-zstd-decompressor.h"

typedef struct {
GHashTable *issue_tags; /* of utf8:AsValidatorIssueTag */
Expand Down Expand Up @@ -3531,11 +3532,17 @@ as_validator_validate_file (AsValidator *validator, GFile *metadata_file)
if (file_stream == NULL)
return FALSE;

if ((g_strcmp0 (content_type, "application/gzip") == 0) ||
(g_strcmp0 (content_type, "application/x-gzip") == 0)) {
if (as_str_equal0 (content_type, "application/zstd")) {
/* decompress the Zstd stream */
conv = G_CONVERTER (as_zstd_decompressor_new ());
stream_data = g_converter_input_stream_new (file_stream, conv);

} else if (as_str_equal0 (content_type, "application/gzip") ||
as_str_equal0 (content_type, "application/x-gzip")) {
/* decompress the GZip stream */
conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
stream_data = g_converter_input_stream_new (file_stream, conv);

} else {
stream_data = g_object_ref (file_stream);
}
Expand Down
126 changes: 126 additions & 0 deletions src/as-zstd-decompressor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2022 Richard Hughes <[email protected]>
*
* Licensed under the GNU Lesser General Public License Version 2.1
*
* 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.1 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, see <http://www.gnu.org/licenses/>.
*/

#include "as-zstd-decompressor.h"

#include "config.h"

#include <gio/gio.h>
#include <zstd.h>

static void as_zstd_decompressor_iface_init (GConverterIface *iface);

struct _AsZstdDecompressor {
GObject parent_instance;
ZSTD_DStream *zstdstream;
};

G_DEFINE_TYPE_WITH_CODE (AsZstdDecompressor,
as_zstd_decompressor,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, as_zstd_decompressor_iface_init))

static void
as_zstd_decompressor_finalize (GObject *object)
{
AsZstdDecompressor *self = AS_ZSTD_DECOMPRESSOR (object);
ZSTD_freeDStream (self->zstdstream);
G_OBJECT_CLASS (as_zstd_decompressor_parent_class)->finalize (object);
}

static void
as_zstd_decompressor_init (AsZstdDecompressor *self)
{
}

static void
as_zstd_decompressor_constructed (GObject *object)
{
AsZstdDecompressor *self = AS_ZSTD_DECOMPRESSOR (object);
self->zstdstream = ZSTD_createDStream ();
}

static void
as_zstd_decompressor_class_init (AsZstdDecompressorClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = as_zstd_decompressor_finalize;
object_class->constructed = as_zstd_decompressor_constructed;
}

AsZstdDecompressor *
as_zstd_decompressor_new (void)
{
return g_object_new (AS_TYPE_ZSTD_DECOMPRESSOR, NULL);
}

static void
as_zstd_decompressor_reset (GConverter *converter)
{
AsZstdDecompressor *self = AS_ZSTD_DECOMPRESSOR (converter);
ZSTD_initDStream (self->zstdstream);
}

static GConverterResult
as_zstd_decompressor_convert (GConverter *converter,
const void *inbuf,
gsize inbuf_size,
void *outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
AsZstdDecompressor *self = AS_ZSTD_DECOMPRESSOR (converter);
ZSTD_outBuffer output = {
.dst = outbuf,
.size = outbuf_size,
.pos = 0,
};
ZSTD_inBuffer input = {
.src = inbuf,
.size = inbuf_size,
.pos = 0,
};
size_t res;

res = ZSTD_decompressStream (self->zstdstream, &output, &input);
if (res == 0)
return G_CONVERTER_FINISHED;
if (ZSTD_isError (res)) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"cannot decompress data: %s",
ZSTD_getErrorName (res));
return G_CONVERTER_ERROR;
}
*bytes_read = input.pos;
*bytes_written = output.pos;
return G_CONVERTER_CONVERTED;
}

static void
as_zstd_decompressor_iface_init (GConverterIface *iface)
{
iface->convert = as_zstd_decompressor_convert;
iface->reset = as_zstd_decompressor_reset;
}
34 changes: 34 additions & 0 deletions src/as-zstd-decompressor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2022 Richard Hughes <[email protected]>
*
* Licensed under the GNU Lesser General Public License Version 2.1
*
* 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.1 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, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <glib-object.h>

#include "as-macros-private.h"

AS_BEGIN_PRIVATE_DECLS

#define AS_TYPE_ZSTD_DECOMPRESSOR (as_zstd_decompressor_get_type ())
G_DECLARE_FINAL_TYPE (AsZstdDecompressor, as_zstd_decompressor, AS, ZSTD_DECOMPRESSOR, GObject)

AsZstdDecompressor *as_zstd_decompressor_new (void);

AS_END_PRIVATE_DECLS
7 changes: 5 additions & 2 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ aslib_src = [
'as-tag.c',
'as-xml.c',
'as-yaml.c',
'as-zstd-decompressor.c',
# (mostly) public
'as-agreement.c',
'as-agreement-section.c',
Expand Down Expand Up @@ -135,7 +136,8 @@ aslib_priv_headers = [
'as-utils-private.h',
'as-validator-issue-tag.h',
'as-xml.h',
'as-yaml.h'
'as-yaml.h',
'as-zstd-decompressor.h',
]

as_version_h = configure_file(
Expand Down Expand Up @@ -188,7 +190,8 @@ aslib_deps = [glib_dep,
xmlb_dep,
xml2_dep,
yaml_dep,
libsystemd_dep]
libsystemd_dep,
zstd_dep]
if get_option ('stemming')
aslib_deps += [stemmer_lib]
endif
Expand Down
Binary file added tests/samples/dummy.tar.zst
Binary file not shown.
Loading

0 comments on commit 1bde5bf

Please sign in to comment.