package Applications::BackupAndRestore;
use strict;
use warnings;

our $VERSION = 0.01;
our $DEBUG   = 1;

#use AutoSplit; autosplit('../App/BackupAndRestore', '../auto/', 0, 1, 1);

use Glib qw(TRUE FALSE);
use Gtk2;
use Gtk2::GladeXML;
use Gtk2::Gdk::Keysyms;
use Gnome2::GConf;

use Cwd qw(abs_path);
use File::Basename qw(basename dirname);
use Number::Bytes::Human qw( format_bytes );
use POSIX qw(strftime);
use Unicode::UTF8simple;

use Gtk2::Ex::FileLocator::RecycleButton;
use Applications::BackupAndRestore::Helper;

=Globals 		 																						
																											
=cut																										

our $TarOpenCmd = "file-roller";

my $ApplicationName = 'BackupAndRestore';

my $CurrentDat   = "current.dat";
my $ProcessDat   = "process.dat";
my $ExcludesFile = "excludes.txt";

my @ColumnTypes = qw(
  Glib::String
  Glib::String
  Glib::String
  Glib::String
  Glib::UInt
  Glib::String
  Glib::UInt
  Glib::String
  Glib::String
  Glib::Boolean
  Glib::UInt
);

use enum qw(
  COL_DATE
  COL_HDATE
  COL_TIME
  COL_NAME
  COL_SIZE
  COL_HSIZE
  COL_FILES
  COL_LABEL
  COL_PATH
  COL_CURRENT
  COL_WEIGHT
);
use enum qw(
  EXCLUDE_FOLDER
  EXCLUDE_FILE
  EXCLUDE_PATTERN
);

=AUTOLOAD		 																						
																											
=cut																										

use AutoLoader;
our $AUTOLOAD;

sub AUTOLOAD {
   my $this = shift;
   my $name = substr $AUTOLOAD, rindex( $AUTOLOAD, ':' ) + 1;

   #printf "%s\n", $name if $DEBUG > 3;
   my $widget = $this->{gladexml}->get_widget($name);
   return $widget if ref $widget;
   die "AUTOLOAD: Unknown widget '$name'";
}

=new				 																						
																											
=cut																										

sub new {
   my ($self) = @_;
   my $class = ref($self) || $self;
   my $this = bless {}, $class;

   printf "%s\n", dirname abs_path $0 if $DEBUG > 3;

   chdir dirname abs_path $0 if -f $0;

   $this->{client} = Gnome2::GConf::Client->get_default;

   $this->{gladexml} = Gtk2::GladeXML->new("../bin/$ApplicationName.glade");
   $this->{gladexml}->signal_autoconnect_from_package($this);

   $this->init;

   return $this;
}

=run						 																				
																											
=cut																										

sub run {
   Gtk2->init;
   my $class = shift;
   my $this  = $class->new(@_);
   $this->window->present;
   Gtk2->main;
   return;
}

=gconf					 																				
																											
=cut																										

sub gconf {
   my ( $this, $key, $value ) = @_;
   my $app_key = "/apps/" . $ApplicationName . "/$key";

   $this->{client}->set( $app_key, { type => 'string', 'value' => $value } )
     if defined $value;

   return $this->{client}->get_string($app_key);
}

=gui init				 																				
																											
=cut																										

sub init {
   my ($this) = @_;
   print "init $this\n" if $DEBUG > 3;

   # GUI init
   #$this->gconf( 'store-folder', '' );
   #$this->gconf( 'store-folder-name', '' );

   $this->exclude_combo->set_active(0);    # Gtk2::GladeXML macht es nicht

   my $button;
   $this->{folder_recycle_button} = new Gtk2::Ex::FileLocator::RecycleButton;
   $this->{folder_recycle_button}->show;
   $this->folder_box->pack_start( $this->{folder_recycle_button},
      FALSE, FALSE, 0 );
   $this->{folder_recycle_button}->signal_connect( 'current_folder_changed',
      sub { $this->on_folder_recycle_button(@_) } );

   #configure;
   $this->store_folder->set_current_folder( $this->gconf("store-folder")
        || $ENV{HOME} );
   $this->store_folder_name->set_text( $this->gconf("store-folder-name")
        || "Backup" );
   $this->folder->set_current_folder( $this->gconf("current-backup-folder")
        || $ENV{HOME} );

   $this->configure_expander;
   $this->build_tree;

   $this->log_init;
   $this->log_add_text( "*** $ApplicationName\n", "Version: $VERSION\n", "\n",
   );
   $this->log_add_text( $this->get_tar_version );
}

=build_tree				 																				
																											
=cut																										

sub build_tree {
   my ($this) = @_;
   print "build_tree\n" if $DEBUG > 3;

   #this will create a treeview, specify $tree_store as its model
   my $tree_view = $this->tree_view;

   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   my $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Time" );
   $tree_column->set_visible(FALSE);

   #create a renderer
   my $renderer = Gtk2::CellRendererText->new;
   $tree_column->pack_start( $renderer, FALSE );
   $tree_column->add_attribute( $renderer, text => COL_TIME );

   #$tree_column->set_sort_column_id(COL_TIME);

   #add $tree_column to the treeview
   $tree_view->append_column($tree_column);

   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Date" );

   #create a renderer
   $renderer = Gtk2::CellRendererPixbuf->new;
   $renderer->set( 'icon-name' => 'tgz' );
   $tree_column->pack_start( $renderer, FALSE );

   #create a renderer
   $renderer = Gtk2::CellRendererText->new;
   $tree_column->pack_start( $renderer, FALSE );
   $tree_column->add_attribute( $renderer, text       => COL_NAME );
   $tree_column->add_attribute( $renderer, weight_set => COL_CURRENT );
   $tree_column->add_attribute( $renderer, weight     => COL_WEIGHT );

   #$tree_column->set_sort_column_id(COL_TIME);

   #add $tree_column to the treeview
   $tree_view->append_column($tree_column);

   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Changed files" );
   $tree_column->set_visible(TRUE);

   #create a renderer
   $renderer = Gtk2::CellRendererText->new;
   $tree_column->pack_start( $renderer, FALSE );
   $tree_column->add_attribute( $renderer, text       => COL_FILES );
   $tree_column->add_attribute( $renderer, weight_set => COL_CURRENT );
   $tree_column->add_attribute( $renderer, weight     => COL_WEIGHT );

   #$tree_column->set_sort_column_id(COL_PATH);

   #add $tree_column to the treeviewGtk2::CellRenderer
   $tree_view->append_column($tree_column);

   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Size" );

   #create a renderer
   $renderer = Gtk2::CellRendererText->new;
   $tree_column->pack_start( $renderer, FALSE );
   $tree_column->add_attribute( $renderer, text       => COL_HSIZE );
   $tree_column->add_attribute( $renderer, weight_set => COL_CURRENT );
   $tree_column->add_attribute( $renderer, weight     => COL_WEIGHT );

   #$tree_column->set_sort_column_id(COL_SIZE);

   #add $tree_column to the treeview
   $tree_view->append_column($tree_column);

   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Size in bytes" );
   $tree_column->set_visible(FALSE);

   #create a renderer
   $renderer = Gtk2::CellRendererText->new;
   $tree_column->pack_start( $renderer, FALSE );
   $tree_column->add_attribute( $renderer, text => COL_SIZE );

   #$tree_column->set_sort_column_id(COL_SIZE);

   #add $tree_column to the treeviewGtk2::CellRenderer
   $tree_view->append_column($tree_column);

   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Label" );

   #$tree_column->set_visible(FALSE);

   #create a renderer
   $renderer = Gtk2::CellRendererText->new;
   $tree_column->pack_start( $renderer, FALSE );
   $tree_column->add_attribute( $renderer, text       => COL_LABEL );
   $tree_column->add_attribute( $renderer, weight_set => COL_CURRENT );
   $tree_column->add_attribute( $renderer, weight     => COL_WEIGHT );

   #$tree_column->set_sort_column_id(COL_PATH);

   #add $tree_column to the treeviewGtk2::CellRenderer
   $tree_view->append_column($tree_column);

   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Path" );
   $tree_column->set_visible(FALSE);

   #create a renderer
   $renderer = Gtk2::CellRendererText->new;
   $tree_column->pack_start( $renderer, FALSE );
   $tree_column->add_attribute( $renderer, text => COL_PATH );

   #$tree_column->set_sort_column_id(COL_PATH);

   #add $tree_column to the treeviewGtk2::CellRenderer
   $tree_view->append_column($tree_column);

}

=backup	 																								
																											
=cut																										

sub on_backup_folder_changed {
   my ($this) = @_;
   print "on_backup_folder_changed\n" if $DEBUG > 0;
   $this->gconf( "current-backup-folder", $this->folder->get_filename );
   $this->restore_folder->set_current_folder( $this->folder->get_filename );
   $this->fill_tree;
   return;
}

sub on_folder_recycle_button {
   my ($this) = @_;
   printf " on_folder_recycle_button %s\n",
     $this->{folder_recycle_button}->get_current_folder
     if $DEBUG > 0;
   $this->folder->set_current_folder(
      $this->{folder_recycle_button}->get_current_folder );

   #$this->folder->set_current_folder( shift(@folders) or $ENV{HOME} );
   return;
}

=tree 	 																								
																											
=cut																										

use Tie::DataDumper;

sub fill_tree {
   my ($this) = @_;

   #$this->window->set_sensitive(FALSE);
   #Gtk2->main_iteration while Gtk2->events_pending;

   $this->restore_button->set_sensitive(FALSE);

   #fill it with arbitry data

   my $folder = $this->get_store_folder;
   printf "fill_tree %s\n", $folder if $DEBUG > 3;

   my $tree_store = Gtk2::TreeStore->new(@ColumnTypes);

   if ( -e $folder ) {
      my ( $day_iter, $day, $day_folder_size, $day_folder_files ) =
        ( undef, "", 0, 0 );
      my $current_dat = "$folder/$CurrentDat";

      my $date_of_last_restore = $this->fetch_restore_date($folder);

      #printf "%s\n", $date_of_last_restore;

      my @filenames = reverse grep { m/\.tar\.bz2$/ } get_files($folder);

      # initialize label of first full backup if {first}.info.txt not exists
      if (@filenames) {
         my $filename = $filenames[ @filenames - 1 ];
         my $infofile =
           "$folder/" . basename( $filename, ".tar.bz2" ) . ".info.txt";
         my $info = $this->get_backup_info( $filename, $infofile );
         $info->{label} = 'Full backup';
         tied(%$info)->save;
      }

      foreach my $filename (@filenames) {

         # get basename
         my $basename = basename( $filename, ".tar.bz2" );

         # calculate size
         my $tardat = "$folder/$basename.dat.bz2";
         my $size = ( -s $filename ) + ( -s $tardat );
         $size += -s $current_dat unless $day;

         ################################################################
         my $infofile = "$folder/$basename.info.txt";
         my $info = $this->get_backup_info( $filename, $infofile );
         ################################################################

         # append day folder
         my ( $date, $time ) = split / /o, $basename;
         if ( $date ne $day ) {
            $tree_store->set( $day_iter, COL_SIZE, $day_folder_size, COL_HSIZE,
               format_bytes($day_folder_size),
               COL_FILES, $day_folder_files, )
              if ref $day_iter;

            $day      = $date;
            $day_iter = $tree_store->append(undef);
            $tree_store->set(
               $day_iter,          COL_DATE,
               $date,              COL_HDATE,
               format_date($date), COL_TIME,
               $time,              COL_NAME,
               format_date($date), COL_SIZE,
               $day_folder_size,   COL_FILES,
               $day_folder_files,  COL_PATH,
               $filename,          COL_LABEL,
               "",                 COL_CURRENT,
               FALSE,              COL_WEIGHT,
               600,
            );

            $day_folder_size  = 0;
            $day_folder_files = 0;
         }

         #printf "%s\n", $tardat unless -s $tardat if $DEBUG > 3;
         $day_folder_size  += $size;
         $day_folder_files += scalar @{ $info->{files} };

         # day-time column
         my $iter = $tree_store->append($day_iter);
         $tree_store->set(
            $iter, COL_DATE, $date,
            COL_HDATE,
            format_date($date),
            COL_TIME, $time, COL_NAME, $time, COL_SIZE, $size,
            COL_HSIZE,
            format_bytes($size),
            COL_FILES,
            scalar @{ $info->{files} },
            COL_LABEL,
            __("$info->{label}") 
              . ( "$info->{label}" ? ", " : "" )
              . (
               $date_of_last_restore eq $basename
               ? __("currently restored")
               : ""
              ),
            COL_PATH,
            $filename,
            COL_CURRENT,
            $date_of_last_restore eq $basename,
            COL_WEIGHT,
            800,
         );
      }

      # append last day folder
      $tree_store->set( $day_iter, COL_SIZE, $day_folder_size, COL_HSIZE,
         format_bytes($day_folder_size),
         COL_FILES, $day_folder_files, )
        if ref $day_iter;
   }

   $this->size_all->set_text( format_bytes( folder_size($folder) ) );

   #this will create a treeview, specify $tree_store as its model
   $this->tree_view->set_model($tree_store);
   $this->exclude_configure;

   #$this->window->set_sensitive(TRUE);
}

sub get_backup_info {
   my ( $this, $filename, $infoname ) = @_;

   tie my %info, 'Tie::DataDumper', $infoname
     or warn "Problem tying %info: $!";

   $info{files} = [ $this->get_changed_files($filename) ]
     unless exists $info{files};
   $info{label} = '' unless exists $info{label};

   return \%info;
}

sub get_changed_files {
   my ( $this, $filename ) = @_;
   my $cmd = qq{ env LANG=en_GB.utf8 nice --adjustment=17 \\
			 env LANG=en_GB.utf8 tar --list \\
				 --file "$filename" \\
				 | nice --adjustment=17 grep -E "[^//]\$"
			};
   printf "cmd %s\n", $cmd if $DEBUG > 0;

   my @changed_files = `$cmd`;
   chomp @changed_files;

   printf "changed_files %s\n", scalar @changed_files if $DEBUG > 3;
   return @changed_files;
}

sub get_store_folder {
   my ($this) = @_;
   return sprintf "%s%s", $this->get_main_store_folder || "",
     $this->folder->get_filename || "";
}

sub on_tree_view_button_press_event {
   my ( $this, $widget, $event ) = @_;

#print "on_tree_view_button_press_event $this, $widget", $event->type, "\n" if $DEBUG > 3;

   $this->restore_button->set_sensitive(TRUE);
   $this->{tree_view_2button_press} = $event->type eq "2button-press";

   return;
}

sub on_tree_view_button_release_event {
   my ( $this, $widget, $event ) = @_;

#print "on_tree_view_button_release_event $this, $widget", $this->{tree_view_2button_press}, "\n" if $DEBUG > 3;

   my $selected = $this->tree_view->get_selection->get_selected;

   if ( ref $selected ) {
      my ( $hdate, $time ) =
        $this->tree_view->get_model->get( $selected, COL_HDATE, COL_TIME );
      $this->restore_backup_from_label->set_text("$hdate $time");

      if ( $this->{tree_view_2button_press} ) {
         my $path = $this->tree_view->get_model->get( $selected, COL_PATH );

         printf "*** %s\n", $path if $DEBUG > 3;

         system $TarOpenCmd, $path;
      }
   }

   return;
}

=backup 	 																								
																											
=cut																										

sub on_backup_button_clicked {
   my ($this) = @_;
   print "on_backup_button_clicked $this\n" if $DEBUG > 3;

   $this->window->set_sensitive(FALSE);
   $this->backup_changed_files_label->set_text(0);
   $this->backup_folders_label->set_text(0);
   $this->backup_elapsed_time_label->set_text( sprintf "%s", strtime(0) );
   $this->backup_estimated_time_label->set_text( sprintf "%s / %s",
      map { strtime(0) } ( 0, 0 ) );
   $this->backup_file_label->set_text("");
   $this->backup_progress(0);
   $this->backup_dialog->present;

   $this->backup_folder;

   $this->{folder_recycle_button}->add_filename( $this->folder->get_filename );
   $this->fill_tree;

   $this->backup_dialog->hide;
   $this->window->set_sensitive(TRUE);
   return;
}

sub rmdir_p {
   my ($folder) = @_;
   while ( rmdir $folder ) {
      $folder = dirname $folder;
   }
   return;
}

my @SIGS = qw(KILL HUP TERM INT);

sub backup_folder {
   my ($this) = @_;

   #$this->{backup_folder} = TRUE;

   my $date = strftime( "%F %X", localtime );

   $this->log_add_text( sprintf "\n%s\n", "*" x 42 );
   $this->log_add_text( sprintf __("%s starting backup . . .\n"), $date );

   $this->backup_progress(0);
   $this->backup_dialog->{startTime} = time;

   my $folder    = $this->folder->get_filename;
   my $store     = $this->get_store_folder;
   my $mainstore = $this->get_main_store_folder;

   my $current_dat = "$store/$CurrentDat";
   my $process_dat = "$store/$ProcessDat";
   my $archive     = "$store/$date.tar.bz2";
   my $tardat      = "$store/$date.dat.bz2";
   my $excludes    = "$store/$ExcludesFile";

   $this->{cleanup} = sub {
      print "cleanup @_\n" if $DEBUG > 3;
      unlink $tardat;
      unlink $process_dat;
      unlink $archive;
      unless ( -s $current_dat ) {
         unlink $current_dat;
         unlink $excludes;
         rmdir_p $store;
      }
      exit if @_;
   };

   $SIG{$_} = $this->{cleanup} foreach @SIGS;

   system "mkdir", "-p", $store;
   system "cp", $current_dat, $process_dat if -e $current_dat;

   system "touch", $current_dat;
   $this->save_excludes;

   my $cmd = qq{ env LANG=en_GB.utf8 nice --adjustment=17 \\
	 env LANG=en_GB.utf8 tar --create \\
		 --verbose \\
		 --directory "$folder" \\
		 --file "$archive" \\
		 --listed-incremental "$process_dat" \\
		 --preserve-permissions \\
		 --ignore-failed-read \\
		 --exclude "$mainstore" \\
		 --exclude-from "$excludes" \\
		 --bzip2 \\
		 ./ 2>&1 \\
	};

   printf "cmd %s\n", $cmd if $DEBUG > 3;
   die $! unless $this->{tarpid} = open TAR, "-|", $cmd;

   my $size       = 1;
   my $files      = {};
   my $total_size = 1;
   my $folders    = 0;
   my $utf8       = Unicode::UTF8simple->new;
   while (<TAR>) {

      #print $_ if $DEBUG > 3;
      #last unless $this->{backup_folder};
      chomp;
      $_ = $utf8->fromUTF8( "iso-8859-1", $_ );

      if (s|^\./||o) {
         my $path = "$folder/$_";

         if ( -d $path ) {
            $this->backup_folders_label->set_text( ++$folders );
            Gtk2->main_iteration while Gtk2->events_pending;
         }
         else {
            unless ( exists $files->{$path} ) {
               $total_size += $files->{$path} = ( -s $path ) || 0;
               $this->backup_changed_files_label->set_text(
                  sprintf "%d [%sB]",
                  scalar keys %$files,
                  format_bytes($total_size)
               );
            }

            $files->{$path} = 0
              unless defined $files->{$path};    # wegen: -s link = 0

            my @times =
              map { strtime($_) }
              estimated_time( $this->backup_dialog->{startTime},
               $size, $total_size );

            $this->backup_elapsed_time_label->set_text( sprintf "%s",
               $times[0] );
            $this->backup_estimated_time_label->set_text( sprintf "%s / %s",
               @times[ 1, 2 ] );
            $this->backup_file_label->set_text( sprintf "%s [%sB]",
               $path, format_bytes( $files->{$path} ) );

            $this->backup_progress( $size / $total_size );

            $size += $files->{$path};
         }

      }
      elsif ( m|^tar: \./(.*?): Directory is new$|o
         || m|^tar: \./(.*?): Directory has been renamed from.*$|o )
      {

         #print "$1\n" if $DEBUG > 3;
         $files->{$_} = -s $_ foreach get_files("$folder/$1");
         $total_size += folder_size("$folder/$1");
         $this->backup_changed_files_label->set_text(
            sprintf "%d [%sB]",
            scalar keys %$files,
            format_bytes($total_size)
         );
         Gtk2->main_iteration while Gtk2->events_pending;
      }
      elsif (/^(tar: )?(Terminated|Killed|Hangup)$/o) {
         print "$_\n" if $DEBUG > 3;
      }
      else {
         print "$_\n" if $DEBUG > 3;
      }
   }
   close TAR;

   printf "tar returned %s\n", $? if $DEBUG > 3;

   if ($?) {    # cancel / erroor ...
      &{ $this->{cleanup} }();
   }
   else {       # everything is fine
      my $retval = system qq{ nice --adjustment=17 \\
			bzip2 -c9 "$process_dat" >  "$tardat" \\
		};
      printf "bzip2 returned %s\n", $retval if $DEBUG > 3;

      $SIG{$_} = 'IGNORE' foreach @SIGS;

      system "cp", $process_dat, $current_dat;
      unlink $process_dat;

      #store date
      $this->store_restore_date($archive);
   }

   delete $SIG{$_} foreach @SIGS;

   #$this->{backup_folder} = FALSE;
   $this->{tarpid} = 0;

   if ( $this->backup_dialog->{startTime} ) {
      $this->log_add_text( sprintf __("Changed files: %d\n"),
         scalar keys %$files );
      $this->log_add_text( sprintf __("Folders: %d\n"), $folders );
      $this->log_add_text( sprintf __("Totol size: %s\n"),
         format_bytes($total_size) );
      $this->log_add_text( sprintf __("Totol time: %s\n"),
         strtime( localtime( time - $this->backup_dialog->{startTime} ) ) );
   }

   $this->log_add_text( sprintf __("%s backup done.\n"),
      strftime( "%F %X", localtime ) )
     if $this->backup_dialog->{startTime};
}

sub backup_progress {
   my ( $this, $fraction ) = @_;

   $this->backup_progressbar->set_fraction($fraction);
   $this->backup_progressbar->set_text( sprintf "%.2f %%", $fraction * 100 );

#$this->backup_dialog->set_title( sprintf "Backup in progress %.2f %%", $fraction * 100 );

   Gtk2->main_iteration while Gtk2->events_pending;

   return;
}

sub on_cancel_backup {
   my ($this) = @_;
   printf "on_cancel_backup %s\n", $this->{tarpid} if $DEBUG > 3;
   system "pkill", "-P", $this->{tarpid};
   $this->backup_dialog->{startTime} = 0;
   $this->backup_progressbar->set_fraction(1);
   $this->backup_progressbar->set_text( __ "canceling backup ..." );

   #$this->{backup_folder} = FALSE;
   $this->log_add_text( sprintf __("%s backup canceled.\n"),
      strftime( "%F %X", localtime ) );
   return 1;
}

=exclude 																								
																											
=cut																										

sub exclude_configure {
   my ($this) = @_;

   #printf "exclude_configure %s\n", $this->get_excludes_filename if $DEBUG > 3;

   $this->exclude_clear;

   my $folder = $this->folder->get_filename || "";

   my $excludes = $this->get_excludes_filename;

   #unlink $excludes;

   if ( -e $excludes ) {
      my @excludes = `cat "$excludes"`;
      foreach (@excludes) {
         chomp;
         next unless $_;
         if ( -f "$folder/$_" ) {
            $this->exclude_add( EXCLUDE_FILE, "$folder/$_" );
         }
         elsif ( -d "$folder/$_" ) {
            $this->exclude_add( EXCLUDE_FOLDER, "$folder/$_" );
         }
         else {
            $this->exclude_add( EXCLUDE_PATTERN, $_ );
         }
      }

   }
   elsif ( $ENV{HOME} =~ /^\Q$folder/ ) {
      $this->exclude_add( EXCLUDE_PATTERN, "*.Trash*" );
      $this->exclude_add( EXCLUDE_FILE,    "$ENV{HOME}/.xsession-errors" );
   }

}

sub exclude_clear {
   my ($this) = @_;
   $this->exclude_box->remove($_) foreach $this->exclude_box->get_children;
   return;
}

sub on_exclude_add {
   my ($this) = @_;

  #printf "on_exclude_add %s\n", $this->exclude_combo->get_active if $DEBUG > 3;
   $this->exclude_add( $this->exclude_combo->get_active );
   return;
}

sub exclude_add {
   my ( $this, $index, @values ) = @_;

   #printf "exclude_add %s\n", $index if $DEBUG > 3;

   my $widget = undef;
   $widget = $this->exclude_folder_add(@values)  if $index == EXCLUDE_FOLDER;
   $widget = $this->exclude_file_add(@values)    if $index == EXCLUDE_FILE;
   $widget = $this->exclude_pattern_add(@values) if $index == EXCLUDE_PATTERN;
   return unless ref $widget;

   $this->exclude_combo->set_active($index);

   my $label =
     new Gtk2::Label( sprintf "%s:", $this->exclude_combo->get_active_text );
   my $remove_button = Gtk2::Button->new_from_stock('gtk-remove');

   my $hbox = new Gtk2::HBox( 0, 6 );
   $hbox->pack_start( $label,         FALSE, FALSE, 0 );
   $hbox->pack_start( $widget,        TRUE,  TRUE,  0 );
   $hbox->pack_start( $remove_button, FALSE, FALSE, 0 );
   $hbox->show_all;

   $remove_button->signal_connect( 'clicked',
      sub { $this->exclude_folder_remove($hbox) } );

   $this->exclude_box->add($hbox);

   $this->save_excludes;
   return;
}

sub exclude_folder_remove {
   my ( $this, $widget ) = @_;

   #printf "exclude_folder_remove %d\n", $widget if $DEBUG > 3;
   $this->exclude_box->remove($widget);
   $this->save_excludes;
   return;
}

sub exclude_folder_add {
   my ( $this, $folder ) = @_;

#printf "exclude_folder_add %s\n", $folder || $this->folder->get_filename if $DEBUG > 3;
   my $widget =
     new Gtk2::FileChooserButton( __("Select folder"), 'select-folder' );

   $widget->set_current_folder( $folder || $this->folder->get_filename );
   $widget->{pattern} = $folder || $this->folder->get_filename;

   $widget->signal_connect(
      'current-folder-changed',
      sub {
         $widget->{pattern} = $widget->get_filename;
         $this->save_excludes;
      }
   );

   return $widget;
}

sub exclude_file_add {
   my ( $this, $file ) = @_;

#printf "** exclude_file_add %s\n", $file || $this->folder->get_filename if $DEBUG > 3;
   my $widget = new Gtk2::FileChooserButton( __("Select file"), 'open' );

   if ( -f $file ) {
      $widget->set_filename($file);
      $widget->{pattern} = $file;
   }
   else {
      $widget->set_current_folder( $this->folder->get_filename );
   }

   $widget->signal_connect(
      'current-folder-changed',
      sub {
         return unless $widget->get_filename;
         $widget->{pattern} = $widget->get_filename;
         $this->save_excludes;
      }
   );

   return $widget;
}

sub exclude_pattern_add {
   my ( $this, $pattern ) = @_;

   #printf "exclude_pattern_add %s\n", $pattern || "" if $DEBUG > 3;
   my $widget = new Gtk2::Entry;

   if ($pattern) {
      $widget->set_text($pattern);
      $widget->{pattern} = $pattern;
   }

   $widget->signal_connect(
      'changed',
      sub {
         $widget->{pattern} = $widget->get_text;
         $this->save_excludes;
      }
   );

   return $widget;
}

sub save_excludes {
   my ($this) = @_;
   return unless -e $this->get_store_folder . "/$CurrentDat";
   my $folder   = $this->folder->get_filename;
   my $excludes = $this->get_excludes_filename;

   #printf "save_excludes\n" if $DEBUG > 3;

   open( EXCLUDES, ">", $excludes ) || die $!;
   printf EXCLUDES "%s\n", join "\n", map { s/^\Q$folder\E\/?//; $_ }
     grep { $_ }
     map  { ( $_->get_children )[1]->{pattern} }
     $this->exclude_box->get_children;
   close EXCLUDES;
   return;
}

sub get_excludes_filename {
   my ($this)   = @_;
   my $store    = $this->get_store_folder;
   my $excludes = "$store/$ExcludesFile";
   return $excludes;
}

=restore 																								
																											
=cut																										

sub on_restore_button_clicked {
   my ($this) = @_;
   print "on_restore_button_clicked $this\n" if $DEBUG > 3;
   $this->window->set_sensitive(FALSE);
   $this->restore_dialog->show;
   return;
}

sub on_restore_dialog_cancel {
   my ( $this, $widget ) = @_;
   print "on_restore_folder_dialog_cancel $this\n" if $DEBUG > 3;
   $this->restore_dialog->hide;
   $this->window->set_sensitive(TRUE);
   return 1;
}

sub on_restore_dialog_ok {
   my ( $this, $widget ) = @_;
   $this->restore_dialog->hide;
   $this->restore_backup;
   $this->fill_tree;
   $this->window->set_sensitive(TRUE);
   return;
}

sub restore_backup {
   my ($this) = @_;

   my $restore_to_folder = $this->restore_folder->get_filename;
   my @files             = $this->get_files_to_restore;

   $this->log_add_text( sprintf "\n%s\n", "*" x 42 );
   $this->log_add_text( sprintf __("%s restore . . .\n"),
      strftime( "%F %X", localtime ) );

   my $store = $this->get_store_folder;

   # restore process.dat and old $CurrentDat
   #my $date        = basename( $files[0], ".tar.bz2" );
   #my $current_dat = "$store/$CurrentDat";
   #my $process_dat = "$store/process.dat";
   #my $archive_dat = "$store/$date.dat.bz2";
   #system "bzip2 -c -d '$archive_dat' >'$process_dat'";

   #printf "bzip2 -c -d '$archive_dat' >'$current_dat'\n";
   #system "bzip2 -c -d '$archive_dat' >'$current_dat'";
   #unlink $process_dat;

   my $utf8 = Unicode::UTF8simple->new;

   printf "***restore_backup to folder: %s\n", $restore_to_folder if $DEBUG > 3;
   foreach my $file (@files) {

      #printf "file: %s\n", $file if $DEBUG > 3;
      $this->log_add_text(
         sprintf __("restore backup from %s\n"),
         basename( $file, ".tar.bz2" )
      );

      my $cmd = qq{ env LANG=en_GB.utf8 nice --adjustment=17 \\
		 env LANG=en_GB.utf8 tar --extract \\
			--verbose \\
			--directory "$restore_to_folder" \\
			--file "$file" \\
			--preserve-permissions \\
			--listed-incremental /dev/null \\
			./ 2>&1 \\
		};

      printf "cmd %s\n", $cmd if $DEBUG > 3;
      die $! unless $this->{tarpid} = open TAR, "-|", $cmd;
      while (<TAR>) {

         #print $_ if $DEBUG > 0;
         chomp;

         #last unless $this->{backup_folder};
         #chomp;
         #$_ = $utf8->fromUTF8( "iso-8859-1", $_ );
         #Gtk2->main_iteration while Gtk2->events_pending;
      }
      close TAR;
      printf "tar returned %s\n", $? if $DEBUG > 3;

      if ($?) {    # cancel / erroor ...
      }
      else {       # everything is fine
      }

   }

   #store date
   $this->store_restore_date( $files[$#files] );

   #finish
   $this->{tarpid} = 0;
   $this->log_add_text( sprintf __("%s restore done . . .\n"),
      strftime( "%F %X", localtime ) );
}

sub get_files_to_restore {
   my ($this) = @_;

   my @files = ();

   my $selected = $this->tree_view->get_selection->get_selected;
   my $file     = $this->tree_view->get_model->get( $selected, COL_PATH );
   my $folder   = dirname $file;

   #printf "***get_files_to_restore file: %s\n", $file if $DEBUG > 3;
   #printf "***get_files_to_restore folder %s\n", $folder if $DEBUG > 3;

   foreach my $filename ( grep { m/\.tar\.bz2$/ } get_files($folder) ) {
      push @files, $filename;
      last if $filename eq $file;
   }

   return @files;
}

=schedule		 																						
																											
=cut																										

sub on_schedule_enabled_button_toggled {
   my ( $this, $widget ) = @_;
   print "on_schedule_enabled_button_toggled $this\n" if $DEBUG > 3;
   $this->time_hbox->set_sensitive( $widget->get_active );
   $this->wdays_hbox->set_sensitive( $widget->get_active );
   return;
}

=store			 																						
																											
=cut																										

sub on_store_folder_changed {
   my ($this) = @_;
   printf "on_store_folder_changed %s\n", $this->get_main_store_folder
     if $DEBUG > 3;

   my $store = $this->get_main_store_folder;

   #system "mkdir", "-p", $store;
   $this->gconf( 'store-folder',      $this->store_folder->get_filename );
   $this->gconf( 'store-folder-name', $this->store_folder_name->get_text );

   my @folders = $this->get_store_folders;
   $this->{folder_recycle_button}->add_filename($_) foreach @folders;

   $this->fill_tree;
   return;
}

sub get_main_store_folder {
   my ($this) = @_;
   return sprintf "%s/%s", $this->store_folder->get_filename,
     $this->store_folder_name->get_text;
}

sub get_store_folders {
   my ($this) = @_;
   my $store = $this->get_main_store_folder;
   return map { s/^$store//; $_; }
     grep { -e "$_/$CurrentDat" } get_all_sub_folders($store);
}

sub on_store_folder_name_key_release_event {
   my ( $this, $widget, $event ) = @_;
   if (  $event->keyval == $Gtk2::Gdk::Keysyms{KP_Enter}
      or $event->keyval == $Gtk2::Gdk::Keysyms{Return} )
   {
      printf "on_store_folder_name_changed %s\n", $event->keyval if $DEBUG > 3;
      $this->on_store_folder_changed;
   }
}

=expander		 																						
																											
=cut																										

sub configure_expander {
   my ($this) = @_;
   printf "*** configure_expander\n" if $DEBUG > 3;

   $this->exclude_expander->set_expanded( $this->gconf('exclude_expander') )
     if defined $this->gconf('exclude_expander');

   $this->schedule_expander->set_expanded( $this->gconf('schedule_expander') )
     if defined $this->gconf('schedule_expander');

   $this->store_expander->set_expanded( $this->gconf('store_expander') )
     if defined $this->gconf('store_expander');

   $this->log_expander->set_expanded( $this->gconf('log_expander') )
     if defined $this->gconf('log_expander');

   return;
}

sub on_expander_activate {
   my ( $this, $widget ) = @_;
   printf "%s, %s\n", $widget->get_name, not $widget->get_expanded ? 1 : 0
     if $DEBUG > 3;
   $this->gconf( $widget->get_name, not $widget->get_expanded ? 1 : 0 );
   return;
}

=expander nop	 																						
	disables expander																					
=cut																										

sub expander_nop {
   my ( $this, $expander ) = @_;
   $expander->set_expanded(FALSE);
   return;
}

=log	 																									
																											
=cut																										

sub log_init {
   my ($this) = @_;
   my $tview  = $this->log_textview;
   my $buffer = $tview->get_buffer();
   $this->{log_end_mark} =
     $buffer->create_mark( 'end', $buffer->get_end_iter, FALSE );
   $buffer->signal_connect( insert_text => \&on_log_insert_text, $this );
}

sub log_add_text {
   my ( $this, @text ) = @_;
   my $tview   = $this->log_textview;
   my $content = join "", @text;
   my $buffer  = $tview->get_buffer();
   $buffer->insert( $buffer->get_end_iter, $content );
   Gtk2->main_iteration while Gtk2->events_pending;
}

sub log_clear {
   my ($this) = @_;
   my $tview  = $this->log_textview;
   my $buffer = $tview->get_buffer();
   $buffer->set_text("");
}

sub on_log_insert_text {
   my $this  = pop @_;
   my $tview = $this->log_textview;
   $tview->scroll_mark_onscreen( $this->{log_end_mark} );
}

sub get_tar_version {
   my ($this) = @_;
   my $cmd = qq{ tar --version };
   return `$cmd`;
}

sub store_restore_date {
   my ( $this, $file ) = @_;
   my $store        = dirname($file);
   my $restore_date = basename( $file, ".tar.bz2" );
   my $date_txt     = "$store/date.txt";
   system "echo '$restore_date' > '$date_txt'";

   #printf "%s\n", "echo '$restore_date' > '$date_txt'";

}

sub fetch_restore_date {
   my ( $this, $store ) = @_;
   my $date_txt = "$store/date.txt";
   return "" unless -e $date_txt;
   my $date = `cat '$date_txt'`;
   chomp $date;
   printf "*** %s\n", $date;

   return $date;
}

=gtk_main_quit 																						
																											
	$this->gtk_main_quit;																			
																											
	calls Gtk2->main_quit;																			
																											
=cut																										

sub gtk_main_quit {
   my ($this) = @_;
   print "gtk_main_quit\n" if $DEBUG > 3;
   Gtk2->main_quit;
   return;
}

sub DESTROY {
   my ($this) = @_;
   return;
}

1;
__END__
