#!/usr/bin/perl
################################################################################
# Backup Configuration Tool                                                    # 
#                                                                              #
# Copyright (C) 2008 Mandriva                                                  #
#                                                                              #
# Thierry Vignaud <tvignaud at mandriva dot com>                               #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License Version 2 as            #
# published by the Free Software Foundation.                                   #
#                                                                              #
# 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.   #
################################################################################

use warnings;
use strict;

use lib qw(/usr/lib/libDrakX);
use standalone;     #- warning, standalone must be loaded very first, for 'explanations'
use common;
use interactive;
use MDV::Snapshot::Common;
use MDV::Snapshot::Hal;

# i18n: IMPORTANT: to get correct namespace (drakconf instead of libDrakX)
BEGIN { unshift @::textdomains, 'draksnapshot' }

use mygtk3 qw(gtknew); #- do not import gtkadd which conflicts with ugtk2 version
use ugtk3 qw(:create :dialogs :helpers :wrappers);
use Gtk3::SimpleList;
use interactive;

ugtk3::add_icon_path("/usr/share/draksnapshot/pixmaps/");

######### read config

my @ordered_intervals = qw(hourly daily weekly monthly);
my %default_values = (
    "hourly" => 6,
    "daily" => 7,
    "weekly" => 4,
    "monthly" => "",
);

my ($backup_list, $exclude_list) = map { 
    my $key = $_; 
    my $list = Gtk3::SimpleList->new(''  => 'text');
    # properly size when not embedded:
    $list->set_size_request($::isEmbedded ? -1 : 500, 100);
    $list->set_headers_visible(0);
    @{$list->{data}} = map { [ (split(/\s/, $_, 3))[1] ] } grep { /^$key\s/ } cat_($config_file);
    $list;
} qw(backup exclude);


my $interval_exists;
 my %default_intervals = (
     map {
         if (my ($type, $retain) = /^retain\s*(\S*)\s*(\S*)/) {
	    $interval_exists = 1;
            $type => $retain;
        }
    } grep { /^retain\s/ } cat_($config_file)
);

if ($interval_exists) {
    # If there is some interval in the config file, keep them as is
    $default_intervals{$_} ||= 0 foreach @ordered_intervals;
} else {
    # Else we had broken it (#58534) so restore defaults
    %default_intervals = %default_values;
}

######### GUI

$ugtk3::wm_icon = "draksnapshot-big";
my $my_win = ugtk3->new(N("Backup snapshots configuration"));


unless ($::isEmbedded) {
    $my_win->{window}->set_border_width(5);
    $my_win->{window}->set_default_size(540,460);
}
$my_win->{window}->signal_connect(delete_event => \&quit);


### menus definition
# the menus are not shown
# but they provides shiny shortcut like C-q
my @menu_items = (
    { path => N("/_File"), item_type => '<Branch>' },
    { path => N("/File/_Quit"), accelerator => N("<control>Q"), callback => \&quit },
    { path => N("/_Help"), item_type => '<LastBranch>' },
    { path => N("/Help/_About...") } 
);
my $_menubar = $::isEmbedded ? Gtk3::MenuBar->new($my_win->{rwindow}, @menu_items) : undef;
#my $_menubar = create_factory_menu($my_win->{rwindow}, @menu_items);
######### menus end


my $where;

# if not configured, just default where will be mounted the discs by HAL:
if (!$backup_directory || $::testing) {
    my $dbus = get_system_bus();
    if ($dbus) {#decting by dbus is broken
   #        my @discs = map { $_ } eval { find_removable_volumes($dbus) };
   #     $backup_directory = $discs[0];
    }
    if (!$backup_directory) #not defined either by common.pm, rsnapshot.conf, or hal/udisks2 (current situation); Set a dummy path to be changed by user
    {
      $backup_directory="~/not_existing_path";
    }
}

my $dialog = ugtk3::new(N("Backup snapshots configuration"));
my $d_window = $dialog->{window};
gtkadd($dialog->{rwindow},
       gtknew('VBox', children => [
           0, gtknew('Title2', label => N("Backup list")),
           1, format_list($backup_list, 1),
           0, gtknew('Title2', label => N("Exclude list")),
           1, format_list($exclude_list),
           0, gtknew('HButtonBox', layout => 'end',
                     children_tight => [ 
                         gtknew('Button', text => N("Close"), clicked => sub { $d_window->hide }),
                         ]),
       ]));

my $is_enabled = to_bool(glob("/etc/cron.*/rsnapshot"));

my ($box, $button);
gtkadd($my_win->{window},
       gtknew('VBox', children => [
           if_(!$::isEmbedded, 0, Gtk3::Banner->new('draksnapshot-big',
                                                    N("Backup snapshots configuration"))),
           0, gtknew('Title1', label => N("Settings")),
           0, gtknew('CheckButton', text => N("Enable Backups"), active_ref => \$is_enabled,
                     toggled => sub {
                         my ($w) = @_;
                         return if !$w || !$box;
                         $box->set_sensitive($w->get_active);
                     }),
           0, $box = gtknew('VBox', sensitive => $is_enabled, children => [
               0, gtknew('CheckButton', text => N("Backup the whole system"),
                         toggled => sub {
                             my ($w) = @_;
                             $button->set_sensitive(!$w->get_active);
                         }),
               0, gtknew('HBox', spacing => 5, children => [
                   0, gtknew('Label_Left', text => N("Where to backup")),
                   1, $where =
                     gtknew('Entry', text => $backup_directory),
                   0, gtknew('Button', text => N("Browse"),
                             clicked => sub {
                                 my $file_dlg;
                                 $file_dlg =
                                   gtknew('FileChooserDialog', title => N("Path selection"), action => 'select_folder',
                                          transient_for => $my_win->{real_window}, modal => 1);
                                 $file_dlg->set_filename($where->get_text);
                                 $file_dlg->show;
                                 my $answer = $file_dlg->run;
                                 if ($answer eq 'ok') {
                                     $where->set_text($file_dlg->get_filename);
                                 }
                                 $file_dlg->destroy;
                             },
                         ),
               ]),


               0, gtknew('HButtonBox', layout => 'start', border_width => 5, spacing => 5, children_loose => [
                   # does not exist before 2009.0, if you need it, you can revert to Button type if needed ( CS4 )
                   $button = gtknew('Install_Button',
                                    text => N("Advanced"), clicked => sub { $d_window->show }),
               ]),
           ]),
           0, gtknew('HButtonBox', layout => 'end', border_width => 5, spacing => 5, children_loose => [
               gtknew('Button', text => N("Restore"), clicked => \&restore),
               gtknew('Button', text => N("Apply"), clicked => \&save),
               gtknew('Button', text => $::isEmbedded ? N("Cancel") : N("Close"), clicked => sub { quit() })
           ])
       ])
   );

$my_win->{window}->set_size_request(550, -1);
$my_win->{window}->show_all;
$my_win->main;


######### callbacks & helpers

sub format_list {
    my ($list, $o_check) = @_;
    gtknew('HBox',
           children => [
               0, gtkset_size_request(Gtk3::Alignment->new(0, 0, 0, 0), 35, 1),
               1, gtknew('ScrolledWindow', child => $list),
               0, gtknew('VBox', border_width => 5, spacing => 5,
                         children_tight => [
                             # FIXME: add "up" & "down" buttons? "edit" button?
                             gtknew('Button', text => N("Add"), clicked => sub {
                                        add($list, $o_check);
                                    }),
                             gtknew('Button', text => N("Remove"), clicked => sub {
                                        my ($tree, $iter) = $list->get_selection->get_selected;
                                        return if !$iter;
                                        #my $removed_idx = $tree->get($iter, 5);
                                        $tree->remove($iter);
                                        #sensitive_buttons(0);
                                        #$modified++;
                                    }),
                         ]),
           ],
       );
}

sub quit() { ugtk3->exit(0) }


sub add {
    my ($list, $check, $o_iter) = @_;
    my $model = $list->get_model;
    my $dlg = gtknew('Dialog', transient_for => $my_win->{real_window}, title => N("Add"));
    my $browse = gtknew('Button', text => N("browse"));
    my $file  = gtknew('Entry', $o_iter ? (text => $model->get($o_iter, 1)) : ());
    my $alrd_exsts = defined $o_iter;
     
    $browse->signal_connect(clicked => sub {
                                my $file_dlg = Gtk3::FileChooserDialog->new(N("Path selection"), $dlg, 'select_folder', N("Cancel") => 'cancel', N("OK") => 'ok');
                                $file_dlg->set_modal(1);
                                $file_dlg->set_transient_for($dlg);
                                $file_dlg->show;
                                $file_dlg->set_filename($file->get_text);
                                my $answer_add=$file_dlg->run;

                                if ($answer_add eq 'cancel') {
                                    $file_dlg->destroy;
                                } else {
                                if ($answer_add eq 'ok') {
                                    $file->set_text($file_dlg->get_filename);
                                    $file_dlg->destroy;
                                }
                                                             }
                                                 });


   gtkpack_($dlg->get_content_area,
             0, gtknew('Title2', label => N("Path")),
             0, gtknew('HBox', border_width => 18, children => [
                1, $file,
                0, $browse
            ]),
       );


    #$dlg->set_has_separator(0);

    gtkadd($dlg->get_action_area,
           create_okcancel(my $w =
                             {
                                 cancel_clicked => sub { $dlg->destroy },
                                 ok_clicked => sub {
                                     my $path = $file->get_text;
                                     if ($check && $path !~ m!^/!) {
                                         err_dialog(N("Warning"), 
                                                     N("The first character of the path must be a slash (\"/\"):\n\"%s\"", $path));
                                         return 1;
                                     }
                                     # create new item if needed (that is when adding a new one) at end of list
                                     if (!$o_iter) {
                                         push @{$list->{data}}, $path;
                                     }
                                     $dlg->destroy;
                                 }
                             },
                       ),
       );
     
    $w->{ok}->set_sensitive(!$model->get($o_iter, 0)) if $alrd_exsts;
    $dlg->show_all;

}

sub save() {
    save_keyword('retain', map {
        my $val = $default_intervals{$_};
	if_($val, join("\t", 'retain', $_, $val));
    } @ordered_intervals);

    if (!$button->get('sensitive')) { # Mandriva defaults
        @{$backup_list->{data}} = '/'; #qw(/bin /boot /etc /home /lib /lib64 /opt);
        @{$exclude_list->{data}} = qw(/media /mnt /proc /sys /tmp /var/run /var/tmp *~);
    }

    my $where2snapshot = $where->get_text;

    save_keyword('backup',  map { join("\t", 'backup',  @$_[0], 'localhost/') } @{$backup_list->{data}});
    save_keyword('no_create_root',  "no_create_root\t1");
    save_keyword('exclude', map { join("\t", 'exclude', $_)                   }
                   uniq($where2snapshot, map { @$_[0] } @{$exclude_list->{data}}));
    save_keyword('snapshot_root', join("\t", 'snapshot_root', $where2snapshot));
    generate_cron_entry();
}

sub fork_exec {
    my $pid = run_program::raw({ detach => 1 }, @_);
    return $pid;
}

sub restore() {
    fork_exec('/usr/sbin/draksnapshot-restore');
}

sub save_keyword {
    my ($keyword, @values) = @_;
    my ($removed, $done);
    my $new_val = join('', map { "$_\n" } @values);
    substInFile {
        if (/^$keyword/ || /^#$keyword/ && !$removed) {
            undef $_;
            $removed++;
        }
        if ($removed && !$done) {
            $done++;
            $_ .= $new_val;
        }
    } $config_file;
    # be safe:
    append_to_file($config_file, $new_val) if !$done;
}



sub generate_cron_entry() {
    # handle upgrading from old scheme to new scheme:
    unlink("$::prefix/etc/cron.d/rsnapshot");

    my $cron_file = "$::prefix/etc/cron.\%s/rsnapshot";

    if (!$is_enabled) { # Mandriva defaults
        unlink(sprintf($cron_file, $_)) foreach qw(hourly daily weekly monthly);
        return;
    }

    foreach my $type (qw(hourly daily weekly monthly)) {
        my $file = sprintf($cron_file, $type);
        output_with_perm($file, 0755,
                         qq(#!/bin/sh
# WARNING: This file is autogenerated from /etc/rsnapshot.conf.
# WARNING: Please alter /etc/rsnapshot.conf instead of $cron_file
#          Then rerun draksnapshot-config
#
# $file: crontab fragment for rsnapshot

/usr/bin/rsnapshot $type > /dev/null
));
    }
}
