#!/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 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 mygtk2 qw(gtknew); #- do not import gtkadd which conflicts with ugtk2 version
use ugtk2 qw(:create :dialogs :helpers :wrappers);
use Gtk2::SimpleList;
use interactive;

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

######### read config

my $product_id = common::parse_LDAP_namespace_structure(cat_("$root/etc/product.id"));

my @ordered_intervals = qw(hourly daily weekly monthly);

my ($backup_list, $exclude_list) = map { 
    my $key = $_; 
    my $list = Gtk2::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 %default_intervals = (
    map { 
        if (my ($type, $interval) = /^interval\s*(\S*)\s*(\S*)/) {
            $type => $interval;
        }
    } grep { /^interval\s/ } cat_($config_file)
);

# initialize commented out fields:    
$default_intervals{$_} ||= undef foreach @ordered_intervals;


######### GUI

$ugtk2::wm_icon = "draksnapshot-big";
my $my_win = ugtk2->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 ? create_factory_menu($my_win->{rwindow}, @menu_items) : undef;
######### menus end


my %interval_titles = (
    'hourly' => N("Hourly snapshots"),
    'daily' => N("Daily snapshots"),
    'weekly' => N("Weekly snapshots"),
    'monthly' => N("Monthly snapshots"),
);


my (%entries, $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) {
        my @discs = map { $_->GetProperty('volume.mount_point') } eval { find_removable_volumes($dbus) };
        $backup_directory = $discs[0];
    }
}

my $dialog = ugtk2->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, Gtk2::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;
                             },
                         ),
               ]),

               if_(0, 0, gtknew('Title2', label => N("Number of snapshots to keep stored")),
                   0, gtknew('Table', col_spacings => 10, row_spacings => 5, homogeneous => 1, children => [
                       map { 
                           [ 
                               gtknew('Label_Left', text => $interval_titles{$_}),
                               $entries{$_} = gtknew('SpinButton', value => $default_intervals{$_}, lower => 0, upper => 100,
                                                     tip => N("The number of snapshots for this type of interval (\"%s\") that will be stored", $_)
                                                 )
                           ];
                       } @ordered_intervals
                   ])),
               0, gtknew('HButtonBox', layout => 'start', border_width => 5, spacing => 5, children_loose => [
                   $button = gtknew($product_id->{version} =~ /200[6-8]/ ? 'Button' : '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("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(Gtk2::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() { ugtk2->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 = Gtk2::FileSelection->new(N("Path selection"));
                                $file_dlg->set_modal(1);
                                $file_dlg->set_transient_for($dlg);
                                $file_dlg->show;
                                $file_dlg->set_filename($file->get_text);
                                $file_dlg->cancel_button->signal_connect(clicked => sub { $file_dlg->destroy });
                                $file_dlg->ok_button->signal_connect(clicked => sub {
                                                                         $file->set_text($file_dlg->get_filename);
                                                                         $file_dlg->destroy;
                                                                     });
                            });

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

    #$dlg->set_has_separator(0);

    gtkadd($dlg->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('interval', map {
        my $val = $entries{$_}->get_text;
        if_($val, join("\t", 'interval', $_, $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 save_keyword {
    my ($keyword, @values) = @_;
    my ($removed, $done);
    my $new_val = join('', map { "$_\n" } @values);
    substInFile {
        if (/^$keyword/ || /^#$keyword/ && !$removed) {
            undef $_;
            $removed++;
        }
        if ($removed == 1 && !$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
));
    }
}
