/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Engrampa
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <config.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <iconv.h>
#include <glib.h>
#include <glib/gconvert.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <langinfo.h>
#include <zip.h>
#include <sys/wait.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-proc-error.h"
#include "fr-process.h"
#include "fr-init.h"
#include "fr-command-zip.h"
#include "fr-command-7z.h"
#include "uchardet.h"
#include "gtk-utils.h"

#ifdef ENABLE_MAGIC
#include <magic.h>
#endif

#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;
// 	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;

// 	GList               *before_extract_dest_dir_content;    /* 存储解压前时目的路径的目录列表，用于解压完成后处理乱码问题 */
// };

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;
	GPid sub_pid;
} 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 gchar *g_from_codeset = NULL;
static gboolean g_use_convmv = FALSE;
static gboolean g_need_conv_GBK_encode = FALSE;

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,
			NULL
		};

		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_BOXED,
			      G_TYPE_NONE, 2,
			      G_TYPE_INT,
			      FR_TYPE_PROC_ERROR);
	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__BOOLEAN,
			      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
archive_sticky_only_cb (FrProcess *process,
			FrArchive *archive)
{
	fr_archive_stoppable (archive, FALSE);
	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->extraction_destination = NULL;
	archive->priv->temp_extraction_dir = NULL;
	archive->priv->cancellable = g_cancellable_new ();
	archive->usage_mode = FR_USAGE_MODE_UNKNOWN;

	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)
{
#ifdef ENABLE_MAGIC
	static magic_t magic = NULL;

	if (! magic) {
		magic = magic_open (MAGIC_MIME_TYPE);
		if (magic)
			magic_load (magic, NULL);
		else
			g_warning ("unable to open magic database");
	}

	if (magic) {
		const char * mime_type = NULL;
		char * filepath = g_file_get_path (file);

		if (filepath) {
			mime_type = get_static_string (magic_file (magic, filepath));
			// if (g_strcmp0(mime_type, "application/gzip") == 0)
			//     mime_type = get_static_string("application/x-gzip");
			g_free (filepath);
		}

		if (mime_type) {
			return mime_type;
		}

		g_warning ("unable to detect filetype from magic: %s",
			   magic_error (magic));
	}
#else
	static const struct magic {
		const unsigned int off;
		const unsigned int len;
		const char * const id;
		const char * const mime_type;
	} magic_ids [] = {
		/* magic ids taken from magic/Magdir/archive from the file-4.21 tarball */
		{ 0, 21, "!<arch>" "\x0a" "debian-binary",       "application/vnd.debian.binary-package" },
		{ 0,  8, "!<arch>" "\x0a",                       "application/x-archive"       },
		{ 0,  6, "7z\274\257\047\034",                   "application/x-7z-compressed" },
		{ 7,  7, "**ACE**",                              "application/x-ace"           },
		{ 0,  2, "\x60\xea",                             "application/x-arj"           },
		{ 0,  4, "\xce\xb2\xcf\x81",                     "application/x-brotli"        },
		{ 0,  3, "BZh",                                  "application/x-bzip2"         },
		{ 0,  2, "\037\213",                             "application/x-gzip"          },
		{ 2,  3, "-lh",                                  "application/x-lzh-compressed"},
		{ 0,  4, "LZIP",                                 "application/x-lzip"          },
		{ 0,  9, "\x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a", "application/x-lzop",         },
		{ 0,  4, "Rar!",                                 "application/x-rar"           },
		{ 0,  4, "\xed\xab\xee\xdb",                     "application/x-rpm"           },
		{ 0,  4, "RZIP",                                 "application/x-rzip"          },
		{ 0,  6, "\3757zXZ\000",                         "application/x-xz"            },
		{ 20, 4, "\xdc\xa7\xc4\xfd",                     "application/x-zoo",          },
		{ 0,  4, "PK\003\004",                           "application/zip"             },
		{ 0,  8, "PK00PK\003\004",                       "application/zip"             },
		{ 0,  4, "LRZI",                                 "application/x-lrzip"         },
		{ 0,  2, "\x1f\x9d",                             "application/x-compress"      }, /* using Lempel-Ziv-Welch   algorithm */
		{ 0,  2, "\x1f\xa0",                             "application/x-compress"      }, /* using Lempel-Ziv-Huffman algorithm */
	};

	char   buffer[32];
	size_t i;

	if (! g_load_file_in_buffer (file, buffer, sizeof (buffer), NULL))
		return NULL;

	for (i = 0; i < G_N_ELEMENTS (magic_ids); i++) {
		const struct magic * const magic = &magic_ids[i];

		if (sizeof (buffer) < (magic->off + magic->len))
			g_warning ("buffer underrun for mime type '%s' magic",
				   magic->mime_type);
		else if (! memcmp (buffer + magic->off, magic->id, magic->len))
			return magic->mime_type;
	}
#endif

	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)
{
	if (command_type == 0)
		return FALSE;

	archive->command = FR_COMMAND (g_object_new (command_type,
					             "process", archive->process,
					             "mime-type", mime_type,
					             NULL));

	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
check_zip_is_integrity(const char *file_path)
{

	GPtrArray *argv = NULL;
	gchar *stdout_str;
	gchar *stderr_str;
	gint estatus;
	GError *error = NULL;
	int i = 0;
	gboolean result = TRUE;

	argv = g_ptr_array_new ();
	g_ptr_array_add (argv, (gpointer)"/usr/bin/7za");
	g_ptr_array_add (argv, (gpointer)"t");
	g_ptr_array_add (argv, (gpointer)file_path);
	g_ptr_array_add (argv, NULL);


#ifdef MATE_ENABLE_DEBUG
	{
		for(i = 0;i < argv->len;i++) {
			g_print("%s ", (gchar *)(argv->pdata[i]));
		}
		g_print("\n");
	}
#endif
	printf("开始检测\n");


	g_spawn_sync (NULL, (char**)argv->pdata, NULL, G_SPAWN_DEFAULT, NULL, NULL, &stdout_str, &stderr_str, &estatus, &error);
printf("停止检测\n");

	printf("消息输出====out====%s=======\n",stdout_str);
        printf("消息输出===error=====%s=======\n",stderr_str);
	g_print("out:%s \n", stdout_str);
	g_print("error:%s \n", stderr_str);

	if (g_strstr_len(stderr_str, -1, "ERRORS:")) {
		result = FALSE;
	}

	g_free(stdout_str);
	g_free(stderr_str);
	g_ptr_array_free(argv, FALSE);

	return result;
}
static gboolean rachive_size(const char *file_name)
{
	GPtrArray *argv = NULL;
	int size=0;
	gchar *stdout_str;
	gchar *stderr_str;
	gint estatus;
	GError *error = NULL;
	FILE *fp;
	int index=0;
	gchar **number;
	int num=0;
	argv = g_ptr_array_new ();
	g_ptr_array_add (argv, (gpointer)"du");
	g_ptr_array_add (argv, (gpointer)"-m");
	g_ptr_array_add (argv, (gpointer)file_name);
	g_ptr_array_add (argv, NULL);
	g_spawn_sync (NULL, (char**)argv->pdata, NULL, G_SPAWN_DEFAULT, NULL, NULL, &stdout_str, &stderr_str, &estatus, &error);
	printf("rachive_size====out====%s===\n",stdout_str);
        printf("消息输出===error=====%s===rachive_size====\n",stderr_str);
	number=g_strsplit(stdout_str, "\t",2);
	num=atoi(number[0]);
	printf("read ret===lennumber=111111111111111111=%d\n",num);
	if(num<100){
		return TRUE;
	}else{
		return FALSE;
	}
}

static gboolean
is_encry_zip_file(FrArchive  *archive)
{
	int errcode = 0;
	zip_error_t err;
	zip_t *archive_zip = zip_open(g_file_get_path (archive->file), ZIP_RDONLY, &errcode);
	zip_error_init_with_code(&err, errcode);
	if (archive_zip == NULL) {
		return FALSE;
	}

	const zip_int64_t nofEntries = zip_get_num_entries(archive_zip, 0);
	for (zip_int64_t index = 0; index < nofEntries; index++) {
		zip_stat_t statBuffer = {0};
		if (zip_stat_index(archive_zip, (zip_uint64_t)index, ZIP_FL_ENC_RAW, &statBuffer)) {
			continue;
		}

		if (statBuffer.encryption_method != 0) {
			zip_close(archive_zip);
			return TRUE;
		}
	}

	zip_close(archive_zip);
	return FALSE;
}

static gboolean
check_is_zip_bomb_file(const char *file_path)
{
	GPtrArray *argv = NULL;
	gchar *stdout_str;
	gchar *stderr_str;
	gint estatus;
	GError *error = NULL;
	int i = 0;
	gboolean result = FALSE;

	argv = g_ptr_array_new ();
	g_ptr_array_add (argv, (gpointer)"/usr/bin/unzip");
	g_ptr_array_add (argv, (gpointer)"-t");
	g_ptr_array_add (argv, (gpointer)file_path);
	g_ptr_array_add (argv, NULL);


#ifdef MATE_ENABLE_DEBUG
	{
		for(i = 0;i < argv->len;i++) {
			g_print("%s ", (gchar *)(argv->pdata[i]));
		}
		g_print("\n");
	}
	printf("开始检测\n");

#endif

	g_spawn_sync (NULL, (char**)argv->pdata, NULL, G_SPAWN_DEFAULT, NULL, NULL, &stdout_str, &stderr_str, &estatus, &error);

#ifdef MATE_ENABLE_DEBUG
	printf("停止检测\n");

	printf("消息输出====out====%s=======\n",stdout_str);

        printf("消息输出===error=====%s=======\n",stderr_str);

#endif
	if ((strstr (stdout_str, "possible zip bomb") != NULL)) {
		result |= TRUE;
	}

	if ((strstr (stderr_str, "possible zip bomb") != NULL)) {
		result |= TRUE;
	}
	fprintf(stderr, "cur file is bomb zip file = %s\n", result ? "true" : "false");

	g_free(stdout_str);
	g_free(stderr_str);
	g_ptr_array_free(argv, FALSE);

	return result;
}

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;

	// 校验.zip文件的完整性，如果不完整使用7za解压时可能解压不出来，需要使用unzip解压；解决问题：180093、178942、150090
	int is_zip = 0;

	/***
	 * 本次修正针对 7z 命令解压zip文件频繁出现乱码问题
	 * 对于分卷 和 加密的zip压缩文件使用7za命令进行处理
	 * 其他的情况均使用unzip命令进行处理
	 */

	fprintf(stderr, "cur file is multi volume file =%s\n", archive->is_zip_volumn ? "true" : "false");

	if (is_mime_type(mime_type,"application/zip")) {
		is_zip = TRUE;
		if (archive->is_zip_volumn || is_encry_zip_file(archive) || check_is_zip_bomb_file(g_file_get_path (archive->file))) {
			is_zip = FALSE;
		}
	}

	command_type = get_command_type_from_mime_type(mime_type, requested_capabilities, is_zip);
	/* 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, is_zip);
	}
	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,
					  int        is_zip)
{
	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, is_zip);

	return create_command_from_type (archive,
					 mime_type,
					 command_type,
					 requested_capabilities);
}


static void
action_started (FrCommand *command,
		FrAction   action,
		FrArchive *archive)
{
#ifdef MATE_ENABLE_DEBUG
	debug (DEBUG_INFO, "%s [START] (FR::Archive)\n", get_action_name (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)
{
	extern gboolean kylin_task_41805;
	char *content_uri;
	char *parent_path;
	GFile *parent_source, *parent_destination, *destination, *old_file;
	GError *error = NULL;

	// 如果压缩包一级子目录只有一个对象，就将其移动到父目录的同级目录。如果有多个对象，不移动
	content_uri = get_dir_content_if_unique(archive->priv->extraction_destination);
	if (content_uri == NULL)
		return;

	// 找到父目录的父目录
	parent_path = remove_level_from_path(archive->priv->extraction_destination);

	// 首先将父目录重新命名，保证解压文件命名时不会冲突。
	char* new_parent_name = NULL;
	new_parent_name = get_alternative_uri(parent_path, dir_name_from_path (archive->priv->extraction_destination));
	parent_source = g_file_new_for_uri(archive->priv->extraction_destination);
	parent_destination = g_file_new_for_uri(new_parent_name);

	const char *parent_source_path = g_file_get_path(parent_source);
	const char *parent_destination_path = g_file_get_path(parent_destination);

	char ccommand[10000];
	snprintf(ccommand, sizeof(ccommand), "mv '%s' '%s'", parent_source_path, parent_destination_path);
	printf("rename parent dir ccommand: %s\n", ccommand);
	system(ccommand);

	// 解压文件重命名，避免名称冲突
	char* new_object_name = NULL;
	new_object_name = get_alternative_uri(parent_path, dir_name_from_path (content_uri));
	destination = g_file_new_for_uri(new_object_name);
	const char *destination_path = g_file_get_path(destination);

	char ccommand0[10000];
	snprintf(ccommand0, sizeof(ccommand0), "mv '%s'/* '%s'", parent_destination_path, destination_path);
	printf("move here ccommand: %s\n", ccommand0);
	system(ccommand0);

	// 释放资源
	g_object_unref(destination);
	g_object_unref(parent_source);
	g_object_unref(parent_destination);

	old_file = g_file_new_for_uri(new_parent_name);
	if (!g_file_delete(old_file, NULL, &error))
	{
		g_warning("could not remove directory %s: %s", new_parent_name, error->message);
		g_clear_error(&error);
	}
	g_object_unref(old_file);

	g_free(parent_path);
	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);
}

gboolean zip_need_conv_encode(FrArchive *archive)
{
	const gchar *filetype = g_strdup(archive->command->mime_type);
	gboolean is_need_conv_encode = FALSE;
	if (strcmp("application/zip", filetype) == 0) {
		gchar *filename = g_strdup(archive->command->filename);
		gchar *command_str;
		command_str = g_strdup_printf("zipinfo -1 \"%s\"", filename);

		// 使用popen启动ls命令，并设置为读取模式
		FILE *fp;
		fp = popen(command_str, "r");
		if (fp == NULL) {
			printf("Failed to run command\n");
			exit(1);
		}

		// 读取命令的输出
		char buff[1024];
		memset(buff, 0, sizeof buff);

		while (fgets(buff, sizeof(buff)-1, fp) != NULL) {
			printf("%s", buff);
			const gchar *encoding = detection_encoding (buff);
			if (strcmp(encoding, "") ==0 ||
				strcmp(encoding, "UTF-8") != 0
				&& strcmp(encoding, "UTF8") != 0
				&& strcmp(encoding, "ASCII") != 0
				&& !g_str_has_prefix(encoding, "ISO-8859-"))
			{
				is_need_conv_encode = TRUE;
				break;
			}
			memset(buff, 0, sizeof buff);
		}

		// 关闭popen创建的管道
		pclose(fp);
	}
	return is_need_conv_encode;
}

static gint
dir_content_compare(gconstpointer a, gconstpointer b)
{
	return strcmp(a, b);
}

/*
 * xxxxxxxxxx.txt
 *      |      |
 *      v      v
 *  base_name  suffix
 */
static GString *
handle_too_long_name(const char *name)
{
	GString *suffix         = g_string_new("");
	GString *base_name      = g_string_new("");
	GString *process_result = g_string_new("");
	guint   total_len       = 254;
	gchar   *suffix_ptr     = NULL;
	gchar   *cursor         = NULL;
	gchar   *begin          = NULL;
	gchar   *end            = NULL;
	guint   base_name_byte  = 0;

	suffix_ptr = g_utf8_strrchr(name, -1, '.');
	if (suffix_ptr) {
		base_name_byte = total_len - strlen(suffix_ptr);
		g_string_append_len(base_name, name, suffix_ptr - name);
		g_string_append(suffix, suffix_ptr);
	} else {
		base_name_byte = total_len;
		g_string_append(base_name, name);
	}

	begin = end = cursor = base_name->str;
	while ((cursor = g_utf8_find_next_char(cursor, begin + total_len)) != NULL) {
		if ((cursor - begin) > base_name_byte)
			break;
		else
			end = cursor;
	}

	g_string_append_len(process_result, begin, end - begin);
	g_string_append(process_result, suffix->str);

	g_string_free(suffix, TRUE);
	g_string_free(base_name, TRUE);

	return process_result;
}

static void
decode_file_name_by_convmv( const gchar *file_path,
			    const gchar *to_codeset,
			    const gchar *from_codeset)
{
	GPtrArray *argv = NULL;
	gchar *stdout_str;
	gchar *stderr_str;
	gint estatus;
	GError *error = NULL;
	int i = 0;

	argv = g_ptr_array_new ();
	g_ptr_array_add (argv, (gpointer)"/usr/bin/convmv");
	g_ptr_array_add (argv, (gpointer)"-f");
	/* 当前系统中convmv不识别GB18030编码，故转为GBK; windows上文件名编码为GB18030，linux系统上文件名编码为utf-8 */
	if (from_codeset == NULL || strlen(from_codeset) == 0 || strcmp(from_codeset, "GB18030") == 0)
		g_ptr_array_add (argv, (gpointer)"GBK");
	else
		g_ptr_array_add (argv, (gpointer)from_codeset);
	g_ptr_array_add (argv, (gpointer)"-t");
	g_ptr_array_add (argv, (gpointer)to_codeset);
	g_ptr_array_add (argv, (gpointer)"--notest");
	g_ptr_array_add (argv, (gpointer)"-r");
	g_ptr_array_add (argv, (gpointer)file_path);
	g_ptr_array_add (argv, NULL);

#ifdef MATE_ENABLE_DEBUG
	{
		for(i = 0;i < argv->len;i++) {
			g_print("%s ", (gchar *)(argv->pdata[i]));
		}
		g_print("\n");
	}
#endif

	g_spawn_sync (NULL, (char**)argv->pdata, NULL, G_SPAWN_DEFAULT, NULL, NULL, &stdout_str, &stderr_str, &estatus, &error);

	g_print("%s \n", stdout_str);
	g_print("%s \n", stderr_str);

	g_free(stdout_str);
	g_free(stderr_str);
	g_ptr_array_free(argv, FALSE);

	return ;
}

const GString *
handle_file_name_coding(char *path)
{
	/* 文件路径变量 */
	char *original         = g_strdup(path);
	char *separator        = strrchr(original, '/');
	char *base_path        = g_strndup(original, separator - original + 1);
	char *file_name        = g_strdup(separator + 1);
	char *file_name_coding = NULL;

	// /* 编码探测变量 */
	// uchardet_t ud = NULL;

	/* 编码转换变量 */
	gchar *src_str            = NULL;
	gchar *src_str_ptr        = NULL;
	gchar *dest_str           = NULL;
	gchar *dest_str_ptr       = NULL;
	gsize src_str_len         = 0;
	gsize dest_str_len        = 0;
	GIConv cd               = NULL;
	gsize result            = 0;
	GString *new_name        = NULL;
	GString *truncation_name = NULL;

	if (!original || !base_path || !file_name)
		goto CLEAN;
	// fprintf(stderr, "file_name=%s\n", file_name);

	file_name_coding= detection_encoding (file_name);
	// fprintf(stderr, "file_name_coding=%s\n", file_name_coding);

	const char *local_charset = nl_langinfo(CODESET);
	if (local_charset == NULL) {
		local_charset = "UTF-8";
	}

	if (file_name_coding != NULL && g_strcasecmp(file_name_coding, local_charset) == 0) {
		g_free(file_name_coding);
		return g_string_new(path);
	}

	if ( file_name_coding == NULL || strcmp(file_name_coding, "") == 0
	    || g_strcasecmp(file_name_coding, "TIS-620") == 0
	    || g_strcasecmp(file_name_coding, "GB18030") == 0
	    || g_strcasecmp(file_name_coding, "GBK") == 0
	    || g_strcasecmp(file_name_coding, "GB2312") == 0
	    || g_strcasecmp(file_name_coding, "Big5") == 0
	    || g_strcasecmp(file_name_coding, "x-euc-tw") == 0
	    || g_strncasecmp(file_name_coding, "WINDOWS-", strlen("WINDOWS-")) == 0
	    || g_strncasecmp(file_name_coding, "ISO-8859-", strlen("ISO-8859-")) == 0
	    || g_strncasecmp(file_name_coding, "x-mac", strlen("x-mac")) == 0
	    || g_strcasecmp(file_name_coding, "KOI8-R") == 0)
	{
		file_name_coding = g_strdup("GB18030");
	}
	else {
		goto CLEAN;
	}

	if (g_from_codeset == NULL && file_name_coding != NULL && strlen(file_name_coding) > 0) {
		g_from_codeset = g_strdup(file_name_coding);
	}

	if (file_name_coding == NULL || strlen(file_name_coding) == 0) {
		fprintf(stderr, "Not found file_name_coding!\n");
		if (g_from_codeset != NULL) {
			g_free(file_name_coding);
			file_name_coding = g_strdup(g_from_codeset);
		} else {
			goto CLEAN;
		}
	}

	/* 编码转换 */
	src_str = g_strdup(file_name);
	src_str_len = strlen(src_str);
	dest_str_len = src_str_len * 6 + 1;
	dest_str = g_strnfill(dest_str_len, 0);
	dest_str_ptr = dest_str;
	src_str_ptr = src_str;

	if (dest_str == NULL) {
		fprintf(stderr, "alloc memory fail\n");
		goto CLEAN;
	}

	cd = g_iconv_open(local_charset, file_name_coding);
	if (cd == NULL) {
		fprintf(stderr, "coding conver open fail!\n");
		g_use_convmv = TRUE;
		goto CLEAN;
	}

	result = g_iconv(cd, &src_str_ptr, &src_str_len, &dest_str_ptr, &dest_str_len);
	if (result == (gsize)-1) {
		fprintf(stderr, "first coding conver failed! \n");
		g_use_convmv = TRUE;
		goto CLEAN;
	}

	new_name = g_string_new(base_path);

	/* 转换结果如果大于 255，进行截断 */
	if (strlen(dest_str) > 255) {
		truncation_name = handle_too_long_name(dest_str);
		g_string_append(new_name, truncation_name->str);
	} else {
		g_string_append(new_name, dest_str);
	}

	if (rename(original, new_name->str)) {
		fprintf(stderr, "*** kylin *** rename file fail\n");
	}

	//fprintf(stderr, "*** kylin test *** detector coding: %s\t(%s ---> %s)\n", file_name_coding, file_name, dest_str);

CLEAN:
	g_free(original);
	g_free(base_path);
	g_free(file_name);
	g_free(file_name_coding);

	// if (ud != NULL)
	// 	uchardet_delete(ud);

	if (src_str != NULL)
		g_free(src_str);

	if (dest_str != NULL)
		g_free(dest_str);

	if (cd != NULL)
		g_iconv_close(cd);

	// if (new_name != NULL)
	// 	g_string_free(new_name, TRUE);

	if (truncation_name != NULL)
		g_string_free(truncation_name, TRUE);

	return new_name;
}

static void
handle_coding(FrArchive *archive)
{
	GList *content;
	GList *scan;
	GString *base_path;
	DIR *dir;
	DIR *sub_dir;
	struct dirent *ent;
	GFile *file;
	char *dest_path = NULL;

	/* 计时器 */
	GTimer *timer;
	gdouble elapsed;

	timer = g_timer_new();

#ifdef MATE_ENABLE_DEBUG
	debug (DEBUG_INFO, "extraction_destination=%s\n", archive->priv->extraction_destination);
#endif
	content = g_get_dir_content(archive->priv->extraction_destination);

	/* 获取增量内容 */
	for (scan = archive->priv->before_extract_dest_dir_content; scan; scan = scan->next) {
		GList *item;
		if ((item = g_list_find_custom(content, scan->data, dir_content_compare)) != NULL) {
			content = g_list_remove_link(content, item);
			g_free(item->data);
			g_list_free(item);
		}
	}

	/* 获取基础路径 */
	file = g_file_new_for_uri(archive->priv->extraction_destination);
	dest_path = g_file_get_path(file);
	g_object_unref(file);

	base_path = g_string_new(dest_path);
	if (!g_str_has_suffix(base_path->str, "/")) {
		g_string_append(base_path, "/");
	}

	GQueue *queue = g_queue_new();
	for (scan = content; scan; scan = scan->next) {
		GString *path = g_string_new(base_path->str);
		g_string_append(path, scan->data);
		g_queue_push_tail(queue, path);
	}

	g_string_free(base_path, TRUE);
	g_list_free_full(content, g_free);

	g_from_codeset = NULL;
	g_use_convmv = FALSE;
	g_need_conv_GBK_encode = zip_need_conv_encode(archive);
	while(!g_queue_is_empty(queue)) {
		GString *curr_folder = g_queue_pop_head(queue);
		//task412225    不管文件还是目录，先转码一下，是文件的话处理结束continue，目录的话进下面的while
		GString * convert_folder = handle_file_name_coding(curr_folder->str);
		dir = opendir(convert_folder->str);
		if (dir == NULL) {
			/* 文件 */
			g_string_free(curr_folder, TRUE);
			continue;
		}
		//处理当前目录下的内容：文件转码、目录添加到queue
		while ((ent = readdir(dir)) != NULL) {
			char *name = ent->d_name;
			if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
				continue;
			GString *path = g_string_new(convert_folder->str);
			g_string_append(path, "/");
			g_string_append(path, name);
			//task425683  内部目录也要转码
			GString * convert_subfolder = handle_file_name_coding(path->str);
			sub_dir = opendir(convert_subfolder->str);
			if (sub_dir != NULL) {
				/* 是目录 */
				closedir(sub_dir);
				g_queue_push_tail(queue, convert_subfolder);
			} else {
				/* 是文件 */
				g_string_free(path, TRUE);
			}
		}

		closedir(dir);
		g_string_free(curr_folder, TRUE);
	}

	if (g_use_convmv == TRUE)
		decode_file_name_by_convmv(dest_path, "UTF-8", g_from_codeset);

	g_free(dest_path);
	if (g_from_codeset != NULL)
		g_free(g_from_codeset);

	elapsed = g_timer_elapsed(timer, NULL);
	fprintf(stdout, "*** kylin *** handle coding time consuming: %f\n", elapsed);
	g_timer_destroy(timer);

	return;
}

static void
delete_concate_tar_file(GFile *file)
{
	if (file == NULL)
		return;

	gchar *path = g_file_get_path(file);
	delete_dir_and_content(path);
	g_free(path);
}

static void
action_performed (FrCommand   *command,
		  FrAction     action,
		  FrProcError *error,
		  FrArchive   *archive)
{
#ifdef MATE_ENABLE_DEBUG
	debug (DEBUG_INFO, "%s [DONE] (FR::Archive)\n", get_action_name (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) {
				if (archive->priv->dropped_items_data != NULL)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) {
				handle_coding(archive);
				break;
			}
			/* 处理编码问题，防止乱码 */
			handle_coding(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);
		}

		/* tar分卷压缩文件解压后合成的临时完整文件需要删除，释放空间; 当前仅右键压缩文件“解压到”“解压到此”方式使用时释放；双击打开方式不释放 */
		if (archive->is_tar_volumn && (archive->usage_mode & (FR_USAGE_MODE_EXTRACT_TO | FR_USAGE_MODE_EXTRACT_HERE)))
			delete_concate_tar_file(archive->file);

		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,
		   int is_zip)
{
	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, is_zip)) {
		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 const char *tar_combine_mime_types[] = {

				 "application/x-brotli-compressed-tar",
				 "application/x-bzip-compressed-tar",
				 "application/x-7z-compressed-tar",
				 "application/x-lrzip-compressed-tar",
				 "application/x-lzip-compressed-tar",
			         "application/x-lzma-compressed-tar",
			         "application/x-lzop-compressed-tar",
				 "application/x-tarz",
				 "application/x-xz-compressed-tar",
				 "application/x-zstd-compressed-tar",
			         NULL };

static gboolean
is_tar_combine_mime_type(const char *mime_type)
{
	return  g_strv_contains(tar_combine_mime_types, mime_type);
}

static void
load_local_archive (FrArchive  *archive,
		    const char *password)
{
	FrCommand  *old_command = NULL;
	const char *mime_type = NULL;
	const char *magic_mime_type = NULL;
	const char *file_name_mime_type = NULL;
	const char *file_content_mime_type = NULL;

	if (! g_file_query_exists (archive->file, archive->priv->cancellable)) {
		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, R_OK);
	if (archive->have_permissions == FALSE) {
		fr_archive_action_completed (archive,
							     FR_ACTION_LOADING_ARCHIVE,
							     FR_PROC_ERROR_GENERIC,
							     _("File cannot be read."));
		return;
	}

	archive->have_permissions = check_file_permissions (archive->file, W_OK);

	archive->read_only = ! archive->have_permissions;

	old_command = archive->command;


	file_name_mime_type    = get_mime_type_from_filename (archive->local_copy);
	file_content_mime_type = get_mime_type_from_content (archive->local_copy);
	magic_mime_type        = get_mime_type_from_magic_numbers (archive->local_copy);
	mime_type              = get_mime_type_from_filename (archive->local_copy);

	if  (!is_tar_combine_mime_type(mime_type)) {
		magic_mime_type = get_mime_type_from_magic_numbers (archive->local_copy);
		if (magic_mime_type != NULL &&  is_mime_type(magic_mime_type, "application/x-tar")) {
			// 针对tar.gz格式 file mime-type 识别为x-tar格式
			mime_type = magic_mime_type;
		}
		else if (magic_mime_type != NULL &&  is_mime_type(magic_mime_type, "application/zip")) {
			// 针对tar.gz格式 file mime-type 识别为zip格式
			mime_type = magic_mime_type;
		}
	}

#ifdef MATE_ENABLE_DEBUG
	debug(DEBUG_INFO, "file_name_mime_type %s\n", file_name_mime_type);
	debug(DEBUG_INFO, "file_content_mime_type %s\n", file_content_mime_type);
	debug(DEBUG_INFO, "magic_mime_type %s\n", magic_mime_type);
#endif
	debug(DEBUG_INFO, "mime_type %s\n", mime_type);

	if (! create_command_to_load_archive (archive, mime_type)) {
		mime_type = get_mime_type_from_content (archive->local_copy);
		debug(DEBUG_INFO, "mime_type_from_content %s\n", mime_type);

		if (! create_command_to_load_archive (archive, mime_type)) {
			mime_type = get_mime_type_from_magic_numbers (archive->local_copy);
			debug(DEBUG_INFO, "mime_type_from_magic_numbers %s\n", mime_type);

			if (! create_command_to_load_archive (archive, mime_type)) {
				archive->command = old_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 (old_command != NULL) {
		g_signal_handlers_disconnect_by_data (old_command, archive);
		g_object_unref (old_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,
		      "file", archive->local_copy,
		      "password", password,
		      NULL);
	fr_command_list (archive->command);
}


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 gboolean
concate_tar_volumns_cb(XferData *xfer_data)
{
	int status;
	pid_t pid = waitpid(xfer_data->sub_pid, &status, WNOHANG);
	if (pid == xfer_data->sub_pid)
	{
		/* 更新xfer_data->archive->file等信息 */
		xfer_data->archive->original_file = xfer_data->archive->file;
		xfer_data->archive->file = g_file_new_for_uri(xfer_data->uri);
		if (xfer_data->archive->local_copy != NULL)
			g_object_unref(xfer_data->archive->local_copy);
		xfer_data->archive->local_copy = g_file_dup(xfer_data->archive->file);

		xfer_data->source_id = g_idle_add(copy_remote_file_done_cb, xfer_data);

		return FALSE;
	}

	return TRUE;
}

static void
concate_tar_volumns_async(XferData *xfer_data)
{
	/* 1、取原始文件名，从中获取合并后的tar包名称，并创建临时存放目录 */
	char *volumn_file_path = g_file_get_path(xfer_data->archive->file);
	gchar *volumn_path = g_path_get_dirname(volumn_file_path);
	gchar *last_dot = g_strrstr(volumn_file_path, ".");
	gchar *path_nofix = g_strndup(volumn_file_path, last_dot - volumn_file_path);
	gchar *file_name = g_strrstr(path_nofix, "/");
	++file_name;
	gchar *tmp_dir = g_get_tmp_dir();
	gchar *prog_name = g_get_prgname();
	gchar *first_dot = g_strstr_len(file_name, strlen(file_name), ".");
	gchar *file_short_name = g_strndup(file_name, first_dot - file_name);
	gchar *tar_full_name = g_strjoin("/", tmp_dir, prog_name, file_short_name, file_name, NULL);
	gchar *base_dir = g_path_get_dirname(tar_full_name);
	GPtrArray *argv = NULL;
	gchar *cmd = NULL;
	GError *gerror = NULL;
	GFile *file = g_file_new_for_path(tar_full_name);

	delete_dir_and_content(base_dir);
	g_mkdir_with_parents(base_dir, 0777);

	/* 2、开启子进程合并tar包到临时目录中 */
	argv = g_ptr_array_new();
	g_ptr_array_add(argv, (gpointer) "bash");
	g_ptr_array_add(argv, (gpointer) "-c");
	cmd = g_strdup_printf("cat '%s/%s'.* > '%s'", volumn_path, file_name, tar_full_name);
	g_ptr_array_add(argv, (gpointer)cmd);
	g_ptr_array_add(argv, NULL);
	if (!g_spawn_async(volumn_path,
			   (gchar **)argv->pdata,
			   NULL,
			   (G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD),
			   NULL,
			   (gpointer)xfer_data,
			   &(xfer_data->sub_pid),
			   &gerror))
	{
		GError *error;
		error = g_error_new(G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Failed to concate the archives"));
		fr_archive_copy_done(xfer_data->archive, FR_ACTION_LOADING_ARCHIVE, error);
		g_error_free(error);
	}

	/* 3、定时器监控子进程结束,结束后更新archive->file等信息，结束后进行后续操作 */
	if (xfer_data->uri)
		g_free(xfer_data->uri);
	xfer_data->uri = g_file_get_uri(file);
	xfer_data->source_id = g_timeout_add(100, concate_tar_volumns_cb, xfer_data);

	g_object_unref(file);
	g_free(cmd);
	g_ptr_array_free(argv, FALSE);
	g_free(gerror);
	g_free(base_dir);
	g_free(tar_full_name);
	g_free(file_short_name);
	g_free(path_nofix);
	g_free(volumn_path);
	g_free(volumn_file_path);
	return;
}

static void
copy_remote_file(FrArchive *archive,
		 const char *password)
{
	XferData *xfer_data;

	if (! g_file_query_exists (archive->file, archive->priv->cancellable)) {
		GError *error;

		error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Archive not found"));
		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);

	archive->is_zip_volumn = FALSE;
	if (check_is_zip_volumn(g_file_get_path(archive->file)))
		archive->is_zip_volumn = TRUE;

	archive->is_7z_volumn = FALSE;
	if (check_is_7z_volumn(g_file_get_path(archive->file)))
		archive->is_7z_volumn = TRUE;

	/* tar命令split的分卷需要先合并再进行后续的操作 */
	archive->is_tar_volumn = FALSE;
	archive->original_file = NULL;
	if (check_is_tar_volumn(g_file_get_path(archive->file)))
	{
		archive->is_tar_volumn = TRUE;
		concate_tar_volumns_async(xfer_data);
		return;
	}

	else 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);
}


/* -- 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);
	if (! symlink (base_dir, dir))
		g_warning("Could not create the symbolic link '%s', pointing to '%s'", dir, base_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;
}

static gboolean
save_list_to_temp_file (GList   *file_list,
		        char   **list_dir,
		        char   **list_filename,
		        GError **error,
			gboolean   zipor7z)
{
	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(zipor7z)
                        {
                                if ((g_output_stream_write (G_OUTPUT_STREAM (ostream), "\"", strlen ("\""), NULL, error) < 0)
                                || (g_output_stream_write (G_OUTPUT_STREAM (ostream), filename, strlen (filename), NULL, error) < 0)
                                || (g_output_stream_write (G_OUTPUT_STREAM (ostream), "\"", strlen ("\""), NULL, error) < 0)
                                || (g_output_stream_write (G_OUTPUT_STREAM (ostream), "\n", 1, NULL, error) < 0))
                                {
                                        error_occurred = TRUE;
                                }
                        }
                        else
                        {
                                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;
	char     *tmp_archive_dir = NULL;
	char     *archive_filename = NULL;
	char     *tmp_archive_filename = 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, TRUE);

	/* dest_dir is the destination folder inside the archive */

	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));
		}
	}
	else {
		tmp_base_dir = g_strdup (base_dir);
		new_file_list = path_list_dup(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",
				       &archive->process->error);
		return;
	}

	archive->command->creating_archive = ! g_file_query_exists (archive->local_copy, archive->priv->cancellable);

	/* create the new archive in a temporary sub-directory, this allows
	 * to cancel the operation without losing the original archive and
	 * removing possible temporary files created by the command. */

	{
		GFile *local_copy_parent;
		char  *archive_dir;
		GFile *tmp_file;

		/* create the new archive in a sub-folder of the original
		 * archive this way the 'mv' command is fast. */

		local_copy_parent = g_file_get_parent (archive->local_copy);
		archive_dir = g_file_get_path (local_copy_parent);
		tmp_archive_dir = get_temp_work_dir (archive_dir);
		archive_filename = g_file_get_path (archive->local_copy);
		tmp_archive_filename = g_build_filename (tmp_archive_dir, file_name_from_path (archive_filename), NULL);
		tmp_file = g_file_new_for_path (tmp_archive_filename);
		g_object_set (archive->command, "file", tmp_file, NULL);

		if (! archive->command->creating_archive) {
			/* copy the original archive to the new position */

			fr_process_begin_command (archive->process, "cp");
			fr_process_add_arg (archive->process, "-f");
			fr_process_add_arg (archive->process, archive_filename);
			fr_process_add_arg (archive->process, tmp_archive_filename);
			fr_process_end_command (archive->process);
		}

		g_object_unref (tmp_file);
		g_free (archive_dir);
		g_object_unref (local_copy_parent);
	}

	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;
		gboolean  zipor7z = FALSE;
		if (is_mime_type(archive->command->mime_type, "application/zip")
		   || is_mime_type(archive->command->mime_type, "application/x-7z-compressed") )
		{
			zipor7z = TRUE;
		}

		if (! save_list_to_temp_file (new_file_list, &list_dir, &list_filename, &error,zipor7z)) {
			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",
					       &archive->process->error);
			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);

		/* move the new archive to the original position */
		if (archive->command->operFlag == OPER_TAR_SPLIT_VOLUMN)
		{
			GString *gstr_volumn_size = g_string_new(NULL);
			GString *gstr_split_prefix = g_string_new(archive_filename);

			fr_process_begin_command(archive->process, "/usr/bin/split");
			fr_process_add_arg(archive->process, "-d");
			fr_process_add_arg(archive->process, "-a");
			fr_process_add_arg(archive->process, "3");
			fr_process_add_arg(archive->process, "--numeric-suffixes=1");
			fr_process_add_arg(archive->process, "-b");
			g_string_sprintf(gstr_volumn_size, "%ld", archive->command->volume_size);
			fr_process_add_arg(archive->process, gstr_volumn_size->str);
			fr_process_add_arg(archive->process, tmp_archive_filename);
			g_string_append(gstr_split_prefix, ".");
			fr_process_add_arg(archive->process, gstr_split_prefix->str);
			fr_process_end_command(archive->process);

			g_string_free(gstr_volumn_size, TRUE);
			g_string_free(gstr_split_prefix, TRUE);
		}
		else
		{
			fr_process_begin_command(archive->process, "mv");
			fr_process_add_arg(archive->process, "-f");
			fr_process_add_arg(archive->process, tmp_archive_filename);
			fr_process_add_arg(archive->process, archive_filename);
			fr_process_end_command(archive->process);
		}

		/* remove the temp sub-directory */

		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_archive_dir);
		fr_process_end_command (archive->process);

		/* remove the base dir */

		if (base_dir_created) {
			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_archive_filename);
	g_free (archive_filename);
	g_free (tmp_archive_dir);
	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_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);
	archive->command->creating_archive = ! g_file_query_exists (archive->local_copy, archive->priv->cancellable);
	g_object_set (archive->command,
		      "file", archive->local_copy,
		      "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) {
		guint 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;

		gboolean  zipor7z = FALSE;
		if (is_mime_type(archive->command->mime_type, "application/zip")
		   || is_mime_type(archive->command->mime_type, "application/x-7z-compressed") )
		{
			zipor7z = TRUE;
		}
		if (save_list_to_temp_file (tmp_file_list, &list_dir, &list_filename, NULL,zipor7z)) {
			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)
{
	char *tmp_archive_dir = NULL;
	char *archive_filename = NULL;
	char *tmp_archive_filename = NULL;

	g_return_if_fail (archive != NULL);

	if (archive->read_only)
		return;

	fr_archive_stoppable (archive, TRUE);
	archive->command->creating_archive = FALSE;
	g_object_set (archive->command, "compression", compression, NULL);

	/* create the new archive in a temporary sub-directory, this allows
	 * to cancel the operation without losing the original archive and
	 * removing possible temporary files created by the command. */

	{
		GFile *local_copy_parent;
		char  *archive_dir;
		GFile *tmp_file;

		/* create the new archive in a sub-folder of the original
		 * archive this way the 'mv' command is fast. */

		local_copy_parent = g_file_get_parent (archive->local_copy);
		archive_dir = g_file_get_path (local_copy_parent);
		tmp_archive_dir = get_temp_work_dir (archive_dir);
		archive_filename = g_file_get_path (archive->local_copy);
		tmp_archive_filename = g_build_filename (tmp_archive_dir, file_name_from_path (archive_filename), NULL);
		tmp_file = g_file_new_for_path (tmp_archive_filename);
		g_object_set (archive->command, "file", tmp_file, NULL);

		if (! archive->command->creating_archive) {
			/* copy the original archive to the new position */

			fr_process_begin_command (archive->process, "cp");
			fr_process_add_arg (archive->process, "-f");
			fr_process_add_arg (archive->process, archive_filename);
			fr_process_add_arg (archive->process, tmp_archive_filename);
			fr_process_end_command (archive->process);
		}

		g_object_unref (tmp_file);
		g_free (archive_dir);
		g_object_unref (local_copy_parent);
	}

	/* uncompress, delete and recompress */

	fr_command_uncompress (archive->command);
	delete_from_archive (archive, file_list);
	fr_command_recompress (archive->command);

	/* move the new archive to the original position */

	fr_process_begin_command (archive->process, "mv");
	fr_process_add_arg (archive->process, "-f");
	fr_process_add_arg (archive->process, tmp_archive_filename);
	fr_process_add_arg (archive->process, archive_filename);
	fr_process_end_command (archive->process);

	/* remove the temp sub-directory */

	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_archive_dir);
	fr_process_end_command (archive->process);

	g_free (tmp_archive_filename);
	g_free (archive_filename);
	g_free (tmp_archive_dir);
}


/* -- 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);

	gboolean is_7z = is_mime_type(archive->command->mime_type, "application/x-7z-compressed");


	if (file_list == NULL) {
		if (!is_7z) {
			fr_command_extract (command,
					NULL,
					file_list,
					dest_dir,
					overwrite,
					skip_older,
					junk_paths);
		}
		else {
			fr_command_extract_from_archive (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;

		gboolean  zipor7z = FALSE;
		if (is_mime_type(archive->command->mime_type, "application/zip")
		   || is_mime_type(archive->command->mime_type, "application/x-7z-compressed") )
		{
			zipor7z = TRUE;
		}

		if (save_list_to_temp_file (file_list, &list_dir, &list_filename, NULL,zipor7z)) {
			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;
			if (!is_7z) {
				fr_command_extract (command,
						    NULL,
						    chunk_list,
						    dest_dir,
						    overwrite,
						    skip_older,
						    junk_paths);
			}
			else {
				fr_command_extract_from_archive (command,
								 NULL,
								 file_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);
	g_object_set (archive->command, "file", archive->local_copy, NULL);

	/* 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)) {
		guint 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) {
		guint 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;

	g_list_free_full (g_steal_pointer(&archive->priv->before_extract_dest_dir_content), g_free);
	archive->priv->before_extract_dest_dir_content = g_get_dir_content(archive->priv->extraction_destination);

	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;
}

char *
get_extract_destination (const char *selected_dir_uri, char *file_path,
			      GError **error)
{
	char  *destination = NULL;
	int    n = 0;
	GFile *directory = NULL;
	char *new_name = NULL;
	GFile *file = g_file_new_for_path(file_path);
	char* name = g_file_get_basename (file);
	g_object_unref(file);
	const char *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));
	char *new_name_escaped = g_uri_escape_string (new_name, "", FALSE);

	char *desired_destination = g_strconcat (selected_dir_uri, "/", new_name_escaped, NULL);

	g_free (new_name_escaped);
	g_free (new_name);
	g_free (name);

	do {
		*error = NULL;

		g_free (destination);
		if (n == 0)
			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;
}

static char *
get_extract_here_destination (GFile   *file,
			      GError **error)
{
	char  *desired_destination;
	char  *destination = NULL;
	int    n = 0;
	GFile *directory;

	desired_destination = get_desired_destination_for_archive (file);
	do {
		*error = NULL;

		g_free (destination);
		if (n == 0)
			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;
}

gchar *get_filename_dstpath (FrArchive  *archive)
{
	gchar   *destination;
	char *volumn_file_path;
	if (archive->is_tar_volumn) {
		volumn_file_path = g_file_get_path(archive->original_file);
		gchar *last_dot = g_strrstr(volumn_file_path, ".");
		destination = g_strndup(volumn_file_path, last_dot - volumn_file_path);
	}
	else if (archive->is_7z_volumn || archive->is_zip_volumn) {
		volumn_file_path = g_file_get_path(archive->file);
		gchar *last_dot = g_strrstr(volumn_file_path, ".");
		destination = g_strndup(volumn_file_path, last_dot - volumn_file_path);
	}
	else {
		destination = g_file_get_path(archive->file);
	}
	volumn_file_path = NULL;
	g_free(volumn_file_path);

	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;

	gchar *path_nofix = get_filename_dstpath(archive);
	GFile *file = g_file_new_for_path(path_nofix);

	destination = get_extract_here_destination(file, &error);

	g_object_unref(file);
	g_free(path_nofix);

	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,
		      "file", archive->local_copy,
		      "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;
}
