/* GAdmin-Rsync - An easy to use GTK+ frontend for the rsync backup client and server.
 * Copyright (C) 2007-2011 Magnus Loef <magnus-swe@telia.com> 
 *
 * 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 3 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include "gettext.h"
#include "widgets.h"
#include "show_info.h"
#include "allocate.h"
#include "save_backup_settings.h"
#include "cron_functions.h"
#include "commands.h"


extern gchar *global_settings_dir;
extern gchar *global_scripts_dir;
extern gchar *global_backup_name;

/* Error info flags */
int error_src_dst_info = 1;
int error_server_server_info = 1;


int has_value(gchar *input)
{
    int ret = 0;

    if( input!=NULL && strlen(input) > 0 )
      ret = 1;

    return ret;
}


void free_values(gchar *a1, gchar *a2, gchar *a3, 
		 gchar *a4, gchar *a5, gchar *a6,
		 gchar *a7, gchar *a8, gchar *a9)
{
    if( a1!=NULL ) g_free(a1);
    if( a2!=NULL ) g_free(a2);
    if( a3!=NULL ) g_free(a3);
    if( a4!=NULL ) g_free(a4);
    if( a5!=NULL ) g_free(a5);
    if( a6!=NULL ) g_free(a6);
    if( a7!=NULL ) g_free(a7);
    if( a8!=NULL ) g_free(a8);
}


/* For each in the rsync treeview, save backup settings and scripts */
gboolean save_foreach(GtkTreeModel *model, GtkTreePath *path,
                      GtkTreeIter *iter, struct w *widgets)
{
    FILE *fp;
    gchar *settings_file, *setting=NULL, *script_file, *script, *log_file, *info;
    gchar *src_server=NULL, *src_path=NULL, *dst_server=NULL, *dst_path=NULL;
    gchar *include_files=NULL, *exclude_files=NULL;
    gint include_in_backup_script = 0;
    gint del_dst_not_in_src = 0;
    gchar *user=NULL, *port=NULL, *priv_key_path;

    gchar *source=NULL, *destination=NULL;

    /* Get source server */
    gtk_tree_model_get(model, iter, 0, &src_server, -1);

    /* Get source path */
    gtk_tree_model_get(model, iter, 1, &src_path, -1);

    /* Get destination server */
    gtk_tree_model_get(model, iter, 2, &dst_server, -1);

    /* Get destination path */
    gtk_tree_model_get(model, iter, 3, &dst_path, -1);

    /* Get include files */
    gtk_tree_model_get(model, iter, 4, &include_files, -1);

    /* Get exclude files */
    gtk_tree_model_get(model, iter, 5, &exclude_files, -1);

    /* Get include in backup checkbox value */
    gtk_tree_model_get(model, iter, 6, &include_in_backup_script, -1);

    /* Get "del dest not in source" checkbox value */
    gtk_tree_model_get(model, iter, 7, &del_dst_not_in_src, -1);

    /* Get user */
    gtk_tree_model_get(model, iter, 8, &user, -1);
    /* Get port */
    gtk_tree_model_get(model, iter, 9, &port, -1);
    /* Get priv key path */
    gtk_tree_model_get(model, iter, 10, &priv_key_path, -1);


    /* There must always be a source and a destination directory */
    if( ! has_value(src_path) || ! has_value(dst_path) )
    {
	if( error_src_dst_info )
	{
	    error_src_dst_info = 0; /* Dont show this again */
	    info = g_strdup_printf(_("Error: This backup is missing a required source or destination path.\n"));
	    show_info(info);
	    g_free(info);
	}
    }

    /* Rsync cant do server to server backups */
    if( has_value(src_server) && has_value(dst_server) )
    {
	if( error_server_server_info )
	{
	    error_server_server_info = 0;
	    info = g_strdup_printf(_("Error: Server to server backups are not supported by rsync.\n"));
	    show_info(info);
	    g_free(info);
	}
    }

    /* Open the settings file for appending */
    settings_file = g_strdup_printf("%s/%s", global_settings_dir, global_backup_name);
    if((fp=fopen(settings_file, "a"))==NULL)
    {
	info = g_strdup_printf(_("Error: Can not write settings here:\n%s\n"), settings_file);
        show_info(info);
        g_free(info);

	free_values(src_server, src_path, dst_server, 
                 dst_path, user, port, priv_key_path, 
                         include_files, exclude_files);

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

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

        return TRUE; /* Stop iterating on write failure */
    }

    /* Append settings to the settings file */
    if( has_value(src_server) )
    {
	setting = g_strdup_printf("src_server %s\n", src_server);
	fputs(setting, fp);
	g_free(setting);
    }
    if( has_value(src_path) )
    {
	setting = g_strdup_printf("src_path %s\n", src_path);
	fputs(setting, fp);
	g_free(setting);
    }
    if( has_value(dst_server) )
    {
	setting = g_strdup_printf("dst_server %s\n", dst_server);
	fputs(setting, fp);
	g_free(setting);
    }    
    if( has_value(dst_path) )
    {
	setting = g_strdup_printf("dst_path %s\n", dst_path);
	fputs(setting, fp);
	g_free(setting);
    }
    if( has_value(user) )
    {
	setting = g_strdup_printf("server_user %s\n", user);
	fputs(setting, fp);
	g_free(setting);
    }    
    if( has_value(port) )
    {
	setting = g_strdup_printf("server_port %s\n", port);
	fputs(setting, fp);
	g_free(setting);
    }
    if( has_value(priv_key_path) )
    {
	setting = g_strdup_printf("priv_key_path %s\n", priv_key_path);
	fputs(setting, fp);
	g_free(setting);
    }
    if( has_value(include_files) )
    {
	setting = g_strdup_printf("include_files %s\n", include_files);
	fputs(setting, fp);
	g_free(setting);
    }
    if( has_value(exclude_files) )
    {
	setting = g_strdup_printf("exclude_files %s\n", exclude_files);
	fputs(setting, fp);
	g_free(setting);
    }
    setting = g_strdup_printf("include_value %d\n", include_in_backup_script);
    fputs(setting, fp);
    g_free(setting);

    setting = g_strdup_printf("del_dst_files_value %d\n\n", del_dst_not_in_src);
    fputs(setting, fp);
    g_free(setting);

    /* Close the settings file */
    fclose(fp);

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


    /* Return false if this backup should not be written
       to the backup script. Iteration will continue. */
    if( ! include_in_backup_script )
    {
	free_values(src_server, src_path, dst_server, 
                 dst_path, user, port, priv_key_path, 
                         include_files, exclude_files);
	return FALSE;
    }


    /* Open the backup script file for appending */
    script_file = mk_backup_script_path(global_backup_name);
    if((fp=fopen(script_file, "a"))==NULL)
    {
	info = g_strdup_printf(_("Error: Can not write backup script here:\n%s\n"), script_file);
        show_info(info);
        g_free(info);

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

	free_values(src_server, src_path, dst_server, 
                 dst_path, user, port, priv_key_path, 
                         include_files, exclude_files);
        return TRUE; /* Stop iterating on write failure */
    }



    /* Create the log file path */
    log_file = mk_log_path(global_backup_name);



/* --- Local to Local backup --- */
    if( ! has_value(src_server) && ! has_value(dst_server) )
    {
	/* rsync -av -e /src /dst */

	/* Start timestamp */
	fputs("START_TIME=`date +%Y-%m-%d_%H:%M:%S`;\n", fp);

	/* Only log if theres no destination path.
	   USB-disks with sleep-modes can loose contact and dismount or not mount etc */
	setting = g_strconcat("if [ ! -e '", dst_path, "' ]; then\n",
	"	MISSING_PATH=1\n",
	"	echo -n Missing_destination_path:_ >> ", log_file,"\n",
	"else\n",
	"	MISSING_PATH=0\n",
	NULL);
	fputs(setting, fp);
	g_free(setting);

	if( del_dst_not_in_src )
	  fputs("rsync --archive --human-readable --verbose --stats --delete --force", fp);
	else
	  fputs("rsync --archive --human-readable --verbose --stats", fp);

	/* Add a detailed log for each backup */
	setting = g_strdup_printf(" --log-file=%s.details", log_file);
	fputs(setting, fp);
	g_free(setting);

	if( has_value(include_files) )
	{
	    fputs(" --include=\"", fp);
	    fputs(include_files, fp);
	    fputs("\"", fp);
	}

	if( has_value(exclude_files) )
	{
	    fputs(" --exclude=\"", fp);
	    fputs(exclude_files, fp);
	    fputs("\"", fp);
	}

	fputs(" '", fp);
	fputs(src_path, fp);
	fputs("' '", fp);
	fputs(dst_path, fp);
	fputs("'", fp);
	fputs("\nfi\n\n", fp);

	/* Save source and destination */
	source = g_strdup_printf("%s", src_path);
	destination = g_strdup_printf("%s", dst_path);
    }
/* --- Local to Local backup end --- */



/* --- Remote to Local backup --- */
    if( has_value(src_server) )
    {
	/* rsync -av -e "ssh -l root -p 30000 -i /path/keys/root.192.168.0.100.key" root@192.168.0.100:/src /dest */

	/* Start timestamp */
	fputs("START_TIME=`date +%Y-%m-%d_%H:%M:%S`;\n", fp);

	setting = g_strconcat("if [ ! -e '", dst_path, "' ]; then\n",
	"	MISSING_PATH=1\n",
	"	echo -n Missing_destination_path:_ >> ", log_file,"\n",
	"else\n",
	"	MISSING_PATH=0\n",
	NULL);
	fputs(setting, fp);
	g_free(setting);

	if( del_dst_not_in_src )
	  fputs("rsync --archive --progress --human-readable --verbose --stats --delete --force", fp);
	else
	  fputs("rsync --archive --progress --human-readable --verbose --stats", fp);

	/* Add a detailed log for each backup */
	setting = g_strdup_printf(" --log-file=%s.details", log_file);
	fputs(setting, fp);
	g_free(setting);

	
	if( has_value(include_files) )
	{
	    fputs(" --include=\"", fp);
	    fputs(include_files, fp);
	    fputs("\"", fp);
	}

	if( has_value(exclude_files) )
	{
	    fputs(" --exclude=\"", fp);
	    fputs(exclude_files, fp);
	    fputs("\"", fp);
	}

	fputs(" -e \"", fp);
	fputs(SSH_BINARY, fp);
	fputs(" -l ", fp);
	fputs(user, fp);
	fputs(" -p ", fp);
	fputs(port, fp);
	fputs(" -i ", fp);
	fputs(priv_key_path, fp);
	fputs("\" ", fp);
	fputs(user, fp);
	fputs("@", fp);
	fputs(src_server, fp);
	fputs(":'", fp);
	fputs(src_path, fp);
	fputs("' '",fp);
	fputs(dst_path, fp);
	fputs("'",fp);
	fputs("\nfi\n\n", fp);

	/* For the log function */
	source = g_strdup_printf("%s%s", src_server, src_path);
	destination = g_strdup_printf("%s", dst_path);
    }
/* --- Remote to Local backup and --- */



/* --- Local to Remote backup --- */
    if( has_value(dst_server) )
    {
	/* rsync -av -e "ssh -l root -p 30000 -i /path/keys/root.192.168.0.100.key" /src root@192.168.0.100:/dst */

	/* Start timestamp */
	fputs("START_TIME=`date +%Y-%m-%d_%H:%M:%S`;\n", fp);

	setting = g_strconcat("if [ ! -e '", src_path, "' ]; then\n",
	"	MISSING_PATH=1\n",
	"	echo -n Missing_source_path:_ >> ", log_file,"\n",
	"else\n",
	"	MISSING_PATH=0\n",
	NULL);
	fputs(setting, fp);
	g_free(setting);

	if( del_dst_not_in_src )
	  fputs("rsync --archive --progress --human-readable --verbose --stats --delete --force", fp);
	else
	  fputs("rsync --archive --progress --human-readable --verbose --stats", fp);

	/* Add a detailed log for each backup */
	setting = g_strdup_printf(" --log-file=%s.details", log_file);
	fputs(setting, fp);
	g_free(setting);

	if( has_value(include_files) )
	{
	    fputs(" --include=\"", fp);
	    fputs(include_files, fp);
	    fputs("\"", fp);
	}

	if( has_value(exclude_files) )
	{
	    fputs(" --exclude=\"", fp);
	    fputs(exclude_files, fp);
	    fputs("\"", fp);
	}

	fputs(" -e \"", fp);
	fputs(SSH_BINARY, fp);
	fputs(" -l ", fp);
	fputs(user, fp);
	fputs(" -p ", fp);
	fputs(port, fp);
	fputs(" -i ", fp);
	fputs(priv_key_path, fp);
	fputs("\" '", fp);
	fputs(src_path, fp);
	fputs("' ", fp);
	fputs(user, fp);
	fputs("@", fp);
	fputs(dst_server, fp);
	fputs(":'", fp);
	fputs(dst_path, fp);
	fputs("'",fp);
	fputs("\nfi\n\n", fp);

	/* For the script file */
	source = g_strdup_printf("%s", src_path);
	destination = g_strdup_printf("%s%s", dst_server, dst_path);
    }
/* --- Local to Remote backup and --- */



    /* Append a log function in the backup script after each
       rsync line that appends to the current backups logfile */
    script = g_strconcat("\nif [ $? -eq 0 ] && [ $MISSING_PATH -eq 0 ]; then\n",
    "   STOP_TIME=`date +%Y-%m-%d_%H:%M:%S`;\n",
    "   echo \"$START_TIME $STOP_TIME Backup successful: Source: [", source, "] Destination: [", destination, "]\" >> ", log_file,
    "\nelse\n"
    "   STOP_TIME=`date +%Y-%m-%d_%H:%M:%S`;\n",
    "   echo \"$START_TIME $STOP_TIME Backup failure:    Source: [", source, "] Destination: [", destination, "]\" >> ", log_file,
    "\nfi\n\n",
    NULL);

    fputs(script, fp);
    /* Close the script file */
    fclose(fp);

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

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

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

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

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

    free_values(src_server, src_path, dst_server, 
             dst_path, user, port, priv_key_path, 
                     include_files, exclude_files);

    /* Return false to keep the foreach func going */
    return FALSE;
}


/* Saves the backup settings, backup script and
   schedules the backup via cron/crontab if specified. */
void save_backup_settings(struct w *widgets)
{
    FILE *fp;
    int i = 0;
    gchar *cmd, *info, *settings_file, *script_path, *script, *cron_line;
    char *script_days = NULL;
    G_CONST_RETURN gchar *script_month_day = NULL, *script_hour = NULL, *script_minute = NULL;

    /* Only show one error popup per error type */
    error_src_dst_info       = 1;
    error_server_server_info = 1;

    /* Get the backup name and use it as the filename */
    global_backup_name = gtk_combo_box_get_active_text(GTK_COMBO_BOX(widgets->rsync_set_combo[0]));

    if( global_backup_name == NULL || strlen(global_backup_name) < 3 )
    {
	info = g_strdup_printf(_("Error: The backup name is too short or missing.\n"));
        show_info(info);
        g_free(info);
	return;
    }

    /* Remove cron lines matching this backup from crontab */
    script_path = mk_backup_script_path(global_backup_name);
    if( script_path == NULL || strlen(script_path) < 3 )
    {
	info = g_strdup_printf(_("Error: script path too short.\n"));
        show_info(info);
        g_free(info);

        g_free(script_path);
        return;
    }
    /* Remove the cron lines */
    del_cron(script_path);


    /* Make sure we have a script file before we begin appending to it */
    if((fp=fopen(script_path, "w+"))==NULL)
    {
	info = g_strdup_printf(_("Error: Can not write the backup script here:\n%s\n"), script_path);
        show_info(info);
        g_free(info);
        g_free(script_path);
        return;
    }

    /* Fedora etc requires a lock file as: /var/lock/sybsys/ScriptName
       Otherwise "Backup at shutdown" scripts will be terminated by killall and halt scripts. */

    /* Backup at computer shutdown */
    if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->backup_at_shutdown_check_button)) )
    {
	script = g_strconcat("#!/bin/sh\n\n",
	"### Add RedHat and Fedora Lockfiles for backup at shutdown ###\n",
	"if [ -d /var/lock/subsys ]; then\n",
	"   touch /var/lock/subsys/gadmin-rsync-", global_backup_name, ".sh\n",
	"   touch /var/lock/subsys/rsync\n",
	"else\n",
	"### Add Debian and Ubuntu Lockfiles for backup at shutdown ###\n",
	"if [ -d /var/lock ]; then\n",
	"   touch /var/lock/gadmin-rsync-", global_backup_name, ".sh\n",
	"   touch /var/lock/rsync\n",
	"fi\n",
	"fi\n\n",
	NULL);
    }
    else
    {
	script = g_strconcat("#!/bin/sh\n\n",
	NULL);
    }
    fputs(script, fp);
    fclose(fp);

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

    /* Chmod the script file to 755 so cron can run it. */
    cmd = g_strdup_printf("chmod 755 \"%s\"", script_path);
    if( ! run_command(cmd) )
    {
	info = g_strdup_printf(_("Error: Can not make the backup script executable.\n"));
    	show_info(info);
    	g_free(info);
    	/* Dont return */
    }
    if( cmd!=NULL )
      g_free(cmd);


    /* Create an an empty settings file before we begin appending to it */
    settings_file = g_strdup_printf("%s/%s", global_settings_dir, global_backup_name);
    if((fp=fopen(settings_file, "w+"))==NULL)
    {
	info = g_strdup_printf(_("Error: Can not write backup settings here:\n%s\n"), settings_file);
        show_info(info);
        g_free(info);
        g_free(settings_file);
        g_free(script_path);
        return;
    }
    fclose(fp);


    /* Iterate the treeview and write the settings and script files */
    gtk_tree_model_foreach(GTK_TREE_MODEL(widgets->backup_store), (GtkTreeModelForeachFunc) save_foreach, widgets);


    /* Backup at computer shutdown */
    if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->backup_at_shutdown_check_button)) )
    {
	/* Append lock file removal to the end of the backup script. */
	if((fp=fopen(script_path, "a"))==NULL)
	{
	    info = g_strdup_printf(_("Error: Can not write the backup script here:\n%s\n"), script_path);
	    show_info(info);
	    g_free(info);
	    g_free(script_path);
	    return;
	}
	script = g_strconcat("#!/bin/sh\n\n",
	"### Remove RedHat and Fedora Lockfiles for backup at shutdown ###\n",
	"if [ -f /var/lock/subsys/gadmin-rsync-", global_backup_name, ".sh ]; then\n",
	"   rm -f /var/lock/subsys/gadmin-rsync-", global_backup_name, ".sh\n",
	"   rm -f /var/lock/subsys/rsync\n",
	"fi\n",
	"### Remove Debian and Ubuntu Lockfiles for backup at shutdown ###\n",
	"if [ -f /var/lock/gadmin-rsync-", global_backup_name, ".sh ]; then\n",
	"   rm -f /var/lock/gadmin-rsync-", global_backup_name, ".sh\n",
	"   rm -f /var/lock/rsync\n",
	"fi\n\n",
	NULL);

	fputs(script, fp);
	fclose(fp);
	if( script!=NULL )
	  g_free(script);
    }

    g_free(script_path);


    /* Append shedule_month, schedule_days and schedule_time values last in the settings file */
    if((fp=fopen(settings_file, "a"))==NULL)
    {
	info = g_strdup_printf(_("Error: Can not write backup schedule here:\n%s\n"), settings_file);
        show_info(info);
        g_free(info);
        g_free(settings_file);
        return;
    }

    /* Backup at computer shutdown */
    if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->backup_at_shutdown_check_button)) )
    {
        fputs("schedule_backup_at_computer_shutdown: yes\n", fp);

	/* Write a "Backup at shutdown" script in the SYSTEM_SHUTDOWN_DIR */
	add_to_system_shutdown(global_backup_name);
	
    }
    else
    {
	/* Remove this shutdown at backup script if it exists */
	remove_from_system_shutdown(global_backup_name);
    }


    /* Return if scheduling is disabled */
    if( ! gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->schedule_check_button[0])) )
    {
	/* The cron line has already been removed */
	g_free(settings_file);
	
	fclose(fp);
	return;
    }

    script_month_day = gtk_entry_get_text(GTK_ENTRY(widgets->schedule_spin_button[0]));
    /* Run every month is selected */
    if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->schedule_check_button[1])) )
    {
        fputs("schedule_every_month_day\n", fp);
        script_month_day = g_strdup_printf("%s", "*");
    }
    else
    {
	fputs("schedule_month_day ", fp);
        fputs(script_month_day, fp);
	fputs("\n", fp);
    }

    fputs("schedule_days ", fp);
    script_days = allocate(1024);
    for(i=2; i<9; i++)
    {
	if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->schedule_check_button[i])) )
	{    
	    fputs("1", fp);

	    if( i == 2 )
	      strcat(script_days, "1,");
	    if( i == 3 )
	      strcat(script_days, "2,");
	    if( i == 4 )
	      strcat(script_days, "3,");
	    if( i == 5 )
	      strcat(script_days, "4,");
	    if( i == 6 )
	      strcat(script_days, "5,");
	    if( i == 7 )
	      strcat(script_days, "6,");
	    if( i == 8 )
	      strcat(script_days, "7");
	}
	else
	  fputs("0", fp);
    }
    fputs("\n", fp);

    /* If there are no script days, add a single '*' for any day */
    if( script_days == NULL || strlen(script_days) < 1 )
      snprintf(script_days, 2, "%s", "*");

    /* Remove single commas ',' at the end of script days */
    if( script_days[strlen(script_days)-1]==',' )
      script_days[strlen(script_days)-1]='\0';


    /* Append schedule for hour and minute in the settings file */
    script_hour   = gtk_entry_get_text(GTK_ENTRY(widgets->schedule_spin_button[1]));
    script_minute = gtk_entry_get_text(GTK_ENTRY(widgets->schedule_spin_button[2]));

    fputs("schedule_time ", fp);
    fputs(script_hour, fp);
    fputs(":", fp);
    fputs(script_minute, fp);
    fputs("\n", fp);

    /* Run every hour */
    if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->schedule_check_button[9])) )
    {
        fputs("schedule_every_hour\n", fp);
        script_hour = g_strdup_printf("%s", "*");
    }
    /* Run every minute */
    if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->schedule_check_button[10])) )
    {
        fputs("schedule_every_minute\n", fp);
        script_minute = g_strdup_printf("%s", "*");
    }

    /* Close the settings file */
    fclose(fp);
    g_free(settings_file);

    /* Schedule this backup in crontab */
    cron_line = g_strdup_printf("%s %s %s * %s root", script_minute, script_hour, script_month_day, script_days);
    free(script_days);

    /* Removes the old cron line, adds a new one and HUP's crond */
    schedule_cron(cron_line, global_backup_name);

    g_free(cron_line);
}
