[gnome-disk-utility] Add support for restoring XZ compressed disk images



commit 67c7e1ddd61f07c9bae36654ca73e309290eec39
Author: David Zeuthen <zeuthen gmail com>
Date:   Thu Jun 13 12:53:49 2013 -0700

    Add support for restoring XZ compressed disk images
    
    Also include [1] MIME types for disk images, e.g. introduce
    
     application/x-raw-disk-image
     application/x-raw-disk-image-xz-compressed
    
    Also add a new desktop file for a "Disk Image Writer" application that
    associates these mime-types to the command
    
     gnome-disks --restore-disk-image %U
    
    to allow the end user to very easily copy a disk image to a device
    from either his desktop shell or web browser. This feature is useful
    for OS vendors distributing e.g. "rescue images".
    
    [1] : Eventually these mimetypes should be part of the standard
    shared-mime-info distribution from freedesktop.org, see
    
     https://bugs.freedesktop.org/show_bug.cgi?id=64845
    
    for these. Until that happens, we'll just ship them as part of
    gnome-disk-utility.
    
    Signed-off-by: David Zeuthen <zeuthen gmail com>

 configure.ac                             |    6 +-
 data/Makefile.am                         |   18 ++-
 data/gdu-mimetypes.xml                   |   16 ++
 data/gnome-disk-image-mounter.desktop.in |    4 +-
 data/gnome-disk-image-writer.desktop.in  |   11 ++
 po/POTFILES.in                           |    2 +
 src/disk-image-mounter/main.c            |    4 +-
 src/disks/Makefile.am                    |    3 +
 src/disks/gducreatediskimagedialog.c     |    4 +-
 src/disks/gdurestorediskimagedialog.c    |   83 ++++++++---
 src/disks/gdutypes.h                     |    3 +
 src/disks/gduwindow.c                    |    4 +-
 src/disks/gduxzdecompressor.c            |  238 ++++++++++++++++++++++++++++++
 src/disks/gduxzdecompressor.h            |   40 +++++
 src/libgdu/gduutils.c                    |   14 ++-
 src/libgdu/gduutils.h                    |    3 +-
 16 files changed, 424 insertions(+), 29 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index e38fcf0..27140ec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -110,6 +110,7 @@ CANBERRA_REQUIRED=0.1
 LIBDVDREAD_REQUIRED=4.2.0
 GSD_PLUGIN_REQUIRED=3.6
 LIBNOTIFY_REQUIRED=0.7
+LIBLZMA_REQUIRED=5.1.0
 
 PKG_CHECK_MODULES(GLIB2, [gmodule-2.0 gio-unix-2.0 >= $GLIB2_REQUIRED])
 PKG_CHECK_MODULES(UDISKS2, [udisks2 >= $UDISKS2_REQUIRED])
@@ -118,8 +119,9 @@ PKG_CHECK_MODULES(LIBSECRET1, [libsecret-1 >= $LIBSECRET1_REQUIRED])
 PKG_CHECK_MODULES(PWQUALITY, [pwquality >= $PWQUALITY_REQUIRED])
 PKG_CHECK_MODULES(CANBERRA, [libcanberra-gtk3 >= $CANBERRA_REQUIRED])
 PKG_CHECK_MODULES(LIBDVDREAD, [dvdread >= $LIBDVDREAD_REQUIRED])
-PKG_CHECK_MODULES(GSD_PLUGIN, [gnome-settings-daemon >= GSD_PLUGIN_REQUIRED])
-PKG_CHECK_MODULES(LIBNOTIFY, [libnotify >= LIBNOTIFY_REQUIRED])
+PKG_CHECK_MODULES(GSD_PLUGIN, [gnome-settings-daemon >= $GSD_PLUGIN_REQUIRED])
+PKG_CHECK_MODULES(LIBNOTIFY, [libnotify >= $LIBNOTIFY_REQUIRED])
+PKG_CHECK_MODULES(LIBLZMA, [liblzma >= $LIBLZMA_REQUIRED])
 
 gsd_plugindir='${libdir}/gnome-settings-daemon-3.0'
 AC_SUBST([gsd_plugindir])
diff --git a/data/Makefile.am b/data/Makefile.am
index 33ac654..726589e 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -2,8 +2,17 @@ NULL =
 
 SUBDIRS = ui icons
 
+# TODO: Move gdu-mimetypes.xml to shared-mime-info, see https://bugs.freedesktop.org/show_bug.cgi?id=64845
+mimedir = $(datadir)/mime/packages
+mime_DATA = gdu-mimetypes.xml
+
 desktopdir = $(datadir)/applications
-desktop_in_files = gnome-disks.desktop.in gnome-disk-image-mounter.desktop.in
+desktop_in_files =                             \
+       gnome-disks.desktop.in                  \
+       gnome-disk-image-mounter.desktop.in     \
+       gnome-disk-image-writer.desktop.in      \
+       $(NULL)
+
 desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
 
 # GSettings schemas
@@ -23,7 +32,14 @@ EXTRA_DIST =                         \
 CLEANFILES =                   \
        $(gsettings_SCHEMAS)    \
        $(desktop_DATA)         \
+       $(mime_DATA)            \
        $(NULL)
 
+install-data-hook:
+       update-mime-database $(datadir)/mime
+
+uninstall-hook:
+       update-mime-database $(datadir)/mime
+
 clean-local :
        rm -f *~
diff --git a/data/gdu-mimetypes.xml b/data/gdu-mimetypes.xml
new file mode 100644
index 0000000..b108bae
--- /dev/null
+++ b/data/gdu-mimetypes.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info";>
+   <mime-type type="application/x-raw-disk-image">
+     <comment>Raw disk image</comment>
+     <generic-icon name="application-x-cd-image"/>
+     <glob pattern="*.raw-disk-image"/>
+     <glob pattern="*.img"/>
+   </mime-type>
+   <mime-type type="application/x-raw-disk-image-xz-compressed">
+     <comment>Raw disk image (XZ-compressed)</comment>
+     <sub-class-of type="application/x-xz"/>
+     <generic-icon name="application-x-cd-image"/>
+     <glob pattern="*.raw-disk-image.xz"/>
+     <glob pattern="*.img.xz"/>
+   </mime-type>
+</mime-info>
diff --git a/data/gnome-disk-image-mounter.desktop.in b/data/gnome-disk-image-mounter.desktop.in
index 23d5b62..709ed26 100644
--- a/data/gnome-disk-image-mounter.desktop.in
+++ b/data/gnome-disk-image-mounter.desktop.in
@@ -4,8 +4,8 @@ _Name=Disk Image Mounter
 _Comment=Mount Disk Images
 Exec=gnome-disk-image-mounter %U
 Icon=drive-removable-media
-MimeType=application/x-cd-image;
+MimeType=application/x-cd-image;application/x-raw-disk-image;
 Terminal=false
 StartupNotify=false
 Type=Application
-NoDisplay=true
+NoDisplay=false
diff --git a/data/gnome-disk-image-writer.desktop.in b/data/gnome-disk-image-writer.desktop.in
new file mode 100644
index 0000000..3e587e4
--- /dev/null
+++ b/data/gnome-disk-image-writer.desktop.in
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Encoding=UTF-8
+_Name=Disk Image Writer
+_Comment=Write Disk Images to Devices
+Exec=gnome-disks --restore-disk-image %U
+Icon=drive-removable-media
+MimeType=application/x-cd-image;application/x-raw-disk-image;application/x-raw-disk-image-xz-compressed;
+Terminal=false
+StartupNotify=false
+Type=Application
+NoDisplay=false
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ae356a5..0332b4b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,7 @@
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
 data/gnome-disk-image-mounter.desktop.in
+data/gnome-disk-image-writer.desktop.in
 data/gnome-disks.desktop.in
 data/org.gnome.Disks.gschema.xml.in.in
 data/org.gnome.settings-daemon.plugins.gdu-sd.gschema.xml.in.in
@@ -53,6 +54,7 @@ src/disks/gdurestorediskimagedialog.c
 src/disks/gduunlockdialog.c
 src/disks/gduvolumegrid.c
 src/disks/gduwindow.c
+src/disks/gduxzdecompressor.c
 src/disks/main.c
 src/libgdu/gduutils.c
 src/notify/gdusdmonitor.c
diff --git a/src/disk-image-mounter/main.c b/src/disk-image-mounter/main.c
index 95977c5..0eff7b1 100644
--- a/src/disk-image-mounter/main.c
+++ b/src/disk-image-mounter/main.c
@@ -83,7 +83,9 @@ do_filechooser (void)
                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                         _("_Mount"), GTK_RESPONSE_ACCEPT,
                                         NULL);
-  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (dialog), TRUE);
+  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (dialog),
+                                                    TRUE,   /* set_file_types */
+                                                    FALSE); /* allow_compressed */
   gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
 
   /* Add a RO check button that defaults to RO */
diff --git a/src/disks/Makefile.am b/src/disks/Makefile.am
index 0c76be9..9c91946 100644
--- a/src/disks/Makefile.am
+++ b/src/disks/Makefile.am
@@ -52,6 +52,7 @@ gnome_disks_SOURCES =                                                         \
        gduerasemultipledisksdialog.h   gduerasemultipledisksdialog.c   \
        gdudvdsupport.h                 gdudvdsupport.c                 \
        gdulocaljob.h                   gdulocaljob.c                   \
+       gduxzdecompressor.h             gduxzdecompressor.c             \
        $(enum_built_sources)                                           \
        $(NULL)
 
@@ -72,6 +73,7 @@ gnome_disks_CFLAGS =                                  \
        $(PWQUALITY_CFLAGS)                             \
        $(CANBERRA_CFLAGS)                              \
        $(LIBDVDREAD_CFLAGS)                            \
+       $(LIBLZMA_CFLAGS)                               \
        $(WARN_CFLAGS)                                  \
        -lm                                             \
        $(NULL)
@@ -85,6 +87,7 @@ gnome_disks_LDADD =                                   \
        $(PWQUALITY_LIBS)                               \
        $(CANBERRA_LIBS)                                \
        $(LIBDVDREAD_LIBS)                              \
+       $(LIBLZMA_LIBS)                                 \
         $(top_builddir)/src/libgdu/libgdu.la           \
        $(NULL)
 
diff --git a/src/disks/gducreatediskimagedialog.c b/src/disks/gducreatediskimagedialog.c
index e5c36cd..dd7958b 100644
--- a/src/disks/gducreatediskimagedialog.c
+++ b/src/disks/gducreatediskimagedialog.c
@@ -284,7 +284,9 @@ create_disk_image_populate (DialogData *data)
   g_time_zone_unref (tz);
   g_free (now_string);
 
-  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (data->folder_fcbutton), FALSE);
+  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (data->folder_fcbutton),
+                                                    FALSE,   /* set file types */
+                                                    FALSE);  /* allow_compressed */
 
   /* Source label */
   info = udisks_client_get_object_info (gdu_window_get_client (data->window), data->object);
diff --git a/src/disks/gdurestorediskimagedialog.c b/src/disks/gdurestorediskimagedialog.c
index 46479ae..813a84f 100644
--- a/src/disks/gdurestorediskimagedialog.c
+++ b/src/disks/gdurestorediskimagedialog.c
@@ -26,6 +26,7 @@
 #include "gduestimator.h"
 #include "gdulocaljob.h"
 #include "gdudevicetreemodel.h"
+#include "gduxzdecompressor.h"
 
 /* ---------------------------------------------------------------------------------------------------- */
 
@@ -72,8 +73,8 @@ typedef struct
 
   GCancellable *cancellable;
   GOutputStream *block_stream;
-  GFileInputStream *file_input_stream;
-  guint64 file_size;
+  GInputStream *input_stream;
+  guint64 input_size;
 
   guchar *buffer;
   guint64 total_bytes_read;
@@ -187,7 +188,7 @@ dialog_data_unref (DialogData *data)
       g_clear_object (&data->estimator);
 
       g_clear_object (&data->cancellable);
-      g_clear_object (&data->file_input_stream);
+      g_clear_object (&data->input_stream);
       g_clear_object (&data->block_stream);
       g_mutex_clear (&data->copy_lock);
       g_free (data);
@@ -251,24 +252,54 @@ restore_disk_image_update (DialogData *data)
 
   if (restore_file != NULL)
     {
+      gboolean is_xz_compressed = FALSE;
       GFileInfo *info;
       guint64 size;
       gchar *s;
+
       info = g_file_query_info (restore_file,
+                                G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
                                 G_FILE_ATTRIBUTE_STANDARD_SIZE,
                                 G_FILE_QUERY_INFO_NONE,
                                 NULL,
                                 NULL);
+      if (g_str_has_suffix (g_file_info_get_content_type (info), "-xz-compressed"))
+        is_xz_compressed = TRUE;
       size = g_file_info_get_size (info);
       g_object_unref (info);
 
-      image_size_str = udisks_client_get_size_for_display (gdu_window_get_client (data->window), size, 
FALSE, TRUE);
+      if (is_xz_compressed)
+        {
+          gsize uncompressed_size;
+          uncompressed_size = gdu_xz_decompressor_get_uncompressed_size (restore_file);
+          if (uncompressed_size == 0)
+            {
+              restore_error = g_strdup (_("File does not appear to be XZ compressed"));
+              size = 0;
+            }
+          else
+            {
+              s = udisks_client_get_size_for_display (gdu_window_get_client (data->window), 
uncompressed_size, FALSE, TRUE);
+              /* Translators: Shown for a compressed disk image in the "Size" field.
+               *              The %s is the uncompressed size as a long string, e.g. "4.2 MB (4,300,123 
bytes)".
+               */
+              image_size_str = g_strdup_printf (_("%s when decompressed"), s);
+              g_free (s);
+              size = uncompressed_size;
+            }
+        }
+      else
+        {
+          image_size_str = udisks_client_get_size_for_display (gdu_window_get_client (data->window), size, 
FALSE, TRUE);
+        }
 
       if (data->block_size > 0)
         {
           if (size == 0)
             {
-              restore_error = g_strdup (_("Cannot restore image of size 0"));
+              /* if size is 0, error may be set already.. */
+              if (restore_error == NULL)
+                restore_error = g_strdup (_("Cannot restore image of size 0"));
             }
           else if (size < data->block_size)
             {
@@ -482,7 +513,9 @@ populate_destination_combobox (DialogData *data)
 static void
 restore_disk_image_populate (DialogData *data)
 {
-  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (data->selectable_image_fcbutton), 
TRUE);
+  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (data->selectable_image_fcbutton),
+                                                    TRUE,   /* set file types */
+                                                    TRUE);  /* allow_compressed */
 
   /* Image: Show label if image is known, otherwise show a filechooser button */
   if (data->disk_image_filename != NULL)
@@ -728,7 +761,7 @@ copy_thread_func (gpointer user_data)
   buffer = (guchar*) (((gintptr) (buffer_unaligned + page_size)) & (~(page_size - 1)));
 
   g_mutex_lock (&data->copy_lock);
-  data->estimator = gdu_estimator_new (data->file_size);
+  data->estimator = gdu_estimator_new (data->input_size);
   data->update_id = 0;
   data->start_time_usec = g_get_real_time ();
   g_mutex_unlock (&data->copy_lock);
@@ -737,7 +770,7 @@ copy_thread_func (gpointer user_data)
    * device even if it was only partially read.
    */
   num_bytes_completed = 0;
-  while (num_bytes_completed < data->file_size)
+  while (num_bytes_completed < data->input_size)
     {
       gsize num_bytes_to_read;
       gsize num_bytes_read;
@@ -745,8 +778,8 @@ copy_thread_func (gpointer user_data)
       gint64 now_usec;
 
       num_bytes_to_read = buffer_size;
-      if (num_bytes_to_read + num_bytes_completed > data->file_size)
-        num_bytes_to_read = data->file_size - num_bytes_completed;
+      if (num_bytes_to_read + num_bytes_completed > data->input_size)
+        num_bytes_to_read = data->input_size - num_bytes_completed;
 
       /* Update GUI - but only every 200 ms and only if last update isn't pending */
       g_mutex_lock (&data->copy_lock);
@@ -761,7 +794,7 @@ copy_thread_func (gpointer user_data)
         }
       g_mutex_unlock (&data->copy_lock);
 
-      if (!g_input_stream_read_all (G_INPUT_STREAM (data->file_input_stream),
+      if (!g_input_stream_read_all (data->input_stream,
                                     buffer,
                                     num_bytes_to_read,
                                     &num_bytes_read,
@@ -809,7 +842,7 @@ copy_thread_func (gpointer user_data)
   data->end_time_usec = g_get_real_time ();
 
   /* in either case, close the stream */
-  if (!g_input_stream_close (G_INPUT_STREAM (data->file_input_stream),
+  if (!g_input_stream_close (G_INPUT_STREAM (data->input_stream),
                               NULL, /* cancellable */
                               &error2))
     {
@@ -817,7 +850,7 @@ copy_thread_func (gpointer user_data)
                  error2->message, g_quark_to_string (error2->domain), error2->code);
       g_clear_error (&error2);
     }
-  g_clear_object (&data->file_input_stream);
+  g_clear_object (&data->input_stream);
 
   if (fd != -1 )
     {
@@ -899,10 +932,8 @@ start_copying (DialogData *data)
   else
     file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->selectable_image_fcbutton));
 
-  data->file_input_stream = g_file_read (file,
-                                         NULL,
-                                         &error);
-  if (data->file_input_stream == NULL)
+  data->input_stream = (GInputStream *) g_file_read (file, NULL, &error);
+  if (data->input_stream == NULL)
     {
       if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
         gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error opening file for reading"), error);
@@ -913,6 +944,7 @@ start_copying (DialogData *data)
 
   error = NULL;
   info = g_file_query_info (file,
+                            G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
                             G_FILE_ATTRIBUTE_STANDARD_SIZE,
                             G_FILE_QUERY_INFO_NONE,
                             NULL,
@@ -924,7 +956,22 @@ start_copying (DialogData *data)
       dialog_data_complete_and_unref (data);
       goto out;
     }
-  data->file_size = g_file_info_get_size (info);
+  data->input_size = g_file_info_get_size (info);
+  if (g_str_has_suffix (g_file_info_get_content_type (info), "-xz-compressed"))
+    {
+      GduXzDecompressor *decompressor;
+      GInputStream *decompressed_input_stream;
+
+      data->input_size = gdu_xz_decompressor_get_uncompressed_size (file);
+
+      decompressor = gdu_xz_decompressor_new ();
+      decompressed_input_stream = g_converter_input_stream_new (G_INPUT_STREAM (data->input_stream),
+                                                                G_CONVERTER (decompressor));
+      g_clear_object (&decompressor);
+
+      g_object_unref (data->input_stream);
+      data->input_stream = decompressed_input_stream;
+    }
   g_object_unref (info);
 
   data->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (gdu_window_get_application 
(data->window)),
diff --git a/src/disks/gdutypes.h b/src/disks/gdutypes.h
index 887cc71..50145e9 100644
--- a/src/disks/gdutypes.h
+++ b/src/disks/gdutypes.h
@@ -46,6 +46,9 @@ typedef struct GduDVDSupport GduDVDSupport;
 struct GduLocalJob;
 typedef struct GduLocalJob GduLocalJob;
 
+struct GduXzDecompressor;
+typedef struct GduXzDecompressor GduXzDecompressor;
+
 G_END_DECLS
 
 #endif /* __GDU_TYPES_H__ */
diff --git a/src/disks/gduwindow.c b/src/disks/gduwindow.c
index e5218ff..a7fc766 100644
--- a/src/disks/gduwindow.c
+++ b/src/disks/gduwindow.c
@@ -873,7 +873,9 @@ gdu_window_show_attach_disk_image (GduWindow *window)
                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                         _("_Attach"), GTK_RESPONSE_ACCEPT,
                                         NULL);
-  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (dialog), TRUE);
+  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (dialog),
+                                                    TRUE,   /* set file types */
+                                                    FALSE); /* allow_compressed */
 
   /* Add a RO check button that defaults to RO */
   ro_checkbutton = gtk_check_button_new_with_mnemonic (_("Set up _read-only loop device"));
diff --git a/src/disks/gduxzdecompressor.c b/src/disks/gduxzdecompressor.c
new file mode 100644
index 0000000..b20fe9f
--- /dev/null
+++ b/src/disks/gduxzdecompressor.c
@@ -0,0 +1,238 @@
+/* XZ Decompressor - based on GLib's GZLibDecompressor
+ *
+ * Copyright (C) 2013 David Zeuthen
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * Licensed under GPL version 2 or later.
+ *
+ * Author: David Zeuthen <zeuthen gmail com>
+ *         Alexander Larsson <alexl redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gduxzdecompressor.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <errno.h>
+#include <string.h>
+
+#include <lzma.h>
+
+static void gdu_xz_decompressor_iface_init          (GConverterIface *iface);
+
+struct GduXzDecompressor
+{
+  GObject parent_instance;
+
+  lzma_stream stream;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GduXzDecompressor, gdu_xz_decompressor, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
+                                               gdu_xz_decompressor_iface_init))
+
+static void
+gdu_xz_decompressor_finalize (GObject *object)
+{
+  GduXzDecompressor *decompressor = GDU_XZ_DECOMPRESSOR (object);
+
+  lzma_end (&decompressor->stream);
+
+  G_OBJECT_CLASS (gdu_xz_decompressor_parent_class)->finalize (object);
+}
+
+static void
+init_lzma (GduXzDecompressor *decompressor)
+{
+  lzma_ret ret;
+  memset (&decompressor->stream, 0, sizeof decompressor->stream);
+  ret = lzma_stream_decoder (&decompressor->stream,
+                             UINT64_MAX, /* memlimit */
+                             0);         /* flags */
+  if (ret != LZMA_OK)
+    g_critical ("Error initalizing lzma decoder: %d", ret);
+}
+
+static void
+gdu_xz_decompressor_init (GduXzDecompressor *decompressor)
+{
+  init_lzma (decompressor);
+}
+
+static void
+gdu_xz_decompressor_class_init (GduXzDecompressorClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = gdu_xz_decompressor_finalize;
+}
+
+GduXzDecompressor *
+gdu_xz_decompressor_new (void)
+{
+  GduXzDecompressor *decompressor;
+
+  decompressor = g_object_new (GDU_TYPE_XZ_DECOMPRESSOR,
+                              NULL);
+
+  return decompressor;
+}
+
+static void
+gdu_xz_decompressor_reset (GConverter *converter)
+{
+  GduXzDecompressor *decompressor = GDU_XZ_DECOMPRESSOR (converter);
+  lzma_end (&decompressor->stream);
+  init_lzma (decompressor);
+}
+
+static GConverterResult
+gdu_xz_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)
+{
+  GduXzDecompressor *decompressor = GDU_XZ_DECOMPRESSOR (converter);
+  lzma_ret res;
+
+  decompressor->stream.next_in = (void *)inbuf;
+  decompressor->stream.avail_in = inbuf_size;
+
+  decompressor->stream.next_out = outbuf;
+  decompressor->stream.avail_out = outbuf_size;
+
+  res = lzma_code (&decompressor->stream, LZMA_RUN);
+
+  if (res == LZMA_DATA_ERROR)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                          _("Invalid compressed data"));
+      return G_CONVERTER_ERROR;
+    }
+
+  if (res == LZMA_MEM_ERROR)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                          _("Not enough memory"));
+      return G_CONVERTER_ERROR;
+    }
+
+    if (res == LZMA_FORMAT_ERROR)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                  _("Internal error"));
+      return G_CONVERTER_ERROR;
+    }
+
+    if (res == LZMA_BUF_ERROR)
+      {
+       if (flags & G_CONVERTER_FLUSH)
+         return G_CONVERTER_FLUSHED;
+
+       /* LZMA_FINISH not set, so this means no progress could be made
+        * We do have output space, so this should only happen if we
+        * have no input but need some.
+         */
+
+       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
+                            _("Need more input"));
+       return G_CONVERTER_ERROR;
+      }
+
+  g_assert (res == LZMA_OK || res == LZMA_STREAM_END);
+
+  *bytes_read = inbuf_size - decompressor->stream.avail_in;
+  *bytes_written = outbuf_size - decompressor->stream.avail_out;
+
+  if (res == LZMA_STREAM_END)
+    return G_CONVERTER_FINISHED;
+
+  return G_CONVERTER_CONVERTED;
+}
+
+static void
+gdu_xz_decompressor_iface_init (GConverterIface *iface)
+{
+  iface->convert = gdu_xz_decompressor_convert;
+  iface->reset = gdu_xz_decompressor_reset;
+}
+
+gsize
+gdu_xz_decompressor_get_uncompressed_size (GFile *compressed_file)
+{
+  gchar *path = NULL;
+  gsize ret = 0;
+  GMappedFile *mapped_file = NULL;
+  size_t bufpos = 0;
+  uint64_t memlimit = UINT64_MAX;
+  lzma_index *index_object = NULL;
+  lzma_ret res;
+  GError *error = NULL;
+  uint8_t *buf;
+  gsize len;
+  lzma_stream_flags stream_flags;
+  uint8_t *footer, *index;
+
+  path = g_file_get_path (compressed_file);
+  if (path == NULL)
+    {
+      gchar *uri;
+      uri = g_file_get_uri (compressed_file);
+      g_warning ("No path for URI '%s'. Maybe you need to enable FUSE.", uri);
+      g_free (uri);
+      goto out;
+    }
+
+  mapped_file = g_mapped_file_new (path, FALSE /* writable */, &error);
+  if (mapped_file == NULL)
+    {
+      g_warning ("Error mapping file '%s': %s",
+                 path, error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+
+  buf = (uint8_t*) g_mapped_file_get_contents (mapped_file);
+  len = g_mapped_file_get_length (mapped_file);
+
+  if (len < 12)
+    goto out;
+  footer = buf + len - 12;
+  if (lzma_stream_footer_decode (&stream_flags, footer) != LZMA_OK)
+    goto out;
+  if (stream_flags.backward_size > len - 12)
+    goto out;
+  index = footer - stream_flags.backward_size;
+
+  res = lzma_index_buffer_decode (&index_object,
+                                  &memlimit,
+                                  NULL /* allocator */,
+                                  index,
+                                  &bufpos,
+                                  footer - index);
+  if (res != LZMA_OK)
+    goto out;
+
+  ret = lzma_index_uncompressed_size (index_object);
+
+ out:
+  if (index_object != NULL)
+    lzma_index_end (index_object, NULL);
+  if (mapped_file != NULL)
+    g_mapped_file_unref (mapped_file);
+  g_free (path);
+  return ret;
+}
diff --git a/src/disks/gduxzdecompressor.h b/src/disks/gduxzdecompressor.h
new file mode 100644
index 0000000..bedb34b
--- /dev/null
+++ b/src/disks/gduxzdecompressor.h
@@ -0,0 +1,40 @@
+/* XZ Decompressor - based on GLib's GZLibDecompressor
+ *
+ * Copyright (C) 2013 David Zeuthen
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * Licensed under GPL version 2 or later.
+ *
+ * Author: David Zeuthen <zeuthen gmail com>
+ *         Alexander Larsson <alexl redhat com>
+ */
+
+#ifndef __GDU_XZ_DECOMPRESSOR_H__
+#define __GDU_XZ_DECOMPRESSOR_H__
+
+#include "gdutypes.h"
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_XZ_DECOMPRESSOR         (gdu_xz_decompressor_get_type ())
+#define GDU_XZ_DECOMPRESSOR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_XZ_DECOMPRESSOR, 
GduXzDecompressor))
+#define GDU_XZ_DECOMPRESSOR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDU_TYPE_XZ_DECOMPRESSOR, 
GduXzDecompressorClass))
+#define GDU_IS_XZ_DECOMPRESSOR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_XZ_DECOMPRESSOR))
+#define GDU_IS_XZ_DECOMPRESSOR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_XZ_DECOMPRESSOR))
+#define GDU_XZ_DECOMPRESSOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDU_TYPE_XZ_DECOMPRESSOR, 
GduXzDecompressorClass))
+
+typedef struct GduXzDecompressorClass   GduXzDecompressorClass;
+
+struct GduXzDecompressorClass
+{
+  GObjectClass parent_class;
+};
+
+GType              gdu_xz_decompressor_get_type      (void) G_GNUC_CONST;
+GduXzDecompressor *gdu_xz_decompressor_new           (void);
+
+gsize              gdu_xz_decompressor_get_uncompressed_size (GFile *compressed_file);
+
+G_END_DECLS
+
+#endif /* __GDU_XZ_DECOMPRESSOR_H__ */
diff --git a/src/libgdu/gduutils.c b/src/libgdu/gduutils.c
index ca8e097..d6d046f 100644
--- a/src/libgdu/gduutils.c
+++ b/src/libgdu/gduutils.c
@@ -65,7 +65,8 @@ gdu_utils_has_configuration (UDisksBlock  *block,
 
 void
 gdu_utils_configure_file_chooser_for_disk_images (GtkFileChooser *file_chooser,
-                                                  gboolean        set_file_types)
+                                                  gboolean        set_file_types,
+                                                  gboolean        allow_compressed)
 {
   GtkFileFilter *filter;
   gchar *folder;
@@ -92,8 +93,17 @@ gdu_utils_configure_file_chooser_for_disk_images (GtkFileChooser *file_chooser,
       gtk_file_filter_add_pattern (filter, "*");
       gtk_file_chooser_add_filter (file_chooser, filter); /* adopts filter */
       filter = gtk_file_filter_new ();
-      gtk_file_filter_set_name (filter, _("Disk Images (*.img, *.iso)"));
+      if (allow_compressed)
+        gtk_file_filter_set_name (filter, _("Disk Images (*.img, *.img.xz, *.iso)"));
+      else
+        gtk_file_filter_set_name (filter, _("Disk Images (*.img, *.iso)"));
+      gtk_file_filter_add_pattern (filter, "*.raw-disk-image");
       gtk_file_filter_add_pattern (filter, "*.img");
+      if (allow_compressed)
+        {
+          gtk_file_filter_add_pattern (filter, "*.raw-disk-image.xz");
+          gtk_file_filter_add_pattern (filter, "*.img.xz");
+        }
       gtk_file_filter_add_pattern (filter, "*.iso");
       gtk_file_chooser_add_filter (file_chooser, filter); /* adopts filter */
       gtk_file_chooser_set_filter (file_chooser, filter);
diff --git a/src/libgdu/gduutils.h b/src/libgdu/gduutils.h
index 15822a1..c5dcacd 100644
--- a/src/libgdu/gduutils.h
+++ b/src/libgdu/gduutils.h
@@ -19,7 +19,8 @@ gboolean gdu_utils_has_configuration (UDisksBlock  *block,
                                       gboolean     *out_has_passphrase);
 
 void gdu_utils_configure_file_chooser_for_disk_images (GtkFileChooser *file_chooser,
-                                                       gboolean        set_file_types);
+                                                       gboolean        set_file_types,
+                                                       gboolean        allow_compressed);
 
 void gdu_utils_file_chooser_for_disk_images_update_settings (GtkFileChooser *file_chooser);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]