
/*
 * Osmo - a handy personal organizer
 *
 * Copyright (C) 2007 Tomasz Maka <pasp@users.sourceforge.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <config.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#ifndef WIN32
#include <sys/mman.h>
#endif /* WIN32 */

#include "i18n.h"
#include "backup.h"
#include "gui.h"
#include "notes_preferences_gui.h"
#include "utils.h"
#include "utils_gui.h"
#include "options_prefs.h"

#if defined(BACKUP_SUPPORT) && defined(HAVE_LIBGRINGOTTS)

#include <libgringotts.h>
#include <archive.h>
#include <archive_entry.h>

/*------------------------------------------------------------------------------*/

static struct archive *
create_archive(const gchar *filename) {
    struct archive *archive;
    archive = archive_write_new();
    archive_write_set_format_ustar(archive);
    if (archive_write_open_filename(archive, filename) != ARCHIVE_OK) {
        fprintf(stderr, "%s\n", archive_error_string(archive));
        archive_write_free (archive);
        return NULL;
    }
    return archive;
}

static void
close_archive(struct archive *archive) {
    archive_write_free (archive);
}

static void
add_entry_to_archive(struct archive *archive, gchar *filename, gchar *archive_entry_name) {
    struct archive_entry *entry;
    struct stat st;
    gchar buff[8192];
    gint fd;
    gint len;

    stat(filename, &st);
    entry = archive_entry_new();
    archive_entry_copy_stat(entry, &st);
    archive_entry_set_pathname(entry, archive_entry_name);
    archive_write_header(archive, entry);
    fd = open(filename, O_RDONLY);
    len = read(fd, buff, sizeof (buff));
    while (len > 0) {
        archive_write_data(archive, buff, len);
        len = read(fd, buff, sizeof (buff));
    }
    close(fd);
    archive_entry_free(entry);
}

static void
add_to_archive(gchar *from_dir, gchar *extension, gchar *prefix, struct archive *archive) {
    const gchar *item_name;
    GDir *dir_path = g_dir_open(from_dir, 0, NULL);
    g_return_if_fail(dir_path != NULL);

    while ((item_name = g_dir_read_name(dir_path)) != NULL) {
        gchar *source_filename = g_build_filename(from_dir, item_name, NULL);
        gchar *target_filename;
        if (!g_file_test(source_filename, G_FILE_TEST_IS_REGULAR)) {
            g_free(source_filename);
            continue;
        }

        if (!g_str_has_suffix(item_name, extension)) {
            g_free(source_filename);
            continue;
        }
        target_filename = g_build_filename(prefix, item_name, NULL);
        add_entry_to_archive(archive, source_filename, target_filename);
        
        g_free(source_filename);
        g_free(target_filename);
    }
    g_dir_close(dir_path);
}
/*------------------------------------------------------------------------------*/

static gint
untar_archive (gchar *filename)
{
	struct archive_entry *entry;
	struct archive *archive;

	archive = archive_read_new ();

	if ((archive_read_support_filter_all (archive) != ARCHIVE_OK) ||
	    (archive_read_support_format_all (archive) != ARCHIVE_OK) ||
	    (archive_read_open_filename (archive, filename, 8192) != ARCHIVE_OK))
	{
		archive_read_free (archive);
        fprintf(stderr, "%s\n", archive_error_string(archive));
		return archive_errno (archive);
	}

	while (archive_read_next_header (archive, &entry) == ARCHIVE_OK)
		archive_read_extract (archive, entry, ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM);

	archive_read_free (archive);

	return ARCHIVE_OK;
}


/*------------------------------------------------------------------------------*/
static void
create_backup_file(const gchar *filename, const gchar *password, GUI *appGUI) {
    gchar *backup_filename;
    GRG_CTX context;
    GRG_KEY keyholder;
    gchar *contents;
    gchar *notes;
    gsize len;
    struct archive *archive;

    /* create backup file */
    backup_filename = g_strdup(prefs_get_cache_filename("backup.dat"));
    archive = create_archive(backup_filename);
    if (!archive) {
        utl_gui_create_dialog(GTK_MESSAGE_ERROR, _("Cannot create backup!"), GTK_WINDOW(appGUI->main_window));
        g_unlink(backup_filename);
        g_free(backup_filename);
        return;
    }

    /* add files to archive */
    add_to_archive(prefs_get_config_dir(appGUI), CONFIG_FILENAME, "", archive);
    add_to_archive(prefs_get_data_dir(appGUI), "xml", "", archive);
    notes = g_build_filename(prefs_get_data_dir(appGUI), "notes", NULL);
    add_to_archive(notes, "osm", "notes", archive);
    g_free(notes);

    /* done writing backup */
    close_archive(archive);

    if (g_file_get_contents(backup_filename, &contents, &len, NULL) == FALSE) {
        utl_gui_create_dialog(GTK_MESSAGE_ERROR, _("Cannot create backup!"), GTK_WINDOW(appGUI->main_window));
        g_unlink(backup_filename);
        g_free(backup_filename);
        return;
    }

    context = grg_context_initialize_defaults((unsigned char*) "BCK");
    keyholder = grg_key_gen((unsigned char*) password, -1);

    if (keyholder == NULL || context == NULL) {
        utl_gui_create_dialog(GTK_MESSAGE_ERROR, _("Cannot create backup!"), GTK_WINDOW(appGUI->main_window));
        g_unlink(backup_filename);
        g_free(backup_filename);
        return;
    }

    grg_ctx_set_crypt_algo(context, get_enc_algorithm_value());
    grg_ctx_set_hash_algo(context, get_enc_hashing_value());
    grg_ctx_set_comp_algo(context, get_comp_algorithm_value());
    grg_ctx_set_comp_ratio(context, get_comp_ratio_value());
    grg_encrypt_file(context, keyholder, (unsigned char*) filename, (guchar *) contents, len);
    grg_free(context, contents, len);
    grg_key_free(context, keyholder);
    grg_context_free(context);

    /* clean up*/
    g_unlink(backup_filename);
    g_free(backup_filename);
}

void
backup_create (GUI *appGUI) {

gint ret, p1len, p2len;
gchar *filename, *password, *bpass1, *bpass2;
gchar tmpbuf[BUFFER_SIZE];
GtkWidget *dialog, *passwd_dialog;
GtkWidget *passwd_content, *vbox1, *frame, *label;
GtkWidget *bck_p1_entry, *bck_p2_entry;

	/* select filename and password */

    dialog = utl_gui_create_save_file_dialog (_("Save backup"),
                                          GTK_WINDOW(appGUI->main_window));
    gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
            utl_add_timestamp_to_filename("osmobackup", "bck"));

	ret = gtk_dialog_run(GTK_DIALOG(dialog));
    if (ret == GTK_RESPONSE_CANCEL || ret == GTK_RESPONSE_DELETE_EVENT) {
        gtk_widget_destroy(dialog);
		return;
	}

	filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
    gtk_widget_destroy(dialog);

	if (utl_gui_check_overwrite_file (filename, appGUI->main_window, appGUI) != 0) {
        return;
    } else {
        g_unlink (filename);
    }
	
    passwd_dialog = gtk_dialog_new_with_buttons (_("Password protection"), 
												 GTK_WINDOW(appGUI->main_window),
                                                 GTK_DIALOG_MODAL,
                                                 _("_Cancel"), GTK_RESPONSE_CANCEL,
                                                 _("_OK"), GTK_RESPONSE_ACCEPT,
												 NULL);

	gtk_window_set_position (GTK_WINDOW(passwd_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
	gtk_window_set_default_size (GTK_WINDOW(passwd_dialog), 400, -1);
    gtk_dialog_set_default_response (GTK_DIALOG(passwd_dialog), GTK_RESPONSE_ACCEPT);

	vbox1 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_show (vbox1);
    passwd_content = gtk_dialog_get_content_area(GTK_DIALOG(passwd_dialog));
    gtk_container_add(GTK_CONTAINER(passwd_content), vbox1);

	frame = gtk_frame_new (NULL);
    gtk_widget_show (frame);
    gtk_box_pack_start (GTK_BOX (vbox1), frame, FALSE, FALSE, 0);
    gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
	
    bck_p1_entry = gtk_entry_new ();
    gtk_widget_show (bck_p1_entry);
    gtk_container_add (GTK_CONTAINER (frame), bck_p1_entry);
    gtk_widget_set_margin_left(bck_p1_entry, 16);
    gtk_widget_set_margin_right(bck_p1_entry, 4);
    gtk_widget_set_margin_top(bck_p1_entry, 4);
    gtk_widget_set_margin_bottom(bck_p1_entry, 4);
    gtk_entry_set_invisible_char (GTK_ENTRY (bck_p1_entry), 8226);
    gtk_entry_set_visibility (GTK_ENTRY (bck_p1_entry), FALSE);

    g_snprintf(tmpbuf, BUFFER_SIZE, "<b>%s:</b>", _("Enter password"));
    label = gtk_label_new (tmpbuf);
    gtk_widget_show (label);
    gtk_frame_set_label_widget (GTK_FRAME (frame), label);
    gtk_label_set_use_markup (GTK_LABEL (label), TRUE);

    frame = gtk_frame_new (NULL);
    gtk_widget_show (frame);
    gtk_box_pack_start (GTK_BOX (vbox1), frame, FALSE, FALSE, 0);
    gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);

    bck_p2_entry = gtk_entry_new ();
    gtk_widget_show (bck_p2_entry);
    gtk_container_add (GTK_CONTAINER (frame), bck_p2_entry);
    gtk_widget_set_margin_left(bck_p2_entry, 16);
    gtk_widget_set_margin_right(bck_p2_entry, 4);
    gtk_widget_set_margin_top(bck_p2_entry, 4);
    gtk_widget_set_margin_bottom(bck_p2_entry, 4);
    gtk_entry_set_invisible_char (GTK_ENTRY (bck_p2_entry), 8226);
    gtk_entry_set_visibility (GTK_ENTRY (bck_p2_entry), FALSE);

    g_snprintf(tmpbuf, BUFFER_SIZE, "<b>%s:</b>", _("Re-enter password"));
    label = gtk_label_new (tmpbuf);
    gtk_widget_show (label);
    gtk_frame_set_label_widget (GTK_FRAME (frame), label);
    gtk_label_set_use_markup (GTK_LABEL (label), TRUE);

	while (1) {

		ret = gtk_dialog_run(GTK_DIALOG(passwd_dialog)); 

		if (ret == GTK_RESPONSE_CANCEL || ret == GTK_RESPONSE_DELETE_EVENT) {
			gtk_widget_destroy(passwd_dialog);
			return;
		}

		bpass1 = g_strdup(gtk_entry_get_text(GTK_ENTRY(bck_p1_entry)));
		p1len = strlen(bpass1);
		bpass2 = g_strdup(gtk_entry_get_text(GTK_ENTRY(bck_p2_entry)));
		p2len = strlen(bpass2);

		if (p1len == 0 && p2len == 0) {
			utl_gui_create_dialog (GTK_MESSAGE_ERROR, _("Please enter the password"), GTK_WINDOW(passwd_dialog));
			gtk_widget_grab_focus (bck_p1_entry);
			g_free(bpass1);
			g_free(bpass2);
			continue;
		} else if (p1len != p2len) {
			utl_gui_create_dialog (GTK_MESSAGE_ERROR, _("Passwords do not match!"), GTK_WINDOW(passwd_dialog));
			gtk_widget_grab_focus (bck_p1_entry);
			g_free(bpass1);
			g_free(bpass2);
			continue;
		}

		if (g_utf8_collate (bpass1, bpass2) != 0) {
			utl_gui_create_dialog (GTK_MESSAGE_ERROR, _("Passwords do not match!"), GTK_WINDOW(passwd_dialog));
			gtk_widget_grab_focus (bck_p1_entry);
			g_free(bpass1);
			g_free(bpass2);
			continue;
		} else {
			g_free(bpass1);
			g_free(bpass2);
			break;
		}

	}

	password = g_strdup(gtk_entry_get_text(GTK_ENTRY(bck_p1_entry)));
	gtk_widget_destroy(passwd_dialog);


        create_backup_file(filename, password, appGUI);
        
        g_free(filename);
	g_free(password);
	
	utl_gui_create_dialog (GTK_MESSAGE_INFO, _("Backup file saved successfully!"), GTK_WINDOW(appGUI->main_window));
}

/*------------------------------------------------------------------------------*/

void
backup_restore (GUI *appGUI) {

gchar *filename, *tmp_filename;
gint ret, tErr, passlen;
GtkFileFilter *filter;
GRG_CTX context;
GRG_KEY keyholder;
unsigned char *arch;
long arch_len;
gchar *contents, *password;
gchar tmpbuf[BUFFER_SIZE];
GtkWidget *dialog, *passwd_dialog, *pass_entry;
GtkWidget *passwd_content, *vbox1, *frame, *label;

    dialog = utl_gui_create_open_file_dialog (_("Open backup file"),
                                          GTK_WINDOW(appGUI->main_window));

	filter = gtk_file_filter_new();
    gtk_file_filter_add_pattern(filter, "*.[bB][cC][kK]");
    gtk_file_filter_set_name(GTK_FILE_FILTER(filter), _("Osmo backup files (*.bck)"));
    gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);

    gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);

	ret = gtk_dialog_run(GTK_DIALOG(dialog));
    if (ret == GTK_RESPONSE_CANCEL || ret == GTK_RESPONSE_DELETE_EVENT) {
        gtk_widget_destroy(dialog);
		return;
	}

	filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
    gtk_widget_destroy(dialog);
	
	tErr = -1;

	if (g_file_get_contents (filename, &contents, NULL, NULL) == TRUE) {

		if (contents[0] == 'B' && contents[1] == 'C' && contents[2] == 'K' && g_ascii_isalnum(contents[3])) {
			tErr = 0;
		}

		g_free (contents);
	}

	if (tErr == -1) {
		utl_gui_create_dialog (GTK_MESSAGE_ERROR, _("This is not Osmo backup file"), GTK_WINDOW(appGUI->main_window));
		g_free (filename);
		return;
	}

    passwd_dialog = gtk_dialog_new_with_buttons (_("Password protection"), 
												 GTK_WINDOW(appGUI->main_window),
                                                 GTK_DIALOG_MODAL,
                                                 _("_Cancel"), GTK_RESPONSE_CANCEL,
                                                 _("_OK"), GTK_RESPONSE_ACCEPT,
												 NULL);

	gtk_window_set_position (GTK_WINDOW(passwd_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
	gtk_window_set_default_size (GTK_WINDOW(passwd_dialog), 400, -1);
    gtk_dialog_set_default_response (GTK_DIALOG(passwd_dialog), GTK_RESPONSE_ACCEPT);

	vbox1 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_show (vbox1);
    passwd_content = gtk_dialog_get_content_area(GTK_DIALOG(passwd_dialog));
    gtk_container_add(GTK_CONTAINER(passwd_content), vbox1);

	frame = gtk_frame_new (NULL);
    gtk_widget_show (frame);
    gtk_box_pack_start (GTK_BOX (vbox1), frame, FALSE, FALSE, 0);
    gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
	
    pass_entry = gtk_entry_new ();
    gtk_widget_show (pass_entry);
    gtk_container_add (GTK_CONTAINER (frame), pass_entry);
    gtk_widget_set_margin_left(pass_entry, 16);
    gtk_widget_set_margin_right(pass_entry, 4);
    gtk_widget_set_margin_top(pass_entry, 4);
    gtk_widget_set_margin_bottom(pass_entry, 4);
    gtk_entry_set_invisible_char (GTK_ENTRY (pass_entry), 8226);
    gtk_entry_set_visibility (GTK_ENTRY (pass_entry), FALSE);

    g_snprintf(tmpbuf, BUFFER_SIZE, "<b>%s:</b>", _("Enter password"));
    label = gtk_label_new (tmpbuf);
    gtk_widget_show (label);
    gtk_frame_set_label_widget (GTK_FRAME (frame), label);
    gtk_label_set_use_markup (GTK_LABEL (label), TRUE);

	while (1) {

		ret = gtk_dialog_run(GTK_DIALOG(passwd_dialog)); 

		if (ret == GTK_RESPONSE_CANCEL || ret == GTK_RESPONSE_DELETE_EVENT) {
			gtk_widget_destroy(passwd_dialog);
			g_free(filename);
			return;
		}

		password = g_strdup(gtk_entry_get_text(GTK_ENTRY(pass_entry)));
		passlen = strlen(password);

		if (passlen == 0) {
			utl_gui_create_dialog (GTK_MESSAGE_ERROR, _("Please enter the password"), GTK_WINDOW(passwd_dialog));
			gtk_widget_grab_focus (pass_entry);
			g_free(password);
			continue;
		} else {
			break;
		}

	}

	gtk_widget_destroy(passwd_dialog);

	/* extracting encrypted data */

	tmp_filename = g_strdup (prefs_get_cache_filename (BACKUP_TEMPLATE));
	g_unlink (tmp_filename);

	context = grg_context_initialize_defaults ((unsigned char*) "BCK");
	keyholder = grg_key_gen ((unsigned char*) password, -1);

	if (keyholder == NULL || context == NULL) {
		utl_gui_create_dialog (GTK_MESSAGE_ERROR, _("Cannot create backup!"), GTK_WINDOW(appGUI->main_window));
		return;
	}

	grg_ctx_set_crypt_algo (context, get_enc_algorithm_value());
	grg_ctx_set_hash_algo (context, get_enc_hashing_value());
	grg_ctx_set_comp_algo (context, get_comp_algorithm_value());
	grg_ctx_set_comp_ratio (context, get_comp_ratio_value());

	ret = grg_decrypt_file (context, keyholder, (unsigned char *) filename, &arch, &arch_len);

	if (ret != GRG_OK) {
		utl_gui_create_dialog (GTK_MESSAGE_ERROR, _("Incorrect password!"), GTK_WINDOW(appGUI->main_window));
		grg_key_free (context, keyholder);
		grg_context_free (context);
		g_free(password);
		g_free(filename);
		g_free(tmp_filename);
		return;
	}
		
	tErr = -1;

    g_snprintf(tmpbuf, BUFFER_SIZE, "%s\n\n%s", 
			 _("All your data will be replaced with backup file content."), _("Are you sure?"));

	ret = utl_gui_create_dialog (GTK_MESSAGE_QUESTION, tmpbuf, GTK_WINDOW(appGUI->main_window));
   
    if (ret == GTK_RESPONSE_YES) {
		/* save TAR file */
		g_file_set_contents (tmp_filename, (gchar *) arch, arch_len, NULL);
		tErr = 0;
	}

	g_free (arch);
	grg_key_free (context, keyholder);
	grg_context_free (context);

	/* free strings */

	g_free(password);
	g_free(filename);
	g_free(tmp_filename);

	if (!tErr) {
        g_snprintf(tmpbuf, BUFFER_SIZE, "%s", _("Osmo has to be restarted now..."));
		utl_gui_create_dialog (GTK_MESSAGE_INFO, tmpbuf, GTK_WINDOW(appGUI->main_window));
		gui_quit_osmo (appGUI);
	}

}

/*------------------------------------------------------------------------------*/
static void
delete_dir(gchar *dir) {
    GDir *dir_fd = g_dir_open(dir, 0, NULL);
    if (dir_fd) {
        const gchar *child = g_dir_read_name(dir_fd);
        for (; child; child = g_dir_read_name(dir_fd)) {
            gchar *child_fullname = g_build_filename(dir, child, NULL);
            if (g_file_test(child_fullname, G_FILE_TEST_IS_DIR)) {
                delete_dir(child_fullname);
            } else {
                g_remove(child_fullname);
            }
            g_free(child_fullname);
        }
        g_remove(dir);
    }
}

void
backup_restore_run (GUI *appGUI) {

gchar *tmp_filename, *tmp_dirname, *home_dirname;

	/* is backup file available? */
	tmp_filename = g_strdup (prefs_get_cache_filename (BACKUP_TEMPLATE));
	if (g_file_test (tmp_filename, G_FILE_TEST_IS_REGULAR) == FALSE) {
		g_free (tmp_filename);
		return;
	}

	/* prepare and change to a temporary directory */
	home_dirname = g_get_current_dir();
        tmp_dirname = g_dir_make_tmp(NULL, NULL);
        if(!tmp_dirname) {
            g_free (tmp_filename);
            g_return_if_fail(tmp_dirname != NULL);
        }
	g_chdir (tmp_dirname);

	/* untar files */
	untar_archive (tmp_filename);
        
        /* copy to the correct directories */
        prefs_restore(tmp_dirname, appGUI);
        delete_dir(tmp_dirname);

	/* clean up */

	g_chdir (home_dirname);
	g_free (home_dirname);

	g_free (tmp_dirname);

	g_unlink (tmp_filename);
	g_free (tmp_filename);
}

/*------------------------------------------------------------------------------*/

#endif  /* BACKUP_SUPPORT && HAVE_LIBGRINGOTTS */

