diff options
Diffstat (limited to 'src/fr-archive.c')
-rw-r--r-- | src/fr-archive.c | 3354 |
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; +} |