/*
 * Remmina - The GTK+ Remote Desktop Client
 * Copyright (C) 2009-2010 Vic Lee
 * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
 * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
 *
 * 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.
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU General Public License in all respects
 *  for all of the code used other than OpenSSL. *  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so. *  If you
 *  do not wish to do so, delete this exception statement from your
 *  version. *  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

#define _FILE_OFFSET_BITS 64
#include "config.h"

#ifdef HAVE_LIBSSH

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gmodule.h>
#include <pthread.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include "remmina_public.h"
#include "remmina_log.h"
#include "remmina_pref.h"
#include "remmina_ssh.h"
#include "remmina_sftp_client.h"
#include "remmina_sftp_plugin.h"
#include "remmina_masterthread_exec.h"
#include "remmina/remmina_trace_calls.h"

G_DEFINE_TYPE(RemminaSFTPClient, remmina_sftp_client, REMMINA_TYPE_FTP_CLIENT)

#define SET_CURSOR(cur) \
	if (GDK_IS_WINDOW(gtk_widget_get_window(GTK_WIDGET(client)))) \
	{ \
		gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(client)), cur); \
	}

static void
remmina_sftp_client_class_init(RemminaSFTPClientClass *klass)
{
	TRACE_CALL(__func__);
}

#define GET_SFTPATTR_TYPE(a, type) \
	if (a->type == 0) \
	{ \
		type = ((a->permissions & 040000) ? REMMINA_FTP_FILE_TYPE_DIR : REMMINA_FTP_FILE_TYPE_FILE); \
		type = ((a->permissions & 0120000) ? REMMINA_FTP_FILE_TYPE_LINK : REMMINA_FTP_FILE_TYPE_FILE); \
	} \
	else \
	{ \
		type = (a->type == SSH_FILEXFER_TYPE_DIRECTORY ? REMMINA_FTP_FILE_TYPE_DIR : REMMINA_FTP_FILE_TYPE_FILE); \
		if (a->type == SSH_FILEXFER_TYPE_SYMLINK ) type = REMMINA_FTP_FILE_TYPE_LINK; \
	}

/* ------------------------ The Task Thread routines ----------------------------- */

static gboolean remmina_sftp_client_refresh(RemminaSFTPClient *client);

#define THREAD_CHECK_EXIT \
	(!client->taskid || client->thread_abort)



static gboolean
remmina_sftp_client_thread_update_task(RemminaSFTPClient *client, RemminaFTPTask *task)
{
	TRACE_CALL(__func__);
	if (THREAD_CHECK_EXIT) return FALSE;

	remmina_ftp_client_update_task(REMMINA_FTP_CLIENT(client), task);

	return TRUE;
}

static void
remmina_sftp_client_thread_set_error(RemminaSFTPClient *client, RemminaFTPTask *task, const gchar *error_format, ...)
{
	TRACE_CALL(__func__);
	va_list args;

	task->status = REMMINA_FTP_TASK_STATUS_ERROR;
	g_free(task->tooltip);
	if (error_format) {
		va_start(args, error_format);
		task->tooltip = g_strdup_vprintf(error_format, args);
		va_end(args);
	} else {
		task->tooltip = NULL;
	}

	remmina_sftp_client_thread_update_task(client, task);
}

static void
remmina_sftp_client_thread_set_finish(RemminaSFTPClient *client, RemminaFTPTask *task)
{
	TRACE_CALL(__func__);
	task->status = REMMINA_FTP_TASK_STATUS_FINISH;
	g_free(task->tooltip);
	task->tooltip = NULL;

	remmina_sftp_client_thread_update_task(client, task);
}

static RemminaFTPTask *
remmina_sftp_client_thread_get_task(RemminaSFTPClient *client)
{
	TRACE_CALL(__func__);
	RemminaFTPTask *task;

	if (client->thread_abort) return NULL;

	task = remmina_ftp_client_get_waiting_task(REMMINA_FTP_CLIENT(client));
	if (task) {
		client->taskid = task->taskid;

		task->status = REMMINA_FTP_TASK_STATUS_RUN;
		remmina_ftp_client_update_task(REMMINA_FTP_CLIENT(client), task);
	}

	return task;
}

static gboolean
remmina_sftp_client_thread_download_file(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task,
					 const gchar *remote_path, const gchar *local_path, guint64 *donesize)
{
	TRACE_CALL(__func__);
	sftp_file remote_file;
	FILE *local_file;
	gchar *tmp;
	gchar buf[20480];
	gint len;
	gint response;
	uint64_t size;

	if (THREAD_CHECK_EXIT) return FALSE;

	/* Ensure local dir exists */
	g_strlcpy(buf, local_path, sizeof(buf));
	tmp = g_strrstr(buf, "/");
	if (tmp && tmp != buf) {
		*tmp = '\0';
		if (g_mkdir_with_parents(buf, 0755) < 0) {
			// TRANSLATORS: The placeholder %s is a directory path
			remmina_sftp_client_thread_set_error(client, task, _("Could not create the folder “%s”."), buf);
			return FALSE;
		}
	}

	local_file = g_fopen(local_path, "ab");
	if (!local_file) {
		// TRANSLATORS: The placeholder %s is a file path
		remmina_sftp_client_thread_set_error(client, task, _("Could not create the file “%s”."), local_path);
		return FALSE;
	}

	fseeko(local_file, 0, SEEK_END);
	size = ftello(local_file);
	if (size > 0) {
		response = remmina_sftp_client_confirm_resume(client, local_path);

		switch (response) {
		case GTK_RESPONSE_CANCEL:
		case GTK_RESPONSE_DELETE_EVENT:
			fclose(local_file);
			remmina_sftp_client_thread_set_error(client, task, NULL);
			return FALSE;

		case GTK_RESPONSE_ACCEPT:
			fclose(local_file);
			local_file = g_fopen(local_path, "wb");
			if (!local_file) {
				// TRANSLATORS: The placeholder %s is a file path
				remmina_sftp_client_thread_set_error(client, task, _("Could not create the file “%s”."), local_path);
				return FALSE;
			}
			size = 0;
			break;

		case GTK_RESPONSE_APPLY:
			break;
		}
	}

	tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), remote_path);
	remote_file = sftp_open(sftp->sftp_sess, tmp, O_RDONLY, 0);
	g_free(tmp);

	if (!remote_file) {
		fclose(local_file);
		// TRANSLATORS: The placeholders %s are a file path, and an error message.
		remmina_sftp_client_thread_set_error(client, task, _("Could not open the file “%s” on the server. %s"),
						     remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
		return FALSE;
	}

	if (size > 0) {
		if (sftp_seek64(remote_file, size) < 0) {
			sftp_close(remote_file);
			fclose(local_file);
			remmina_sftp_client_thread_set_error(client, task, "Could not download the file “%s”. %s",
							     remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
			return FALSE;
		}
		*donesize = size;
	}

	while (!THREAD_CHECK_EXIT && (len = sftp_read(remote_file, buf, sizeof(buf))) > 0) {
		if (THREAD_CHECK_EXIT) break;

		if (fwrite(buf, 1, len, local_file) < len) {
			sftp_close(remote_file);
			fclose(local_file);
			remmina_sftp_client_thread_set_error(client, task, _("Could not save the file “%s”."), local_path);
			return FALSE;
		}

		*donesize += (guint64)len;
		task->donesize = (gfloat)(*donesize);

		if (!remmina_sftp_client_thread_update_task(client, task)) break;
	}

	sftp_close(remote_file);
	fclose(local_file);
	return TRUE;
}

static gboolean
remmina_sftp_client_thread_recursive_dir(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task,
					 const gchar *rootdir_path, const gchar *subdir_path, GPtrArray *array)
{
	TRACE_CALL(__func__);
	sftp_dir sftpdir;
	sftp_attributes sftpattr;
	gchar *tmp;
	gchar *dir_path;
	gchar *file_path;
	gint type;
	gboolean ret = TRUE;

	if (THREAD_CHECK_EXIT) return FALSE;

	if (subdir_path)
		dir_path = remmina_public_combine_path(rootdir_path, subdir_path);
	else
		dir_path = g_strdup(rootdir_path);
	tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), dir_path);

	REMMINA_DEBUG ("%s HELLO", __func__);
#if 0
	gchar *tlink = sftp_readlink (sftp->sftp_sess, tmp);
	if (tlink) {
		REMMINA_DEBUG ("%s is a link to %s", tmp, tlink);
		tmp = g_strdup (tlink);
	}
	g_free(tlink);
#endif
	sftpdir = sftp_opendir(sftp->sftp_sess, tmp);
	g_free(tmp);

	if (!sftpdir) {
		remmina_sftp_client_thread_set_error(client, task, _("Could not open the folder “%s”. %s"),
						     dir_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
		g_free(dir_path);
		return FALSE;
	}

	g_free(dir_path);

	while ((sftpattr = sftp_readdir(sftp->sftp_sess, sftpdir))) {
		if (g_strcmp0(sftpattr->name, ".") != 0 &&
		    g_strcmp0(sftpattr->name, "..") != 0) {
			GET_SFTPATTR_TYPE(sftpattr, type);

			tmp = remmina_ssh_convert(REMMINA_SSH(sftp), sftpattr->name);
			if (subdir_path) {
				file_path = remmina_public_combine_path(subdir_path, tmp);
				g_free(tmp);
			} else {
				file_path = tmp;
			}

			if (type == REMMINA_FTP_FILE_TYPE_DIR) {
				ret = remmina_sftp_client_thread_recursive_dir(client, sftp, task, rootdir_path, file_path, array);
				g_free(file_path);
				if (!ret) {
					sftp_attributes_free(sftpattr);
					break;
				}
			} else {
				task->size += (gfloat)sftpattr->size;
				g_ptr_array_add(array, file_path);

				if (!remmina_sftp_client_thread_update_task(client, task)) {
					sftp_attributes_free(sftpattr);
					break;
				}
			}
		}
		sftp_attributes_free(sftpattr);

		if (THREAD_CHECK_EXIT) break;
	}

	sftp_closedir(sftpdir);
	return ret;
}

static gboolean
remmina_sftp_client_thread_recursive_localdir(RemminaSFTPClient *client, RemminaFTPTask *task,
					      const gchar *rootdir_path, const gchar *subdir_path, GPtrArray *array)
{
	TRACE_CALL(__func__);
	GDir *dir;
	gchar *path;
	const gchar *name;
	gchar *relpath;
	gchar *abspath;
	struct stat st;
	gboolean ret = TRUE;

	path = g_build_filename(rootdir_path, subdir_path, NULL);
	dir = g_dir_open(path, 0, NULL);
	if (dir == NULL) {
		g_free(path);
		return FALSE;
	}
	while ((name = g_dir_read_name(dir)) != NULL) {
		if (THREAD_CHECK_EXIT) {
			ret = FALSE;
			break;
		}
		if (g_strcmp0(name, ".") == 0 || g_strcmp0(name, "..") == 0) continue;
		abspath = g_build_filename(path, name, NULL);
		if (g_stat(abspath, &st) < 0) {
			g_free(abspath);
			continue;
		}
		relpath = g_build_filename(subdir_path ? subdir_path : "", name, NULL);
		g_ptr_array_add(array, relpath);
		if (g_file_test(abspath, G_FILE_TEST_IS_DIR)) {
			ret = remmina_sftp_client_thread_recursive_localdir(client, task, rootdir_path, relpath, array);
			if (!ret) break;
		} else {
			task->size += (gfloat)st.st_size;
		}
		g_free(abspath);
	}
	g_free(path);
	g_dir_close(dir);
	return ret;
}

static gboolean
remmina_sftp_client_thread_mkdir(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task, const gchar *path)
{
	TRACE_CALL(__func__);
	sftp_attributes sftpattr;

	sftpattr = sftp_stat(sftp->sftp_sess, path);
	if (sftpattr != NULL) {
		sftp_attributes_free(sftpattr);
		return TRUE;
	}
	if (sftp_mkdir(sftp->sftp_sess, path, 0755) < 0) {
		remmina_sftp_client_thread_set_error(client, task, _("Could not create the folder “%s” on the server. %s"),
						     path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
		return FALSE;
	}
	return TRUE;
}

static gboolean
remmina_sftp_client_thread_upload_file(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task,
				       const gchar *remote_path, const gchar *local_path, guint64 *donesize)
{
	TRACE_CALL(__func__);
	sftp_file remote_file;
	FILE *local_file;
	gchar *tmp;
	gchar buf[20480];
	gint len;
	sftp_attributes attr;
	gint response;
	uint64_t size;

	if (THREAD_CHECK_EXIT) return FALSE;

	tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), remote_path);
	remote_file = sftp_open(sftp->sftp_sess, tmp, O_WRONLY | O_CREAT, 0644);
	g_free(tmp);

	if (!remote_file) {
		remmina_sftp_client_thread_set_error(client, task, _("Could not create the file “%s” on the server. %s"),
						     remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
		return FALSE;
	}
	attr = sftp_fstat(remote_file);
	size = attr->size;
	sftp_attributes_free(attr);
	if (size > 0) {
		response = remmina_sftp_client_confirm_resume(client, remote_path);
		switch (response) {
		case GTK_RESPONSE_CANCEL:
		case GTK_RESPONSE_DELETE_EVENT:
			sftp_close(remote_file);
			remmina_sftp_client_thread_set_error(client, task, NULL);
			return FALSE;

		case GTK_RESPONSE_ACCEPT:
			sftp_close(remote_file);
			tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), remote_path);
			remote_file = sftp_open(sftp->sftp_sess, tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
			g_free(tmp);
			if (!remote_file) {
				remmina_sftp_client_thread_set_error(client, task, _("Could not create the file “%s” on the server. %s"),
								     remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
				return FALSE;
			}
			size = 0;
			break;

		case GTK_RESPONSE_APPLY:
			if (sftp_seek64(remote_file, size) < 0) {
				sftp_close(remote_file);
				remmina_sftp_client_thread_set_error(client, task, "Could not download the file “%s”. %s",
								     remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
				return FALSE;
			}
			break;
		}
	}

	local_file = g_fopen(local_path, "rb");
	if (!local_file) {
		sftp_close(remote_file);
		remmina_sftp_client_thread_set_error(client, task, _("Could not open the file “%s”."), local_path);
		return FALSE;
	}

	if (size > 0) {
		if (fseeko(local_file, size, SEEK_SET) < 0) {
			sftp_close(remote_file);
			fclose(local_file);
			remmina_sftp_client_thread_set_error(client, task, "Could not find the local file “%s”.", local_path);
			return FALSE;
		}
		*donesize = size;
	}

	while (!THREAD_CHECK_EXIT && (len = fread(buf, 1, sizeof(buf), local_file)) > 0) {
		if (THREAD_CHECK_EXIT) break;

		if (sftp_write(remote_file, buf, len) < len) {
			sftp_close(remote_file);
			fclose(local_file);
			remmina_sftp_client_thread_set_error(client, task, _("Could not write to the file “%s” on the server. %s"),
							     remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
			return FALSE;
		}

		*donesize += (guint64)len;
		task->donesize = (gfloat)(*donesize);

		if (!remmina_sftp_client_thread_update_task(client, task)) break;
	}

	sftp_close(remote_file);
	fclose(local_file);
	return TRUE;
}

static gpointer
remmina_sftp_client_thread_main(gpointer data)
{
	TRACE_CALL(__func__);
	RemminaSFTPClient *client = REMMINA_SFTP_CLIENT(data);
	RemminaSFTP *sftp = NULL;
	RemminaFTPTask *task;
	gchar *remote, *local;
	guint64 size;
	GPtrArray *array;
	gint i;
	gchar *remote_file, *local_file;
	gboolean ret;
	gchar *refreshdir = NULL;
	gchar *tmp;
	gboolean refresh = FALSE;
	gchar *host;
	int port;

	task = remmina_sftp_client_thread_get_task(client);
	while (task) {
		size = 0;
		if (!sftp) {
			sftp = remmina_sftp_new_from_ssh(REMMINA_SSH(client->sftp));

			/* we may need to open a new tunnel too */
			host = NULL;
			port = 0;
			if (!remmina_plugin_sftp_start_direct_tunnel(client->gp, &host, &port))
				return NULL;
			(REMMINA_SSH(sftp))->tunnel_entrance_host = host;
			(REMMINA_SSH(sftp))->tunnel_entrance_port = port;

			/* Open a new connection for this subcommand */
			g_debug("[SFTPCLI] %s opening ssh session to %s:%d", __func__, host, port);
			if (!remmina_ssh_init_session(REMMINA_SSH(sftp))) {
				g_debug("[SFTPCLI] remmina_ssh_init_session returned error %s\n", (REMMINA_SSH(sftp))->error);
				remmina_sftp_client_thread_set_error(client, task, (REMMINA_SSH(sftp))->error);
				remmina_ftp_task_free(task);
				break;
			}

			if (remmina_ssh_auth(REMMINA_SSH(sftp), REMMINA_SSH(sftp)->password, client->gp, NULL) != REMMINA_SSH_AUTH_SUCCESS) {
				g_debug("[SFTPCLI] remmina_ssh_auth returned error %s\n", (REMMINA_SSH(sftp))->error);
				remmina_sftp_client_thread_set_error(client, task, (REMMINA_SSH(sftp))->error);
				remmina_ftp_task_free(task);
				break;
			}

			if (!remmina_sftp_open(sftp)) {
				g_debug("[SFTPCLI] remmina_sftp_open returned error %s\n", (REMMINA_SSH(sftp))->error);
				remmina_sftp_client_thread_set_error(client, task, (REMMINA_SSH(sftp))->error);
				remmina_ftp_task_free(task);
				break;
			}
		}

		remote = remmina_public_combine_path(task->remotedir, task->name);
		local = remmina_public_combine_path(task->localdir, task->name);

		switch (task->tasktype) {
		case REMMINA_FTP_TASK_TYPE_DOWNLOAD:
			switch (task->type) {
			case REMMINA_FTP_FILE_TYPE_FILE:
				ret = remmina_sftp_client_thread_download_file(client, sftp, task,
									       remote, local, &size);
				break;

			case REMMINA_FTP_FILE_TYPE_DIR:
				array = g_ptr_array_new();
				ret = remmina_sftp_client_thread_recursive_dir(client, sftp, task, remote, NULL, array);
				if (ret) {
					for (i = 0; i < array->len; i++) {
						if (THREAD_CHECK_EXIT) {
							ret = FALSE;
							break;
						}
						remote_file = remmina_public_combine_path(remote, (gchar *)g_ptr_array_index(array, i));
						local_file = remmina_public_combine_path(local, (gchar *)g_ptr_array_index(array, i));
						ret = remmina_sftp_client_thread_download_file(client, sftp, task,
											       remote_file, local_file, &size);
						g_free(remote_file);
						g_free(local_file);
						if (!ret) break;
					}
				}
				g_ptr_array_foreach(array, (GFunc)g_free, NULL);
				g_ptr_array_free(array, TRUE);
				break;

			default:
				ret = 0;
				break;
			}
			if (ret)
				remmina_sftp_client_thread_set_finish(client, task);
			break;

		case REMMINA_FTP_TASK_TYPE_UPLOAD:
			switch (task->type) {
			case REMMINA_FTP_FILE_TYPE_FILE:
				ret = remmina_sftp_client_thread_upload_file(client, sftp, task,
									     remote, local, &size);
				break;

			case REMMINA_FTP_FILE_TYPE_DIR:
				ret = remmina_sftp_client_thread_mkdir(client, sftp, task, remote);
				if (!ret) break;
				array = g_ptr_array_new();
				ret = remmina_sftp_client_thread_recursive_localdir(client, task, local, NULL, array);
				if (ret) {
					for (i = 0; i < array->len; i++) {
						if (THREAD_CHECK_EXIT) {
							ret = FALSE;
							break;
						}
						remote_file = remmina_public_combine_path(remote, (gchar *)g_ptr_array_index(array, i));
						local_file = g_build_filename(local, (gchar *)g_ptr_array_index(array, i), NULL);
						if (g_file_test(local_file, G_FILE_TEST_IS_DIR))
							ret = remmina_sftp_client_thread_mkdir(client, sftp, task, remote_file);
						else
							ret = remmina_sftp_client_thread_upload_file(client, sftp, task,
												     remote_file, local_file, &size);
						g_free(remote_file);
						g_free(local_file);
						if (!ret) break;
					}
				}
				g_ptr_array_foreach(array, (GFunc)g_free, NULL);
				g_ptr_array_free(array, TRUE);
				break;

			default:
				ret = 0;
				break;
			}
			if (ret) {
				remmina_sftp_client_thread_set_finish(client, task);
				tmp = remmina_ftp_client_get_dir(REMMINA_FTP_CLIENT(client));
				if (g_strcmp0(tmp, task->remotedir) == 0) {
					refresh = TRUE;
					g_free(refreshdir);
					refreshdir = tmp;
				} else {
					g_free(tmp);
				}
			}
			break;
		}

		g_free(remote);
		g_free(local);

		remmina_ftp_task_free(task);
		client->taskid = 0;

		if (client->thread_abort) break;

		task = remmina_sftp_client_thread_get_task(client);
	}

	if (sftp)
		remmina_sftp_free(sftp);

	if (!client->thread_abort && refresh) {
		tmp = remmina_ftp_client_get_dir(REMMINA_FTP_CLIENT(client));
		if (g_strcmp0(tmp, refreshdir) == 0)
			IDLE_ADD((GSourceFunc)remmina_sftp_client_refresh, client);
		g_free(tmp);
	}
	g_free(refreshdir);
	client->thread = 0;

	return NULL;
}

/* ------------------------ The SFTP Client routines ----------------------------- */

static void
remmina_sftp_client_destroy(RemminaSFTPClient *client, gpointer data)
{
	TRACE_CALL(__func__);
	if (client->sftp) {
		remmina_sftp_free(client->sftp);
		client->sftp = NULL;
	}
	client->thread_abort = TRUE;
	/* We will wait for the thread to quit itself, and hopefully the thread is handling things correctly */
	while (client->thread) {
		/* gdk_threads_leave (); */
		sleep(1);
		/* gdk_threads_enter (); */
	}
}

static sftp_dir
remmina_sftp_client_sftp_session_opendir(RemminaSFTPClient *client, const gchar *dir)
{
	TRACE_CALL(__func__);
	sftp_dir sftpdir;

	sftpdir = sftp_opendir(client->sftp->sftp_sess, (gchar *)dir);
	if (!sftpdir) {
		GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
						GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
						_("Could not open the folder “%s”. %s"), dir,
						ssh_get_error(REMMINA_SSH(client->sftp)->session));
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
		return NULL;
	}
	return sftpdir;
}

static gboolean
remmina_sftp_client_sftp_session_closedir(RemminaSFTPClient *client, sftp_dir sftpdir)
{
	TRACE_CALL(__func__);

	if (!sftp_dir_eof(sftpdir)) {
		GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
						GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
						_("Could not read from the folder. %s"), ssh_get_error(REMMINA_SSH(client->sftp)->session));
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
		return FALSE;
	}
	sftp_closedir(sftpdir);
	return TRUE;
}

static void
remmina_sftp_client_on_opendir(RemminaSFTPClient *client, gchar *dir, gpointer data)
{
	TRACE_CALL(__func__);
	sftp_dir sftpdir;
	sftp_attributes sftpattr;
	GtkWidget *dialog;
	gchar *newdir;
	gchar *newdir_conv;
	gchar *tmp;
	gint type;

	if (client->sftp == NULL) return;

	if (!dir || dir[0] == '\0') {
		newdir = g_strdup(".");
	} else if (dir[0] == '/') {
		newdir = g_strdup(dir);
	} else {
		tmp = remmina_ftp_client_get_dir(REMMINA_FTP_CLIENT(client));
		if (tmp) {
			newdir = remmina_public_combine_path(tmp, dir);
			g_free(tmp);
		} else {
			newdir = g_strdup_printf("./%s", dir);
		}
	}

	gchar *tlink = sftp_readlink (client->sftp->sftp_sess, newdir);
	if (tlink) {
		REMMINA_DEBUG ("%s is a link to %s", newdir, tlink);
		newdir = g_strdup (tlink);
		if (sftp_opendir (client->sftp->sftp_sess, newdir)) {
			REMMINA_DEBUG ("%s is a link to a folder", tlink);
		} else {
			REMMINA_DEBUG ("%s is a link to a file", tlink);
			return;
		}
	}
	g_free(tlink);

	tmp = remmina_ssh_unconvert(REMMINA_SSH(client->sftp), newdir);
	newdir_conv = sftp_canonicalize_path(client->sftp->sftp_sess, tmp);
	g_free(tmp);
	g_free(newdir);
	newdir = remmina_ssh_convert(REMMINA_SSH(client->sftp), newdir_conv);
	if (!newdir) {
		dialog = gtk_message_dialog_new(NULL,
						GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
						_("Could not open the folder “%s”. %s"), dir,
						ssh_get_error(REMMINA_SSH(client->sftp)->session));
		gtk_widget_show(dialog);
		g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
		g_free(newdir_conv);
		return;
	}

	sftpdir = remmina_sftp_client_sftp_session_opendir(client, newdir_conv);
	g_free(newdir_conv);
	if (!sftpdir) {
		g_free(newdir);
		return;
	}

	remmina_ftp_client_clear_file_list(REMMINA_FTP_CLIENT(client));

	while ((sftpattr = sftp_readdir(client->sftp->sftp_sess, sftpdir))) {
		if (g_strcmp0(sftpattr->name, ".") != 0 &&
		    g_strcmp0(sftpattr->name, "..") != 0) {
			GET_SFTPATTR_TYPE(sftpattr, type);

			tmp = remmina_ssh_convert(REMMINA_SSH(client->sftp), sftpattr->name);
			remmina_ftp_client_add_file(REMMINA_FTP_CLIENT(client),
						    REMMINA_FTP_FILE_COLUMN_TYPE, type,
						    REMMINA_FTP_FILE_COLUMN_NAME, tmp,
						    REMMINA_FTP_FILE_COLUMN_SIZE, (gfloat)sftpattr->size,
						    REMMINA_FTP_FILE_COLUMN_USER, sftpattr->owner,
						    REMMINA_FTP_FILE_COLUMN_GROUP, sftpattr->group,
						    REMMINA_FTP_FILE_COLUMN_PERMISSION, sftpattr->permissions,
						    REMMINA_FTP_FILE_COLUMN_MODIFIED, sftpattr->mtime,
						    -1);
			g_free(tmp);
		}
		sftp_attributes_free(sftpattr);
	}
	remmina_sftp_client_sftp_session_closedir(client, sftpdir);

	remmina_ftp_client_set_dir(REMMINA_FTP_CLIENT(client), newdir);
	g_free(newdir);
}

static void
remmina_sftp_client_on_newtask(RemminaSFTPClient *client, gpointer data)
{
	TRACE_CALL(__func__);
	if (client->thread) return;

	if (pthread_create(&client->thread, NULL, remmina_sftp_client_thread_main, client))
		client->thread = 0;
}

static gboolean
remmina_sftp_client_on_canceltask(RemminaSFTPClient *client, gint taskid, gpointer data)
{
	TRACE_CALL(__func__);
	GtkWidget *dialog;
	gint ret;

	if (client->taskid != taskid) return TRUE;

	dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
					GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
					_("Are you sure you want to cancel the file transfer in progress?"));
	ret = gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
	if (ret == GTK_RESPONSE_YES) {
		/* Make sure we are still handling the same task before we clear the flag */
		if (client->taskid == taskid) client->taskid = 0;
		return TRUE;
	}
	return FALSE;
}

static gboolean
remmina_sftp_client_on_deletefile(RemminaSFTPClient *client, gint type, gchar *name, gpointer data)
{
	TRACE_CALL(__func__);
	GtkWidget *dialog;
	gint ret = 0;
	gchar *tmp;

	tmp = remmina_ssh_unconvert(REMMINA_SSH(client->sftp), name);
	switch (type) {
	case REMMINA_FTP_FILE_TYPE_DIR:
		ret = sftp_rmdir(client->sftp->sftp_sess, tmp);
		break;

	case REMMINA_FTP_FILE_TYPE_FILE:
		ret = sftp_unlink(client->sftp->sftp_sess, tmp);
		break;
	}
	g_free(tmp);

	if (ret != 0) {
		dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
						GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
						_("Could not delete “%s”. %s"),
						name, ssh_get_error(REMMINA_SSH(client->sftp)->session));
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
		return FALSE;
	}
	return TRUE;
}

static void
remmina_sftp_client_init(RemminaSFTPClient *client)
{
	TRACE_CALL(__func__);
	client->sftp = NULL;
	client->thread = 0;
	client->taskid = 0;
	client->thread_abort = FALSE;

	/* Setup the internal signals */
	g_signal_connect(G_OBJECT(client), "destroy",
			 G_CALLBACK(remmina_sftp_client_destroy), NULL);
	g_signal_connect(G_OBJECT(client), "open-dir",
			 G_CALLBACK(remmina_sftp_client_on_opendir), NULL);
	g_signal_connect(G_OBJECT(client), "new-task",
			 G_CALLBACK(remmina_sftp_client_on_newtask), NULL);
	g_signal_connect(G_OBJECT(client), "cancel-task",
			 G_CALLBACK(remmina_sftp_client_on_canceltask), NULL);
	g_signal_connect(G_OBJECT(client), "delete-file",
			 G_CALLBACK(remmina_sftp_client_on_deletefile), NULL);
}

static gboolean
remmina_sftp_client_refresh(RemminaSFTPClient *client)
{
	TRACE_CALL(__func__);

	GdkDisplay *display = gdk_display_get_default();

	SET_CURSOR(gdk_cursor_new_for_display(display, GDK_WATCH));
	gdk_display_flush(display);

	remmina_sftp_client_on_opendir(client, ".", NULL);

	SET_CURSOR(NULL);

	return FALSE;
}

gint
remmina_sftp_client_confirm_resume(RemminaSFTPClient *client, const gchar *path)
{
	TRACE_CALL(__func__);

	GtkWidget *dialog;
	gint response;
	GtkWidget *hbox;
	GtkWidget *vbox;
	GtkWidget *widget;
	const gchar *filename;

	/* Always reply ACCEPT if overwrite_all was already set */
	if (remmina_ftp_client_get_overwrite_status(REMMINA_FTP_CLIENT(client)))
		return GTK_RESPONSE_ACCEPT;

	/* Always reply APPLY if resume was already set */
	if (remmina_ftp_client_get_resume_status(REMMINA_FTP_CLIENT(client)))
		return GTK_RESPONSE_APPLY;

	if (!remmina_masterthread_exec_is_main_thread()) {
		/* Allow the execution of this function from a non main thread */
		RemminaMTExecData *d;
		gint retval;
		d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
		d->func = FUNC_SFTP_CLIENT_CONFIRM_RESUME;
		d->p.sftp_client_confirm_resume.client = client;
		d->p.sftp_client_confirm_resume.path = path;
		remmina_masterthread_exec_and_wait(d);
		retval = d->p.sftp_client_confirm_resume.retval;
		g_free(d);
		return retval;
	}

	filename = strrchr(path, '/');
	filename = filename ? filename + 1 : path;

	dialog = gtk_dialog_new_with_buttons(_("The file exists already"),
					     GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
					     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
					     _("Resume"), GTK_RESPONSE_APPLY,
					     _("Overwrite"), GTK_RESPONSE_ACCEPT,
					     _("_Cancel"), GTK_RESPONSE_CANCEL,
					     NULL);
	gtk_container_set_border_width(GTK_CONTAINER(dialog), 4);

	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
	gtk_widget_show(hbox);
	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
			   hbox, TRUE, TRUE, 4);

	widget = gtk_image_new_from_icon_name("dialog-question", GTK_ICON_SIZE_DIALOG);
	gtk_widget_show(widget);
	gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 4);

	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 4);

	widget = gtk_label_new(_("The following file already exists in the target folder:"));
	gtk_widget_show(widget);
	gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
	gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
	gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 4);

	widget = gtk_label_new(filename);
	gtk_widget_show(widget);
	gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
	gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
	gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 4);

	response = gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
	return response;
}

RemminaSFTPClient *
remmina_sftp_client_new(void)
{
	TRACE_CALL(__func__);
	return REMMINA_SFTP_CLIENT(g_object_new(REMMINA_TYPE_SFTP_CLIENT, NULL));
}

void
remmina_sftp_client_open(RemminaSFTPClient *client, RemminaSFTP *sftp)
{
	TRACE_CALL(__func__);
	client->sftp = sftp;

	g_idle_add((GSourceFunc)remmina_sftp_client_refresh, client);
}

/*
 * GtkWidget *
 * remmina_sftp_client_new_init(RemminaSFTP *sftp)
 * {
 *      TRACE_CALL(__func__);
 *      GdkDisplay *display;
 *      GtkWidget *client;
 *      GtkWidget *dialog;
 *
 *      display = gdk_display_get_default();
 *      client = remmina_sftp_client_new();
 *
 *
 *      SET_CURSOR(gdk_cursor_new_for_display(display, GDK_WATCH));
 *      gdk_display_flush(display);
 *
 *      if (!remmina_ssh_init_session(REMMINA_SSH(sftp)) ||
 *          remmina_ssh_auth(REMMINA_SSH(sftp), NULL, NULL, NULL) != REMMINA_SSH_AUTH_SUCCESS ||
 *          !remmina_sftp_open(sftp)) {
 *              dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(client)),
 *                                              GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
 *                                              (REMMINA_SSH(sftp))->error, NULL);
 *              gtk_dialog_run(GTK_DIALOG(dialog));
 *              gtk_widget_destroy(dialog);
 *              gtk_widget_destroy(client);
 *              return NULL;
 *      }
 *
 *      SET_CURSOR(NULL);
 *
 *      g_idle_add((GSourceFunc)remmina_sftp_client_refresh, client);
 *      return client;
 * }
 */

#endif
