summaryrefslogtreecommitdiff
path: root/src/fr-archive.c
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-11-09 22:53:33 -0300
committerPerberos <[email protected]>2011-11-09 22:53:33 -0300
commit70438138096a47b2505ac55634cd94947ce378b6 (patch)
treee45e49dda10a71616466500a4ab65d1c54b5f6c1 /src/fr-archive.c
downloadengrampa-70438138096a47b2505ac55634cd94947ce378b6.tar.bz2
engrampa-70438138096a47b2505ac55634cd94947ce378b6.tar.xz
initial
Diffstat (limited to 'src/fr-archive.c')
-rw-r--r--src/fr-archive.c3354
1 files changed, 3354 insertions, 0 deletions
diff --git a/src/fr-archive.c b/src/fr-archive.c
new file mode 100644
index 0000000..1e79812
--- /dev/null
+++ b/src/fr-archive.c
@@ -0,0 +1,3354 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * File-Roller
+ *
+ * Copyright (C) 2001, 2003, 2007, 2008 Free Software Foundation, Inc.
+ *
+ * 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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/param.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "glib-utils.h"
+#include "file-utils.h"
+#include "gio-utils.h"
+#include "file-data.h"
+#include "fr-archive.h"
+#include "fr-command.h"
+#include "fr-error.h"
+#include "fr-marshal.h"
+#include "fr-process.h"
+#include "main.h"
+
+#ifndef NCARGS
+#define NCARGS _POSIX_ARG_MAX
+#endif
+
+
+/* -- DroppedItemsData -- */
+
+
+typedef struct {
+ FrArchive *archive;
+ GList *item_list;
+ char *base_dir;
+ char *dest_dir;
+ gboolean update;
+ char *password;
+ gboolean encrypt_header;
+ FrCompression compression;
+ guint volume_size;
+} DroppedItemsData;
+
+
+static DroppedItemsData *
+dropped_items_data_new (FrArchive *archive,
+ GList *item_list,
+ const char *base_dir,
+ const char *dest_dir,
+ gboolean update,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+{
+ DroppedItemsData *data;
+
+ data = g_new0 (DroppedItemsData, 1);
+ data->archive = archive;
+ data->item_list = path_list_dup (item_list);
+ if (base_dir != NULL)
+ data->base_dir = g_strdup (base_dir);
+ if (dest_dir != NULL)
+ data->dest_dir = g_strdup (dest_dir);
+ data->update = update;
+ if (password != NULL)
+ data->password = g_strdup (password);
+ data->encrypt_header = encrypt_header;
+ data->compression = compression;
+ data->volume_size = volume_size;
+
+ return data;
+}
+
+
+static void
+dropped_items_data_free (DroppedItemsData *data)
+{
+ if (data == NULL)
+ return;
+ path_list_free (data->item_list);
+ g_free (data->base_dir);
+ g_free (data->dest_dir);
+ g_free (data->password);
+ g_free (data);
+}
+
+
+struct _FrArchivePrivData {
+ FakeLoadFunc fake_load_func; /* If returns TRUE, archives are not read when
+ * fr_archive_load is invoked, used
+ * in batch mode. */
+ gpointer fake_load_data;
+ FakeLoadFunc add_is_stoppable_func; /* Returns whether the add operation is
+ * stoppable. */
+ gpointer add_is_stoppable_data;
+ GCancellable *cancellable;
+ char *temp_dir;
+ gboolean continue_adding_dropped_items;
+ DroppedItemsData *dropped_items_data;
+
+ char *temp_extraction_dir;
+ char *extraction_destination;
+ gboolean remote_extraction;
+ gboolean extract_here;
+};
+
+
+typedef struct {
+ FrArchive *archive;
+ char *uri;
+ FrAction action;
+ GList *file_list;
+ char *base_uri;
+ char *dest_dir;
+ gboolean update;
+ char *tmp_dir;
+ guint source_id;
+ char *password;
+ gboolean encrypt_header;
+ FrCompression compression;
+ guint volume_size;
+} XferData;
+
+
+static void
+xfer_data_free (XferData *data)
+{
+ if (data == NULL)
+ return;
+
+ g_free (data->uri);
+ g_free (data->password);
+ path_list_free (data->file_list);
+ g_free (data->base_uri);
+ g_free (data->dest_dir);
+ g_free (data->tmp_dir);
+ g_free (data);
+}
+
+
+#define MAX_CHUNK_LEN (NCARGS * 2 / 3) /* Max command line length */
+#define UNKNOWN_TYPE "application/octet-stream"
+#define SAME_FS (FALSE)
+#define NO_BACKUP_FILES (TRUE)
+#define NO_DOT_FILES (FALSE)
+#define IGNORE_CASE (FALSE)
+#define LIST_LENGTH_TO_USE_FILE 10 /* FIXME: find a good value */
+
+
+enum {
+ START,
+ DONE,
+ PROGRESS,
+ MESSAGE,
+ STOPPABLE,
+ WORKING_ARCHIVE,
+ LAST_SIGNAL
+};
+
+static GObjectClass *parent_class;
+static guint fr_archive_signals[LAST_SIGNAL] = { 0 };
+
+static void fr_archive_class_init (FrArchiveClass *class);
+static void fr_archive_init (FrArchive *archive);
+static void fr_archive_finalize (GObject *object);
+
+
+GType
+fr_archive_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ static const GTypeInfo type_info = {
+ sizeof (FrArchiveClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) fr_archive_class_init,
+ NULL,
+ NULL,
+ sizeof (FrArchive),
+ 0,
+ (GInstanceInitFunc) fr_archive_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "FrArchive",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+fr_archive_class_init (FrArchiveClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class->finalize = fr_archive_finalize;
+
+ class->start = NULL;
+ class->done = NULL;
+ class->progress = NULL;
+ class->message = NULL;
+ class->working_archive = NULL;
+
+ /* signals */
+
+ fr_archive_signals[START] =
+ g_signal_new ("start",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FrArchiveClass, start),
+ NULL, NULL,
+ fr_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1, G_TYPE_INT);
+ fr_archive_signals[DONE] =
+ g_signal_new ("done",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FrArchiveClass, done),
+ NULL, NULL,
+ fr_marshal_VOID__INT_POINTER,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT,
+ G_TYPE_POINTER);
+ fr_archive_signals[PROGRESS] =
+ g_signal_new ("progress",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FrArchiveClass, progress),
+ NULL, NULL,
+ fr_marshal_VOID__DOUBLE,
+ G_TYPE_NONE, 1,
+ G_TYPE_DOUBLE);
+ fr_archive_signals[MESSAGE] =
+ g_signal_new ("message",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FrArchiveClass, message),
+ NULL, NULL,
+ fr_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ fr_archive_signals[STOPPABLE] =
+ g_signal_new ("stoppable",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FrArchiveClass, stoppable),
+ NULL, NULL,
+ fr_marshal_VOID__BOOL,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+ fr_archive_signals[WORKING_ARCHIVE] =
+ g_signal_new ("working_archive",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FrArchiveClass, working_archive),
+ NULL, NULL,
+ fr_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+}
+
+
+void
+fr_archive_stoppable (FrArchive *archive,
+ gboolean stoppable)
+{
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[STOPPABLE],
+ 0,
+ stoppable);
+}
+
+
+void
+fr_archive_stop (FrArchive *archive)
+{
+ if (archive->process != NULL) {
+ fr_process_stop (archive->process);
+ return;
+ }
+
+ if (! g_cancellable_is_cancelled (archive->priv->cancellable))
+ g_cancellable_cancel (archive->priv->cancellable);
+}
+
+
+void
+fr_archive_action_completed (FrArchive *archive,
+ FrAction action,
+ FrProcErrorType error_type,
+ const char *error_details)
+{
+ archive->error.type = error_type;
+ archive->error.status = 0;
+ g_clear_error (&archive->error.gerror);
+ if (error_details != NULL)
+ archive->error.gerror = g_error_new_literal (fr_error_quark (),
+ 0,
+ error_details);
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[DONE],
+ 0,
+ action,
+ &archive->error);
+}
+
+
+static gboolean
+fr_archive_add_is_stoppable (FrArchive *archive)
+{
+ if (archive->priv->add_is_stoppable_func != NULL)
+ return (*archive->priv->add_is_stoppable_func) (archive, archive->priv->add_is_stoppable_data);
+ else
+ return FALSE;
+}
+
+
+static gboolean
+archive_sticky_only_cb (FrProcess *process,
+ FrArchive *archive)
+{
+ fr_archive_stoppable (archive, fr_archive_add_is_stoppable (archive));
+ return TRUE;
+}
+
+
+static void
+fr_archive_init (FrArchive *archive)
+{
+ archive->file = NULL;
+ archive->local_copy = NULL;
+ archive->is_remote = FALSE;
+ archive->command = NULL;
+ archive->is_compressed_file = FALSE;
+ archive->can_create_compressed_file = FALSE;
+
+ archive->priv = g_new0 (FrArchivePrivData, 1);
+ archive->priv->fake_load_func = NULL;
+ archive->priv->fake_load_data = NULL;
+ archive->priv->add_is_stoppable_func = NULL;
+ archive->priv->add_is_stoppable_data = NULL;
+
+ archive->priv->extraction_destination = NULL;
+ archive->priv->temp_extraction_dir = NULL;
+ archive->priv->cancellable = g_cancellable_new ();
+
+ archive->process = fr_process_new ();
+ g_signal_connect (G_OBJECT (archive->process),
+ "sticky_only",
+ G_CALLBACK (archive_sticky_only_cb),
+ archive);
+}
+
+
+FrArchive *
+fr_archive_new (void)
+{
+ return FR_ARCHIVE (g_object_new (FR_TYPE_ARCHIVE, NULL));
+}
+
+
+static GFile *
+get_local_copy_for_file (GFile *remote_file)
+{
+ char *temp_dir;
+ GFile *local_copy = NULL;
+
+ temp_dir = get_temp_work_dir (NULL);
+ if (temp_dir != NULL) {
+ char *archive_name;
+ char *local_path;
+
+ archive_name = g_file_get_basename (remote_file);
+ local_path = g_build_filename (temp_dir, archive_name, NULL);
+ local_copy = g_file_new_for_path (local_path);
+
+ g_free (local_path);
+ g_free (archive_name);
+ }
+ g_free (temp_dir);
+
+ return local_copy;
+}
+
+
+static void
+fr_archive_set_uri (FrArchive *archive,
+ const char *uri)
+{
+ if ((archive->local_copy != NULL) && archive->is_remote) {
+ GFile *temp_folder;
+ GError *err = NULL;
+
+ g_file_delete (archive->local_copy, NULL, &err);
+ if (err != NULL) {
+ g_warning ("Failed to delete the local copy: %s", err->message);
+ g_clear_error (&err);
+ }
+
+ temp_folder = g_file_get_parent (archive->local_copy);
+ g_file_delete (temp_folder, NULL, &err);
+ if (err != NULL) {
+ g_warning ("Failed to delete temp folder: %s", err->message);
+ g_clear_error (&err);
+ }
+
+ g_object_unref (temp_folder);
+ }
+
+ if (archive->file != NULL) {
+ g_object_unref (archive->file);
+ archive->file = NULL;
+ }
+ if (archive->local_copy != NULL) {
+ g_object_unref (archive->local_copy);
+ archive->local_copy = NULL;
+ }
+ archive->content_type = NULL;
+
+ if (uri == NULL)
+ return;
+
+ archive->file = g_file_new_for_uri (uri);
+ archive->is_remote = ! g_file_has_uri_scheme (archive->file, "file");
+ if (archive->is_remote)
+ archive->local_copy = get_local_copy_for_file (archive->file);
+ else
+ archive->local_copy = g_file_dup (archive->file);
+}
+
+
+static void
+fr_archive_remove_temp_work_dir (FrArchive *archive)
+{
+ if (archive->priv->temp_dir == NULL)
+ return;
+ remove_local_directory (archive->priv->temp_dir);
+ g_free (archive->priv->temp_dir);
+ archive->priv->temp_dir = NULL;
+}
+
+
+static void
+fr_archive_finalize (GObject *object)
+{
+ FrArchive *archive;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (FR_IS_ARCHIVE (object));
+
+ archive = FR_ARCHIVE (object);
+
+ fr_archive_set_uri (archive, NULL);
+ fr_archive_remove_temp_work_dir (archive);
+ if (archive->command != NULL)
+ g_object_unref (archive->command);
+ g_object_unref (archive->process);
+ if (archive->priv->dropped_items_data != NULL) {
+ dropped_items_data_free (archive->priv->dropped_items_data);
+ archive->priv->dropped_items_data = NULL;
+ }
+ g_free (archive->priv->temp_extraction_dir);
+ g_free (archive->priv->extraction_destination);
+ g_free (archive->priv);
+
+ /* Chain up */
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static const char *
+get_mime_type_from_content (GFile *file)
+{
+ GFileInfo *info;
+ GError *err = NULL;
+ const char *content_type = NULL;
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, NULL, &err);
+ if (info == NULL) {
+ g_warning ("could not get content type: %s", err->message);
+ g_clear_error (&err);
+ }
+ else {
+ content_type = get_static_string (g_file_info_get_content_type (info));
+ g_object_unref (info);
+ }
+
+ return content_type;
+}
+
+
+static const char *
+get_mime_type_from_magic_numbers (GFile *file)
+{
+ static struct {
+ const char *mime_type;
+ const char *first_bytes;
+ int offset;
+ int len;
+ } sniffer_data [] = {
+ /* Magic numbers taken from magic/Magdir/archive from the
+ * file-4.21 tarball. */
+ { "application/x-7z-compressed", "7z\274\257\047\034", 0, 5 },
+ { "application/x-ace", "**ACE**", 7, 7 },
+ { "application/x-arj", "\x60\xea", 0, 2 },
+ { "application/x-bzip2", "BZh", 0, 3 },
+ { "application/x-gzip", "\037\213", 0, 2 },
+ { "application/x-lzip", "LZIP", 0, 4 },
+ { "application/x-lzop", "\x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a", 0, 9 },
+ { "application/x-rar", "Rar!", 0, 4 },
+ { "application/x-rzip", "RZIP", 0, 4 },
+ { "application/x-xz", "\3757zXZ\000", 0, 6 },
+ { "application/x-zoo", "\xdc\xa7\xc4\xfd", 20, 4 },
+ { "application/zip", "PK\003\004", 0, 4 },
+ { "application/zip", "PK00PK\003\004", 0, 8 },
+ { "application/x-lrzip", "LRZI", 0, 4 },
+ { NULL, NULL, 0 }
+ };
+ char buffer[32];
+ int i;
+
+ if (! g_load_file_in_buffer (file, buffer, 32, NULL))
+ return NULL;
+
+ for (i = 0; sniffer_data[i].mime_type != NULL; i++)
+ if (memcmp (sniffer_data[i].first_bytes,
+ buffer + sniffer_data[i].offset,
+ sniffer_data[i].len) == 0)
+ {
+ return sniffer_data[i].mime_type;
+ }
+
+ return NULL;
+}
+
+
+static const char *
+get_mime_type_from_filename (GFile *file)
+{
+ const char *mime_type = NULL;
+ char *filename;
+
+ if (file == NULL)
+ return NULL;
+
+ filename = g_file_get_path (file);
+ mime_type = get_mime_type_from_extension (get_file_extension (filename));
+ g_free (filename);
+
+ return mime_type;
+}
+
+
+static gboolean
+create_command_from_type (FrArchive *archive,
+ const char *mime_type,
+ GType command_type,
+ FrCommandCaps requested_capabilities)
+{
+ char *filename;
+
+ if (command_type == 0)
+ return FALSE;
+
+ filename = g_file_get_path (archive->local_copy);
+ archive->command = FR_COMMAND (g_object_new (command_type,
+ "process", archive->process,
+ "filename", filename,
+ "mime-type", mime_type,
+ NULL));
+ g_free (filename);
+
+ if (! fr_command_is_capable_of (archive->command, requested_capabilities)) {
+ g_object_unref (archive->command);
+ archive->command = NULL;
+ archive->is_compressed_file = FALSE;
+ }
+ else
+ archive->is_compressed_file = ! fr_command_is_capable_of (archive->command, FR_COMMAND_CAN_ARCHIVE_MANY_FILES);
+
+ return (archive->command != NULL);
+}
+
+
+static gboolean
+create_command_to_load_archive (FrArchive *archive,
+ const char *mime_type)
+{
+ FrCommandCaps requested_capabilities = FR_COMMAND_CAN_DO_NOTHING;
+ GType command_type;
+
+ if (mime_type == NULL)
+ return FALSE;
+
+ /* try with the WRITE capability even when loading, this way we give
+ * priority to the commands that can read and write over commands
+ * that can only read a specific file format. */
+
+ requested_capabilities |= FR_COMMAND_CAN_READ_WRITE;
+ command_type = get_command_type_from_mime_type (mime_type, requested_capabilities);
+
+ /* if no command was found, remove the write capability and try again */
+
+ if (command_type == 0) {
+ requested_capabilities ^= FR_COMMAND_CAN_WRITE;
+ command_type = get_command_type_from_mime_type (mime_type, requested_capabilities);
+ }
+
+ return create_command_from_type (archive,
+ mime_type,
+ command_type,
+ requested_capabilities);
+}
+
+
+static gboolean
+create_command_to_create_archive (FrArchive *archive,
+ const char *mime_type)
+{
+ FrCommandCaps requested_capabilities = FR_COMMAND_CAN_DO_NOTHING;
+ GType command_type;
+
+ if (mime_type == NULL)
+ return FALSE;
+
+ requested_capabilities |= FR_COMMAND_CAN_WRITE;
+ command_type = get_command_type_from_mime_type (mime_type, requested_capabilities);
+
+ return create_command_from_type (archive,
+ mime_type,
+ command_type,
+ requested_capabilities);
+}
+
+
+static void
+action_started (FrCommand *command,
+ FrAction action,
+ FrArchive *archive)
+{
+#ifdef DEBUG
+ debug (DEBUG_INFO, "%s [START] (FR::Archive)\n", action_names[action]);
+#endif
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[START],
+ 0,
+ action);
+}
+
+
+/* -- copy_to_remote_location -- */
+
+
+static void
+fr_archive_copy_done (FrArchive *archive,
+ FrAction action,
+ GError *error)
+{
+ FrProcErrorType error_type = FR_PROC_ERROR_NONE;
+ const char *error_details = NULL;
+
+ if (error != NULL) {
+ error_type = (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ? FR_PROC_ERROR_STOPPED : FR_PROC_ERROR_GENERIC);
+ error_details = error->message;
+ }
+ fr_archive_action_completed (archive, action, error_type, error_details);
+}
+
+
+static void
+copy_to_remote_location_done (GError *error,
+ gpointer user_data)
+{
+ XferData *xfer_data = user_data;
+
+ fr_archive_copy_done (xfer_data->archive, xfer_data->action, error);
+ xfer_data_free (xfer_data);
+}
+
+
+static void
+copy_to_remote_location_progress (goffset current_file,
+ goffset total_files,
+ GFile *source,
+ GFile *destination,
+ goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ XferData *xfer_data = user_data;
+
+ g_signal_emit (G_OBJECT (xfer_data->archive),
+ fr_archive_signals[PROGRESS],
+ 0,
+ (double) current_num_bytes / total_num_bytes);
+}
+
+
+static void
+copy_to_remote_location (FrArchive *archive,
+ FrAction action)
+{
+ XferData *xfer_data;
+
+ xfer_data = g_new0 (XferData, 1);
+ xfer_data->archive = archive;
+ xfer_data->action = action;
+
+ g_copy_file_async (archive->local_copy,
+ archive->file,
+ G_FILE_COPY_OVERWRITE,
+ G_PRIORITY_DEFAULT,
+ archive->priv->cancellable,
+ copy_to_remote_location_progress,
+ xfer_data,
+ copy_to_remote_location_done,
+ xfer_data);
+}
+
+
+/* -- copy_extracted_files_to_destination -- */
+
+
+static void
+move_here (FrArchive *archive)
+{
+ char *content_uri;
+ char *parent;
+ char *parent_parent;
+ char *new_content_uri;
+ GFile *source, *destination, *parent_file;
+ GError *error = NULL;
+
+ content_uri = get_dir_content_if_unique (archive->priv->extraction_destination);
+ if (content_uri == NULL)
+ return;
+
+ parent = remove_level_from_path (content_uri);
+
+ if (uricmp (parent, archive->priv->extraction_destination) == 0) {
+ char *new_uri;
+
+ new_uri = get_alternative_uri_for_uri (archive->priv->extraction_destination);
+
+ source = g_file_new_for_uri (archive->priv->extraction_destination);
+ destination = g_file_new_for_uri (new_uri);
+ if (! g_file_move (source, destination, 0, NULL, NULL, NULL, &error)) {
+ g_warning ("could not rename %s to %s: %s", archive->priv->extraction_destination, new_uri, error->message);
+ g_clear_error (&error);
+ }
+ g_object_unref (source);
+ g_object_unref (destination);
+
+ g_free (archive->priv->extraction_destination);
+ archive->priv->extraction_destination = new_uri;
+
+ g_free (parent);
+
+ content_uri = get_dir_content_if_unique (archive->priv->extraction_destination);
+ if (content_uri == NULL)
+ return;
+
+ parent = remove_level_from_path (content_uri);
+ }
+
+ parent_parent = remove_level_from_path (parent);
+ new_content_uri = get_alternative_uri (parent_parent, file_name_from_path (content_uri));
+
+ source = g_file_new_for_uri (content_uri);
+ destination = g_file_new_for_uri (new_content_uri);
+ if (! g_file_move (source, destination, 0, NULL, NULL, NULL, &error)) {
+ g_warning ("could not rename %s to %s: %s", content_uri, new_content_uri, error->message);
+ g_clear_error (&error);
+ }
+
+ parent_file = g_file_new_for_uri (parent);
+ if (! g_file_delete (parent_file, NULL, &error)) {
+ g_warning ("could not remove directory %s: %s", parent, error->message);
+ g_clear_error (&error);
+ }
+ g_object_unref (parent_file);
+
+ g_free (archive->priv->extraction_destination);
+ archive->priv->extraction_destination = new_content_uri;
+
+ g_free (parent_parent);
+ g_free (parent);
+ g_free (content_uri);
+}
+
+
+static void
+copy_extracted_files_done (GError *error,
+ gpointer user_data)
+{
+ FrArchive *archive = user_data;
+
+ remove_local_directory (archive->priv->temp_extraction_dir);
+ g_free (archive->priv->temp_extraction_dir);
+ archive->priv->temp_extraction_dir = NULL;
+
+ fr_archive_action_completed (archive,
+ FR_ACTION_COPYING_FILES_TO_REMOTE,
+ FR_PROC_ERROR_NONE,
+ NULL);
+
+ if ((error == NULL) && (archive->priv->extract_here))
+ move_here (archive);
+
+ fr_archive_copy_done (archive, FR_ACTION_EXTRACTING_FILES, error);
+}
+
+
+static void
+copy_extracted_files_progress (goffset current_file,
+ goffset total_files,
+ GFile *source,
+ GFile *destination,
+ goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ FrArchive *archive = user_data;
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[PROGRESS],
+ 0,
+ (double) current_file / (total_files + 1));
+}
+
+
+static void
+copy_extracted_files_to_destination (FrArchive *archive)
+{
+ g_directory_copy_async (archive->priv->temp_extraction_dir,
+ archive->priv->extraction_destination,
+ G_FILE_COPY_OVERWRITE,
+ G_PRIORITY_DEFAULT,
+ archive->priv->cancellable,
+ copy_extracted_files_progress,
+ archive,
+ copy_extracted_files_done,
+ archive);
+}
+
+
+static void add_dropped_items (DroppedItemsData *data);
+
+
+static void
+fr_archive_change_name (FrArchive *archive,
+ const char *filename)
+{
+ const char *name;
+ GFile *parent;
+
+ name = file_name_from_path (filename);
+
+ parent = g_file_get_parent (archive->file);
+ g_object_unref (archive->file);
+ archive->file = g_file_get_child (parent, name);
+ g_object_unref (parent);
+
+ parent = g_file_get_parent (archive->local_copy);
+ g_object_unref (archive->local_copy);
+ archive->local_copy = g_file_get_child (parent, name);
+ g_object_unref (parent);
+}
+
+
+static void
+action_performed (FrCommand *command,
+ FrAction action,
+ FrProcError *error,
+ FrArchive *archive)
+{
+#ifdef DEBUG
+ debug (DEBUG_INFO, "%s [DONE] (FR::Archive)\n", action_names[action]);
+#endif
+
+ switch (action) {
+ case FR_ACTION_DELETING_FILES:
+ if (error->type == FR_PROC_ERROR_NONE) {
+ if (! g_file_has_uri_scheme (archive->file, "file")) {
+ copy_to_remote_location (archive, action);
+ return;
+ }
+ }
+ break;
+
+ case FR_ACTION_ADDING_FILES:
+ if (error->type == FR_PROC_ERROR_NONE) {
+ fr_archive_remove_temp_work_dir (archive);
+ if (archive->priv->continue_adding_dropped_items) {
+ add_dropped_items (archive->priv->dropped_items_data);
+ return;
+ }
+ if (archive->priv->dropped_items_data != NULL) {
+ dropped_items_data_free (archive->priv->dropped_items_data);
+ archive->priv->dropped_items_data = NULL;
+ }
+ /* the name of the volumes are different from the
+ * original name */
+ if (archive->command->multi_volume)
+ fr_archive_change_name (archive, archive->command->filename);
+ if (! g_file_has_uri_scheme (archive->file, "file")) {
+ copy_to_remote_location (archive, action);
+ return;
+ }
+ }
+ break;
+
+ case FR_ACTION_EXTRACTING_FILES:
+ if (error->type == FR_PROC_ERROR_NONE) {
+ if (archive->priv->remote_extraction) {
+ copy_extracted_files_to_destination (archive);
+ return;
+ }
+ else if (archive->priv->extract_here)
+ move_here (archive);
+ }
+ else {
+ /* if an error occurred during extraction remove the
+ * temp extraction dir, if used. */
+ g_print ("action_performed: ERROR!\n");
+
+ if ((archive->priv->remote_extraction) && (archive->priv->temp_extraction_dir != NULL)) {
+ remove_local_directory (archive->priv->temp_extraction_dir);
+ g_free (archive->priv->temp_extraction_dir);
+ archive->priv->temp_extraction_dir = NULL;
+ }
+
+ if (archive->priv->extract_here)
+ remove_directory (archive->priv->extraction_destination);
+ }
+ break;
+
+ case FR_ACTION_LISTING_CONTENT:
+ /* the name of the volumes are different from the
+ * original name */
+ if (archive->command->multi_volume)
+ fr_archive_change_name (archive, archive->command->filename);
+ fr_command_update_capabilities (archive->command);
+ if (! fr_command_is_capable_of (archive->command, FR_COMMAND_CAN_WRITE))
+ archive->read_only = TRUE;
+ break;
+
+ default:
+ /* nothing */
+ break;
+ }
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[DONE],
+ 0,
+ action,
+ error);
+}
+
+
+static gboolean
+archive_progress_cb (FrCommand *command,
+ double fraction,
+ FrArchive *archive)
+{
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[PROGRESS],
+ 0,
+ fraction);
+ return TRUE;
+}
+
+
+static gboolean
+archive_message_cb (FrCommand *command,
+ const char *msg,
+ FrArchive *archive)
+{
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[MESSAGE],
+ 0,
+ msg);
+ return TRUE;
+}
+
+
+static gboolean
+archive_working_archive_cb (FrCommand *command,
+ const char *archive_filename,
+ FrArchive *archive)
+{
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[WORKING_ARCHIVE],
+ 0,
+ archive_filename);
+ return TRUE;
+}
+
+
+static void
+fr_archive_connect_to_command (FrArchive *archive)
+{
+ g_signal_connect (G_OBJECT (archive->command),
+ "start",
+ G_CALLBACK (action_started),
+ archive);
+ g_signal_connect (G_OBJECT (archive->command),
+ "done",
+ G_CALLBACK (action_performed),
+ archive);
+ g_signal_connect (G_OBJECT (archive->command),
+ "progress",
+ G_CALLBACK (archive_progress_cb),
+ archive);
+ g_signal_connect (G_OBJECT (archive->command),
+ "message",
+ G_CALLBACK (archive_message_cb),
+ archive);
+ g_signal_connect (G_OBJECT (archive->command),
+ "working_archive",
+ G_CALLBACK (archive_working_archive_cb),
+ archive);
+}
+
+
+gboolean
+fr_archive_create (FrArchive *archive,
+ const char *uri)
+{
+ FrCommand *tmp_command;
+ const char *mime_type;
+
+ if (uri == NULL)
+ return FALSE;
+
+ fr_archive_set_uri (archive, uri);
+
+ tmp_command = archive->command;
+
+ mime_type = get_mime_type_from_filename (archive->local_copy);
+ if (! create_command_to_create_archive (archive, mime_type)) {
+ archive->command = tmp_command;
+ return FALSE;
+ }
+
+ if (tmp_command != NULL) {
+ g_signal_handlers_disconnect_by_data (tmp_command, archive);
+ g_object_unref (G_OBJECT (tmp_command));
+ }
+
+ fr_archive_connect_to_command (archive);
+ archive->read_only = FALSE;
+
+ return TRUE;
+}
+
+
+void
+fr_archive_set_fake_load_func (FrArchive *archive,
+ FakeLoadFunc func,
+ gpointer data)
+{
+ archive->priv->fake_load_func = func;
+ archive->priv->fake_load_data = data;
+}
+
+
+gboolean
+fr_archive_fake_load (FrArchive *archive)
+{
+ if (archive->priv->fake_load_func != NULL)
+ return (*archive->priv->fake_load_func) (archive, archive->priv->fake_load_data);
+ else
+ return FALSE;
+}
+
+
+/* -- fr_archive_load -- */
+
+
+static void
+load_local_archive (FrArchive *archive,
+ const char *password)
+{
+ FrCommand *tmp_command;
+ const char *mime_type;
+
+ if (! g_file_query_exists (archive->file, NULL)) {
+ fr_archive_action_completed (archive,
+ FR_ACTION_LOADING_ARCHIVE,
+ FR_PROC_ERROR_GENERIC,
+ _("File not found."));
+ return;
+ }
+
+ archive->have_permissions = check_file_permissions (archive->file, W_OK);
+ archive->read_only = ! archive->have_permissions;
+
+ tmp_command = archive->command;
+
+ mime_type = get_mime_type_from_filename (archive->local_copy);
+ if (! create_command_to_load_archive (archive, mime_type)) {
+ mime_type = get_mime_type_from_content (archive->local_copy);
+ if (! create_command_to_load_archive (archive, mime_type)) {
+ mime_type = get_mime_type_from_magic_numbers (archive->local_copy);
+ if (! create_command_to_load_archive (archive, mime_type)) {
+ archive->command = tmp_command;
+ archive->content_type = mime_type;
+ fr_archive_action_completed (archive,
+ FR_ACTION_LOADING_ARCHIVE,
+ FR_PROC_ERROR_UNSUPPORTED_FORMAT,
+ _("Archive type not supported."));
+ return;
+ }
+ }
+ }
+
+ if (tmp_command != NULL) {
+ g_signal_handlers_disconnect_by_data (tmp_command, archive);
+ g_object_unref (tmp_command);
+ }
+
+ fr_archive_connect_to_command (archive);
+ archive->content_type = mime_type;
+ if (! fr_command_is_capable_of (archive->command, FR_COMMAND_CAN_WRITE))
+ archive->read_only = TRUE;
+ fr_archive_stoppable (archive, TRUE);
+ archive->command->fake_load = fr_archive_fake_load (archive);
+
+ fr_archive_action_completed (archive,
+ FR_ACTION_LOADING_ARCHIVE,
+ FR_PROC_ERROR_NONE,
+ NULL);
+
+ /**/
+
+ fr_process_clear (archive->process);
+ g_object_set (archive->command, "password", password, NULL);
+ fr_command_list (archive->command);
+ fr_process_start (archive->process);
+}
+
+
+static void
+copy_remote_file_done (GError *error,
+ gpointer user_data)
+{
+ XferData *xfer_data = user_data;
+
+ if (error != NULL)
+ fr_archive_copy_done (xfer_data->archive, FR_ACTION_LOADING_ARCHIVE, error);
+ else
+ load_local_archive (xfer_data->archive, xfer_data->password);
+ xfer_data_free (xfer_data);
+}
+
+
+static void
+copy_remote_file_progress (goffset current_file,
+ goffset total_files,
+ GFile *source,
+ GFile *destination,
+ goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ XferData *xfer_data = user_data;
+
+ g_signal_emit (G_OBJECT (xfer_data->archive),
+ fr_archive_signals[PROGRESS],
+ 0,
+ (double) current_num_bytes / total_num_bytes);
+}
+
+
+static gboolean
+copy_remote_file_done_cb (gpointer user_data)
+{
+ XferData *xfer_data = user_data;
+
+ g_source_remove (xfer_data->source_id);
+ copy_remote_file_done (NULL, xfer_data);
+ return FALSE;
+}
+
+
+static void
+copy_remote_file (FrArchive *archive,
+ const char *password)
+{
+ XferData *xfer_data;
+
+ if (! g_file_query_exists (archive->file, NULL)) {
+ GError *error;
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("The file doesn't exist"));
+ fr_archive_copy_done (archive, FR_ACTION_LOADING_ARCHIVE, error);
+ g_error_free (error);
+ return;
+ }
+
+ xfer_data = g_new0 (XferData, 1);
+ xfer_data->archive = archive;
+ xfer_data->uri = g_file_get_uri (archive->file);
+ if (password != NULL)
+ xfer_data->password = g_strdup (password);
+
+ if (! archive->is_remote) {
+ xfer_data->source_id = g_idle_add (copy_remote_file_done_cb, xfer_data);
+ return;
+ }
+
+ g_copy_file_async (archive->file,
+ archive->local_copy,
+ G_FILE_COPY_OVERWRITE,
+ G_PRIORITY_DEFAULT,
+ archive->priv->cancellable,
+ copy_remote_file_progress,
+ xfer_data,
+ copy_remote_file_done,
+ xfer_data);
+}
+
+
+gboolean
+fr_archive_load (FrArchive *archive,
+ const char *uri,
+ const char *password)
+{
+ g_return_val_if_fail (archive != NULL, FALSE);
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[START],
+ 0,
+ FR_ACTION_LOADING_ARCHIVE);
+
+ fr_archive_set_uri (archive, uri);
+ copy_remote_file (archive, password);
+
+ return TRUE;
+}
+
+
+gboolean
+fr_archive_load_local (FrArchive *archive,
+ const char *uri,
+ const char *password)
+{
+ g_return_val_if_fail (archive != NULL, FALSE);
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[START],
+ 0,
+ FR_ACTION_LOADING_ARCHIVE);
+
+ fr_archive_set_uri (archive, uri);
+ copy_remote_file (archive, password);
+
+ return TRUE;
+}
+
+
+void
+fr_archive_reload (FrArchive *archive,
+ const char *password)
+{
+ char *uri;
+
+ g_return_if_fail (archive != NULL);
+ g_return_if_fail (archive->file != NULL);
+
+ fr_archive_stoppable (archive, TRUE);
+ archive->command->fake_load = fr_archive_fake_load (archive);
+
+ uri = g_file_get_uri (archive->file);
+ fr_archive_load (archive, uri, password);
+ g_free (uri);
+}
+
+
+void
+fr_archive_rename (FrArchive *archive,
+ const char *filename)
+{
+ g_return_if_fail (archive != NULL);
+
+ if (archive->is_compressed_file) {
+ /* If the archive is a compressed file we have to reload it,
+ * because in this case the 'content' of the archive changes
+ * too. */
+ fr_archive_load (archive, filename, NULL);
+ }
+ else {
+ if (archive->file != NULL)
+ g_object_unref (archive->file);
+ archive->file = g_file_new_for_path (filename);
+ fr_command_set_filename (archive->command, filename);
+ }
+}
+
+
+/* -- add -- */
+
+
+static char *
+create_tmp_base_dir (const char *base_dir,
+ const char *dest_path)
+{
+ char *dest_dir;
+ char *temp_dir;
+ char *tmp;
+ char *parent_dir;
+ char *dir;
+
+ if ((dest_path == NULL)
+ || (*dest_path == '\0')
+ || (strcmp (dest_path, "/") == 0))
+ {
+ return g_strdup (base_dir);
+ }
+
+ dest_dir = g_strdup (dest_path);
+ if (dest_dir[strlen (dest_dir) - 1] == G_DIR_SEPARATOR)
+ dest_dir[strlen (dest_dir) - 1] = 0;
+
+ debug (DEBUG_INFO, "base_dir: %s\n", base_dir);
+ debug (DEBUG_INFO, "dest_dir: %s\n", dest_dir);
+
+ temp_dir = get_temp_work_dir (NULL);
+ tmp = remove_level_from_path (dest_dir);
+ parent_dir = g_build_filename (temp_dir, tmp, NULL);
+ g_free (tmp);
+
+ debug (DEBUG_INFO, "mkdir %s\n", parent_dir);
+ make_directory_tree_from_path (parent_dir, 0700, NULL);
+ g_free (parent_dir);
+
+ dir = g_build_filename (temp_dir, "/", dest_dir, NULL);
+ debug (DEBUG_INFO, "symlink %s --> %s\n", dir, base_dir);
+ symlink (base_dir, dir);
+
+ g_free (dir);
+ g_free (dest_dir);
+
+ return temp_dir;
+}
+
+
+static FileData *
+find_file_in_archive (FrArchive *archive,
+ char *path)
+{
+ int i;
+
+ g_return_val_if_fail (path != NULL, NULL);
+
+ i = find_path_in_file_data_array (archive->command->files, path);
+ if (i >= 0)
+ return (FileData *) g_ptr_array_index (archive->command->files, i);
+ else
+ return NULL;
+}
+
+
+static void delete_from_archive (FrArchive *archive, GList *file_list);
+
+
+static GList *
+newer_files_only (FrArchive *archive,
+ GList *file_list,
+ const char *base_dir)
+{
+ GList *newer_files = NULL;
+ GList *scan;
+
+ for (scan = file_list; scan; scan = scan->next) {
+ char *filename = scan->data;
+ char *fullpath;
+ char *uri;
+ FileData *fdata;
+
+ fdata = find_file_in_archive (archive, filename);
+
+ if (fdata == NULL) {
+ newer_files = g_list_prepend (newer_files, g_strdup (scan->data));
+ continue;
+ }
+
+ fullpath = g_strconcat (base_dir, "/", filename, NULL);
+ uri = g_filename_to_uri (fullpath, NULL, NULL);
+
+ if (fdata->modified >= get_file_mtime (uri)) {
+ g_free (fullpath);
+ g_free (uri);
+ continue;
+ }
+ g_free (fullpath);
+ g_free (uri);
+
+ newer_files = g_list_prepend (newer_files, g_strdup (scan->data));
+ }
+
+ return newer_files;
+}
+
+
+void
+fr_archive_set_add_is_stoppable_func (FrArchive *archive,
+ FakeLoadFunc func,
+ gpointer data)
+{
+ archive->priv->add_is_stoppable_func = func;
+ archive->priv->add_is_stoppable_data = data;
+}
+
+
+static GList *
+convert_to_local_file_list (GList *file_list)
+{
+ GList *local_file_list = NULL;
+ GList *scan;
+
+ for (scan = file_list; scan; scan = scan->next) {
+ char *uri = scan->data;
+ char *local_filename;
+
+ local_filename = g_uri_unescape_string (uri, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH);
+ if (local_filename != NULL)
+ local_file_list = g_list_prepend (local_file_list, local_filename);
+ }
+
+ return local_file_list;
+}
+
+
+static gboolean
+save_list_to_temp_file (GList *file_list,
+ char **list_dir,
+ char **list_filename,
+ GError **error)
+{
+ gboolean error_occurred = FALSE;
+ GFile *list_file;
+ GFileOutputStream *ostream;
+
+ if (error != NULL)
+ *error = NULL;
+ *list_dir = get_temp_work_dir (NULL);
+ *list_filename = g_build_filename (*list_dir, "file-list", NULL);
+ list_file = g_file_new_for_path (*list_filename);
+ ostream = g_file_create (list_file, G_FILE_CREATE_PRIVATE, NULL, error);
+
+ if (ostream != NULL) {
+ GList *scan;
+
+ for (scan = file_list; scan != NULL; scan = scan->next) {
+ char *filename = scan->data;
+
+ filename = str_substitute (filename, "\n", "\\n");
+ if ((g_output_stream_write (G_OUTPUT_STREAM (ostream), filename, strlen (filename), NULL, error) < 0)
+ || (g_output_stream_write (G_OUTPUT_STREAM (ostream), "\n", 1, NULL, error) < 0))
+ {
+ error_occurred = TRUE;
+ }
+
+ g_free (filename);
+
+ if (error_occurred)
+ break;
+ }
+ if (! error_occurred && ! g_output_stream_close (G_OUTPUT_STREAM (ostream), NULL, error))
+ error_occurred = TRUE;
+ g_object_unref (ostream);
+ }
+ else
+ error_occurred = TRUE;
+
+ if (error_occurred) {
+ remove_local_directory (*list_dir);
+ g_free (*list_dir);
+ g_free (*list_filename);
+ *list_dir = NULL;
+ *list_filename = NULL;
+ }
+
+ g_object_unref (list_file);
+
+ return ! error_occurred;
+}
+
+
+static GList *
+split_in_chunks (GList *file_list)
+{
+ GList *chunks = NULL;
+ GList *new_file_list;
+ GList *scan;
+
+ new_file_list = g_list_copy (file_list);
+ for (scan = new_file_list; scan != NULL; /* void */) {
+ GList *prev = scan->prev;
+ GList *chunk;
+ int l;
+
+ chunk = scan;
+ l = 0;
+ while ((scan != NULL) && (l < MAX_CHUNK_LEN)) {
+ if (l == 0)
+ l = strlen (scan->data);
+ prev = scan;
+ scan = scan->next;
+ if (scan != NULL)
+ l += strlen (scan->data);
+ }
+ if (prev != NULL) {
+ if (prev->next != NULL)
+ prev->next->prev = NULL;
+ prev->next = NULL;
+ }
+ chunks = g_list_append (chunks, chunk);
+ }
+
+ return chunks;
+}
+
+
+void
+fr_archive_add (FrArchive *archive,
+ GList *file_list,
+ const char *base_dir,
+ const char *dest_dir,
+ gboolean update,
+ gboolean recursive,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+{
+ GList *new_file_list = NULL;
+ gboolean base_dir_created = FALSE;
+ GList *scan;
+ char *tmp_base_dir = NULL;
+ gboolean error_occurred = FALSE;
+
+ if (file_list == NULL)
+ return;
+
+ if (archive->read_only)
+ return;
+
+ g_object_set (archive->command,
+ "password", password,
+ "encrypt_header", encrypt_header,
+ "compression", compression,
+ "volume_size", volume_size,
+ NULL);
+
+ fr_archive_stoppable (archive, fr_archive_add_is_stoppable (archive));
+
+ file_list = convert_to_local_file_list (file_list);
+ tmp_base_dir = g_strdup (base_dir);
+
+ if ((dest_dir != NULL) && (*dest_dir != '\0') && (strcmp (dest_dir, "/") != 0)) {
+ const char *rel_dest_dir = dest_dir;
+
+ tmp_base_dir = create_tmp_base_dir (base_dir, dest_dir);
+ base_dir_created = TRUE;
+
+ if (dest_dir[0] == G_DIR_SEPARATOR)
+ rel_dest_dir = dest_dir + 1;
+
+ new_file_list = NULL;
+ for (scan = file_list; scan != NULL; scan = scan->next) {
+ char *filename = scan->data;
+ new_file_list = g_list_prepend (new_file_list, g_build_filename (rel_dest_dir, filename, NULL));
+ }
+ path_list_free (file_list);
+ }
+ else
+ new_file_list = file_list;
+
+ /* if the command cannot update, get the list of files that are
+ * newer than the ones in the archive. */
+
+ if (update && ! archive->command->propAddCanUpdate) {
+ GList *tmp_file_list;
+
+ tmp_file_list = new_file_list;
+ new_file_list = newer_files_only (archive, tmp_file_list, tmp_base_dir);
+ path_list_free (tmp_file_list);
+ }
+
+ if (new_file_list == NULL) {
+ debug (DEBUG_INFO, "nothing to update.\n");
+
+ if (base_dir_created)
+ remove_local_directory (tmp_base_dir);
+ g_free (tmp_base_dir);
+
+ archive->process->error.type = FR_PROC_ERROR_NONE;
+ g_signal_emit_by_name (G_OBJECT (archive->process),
+ "done",
+ FR_ACTION_ADDING_FILES);
+ return;
+ }
+
+ archive->command->creating_archive = ! g_file_test (archive->command->filename, G_FILE_TEST_EXISTS);
+
+ fr_command_uncompress (archive->command);
+
+ /* when files are already present in a tar archive and are added
+ * again, they are not replaced, so we have to delete them first. */
+
+ /* if we are adding (== ! update) and 'add' cannot replace or
+ * if we are updating and 'add' cannot update,
+ * delete the files first. */
+
+ if ((! update && ! archive->command->propAddCanReplace)
+ || (update && ! archive->command->propAddCanUpdate))
+ {
+ GList *del_list = NULL;
+
+ for (scan = new_file_list; scan != NULL; scan = scan->next) {
+ char *filename = scan->data;
+ if (find_file_in_archive (archive, filename))
+ del_list = g_list_prepend (del_list, filename);
+ }
+
+ /* delete */
+
+ if (del_list != NULL) {
+ delete_from_archive (archive, del_list);
+ fr_process_set_ignore_error (archive->process, TRUE);
+ g_list_free (del_list);
+ }
+ }
+
+ /* add now. */
+
+ fr_command_set_n_files (archive->command, g_list_length (new_file_list));
+
+ if (archive->command->propListFromFile
+ && (archive->command->n_files > LIST_LENGTH_TO_USE_FILE))
+ {
+ char *list_dir;
+ char *list_filename;
+ GError *error = NULL;
+
+ if (! save_list_to_temp_file (new_file_list, &list_dir, &list_filename, &error)) {
+ archive->process->error.type = FR_PROC_ERROR_GENERIC;
+ archive->process->error.status = 0;
+ archive->process->error.gerror = g_error_copy (error);
+ g_signal_emit_by_name (G_OBJECT (archive->process),
+ "done",
+ FR_ACTION_ADDING_FILES);
+ g_clear_error (&error);
+ error_occurred = TRUE;
+ }
+ else {
+ fr_command_add (archive->command,
+ list_filename,
+ new_file_list,
+ tmp_base_dir,
+ update,
+ recursive);
+
+ /* remove the temp dir */
+
+ fr_process_begin_command (archive->process, "rm");
+ fr_process_set_working_dir (archive->process, g_get_tmp_dir());
+ fr_process_set_sticky (archive->process, TRUE);
+ fr_process_add_arg (archive->process, "-rf");
+ fr_process_add_arg (archive->process, list_dir);
+ fr_process_end_command (archive->process);
+ }
+
+ g_free (list_filename);
+ g_free (list_dir);
+ }
+ else {
+ GList *chunks = NULL;
+
+ /* specify the file list on the command line, splitting
+ * in more commands to avoid to overflow the command line
+ * length limit. */
+
+ chunks = split_in_chunks (new_file_list);
+ for (scan = chunks; scan != NULL; scan = scan->next) {
+ GList *chunk = scan->data;
+
+ fr_command_add (archive->command,
+ NULL,
+ chunk,
+ tmp_base_dir,
+ update,
+ recursive);
+ g_list_free (chunk);
+ }
+
+ g_list_free (chunks);
+ }
+
+ path_list_free (new_file_list);
+
+ if (! error_occurred) {
+ fr_command_recompress (archive->command);
+
+ if (base_dir_created) { /* remove the temp dir */
+ fr_process_begin_command (archive->process, "rm");
+ fr_process_set_working_dir (archive->process, g_get_tmp_dir());
+ fr_process_set_sticky (archive->process, TRUE);
+ fr_process_add_arg (archive->process, "-rf");
+ fr_process_add_arg (archive->process, tmp_base_dir);
+ fr_process_end_command (archive->process);
+ }
+ }
+
+ g_free (tmp_base_dir);
+}
+
+
+static void
+fr_archive_add_local_files (FrArchive *archive,
+ GList *file_list,
+ const char *base_dir,
+ const char *dest_dir,
+ gboolean update,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+{
+ fr_archive_stoppable (archive, TRUE);
+ fr_process_clear (archive->process);
+ fr_archive_add (archive,
+ file_list,
+ base_dir,
+ dest_dir,
+ update,
+ FALSE,
+ password,
+ encrypt_header,
+ compression,
+ volume_size);
+ fr_process_start (archive->process);
+}
+
+
+static void
+copy_remote_files_done (GError *error,
+ gpointer user_data)
+{
+ XferData *xfer_data = user_data;
+
+ fr_archive_copy_done (xfer_data->archive, FR_ACTION_COPYING_FILES_FROM_REMOTE, error);
+
+ if (error == NULL)
+ fr_archive_add_local_files (xfer_data->archive,
+ xfer_data->file_list,
+ xfer_data->tmp_dir,
+ xfer_data->dest_dir,
+ FALSE,
+ xfer_data->password,
+ xfer_data->encrypt_header,
+ xfer_data->compression,
+ xfer_data->volume_size);
+ xfer_data_free (xfer_data);
+}
+
+
+static void
+copy_remote_files_progress (goffset current_file,
+ goffset total_files,
+ GFile *source,
+ GFile *destination,
+ goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ XferData *xfer_data = user_data;
+
+ g_signal_emit (G_OBJECT (xfer_data->archive),
+ fr_archive_signals[PROGRESS],
+ 0,
+ (double) current_file / (total_files + 1));
+}
+
+
+static void
+copy_remote_files (FrArchive *archive,
+ GList *file_list,
+ const char *base_uri,
+ const char *dest_dir,
+ gboolean update,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size,
+ const char *tmp_dir)
+{
+ GList *sources = NULL, *destinations = NULL;
+ GHashTable *created_folders;
+ GList *scan;
+ XferData *xfer_data;
+
+ created_folders = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
+ for (scan = file_list; scan; scan = scan->next) {
+ char *partial_filename = scan->data;
+ char *local_uri;
+ char *local_folder_uri;
+ char *remote_uri;
+
+ local_uri = g_strconcat ("file://", tmp_dir, "/", partial_filename, NULL);
+ local_folder_uri = remove_level_from_path (local_uri);
+ if (g_hash_table_lookup (created_folders, local_folder_uri) == NULL) {
+ GError *error = NULL;
+ if (! ensure_dir_exists (local_folder_uri, 0755, &error)) {
+ g_free (local_folder_uri);
+ g_free (local_uri);
+ gio_file_list_free (sources);
+ gio_file_list_free (destinations);
+ g_hash_table_destroy (created_folders);
+
+ fr_archive_action_completed (archive,
+ FR_ACTION_COPYING_FILES_FROM_REMOTE,
+ FR_PROC_ERROR_GENERIC,
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ g_hash_table_insert (created_folders, local_folder_uri, GINT_TO_POINTER (1));
+ }
+ else
+ g_free (local_folder_uri);
+
+ remote_uri = g_strconcat (base_uri, "/", partial_filename, NULL);
+ sources = g_list_append (sources, g_file_new_for_uri (remote_uri));
+ g_free (remote_uri);
+
+ destinations = g_list_append (destinations, g_file_new_for_uri (local_uri));
+ g_free (local_uri);
+ }
+ g_hash_table_destroy (created_folders);
+
+ xfer_data = g_new0 (XferData, 1);
+ xfer_data->archive = archive;
+ xfer_data->file_list = path_list_dup (file_list);
+ xfer_data->base_uri = g_strdup (base_uri);
+ xfer_data->dest_dir = g_strdup (dest_dir);
+ xfer_data->update = update;
+ xfer_data->dest_dir = g_strdup (dest_dir);
+ xfer_data->password = g_strdup (password);
+ xfer_data->encrypt_header = encrypt_header;
+ xfer_data->compression = compression;
+ xfer_data->volume_size = volume_size;
+ xfer_data->tmp_dir = g_strdup (tmp_dir);
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[START],
+ 0,
+ FR_ACTION_COPYING_FILES_FROM_REMOTE);
+
+ g_copy_files_async (sources,
+ destinations,
+ G_FILE_COPY_OVERWRITE,
+ G_PRIORITY_DEFAULT,
+ archive->priv->cancellable,
+ copy_remote_files_progress,
+ xfer_data,
+ copy_remote_files_done,
+ xfer_data);
+
+ gio_file_list_free (sources);
+ gio_file_list_free (destinations);
+}
+
+
+static char *
+fr_archive_get_temp_work_dir (FrArchive *archive)
+{
+ fr_archive_remove_temp_work_dir (archive);
+ archive->priv->temp_dir = get_temp_work_dir (NULL);
+ return archive->priv->temp_dir;
+}
+
+
+void
+fr_archive_add_files (FrArchive *archive,
+ GList *file_list,
+ const char *base_dir,
+ const char *dest_dir,
+ gboolean update,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+{
+ if (uri_is_local (base_dir)) {
+ char *local_dir = g_filename_from_uri (base_dir, NULL, NULL);
+ fr_archive_add_local_files (archive,
+ file_list,
+ local_dir,
+ dest_dir,
+ update,
+ password,
+ encrypt_header,
+ compression,
+ volume_size);
+ g_free (local_dir);
+ }
+ else
+ copy_remote_files (archive,
+ file_list,
+ base_dir,
+ dest_dir,
+ update,
+ password,
+ encrypt_header,
+ compression,
+ volume_size,
+ fr_archive_get_temp_work_dir (archive));
+}
+
+
+/* -- add with wildcard -- */
+
+
+typedef struct {
+ FrArchive *archive;
+ char *source_dir;
+ char *dest_dir;
+ gboolean update;
+ char *password;
+ gboolean encrypt_header;
+ FrCompression compression;
+ guint volume_size;
+} AddWithWildcardData;
+
+
+static void
+add_with_wildcard_data_free (AddWithWildcardData *aww_data)
+{
+ g_free (aww_data->source_dir);
+ g_free (aww_data->dest_dir);
+ g_free (aww_data->password);
+ g_free (aww_data);
+}
+
+
+static void
+add_with_wildcard__step2 (GList *file_list,
+ GList *dirs_list,
+ GError *error,
+ gpointer data)
+{
+ AddWithWildcardData *aww_data = data;
+ FrArchive *archive = aww_data->archive;
+
+ if (error != NULL) {
+ fr_archive_action_completed (archive,
+ FR_ACTION_GETTING_FILE_LIST,
+ (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ? FR_PROC_ERROR_STOPPED : FR_PROC_ERROR_GENERIC),
+ error->message);
+ return;
+ }
+
+ fr_archive_action_completed (archive,
+ FR_ACTION_GETTING_FILE_LIST,
+ FR_PROC_ERROR_NONE,
+ NULL);
+
+ if (file_list != NULL)
+ fr_archive_add_files (aww_data->archive,
+ file_list,
+ aww_data->source_dir,
+ aww_data->dest_dir,
+ aww_data->update,
+ aww_data->password,
+ aww_data->encrypt_header,
+ aww_data->compression,
+ aww_data->volume_size);
+
+ path_list_free (file_list);
+ path_list_free (dirs_list);
+ add_with_wildcard_data_free (aww_data);
+}
+
+
+void
+fr_archive_add_with_wildcard (FrArchive *archive,
+ const char *include_files,
+ const char *exclude_files,
+ const char *exclude_folders,
+ const char *source_dir,
+ const char *dest_dir,
+ gboolean update,
+ gboolean follow_links,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+{
+ AddWithWildcardData *aww_data;
+
+ g_return_if_fail (! archive->read_only);
+
+ aww_data = g_new0 (AddWithWildcardData, 1);
+ aww_data->archive = archive;
+ aww_data->source_dir = g_strdup (source_dir);
+ aww_data->dest_dir = g_strdup (dest_dir);
+ aww_data->update = update;
+ aww_data->password = g_strdup (password);
+ aww_data->encrypt_header = encrypt_header;
+ aww_data->compression = compression;
+ aww_data->volume_size = volume_size;
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[START],
+ 0,
+ FR_ACTION_GETTING_FILE_LIST);
+
+ g_directory_list_async (source_dir,
+ source_dir,
+ TRUE,
+ follow_links,
+ NO_BACKUP_FILES,
+ NO_DOT_FILES,
+ include_files,
+ exclude_files,
+ exclude_folders,
+ IGNORE_CASE,
+ archive->priv->cancellable,
+ add_with_wildcard__step2,
+ aww_data);
+}
+
+
+/* -- fr_archive_add_directory -- */
+
+
+typedef struct {
+ FrArchive *archive;
+ char *base_dir;
+ char *dest_dir;
+ gboolean update;
+ char *password;
+ gboolean encrypt_header;
+ FrCompression compression;
+ guint volume_size;
+} AddDirectoryData;
+
+
+static void
+add_directory_data_free (AddDirectoryData *ad_data)
+{
+ g_free (ad_data->base_dir);
+ g_free (ad_data->dest_dir);
+ g_free (ad_data->password);
+ g_free (ad_data);
+}
+
+
+static void
+add_directory__step2 (GList *file_list,
+ GList *dir_list,
+ GError *error,
+ gpointer data)
+{
+ AddDirectoryData *ad_data = data;
+ FrArchive *archive = ad_data->archive;
+
+ if (error != NULL) {
+ fr_archive_action_completed (archive,
+ FR_ACTION_GETTING_FILE_LIST,
+ (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ? FR_PROC_ERROR_STOPPED : FR_PROC_ERROR_GENERIC),
+ error->message);
+ return;
+ }
+
+ fr_archive_action_completed (archive,
+ FR_ACTION_GETTING_FILE_LIST,
+ FR_PROC_ERROR_NONE,
+ NULL);
+
+ if (archive->command->propAddCanStoreFolders)
+ file_list = g_list_concat (file_list, dir_list);
+ else
+ path_list_free (dir_list);
+
+ if (file_list != NULL) {
+ fr_archive_add_files (ad_data->archive,
+ file_list,
+ ad_data->base_dir,
+ ad_data->dest_dir,
+ ad_data->update,
+ ad_data->password,
+ ad_data->encrypt_header,
+ ad_data->compression,
+ ad_data->volume_size);
+ path_list_free (file_list);
+ }
+
+ add_directory_data_free (ad_data);
+}
+
+
+void
+fr_archive_add_directory (FrArchive *archive,
+ const char *directory,
+ const char *base_dir,
+ const char *dest_dir,
+ gboolean update,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+
+{
+ AddDirectoryData *ad_data;
+
+ g_return_if_fail (! archive->read_only);
+
+ ad_data = g_new0 (AddDirectoryData, 1);
+ ad_data->archive = archive;
+ ad_data->base_dir = g_strdup (base_dir);
+ ad_data->dest_dir = g_strdup (dest_dir);
+ ad_data->update = update;
+ ad_data->password = g_strdup (password);
+ ad_data->encrypt_header = encrypt_header;
+ ad_data->compression = compression;
+ ad_data->volume_size = volume_size;
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[START],
+ 0,
+ FR_ACTION_GETTING_FILE_LIST);
+
+ g_directory_list_all_async (directory,
+ base_dir,
+ TRUE,
+ archive->priv->cancellable,
+ add_directory__step2,
+ ad_data);
+}
+
+
+void
+fr_archive_add_items (FrArchive *archive,
+ GList *item_list,
+ const char *base_dir,
+ const char *dest_dir,
+ gboolean update,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+
+{
+ AddDirectoryData *ad_data;
+
+ g_return_if_fail (! archive->read_only);
+
+ ad_data = g_new0 (AddDirectoryData, 1);
+ ad_data->archive = archive;
+ ad_data->base_dir = g_strdup (base_dir);
+ ad_data->dest_dir = g_strdup (dest_dir);
+ ad_data->update = update;
+ ad_data->password = g_strdup (password);
+ ad_data->encrypt_header = encrypt_header;
+ ad_data->compression = compression;
+ ad_data->volume_size = volume_size;
+
+ g_signal_emit (G_OBJECT (archive),
+ fr_archive_signals[START],
+ 0,
+ FR_ACTION_GETTING_FILE_LIST);
+
+ g_list_items_async (item_list,
+ base_dir,
+ archive->priv->cancellable,
+ add_directory__step2,
+ ad_data);
+}
+
+
+/* -- fr_archive_add_dropped_items -- */
+
+
+static gboolean
+all_files_in_same_dir (GList *list)
+{
+ gboolean same_dir = TRUE;
+ char *first_basedir;
+ GList *scan;
+
+ if (list == NULL)
+ return FALSE;
+
+ first_basedir = remove_level_from_path (list->data);
+ if (first_basedir == NULL)
+ return TRUE;
+
+ for (scan = list->next; scan; scan = scan->next) {
+ char *path = scan->data;
+ char *basedir;
+
+ basedir = remove_level_from_path (path);
+ if (basedir == NULL) {
+ same_dir = FALSE;
+ break;
+ }
+
+ if (strcmp (first_basedir, basedir) != 0) {
+ same_dir = FALSE;
+ g_free (basedir);
+ break;
+ }
+ g_free (basedir);
+ }
+ g_free (first_basedir);
+
+ return same_dir;
+}
+
+
+static void
+add_dropped_items (DroppedItemsData *data)
+{
+ FrArchive *archive = data->archive;
+ GList *list = data->item_list;
+ GList *scan;
+
+ if (list == NULL) {
+ dropped_items_data_free (archive->priv->dropped_items_data);
+ archive->priv->dropped_items_data = NULL;
+ fr_archive_action_completed (archive,
+ FR_ACTION_ADDING_FILES,
+ FR_PROC_ERROR_NONE,
+ NULL);
+ return;
+ }
+
+ /* if all files/dirs are in the same directory call fr_archive_add_items... */
+
+ if (all_files_in_same_dir (list)) {
+ char *first_base_dir;
+
+ first_base_dir = remove_level_from_path (list->data);
+ fr_archive_add_items (data->archive,
+ list,
+ first_base_dir,
+ data->dest_dir,
+ data->update,
+ data->password,
+ data->encrypt_header,
+ data->compression,
+ data->volume_size);
+ g_free (first_base_dir);
+
+ dropped_items_data_free (archive->priv->dropped_items_data);
+ archive->priv->dropped_items_data = NULL;
+
+ return;
+ }
+
+ /* ...else add a directory at a time. */
+
+ for (scan = list; scan; scan = scan->next) {
+ char *path = scan->data;
+ char *base_dir;
+
+ if (! uri_is_dir (path))
+ continue;
+
+ data->item_list = g_list_remove_link (list, scan);
+ if (data->item_list != NULL)
+ archive->priv->continue_adding_dropped_items = TRUE;
+ base_dir = remove_level_from_path (path);
+
+ fr_archive_add_directory (archive,
+ file_name_from_path (path),
+ base_dir,
+ data->dest_dir,
+ data->update,
+ data->password,
+ data->encrypt_header,
+ data->compression,
+ data->volume_size);
+
+ g_free (base_dir);
+ g_free (path);
+
+ return;
+ }
+
+ /* if all files are in the same directory call fr_archive_add_files. */
+
+ if (all_files_in_same_dir (list)) {
+ char *first_basedir;
+ GList *only_names_list = NULL;
+
+ first_basedir = remove_level_from_path (list->data);
+
+ for (scan = list; scan; scan = scan->next) {
+ char *name;
+
+ name = g_uri_unescape_string (file_name_from_path (scan->data), NULL);
+ only_names_list = g_list_prepend (only_names_list, name);
+ }
+
+ fr_archive_add_files (archive,
+ only_names_list,
+ first_basedir,
+ data->dest_dir,
+ data->update,
+ data->password,
+ data->encrypt_header,
+ data->compression,
+ data->volume_size);
+
+ path_list_free (only_names_list);
+ g_free (first_basedir);
+
+ return;
+ }
+
+ /* ...else call fr_command_add for each file. This is needed to add
+ * files without path info. FIXME: doesn't work with remote files. */
+
+ fr_archive_stoppable (archive, FALSE);
+ g_object_set (archive->command,
+ "password", data->password,
+ "encrypt_header", data->encrypt_header,
+ "compression", data->compression,
+ "volume_size", data->volume_size,
+ NULL);
+ fr_process_clear (archive->process);
+ fr_command_uncompress (archive->command);
+ for (scan = list; scan; scan = scan->next) {
+ char *fullpath = scan->data;
+ char *basedir;
+ GList *singleton;
+
+ basedir = remove_level_from_path (fullpath);
+ singleton = g_list_prepend (NULL, (char*)file_name_from_path (fullpath));
+ fr_command_add (archive->command,
+ NULL,
+ singleton,
+ basedir,
+ data->update,
+ FALSE);
+ g_list_free (singleton);
+ g_free (basedir);
+ }
+ fr_command_recompress (archive->command);
+ fr_process_start (archive->process);
+
+ path_list_free (data->item_list);
+ data->item_list = NULL;
+}
+
+
+void
+fr_archive_add_dropped_items (FrArchive *archive,
+ GList *item_list,
+ const char *base_dir,
+ const char *dest_dir,
+ gboolean update,
+ const char *password,
+ gboolean encrypt_header,
+ FrCompression compression,
+ guint volume_size)
+{
+ GList *scan;
+ char *archive_uri;
+
+ if (archive->read_only) {
+ fr_archive_action_completed (archive,
+ FR_ACTION_ADDING_FILES,
+ FR_PROC_ERROR_GENERIC,
+ ! archive->have_permissions ? _("You don't have the right permissions.") : _("This archive type cannot be modified"));
+ return;
+ }
+
+ /* FIXME: make this check for all the add actions */
+ archive_uri = g_file_get_uri (archive->file);
+ for (scan = item_list; scan; scan = scan->next) {
+ if (uricmp (scan->data, archive_uri) == 0) {
+ g_free (archive_uri);
+ fr_archive_action_completed (archive,
+ FR_ACTION_ADDING_FILES,
+ FR_PROC_ERROR_GENERIC,
+ _("You can't add an archive to itself."));
+ return;
+ }
+ }
+ g_free (archive_uri);
+
+ if (archive->priv->dropped_items_data != NULL)
+ dropped_items_data_free (archive->priv->dropped_items_data);
+ archive->priv->dropped_items_data = dropped_items_data_new (
+ archive,
+ item_list,
+ base_dir,
+ dest_dir,
+ update,
+ password,
+ encrypt_header,
+ compression,
+ volume_size);
+ add_dropped_items (archive->priv->dropped_items_data);
+}
+
+
+/* -- remove -- */
+
+
+static gboolean
+file_is_in_subfolder_of (const char *filename,
+ GList *folder_list)
+{
+ GList *scan;
+
+ if (filename == NULL)
+ return FALSE;
+
+ for (scan = folder_list; scan; scan = scan->next) {
+ char *folder_in_list = (char*) scan->data;
+
+ if (path_in_path (folder_in_list, filename))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+archive_type_has_issues_deleting_non_empty_folders (FrArchive *archive)
+{
+ return ! archive->command->propCanDeleteNonEmptyFolders;
+}
+
+
+static void
+delete_from_archive (FrArchive *archive,
+ GList *file_list)
+{
+ gboolean file_list_created = FALSE;
+ GList *tmp_file_list = NULL;
+ gboolean tmp_file_list_created = FALSE;
+ GList *scan;
+
+ /* file_list == NULL means delete all the files in the archive. */
+
+ if (file_list == NULL) {
+ int i;
+
+ for (i = 0; i < archive->command->files->len; i++) {
+ FileData *fdata = g_ptr_array_index (archive->command->files, i);
+ file_list = g_list_prepend (file_list, fdata->original_path);
+ }
+
+ file_list_created = TRUE;
+ }
+
+ if (archive_type_has_issues_deleting_non_empty_folders (archive)) {
+ GList *folders_to_remove;
+
+ /* remove from the list the files contained in folders to be
+ * removed. */
+
+ folders_to_remove = NULL;
+ for (scan = file_list; scan != NULL; scan = scan->next) {
+ char *path = scan->data;
+
+ if (path[strlen (path) - 1] == '/')
+ folders_to_remove = g_list_prepend (folders_to_remove, path);
+ }
+
+ if (folders_to_remove != NULL) {
+ tmp_file_list = NULL;
+ for (scan = file_list; scan != NULL; scan = scan->next) {
+ char *path = scan->data;
+
+ if (! file_is_in_subfolder_of (path, folders_to_remove))
+ tmp_file_list = g_list_prepend (tmp_file_list, path);
+ }
+ tmp_file_list_created = TRUE;
+ g_list_free (folders_to_remove);
+ }
+ }
+
+ if (! tmp_file_list_created)
+ tmp_file_list = g_list_copy (file_list);
+
+ if (file_list_created)
+ g_list_free (file_list);
+
+ fr_command_set_n_files (archive->command, g_list_length (tmp_file_list));
+
+ if (archive->command->propListFromFile
+ && (archive->command->n_files > LIST_LENGTH_TO_USE_FILE))
+ {
+ char *list_dir;
+ char *list_filename;
+
+ if (save_list_to_temp_file (tmp_file_list, &list_dir, &list_filename, NULL)) {
+ fr_command_delete (archive->command,
+ list_filename,
+ tmp_file_list);
+
+ /* remove the temp dir */
+
+ fr_process_begin_command (archive->process, "rm");
+ fr_process_set_working_dir (archive->process, g_get_tmp_dir());
+ fr_process_set_sticky (archive->process, TRUE);
+ fr_process_add_arg (archive->process, "-rf");
+ fr_process_add_arg (archive->process, list_dir);
+ fr_process_end_command (archive->process);
+ }
+
+ g_free (list_filename);
+ g_free (list_dir);
+ }
+ else {
+ for (scan = tmp_file_list; scan != NULL; ) {
+ GList *prev = scan->prev;
+ GList *chunk_list;
+ int l;
+
+ chunk_list = scan;
+ l = 0;
+ while ((scan != NULL) && (l < MAX_CHUNK_LEN)) {
+ if (l == 0)
+ l = strlen (scan->data);
+ prev = scan;
+ scan = scan->next;
+ if (scan != NULL)
+ l += strlen (scan->data);
+ }
+
+ prev->next = NULL;
+ fr_command_delete (archive->command, NULL, chunk_list);
+ prev->next = scan;
+ }
+ }
+
+ g_list_free (tmp_file_list);
+}
+
+
+void
+fr_archive_remove (FrArchive *archive,
+ GList *file_list,
+ FrCompression compression)
+{
+ g_return_if_fail (archive != NULL);
+
+ if (archive->read_only)
+ return;
+
+ fr_archive_stoppable (archive, FALSE);
+ g_object_set (archive->command, "compression", compression, NULL);
+ fr_command_uncompress (archive->command);
+ delete_from_archive (archive, file_list);
+ fr_command_recompress (archive->command);
+}
+
+
+/* -- extract -- */
+
+
+static void
+move_files_to_dir (FrArchive *archive,
+ GList *file_list,
+ const char *source_dir,
+ const char *dest_dir,
+ gboolean overwrite)
+{
+ GList *list;
+ GList *scan;
+
+ /* we prefer mv instead of cp for performance reasons,
+ * but if the destination folder already exists mv
+ * doesn't work correctly. (bug #590027) */
+
+ list = g_list_copy (file_list);
+ for (scan = list; scan; /* void */) {
+ GList *next = scan->next;
+ char *filename = scan->data;
+ char *basename;
+ char *destname;
+
+ basename = g_path_get_basename (filename);
+ destname = g_build_filename (dest_dir, basename, NULL);
+ if (g_file_test (destname, G_FILE_TEST_IS_DIR)) {
+ fr_process_begin_command (archive->process, "cp");
+ fr_process_add_arg (archive->process, "-R");
+ if (overwrite)
+ fr_process_add_arg (archive->process, "-f");
+ else
+ fr_process_add_arg (archive->process, "-n");
+ if (filename[0] == '/')
+ fr_process_add_arg_concat (archive->process, source_dir, filename, NULL);
+ else
+ fr_process_add_arg_concat (archive->process, source_dir, "/", filename, NULL);
+ fr_process_add_arg (archive->process, dest_dir);
+ fr_process_end_command (archive->process);
+
+ list = g_list_remove_link (list, scan);
+ g_list_free (scan);
+ }
+
+ g_free (destname);
+ g_free (basename);
+
+ scan = next;
+ }
+
+ if (list == NULL)
+ return;
+
+ /* 'list' now contains the files that can be moved without problems */
+
+ fr_process_begin_command (archive->process, "mv");
+ if (overwrite)
+ fr_process_add_arg (archive->process, "-f");
+ else
+ fr_process_add_arg (archive->process, "-n");
+ for (scan = list; scan; scan = scan->next) {
+ char *filename = scan->data;
+
+ if (filename[0] == '/')
+ fr_process_add_arg_concat (archive->process, source_dir, filename, NULL);
+ else
+ fr_process_add_arg_concat (archive->process, source_dir, "/", filename, NULL);
+ }
+ fr_process_add_arg (archive->process, dest_dir);
+ fr_process_end_command (archive->process);
+
+ g_list_free (list);
+}
+
+
+static void
+move_files_in_chunks (FrArchive *archive,
+ GList *file_list,
+ const char *temp_dir,
+ const char *dest_dir,
+ gboolean overwrite)
+{
+ GList *scan;
+ int temp_dir_l;
+
+ temp_dir_l = strlen (temp_dir);
+
+ for (scan = file_list; scan != NULL; ) {
+ GList *prev = scan->prev;
+ GList *chunk_list;
+ int l;
+
+ chunk_list = scan;
+ l = 0;
+ while ((scan != NULL) && (l < MAX_CHUNK_LEN)) {
+ if (l == 0)
+ l = temp_dir_l + 1 + strlen (scan->data);
+ prev = scan;
+ scan = scan->next;
+ if (scan != NULL)
+ l += temp_dir_l + 1 + strlen (scan->data);
+ }
+
+ prev->next = NULL;
+ move_files_to_dir (archive, chunk_list, temp_dir, dest_dir, overwrite);
+ prev->next = scan;
+ }
+}
+
+
+static void
+extract_from_archive (FrArchive *archive,
+ GList *file_list,
+ const char *dest_dir,
+ gboolean overwrite,
+ gboolean skip_older,
+ gboolean junk_paths,
+ const char *password)
+{
+ FrCommand *command = archive->command;
+ GList *scan;
+
+ g_object_set (command, "password", password, NULL);
+
+ if (file_list == NULL) {
+ fr_command_extract (command,
+ NULL,
+ file_list,
+ dest_dir,
+ overwrite,
+ skip_older,
+ junk_paths);
+ return;
+ }
+
+ if (command->propListFromFile
+ && (g_list_length (file_list) > LIST_LENGTH_TO_USE_FILE))
+ {
+ char *list_dir;
+ char *list_filename;
+
+ if (save_list_to_temp_file (file_list, &list_dir, &list_filename, NULL)) {
+ fr_command_extract (command,
+ list_filename,
+ file_list,
+ dest_dir,
+ overwrite,
+ skip_older,
+ junk_paths);
+
+ /* remove the temp dir */
+
+ fr_process_begin_command (archive->process, "rm");
+ fr_process_set_working_dir (archive->process, g_get_tmp_dir());
+ fr_process_set_sticky (archive->process, TRUE);
+ fr_process_add_arg (archive->process, "-rf");
+ fr_process_add_arg (archive->process, list_dir);
+ fr_process_end_command (archive->process);
+ }
+
+ g_free (list_filename);
+ g_free (list_dir);
+ }
+ else {
+ for (scan = file_list; scan != NULL; ) {
+ GList *prev = scan->prev;
+ GList *chunk_list;
+ int l;
+
+ chunk_list = scan;
+ l = 0;
+ while ((scan != NULL) && (l < MAX_CHUNK_LEN)) {
+ if (l == 0)
+ l = strlen (scan->data);
+ prev = scan;
+ scan = scan->next;
+ if (scan != NULL)
+ l += strlen (scan->data);
+ }
+
+ prev->next = NULL;
+ fr_command_extract (command,
+ NULL,
+ chunk_list,
+ dest_dir,
+ overwrite,
+ skip_older,
+ junk_paths);
+ prev->next = scan;
+ }
+ }
+}
+
+
+static char*
+compute_base_path (const char *base_dir,
+ const char *path,
+ gboolean junk_paths,
+ gboolean can_junk_paths)
+{
+ int base_dir_len = strlen (base_dir);
+ int path_len = strlen (path);
+ const char *base_path;
+ char *name_end;
+ char *new_path;
+
+ if (junk_paths) {
+ if (can_junk_paths)
+ new_path = g_strdup (file_name_from_path (path));
+ else
+ new_path = g_strdup (path);
+
+ /*debug (DEBUG_INFO, "%s, %s --> %s\n", base_dir, path, new_path);*/
+
+ return new_path;
+ }
+
+ if (path_len <= base_dir_len)
+ return NULL;
+
+ base_path = path + base_dir_len;
+ if (path[0] != '/')
+ base_path -= 1;
+ name_end = strchr (base_path, '/');
+
+ if (name_end == NULL)
+ new_path = g_strdup (path);
+ else {
+ int name_len = name_end - path;
+ new_path = g_strndup (path, name_len);
+ }
+
+ /*debug (DEBUG_INFO, "%s, %s --> %s\n", base_dir, path, new_path);*/
+
+ return new_path;
+}
+
+
+static GList*
+compute_list_base_path (const char *base_dir,
+ GList *filtered,
+ gboolean junk_paths,
+ gboolean can_junk_paths)
+{
+ GList *scan;
+ GList *list = NULL, *list_unique = NULL;
+ GList *last_inserted;
+
+ if (filtered == NULL)
+ return NULL;
+
+ for (scan = filtered; scan; scan = scan->next) {
+ const char *path = scan->data;
+ char *new_path;
+
+ new_path = compute_base_path (base_dir, path, junk_paths, can_junk_paths);
+ if (new_path != NULL)
+ list = g_list_prepend (list, new_path);
+ }
+
+ /* The above operation can create duplicates, we remove them here. */
+ list = g_list_sort (list, (GCompareFunc)strcmp);
+
+ last_inserted = NULL;
+ for (scan = list; scan; scan = scan->next) {
+ const char *path = scan->data;
+
+ if (last_inserted != NULL) {
+ const char *last_path = (const char*)last_inserted->data;
+ if (strcmp (last_path, path) == 0) {
+ g_free (scan->data);
+ continue;
+ }
+ }
+
+ last_inserted = scan;
+ list_unique = g_list_prepend (list_unique, scan->data);
+ }
+
+ g_list_free (list);
+
+ return list_unique;
+}
+
+
+static gboolean
+archive_type_has_issues_extracting_non_empty_folders (FrArchive *archive)
+{
+ /*if ((archive->command->files == NULL) || (archive->command->files->len == 0))
+ return FALSE; FIXME: test with extract_here */
+ return ! archive->command->propCanExtractNonEmptyFolders;
+}
+
+
+static gboolean
+file_list_contains_files_in_this_dir (GList *file_list,
+ const char *dirname)
+{
+ GList *scan;
+
+ for (scan = file_list; scan; scan = scan->next) {
+ char *filename = scan->data;
+
+ if (path_in_path (dirname, filename))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static GList*
+remove_files_contained_in_this_dir (GList *file_list,
+ GList *dir_pointer)
+{
+ char *dirname = dir_pointer->data;
+ int dirname_l = strlen (dirname);
+ GList *scan;
+
+ for (scan = dir_pointer->next; scan; /* empty */) {
+ char *filename = scan->data;
+
+ if (strncmp (dirname, filename, dirname_l) != 0)
+ break;
+
+ if (path_in_path (dirname, filename)) {
+ GList *next = scan->next;
+
+ file_list = g_list_remove_link (file_list, scan);
+ g_list_free (scan);
+
+ scan = next;
+ }
+ else
+ scan = scan->next;
+ }
+
+ return file_list;
+}
+
+
+void
+fr_archive_extract_to_local (FrArchive *archive,
+ GList *file_list,
+ const char *destination,
+ const char *base_dir,
+ gboolean skip_older,
+ gboolean overwrite,
+ gboolean junk_paths,
+ const char *password)
+{
+ GList *filtered;
+ GList *scan;
+ gboolean extract_all;
+ gboolean use_base_dir;
+ gboolean all_options_supported;
+ gboolean move_to_dest_dir;
+ gboolean file_list_created = FALSE;
+
+ g_return_if_fail (archive != NULL);
+
+ fr_archive_stoppable (archive, TRUE);
+
+ /* if a command supports all the requested options use
+ * fr_command_extract directly. */
+
+ use_base_dir = ! ((base_dir == NULL)
+ || (strcmp (base_dir, "") == 0)
+ || (strcmp (base_dir, "/") == 0));
+
+ all_options_supported = (! use_base_dir
+ && ! (! overwrite && ! archive->command->propExtractCanAvoidOverwrite)
+ && ! (skip_older && ! archive->command->propExtractCanSkipOlder)
+ && ! (junk_paths && ! archive->command->propExtractCanJunkPaths));
+
+ extract_all = (file_list == NULL);
+ if (extract_all && (! all_options_supported || ! archive->command->propCanExtractAll)) {
+ int i;
+
+ file_list = NULL;
+ for (i = 0; i < archive->command->files->len; i++) {
+ FileData *fdata = g_ptr_array_index (archive->command->files, i);
+ file_list = g_list_prepend (file_list, g_strdup (fdata->original_path));
+ }
+ file_list_created = TRUE;
+ }
+
+ if (extract_all && (file_list == NULL))
+ fr_command_set_n_files (archive->command, archive->command->n_regular_files);
+ else
+ fr_command_set_n_files (archive->command, g_list_length (file_list));
+
+ if (all_options_supported) {
+ gboolean created_filtered_list = FALSE;
+
+ if (! extract_all && archive_type_has_issues_extracting_non_empty_folders (archive)) {
+ created_filtered_list = TRUE;
+ filtered = g_list_copy (file_list);
+ filtered = g_list_sort (filtered, (GCompareFunc) strcmp);
+ for (scan = filtered; scan; scan = scan->next)
+ filtered = remove_files_contained_in_this_dir (filtered, scan);
+ }
+ else
+ filtered = file_list;
+
+ if (! (created_filtered_list && (filtered == NULL)))
+ extract_from_archive (archive,
+ filtered,
+ destination,
+ overwrite,
+ skip_older,
+ junk_paths,
+ password);
+
+ if (created_filtered_list && (filtered != NULL))
+ g_list_free (filtered);
+
+ if (file_list_created)
+ path_list_free (file_list);
+
+ return;
+ }
+
+ /* .. else we have to implement the unsupported options. */
+
+ move_to_dest_dir = (use_base_dir
+ || ((junk_paths
+ && ! archive->command->propExtractCanJunkPaths)));
+
+ if (extract_all && ! file_list_created) {
+ int i;
+
+ file_list = NULL;
+ for (i = 0; i < archive->command->files->len; i++) {
+ FileData *fdata = g_ptr_array_index (archive->command->files, i);
+ file_list = g_list_prepend (file_list, g_strdup (fdata->original_path));
+ }
+
+ file_list_created = TRUE;
+ }
+
+ filtered = NULL;
+ for (scan = file_list; scan; scan = scan->next) {
+ FileData *fdata;
+ char *archive_list_filename = scan->data;
+ char dest_filename[4096];
+ const char *filename;
+
+ fdata = find_file_in_archive (archive, archive_list_filename);
+
+ if (fdata == NULL)
+ continue;
+
+ if (archive_type_has_issues_extracting_non_empty_folders (archive)
+ && fdata->dir
+ && file_list_contains_files_in_this_dir (file_list, archive_list_filename))
+ continue;
+
+ /* get the destination file path. */
+
+ if (! junk_paths)
+ filename = archive_list_filename;
+ else
+ filename = file_name_from_path (archive_list_filename);
+
+ if ((destination[strlen (destination) - 1] == '/')
+ || (filename[0] == '/'))
+ sprintf (dest_filename, "%s%s", destination, filename);
+ else
+ sprintf (dest_filename, "%s/%s", destination, filename);
+
+ /*debug (DEBUG_INFO, "-> %s\n", dest_filename);*/
+
+ /**/
+
+ if (! archive->command->propExtractCanSkipOlder
+ && skip_older
+ && g_file_test (dest_filename, G_FILE_TEST_EXISTS)
+ && (fdata->modified < get_file_mtime_for_path (dest_filename)))
+ continue;
+
+ if (! archive->command->propExtractCanAvoidOverwrite
+ && ! overwrite
+ && g_file_test (dest_filename, G_FILE_TEST_EXISTS))
+ continue;
+
+ filtered = g_list_prepend (filtered, fdata->original_path);
+ }
+
+ if (filtered == NULL) {
+ /* all files got filtered, do nothing. */
+ debug (DEBUG_INFO, "All files got filtered, nothing to do.\n");
+
+ if (extract_all)
+ path_list_free (file_list);
+ return;
+ }
+
+ if (move_to_dest_dir) {
+ char *temp_dir;
+
+ temp_dir = get_temp_work_dir (destination);
+ extract_from_archive (archive,
+ filtered,
+ temp_dir,
+ overwrite,
+ skip_older,
+ junk_paths,
+ password);
+
+ if (use_base_dir) {
+ GList *tmp_list = compute_list_base_path (base_dir, filtered, junk_paths, archive->command->propExtractCanJunkPaths);
+ g_list_free (filtered);
+ filtered = tmp_list;
+ }
+
+ move_files_in_chunks (archive,
+ filtered,
+ temp_dir,
+ destination,
+ overwrite);
+
+ /* remove the temp dir. */
+
+ fr_process_begin_command (archive->process, "rm");
+ fr_process_add_arg (archive->process, "-rf");
+ fr_process_add_arg (archive->process, temp_dir);
+ fr_process_end_command (archive->process);
+
+ g_free (temp_dir);
+ }
+ else
+ extract_from_archive (archive,
+ filtered,
+ destination,
+ overwrite,
+ skip_older,
+ junk_paths,
+ password);
+
+ if (filtered != NULL)
+ g_list_free (filtered);
+ if (file_list_created)
+ path_list_free (file_list);
+}
+
+
+void
+fr_archive_extract (FrArchive *archive,
+ GList *file_list,
+ const char *destination,
+ const char *base_dir,
+ gboolean skip_older,
+ gboolean overwrite,
+ gboolean junk_paths,
+ const char *password)
+{
+ g_free (archive->priv->extraction_destination);
+ archive->priv->extraction_destination = g_strdup (destination);
+
+ g_free (archive->priv->temp_extraction_dir);
+ archive->priv->temp_extraction_dir = NULL;
+
+ archive->priv->remote_extraction = ! uri_is_local (destination);
+ if (archive->priv->remote_extraction) {
+ archive->priv->temp_extraction_dir = get_temp_work_dir (NULL);
+ fr_archive_extract_to_local (archive,
+ file_list,
+ archive->priv->temp_extraction_dir,
+ base_dir,
+ skip_older,
+ overwrite,
+ junk_paths,
+ password);
+ }
+ else {
+ char *local_destination;
+
+ local_destination = g_filename_from_uri (destination, NULL, NULL);
+ fr_archive_extract_to_local (archive,
+ file_list,
+ local_destination,
+ base_dir,
+ skip_older,
+ overwrite,
+ junk_paths,
+ password);
+ g_free (local_destination);
+ }
+}
+
+
+static char *
+get_desired_destination_for_archive (GFile *file)
+{
+ GFile *directory;
+ char *directory_uri;
+ char *name;
+ const char *ext;
+ char *new_name;
+ char *new_name_escaped;
+ char *desired_destination = NULL;
+
+ directory = g_file_get_parent (file);
+ directory_uri = g_file_get_uri (directory);
+
+ name = g_file_get_basename (file);
+ ext = get_archive_filename_extension (name);
+ if (ext == NULL)
+ /* if no extension is present add a suffix to the name... */
+ new_name = g_strconcat (name, "_FILES", NULL);
+ else
+ /* ...else use the name without the extension */
+ new_name = g_strndup (name, strlen (name) - strlen (ext));
+ new_name_escaped = g_uri_escape_string (new_name, "", FALSE);
+
+ desired_destination = g_strconcat (directory_uri, "/", new_name_escaped, NULL);
+
+ g_free (new_name_escaped);
+ g_free (new_name);
+ g_free (name);
+ g_free (directory_uri);
+ g_object_unref (directory);
+
+ return desired_destination;
+}
+
+
+static char *
+get_extract_here_destination (GFile *file,
+ GError **error)
+{
+ char *desired_destination;
+ char *destination = NULL;
+ int n = 1;
+ GFile *directory;
+
+ desired_destination = get_desired_destination_for_archive (file);
+ do {
+ *error = NULL;
+
+ g_free (destination);
+ if (n == 1)
+ destination = g_strdup (desired_destination);
+ else
+ destination = g_strdup_printf ("%s%%20(%d)", desired_destination, n);
+
+ directory = g_file_new_for_uri (destination);
+ g_file_make_directory (directory, NULL, error);
+ g_object_unref (directory);
+
+ n++;
+ } while (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_EXISTS));
+
+ g_free (desired_destination);
+
+ if (*error != NULL) {
+ g_warning ("could not create destination folder: %s\n", (*error)->message);
+ g_free (destination);
+ destination = NULL;
+ }
+
+ return destination;
+}
+
+
+gboolean
+fr_archive_extract_here (FrArchive *archive,
+ gboolean skip_older,
+ gboolean overwrite,
+ gboolean junk_path,
+ const char *password)
+{
+ char *destination;
+ GError *error = NULL;
+
+ destination = get_extract_here_destination (archive->file, &error);
+ if (error != NULL) {
+ fr_archive_action_completed (archive,
+ FR_ACTION_EXTRACTING_FILES,
+ FR_PROC_ERROR_GENERIC,
+ error->message);
+ g_clear_error (&error);
+ return FALSE;
+ }
+
+ archive->priv->extract_here = TRUE;
+ fr_archive_extract (archive,
+ NULL,
+ destination,
+ NULL,
+ skip_older,
+ overwrite,
+ junk_path,
+ password);
+
+ g_free (destination);
+
+ return TRUE;
+}
+
+
+const char *
+fr_archive_get_last_extraction_destination (FrArchive *archive)
+{
+ return archive->priv->extraction_destination;
+}
+
+
+void
+fr_archive_test (FrArchive *archive,
+ const char *password)
+{
+ fr_archive_stoppable (archive, TRUE);
+
+ g_object_set (archive->command, "password", password, NULL);
+ fr_process_clear (archive->process);
+ fr_command_set_n_files (archive->command, 0);
+ fr_command_test (archive->command);
+ fr_process_start (archive->process);
+}
+
+
+gboolean
+uri_is_archive (const char *uri)
+{
+ GFile *file;
+ const char *mime_type;
+ gboolean is_archive = FALSE;
+
+ file = g_file_new_for_uri (uri);
+ mime_type = get_mime_type_from_magic_numbers (file);
+ if (mime_type == NULL)
+ mime_type = get_mime_type_from_content (file);
+ if (mime_type == NULL)
+ mime_type = get_mime_type_from_filename (file);
+
+ if (mime_type != NULL) {
+ int i;
+
+ for (i = 0; mime_type_desc[i].mime_type != NULL; i++) {
+ if (strcmp (mime_type_desc[i].mime_type, mime_type) == 0) {
+ is_archive = TRUE;
+ break;
+ }
+ }
+ }
+ g_object_unref (file);
+
+ return is_archive;
+}