Applications-BackupAndRestore

 view release on metacpan or  search on metacpan

lib/Applications/BackupAndRestore.pm  view on Meta::CPAN

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

our $VERSION = 0.021;
our $DEBUG   = 1;

=head1 NAME

Applications::BackupAndRestore - a linux frontend for tar

=head1 DESCRIPTION

BackupAndRestore is a backup utility for making incremental backups by using GNU Tar.
Core features:

=over

=item *
Incremental backup with quick and easy restoration of files

=item *
Handels different backup locations

=item *
Full support for excluding files and folders and even file patterns (shell regex)

=item *
Handels different store locations

=item *
Log with all relevant information

=head1 REQUIREMENTS

A Linux

L<GNU Tar|http://www.gnu.org/software/tar/>

L<bzip2|http://www.bzip.org/>

A archive browser like file-roller (for now BackupAndRestore only supports this)

=head1 GUI

=head2 Backup & Restore

The Backup & Restore utility is illustrated in Figure 1-1. 

=head3 Figure 1-1. Backup & Restore

=begin html

<img src="BackupAndRestore/pod/BackupAndRestore.png"><br>

=end html

=head1

To start up Backup & Restore from a terminal window, type B<BackupAndRestore> and then press C<Enter>.

Backup & Restore has a List View where you see every single backup with time, date, changed files and the exact space required on your harddrive.

Above the list view there is a File Chooser Button where you can select a folder to backup. Position the cursor over  File Chooser Button and press the right mouse button. A pop-up menu appears. Choose a folder from the pop-up menu. Drag a folder ico...

Right hand to the File Chooser Button there is a Recycle Button. The recycle button keeps a list of folders you have saved. For example, place the cursor over the recycle button on a Backup & Restore window; then press the left mouse button to see a ...

Below the list view there is a backup button.

=head2 Backup In Progress Notification

The Backup In Progress Notification is illustrated in Figure 1-2. 

=head3 Figure 1-2.  Backup In Progress Notification

=begin html

<img src="BackupAndRestore/pod/BackupInProgressNotification.png"><br>

=end html

=head2 Restore Dialog

The Restore Dialog is illustrated in Figure 1-3. 

=head3 Figure 1-3.  Restore Dialog

=begin html

<img src="BackupAndRestore/pod/RestoreDialog.png"><br>

=end html

=head2 Restore In Progress Notification

The Restore Dialog is illustrated in Figure 1-4. 

=head3 Figure 1-4.  Restore In Progress Notification

=begin html

<img src="BackupAndRestore/pod/RestoreInProgressNotification.png"><br>

=end html

=head2 Remove Backup Dialog

The Remove Backup Dialog is illustrated in Figure 1-5. 

=head3 Figure 1-5. Remove Backup Dialog

=begin html

<img src="BackupAndRestore/pod/RemoveBackupDialog.png"><br>

=end html

=head1 Perl

=head2 Synopsis

	use Applications::BackupAndRestore -run;

	use Applications::BackupAndRestore;

=head2 Functions

=cut

lib/Applications/BackupAndRestore.pm  view on Meta::CPAN


=head1 ARRANGED BY

HOOO@cpan.org

=head1 COPYRIGHT

This is free software; you can redistribute it and/or modify it
under the same terms as L<Perl|perl> itself.

=cut

# gconf
#

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
#

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

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

   # GUI init

   $this->{init} = TRUE;

	my $WindowIcon = "../share/BackupAndRestore/BackupAndRestore.svg";
	$this->window->set_icon_from_file ($WindowIcon)
		if -e $WindowIcon;

	$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( 'selection-changed', sub { $this->on_folder_recycle_button(@_) } );

   #configure;
   $this->restore_extract_modifiction_time->set_active(
      defined $this->gconf("restore-extract-modification-time")
      ? $this->gconf("restore-extract-modification-time")
      : 1
   );
   $this->restore_folder->set_current_folder( $this->gconf("restore-folder") || $ENV{HOME} );
   $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 );

   $this->{init} = FALSE;
   $this->fill_tree;

   print "init done $this\n" if $DEBUG > 0;
}

# build_tree
#

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

   #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_LAST_BACKUP );

lib/Applications/BackupAndRestore.pm  view on Meta::CPAN


   #

   #create a Gtk2::TreeViewColumn to add
   #to $tree_view
   $tree_column = Gtk2::TreeViewColumn->new();
   $tree_column->set_title( __ "Real 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_REAL_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_LAST_BACKUP );
   $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
#

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

   return unless $this->folder->get_filename;
   return if abs_path( $this->folder->get_filename ) eq $this->gconf("current-backup-folder");

   printf "on_backup_folder_changed %s\n", abs_path $this->folder->get_filename if $DEBUG > 0;

   $this->gconf( "current-backup-folder", abs_path $this->folder->get_filename );
   $this->fill_tree;
   return;
}

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

   return unless $this->{folder_recycle_button}->get_filename;
   return if $this->folder->get_filename eq abs_path( $this->{folder_recycle_button}->get_filename );

   printf " on_folder_recycle_button %s\n", abs_path $this->{folder_recycle_button}->get_filename
     if $DEBUG > 0;

   $this->folder->set_current_folder( abs_path $this->{folder_recycle_button}->get_filename );

   return;
}

#tree
#

use Tie::DataDumper;

sub fill_tree {
   my ($this) = @_;
   return if $this->{init};

   $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 > 0;

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

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

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

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

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

      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_REAL_SIZE, $day_folder_real_size, COL_HSIZE,
                  ( sprintf "%sB (%sB)", format_bytes($day_folder_size), format_bytes($day_folder_real_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_LAST_BACKUP, FALSE,            COL_WEIGHT, 600,
               )
            );

            $day_folder_size      = 0;
            $day_folder_real_size = 0;
            $day_folder_files     = 0;
         }

         #printf "%s\n", $tardat unless -s $tardat if $DEBUG > 3;
         $day_folder_size      += $size;
         $day_folder_real_size += $info->{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,
               ( sprintf "%sB (%sB)", format_bytes($size), format_bytes( $info->{size} ) ),
               COL_FILES,
               scalar @{ $info->{files} },
               COL_LABEL,
               __("$info->{label}") 
                 . ( "$info->{label}" ? ", " : "" )
                 . (
                  $date_of_last_backup eq $basename
                  ? __("Last backup.")
                  : ""
                 ),
               COL_PATH,
               $filename,
               COL_LAST_BACKUP,
               $date_of_last_backup eq $basename,
               COL_WEIGHT,
               800,
            )
         );
      }

      # append last day folder
      $tree_store->set(
         $day_iter,
         (
            COL_SIZE, $day_folder_size, COL_HSIZE,
            ( sprintf "%sB (%sB)", format_bytes($day_folder_size), format_bytes($day_folder_real_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{folders} = 0  unless exists $info{folders};
   $info{files}   = [] unless exists $info{files};
   $info{label}   = '' unless exists $info{label};
   $info{size}    = 0  unless exists $info{size};

   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", abs_path( $this->get_main_store_folder || "" ), abs_path( $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 %s %s %s\n", $this, $widget, $this->{tree_view_2button_press}, "" if $DEBUG > 3;

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

   if ( ref $selected ) {

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

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

         system $TarOpenCmd, $path;
      }
      else {
         my $last_backup = $this->tree_view->get_model->get( $selected, COL_LAST_BACKUP );

         printf "*** %s\n", $last_backup ? 1 : 0 if $DEBUG > 3;

         if ($last_backup) {
            $this->backup_remove_button->set_sensitive(TRUE);
         }
         else {
            $this->backup_remove_button->set_sensitive(FALSE);
         }

      }
   }

   return;
}

#backup
#

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_notification->present;

   $this->backup_folder;

   $this->{folder_recycle_button}->set_uri( $this->folder->get_uri );
   $this->fill_tree;

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

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

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_notification->{startTime} = time;

   my $folder    = abs_path $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";
   my $first       = -e $excludes;

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

   $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_notification->{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: ).*?(Warning:)/o) {
         print "$_\n" if $DEBUG > 3;
         $this->log_add_text( $_, "\n" );
      }
      elsif (/^(tar: )?(Terminated|Killed|Hangup)$/o) {
         print "$_\n" if $DEBUG > 3;
         $this->log_add_text( $_, "\n" );
      }
      else {
         print "$_\n" if $DEBUG > 3;
      }
   }
   close TAR;

   # are there some heavy errors
   if ($?) {
      $this->log_add_text( sprintf __("Tar exited with status %s\n"), $? );

      if ( $? == 512 ) {
         $this->log_add_text( __("The 512 exit status means tar thinks it failed for some reason.\n") );
         $this->log_add_text( __("This could be caused by files with no permission.\n") );
         $? = FALSE;
      }
   }

   if ($?) {    # cancel / heavy error
      &{ $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 of current backup
      $this->store_restore_date($archive);

      #store size
      my $infofile = "$store/$date.info.txt";
      my $info = $this->get_backup_info( $archive, $infofile );
      $info->{label}   = "First Backup" unless $first;
      $info->{folders} = $folders;
      $info->{files}   = [ keys %$files ];
      $info->{size}    = $total_size;
      tied(%$info)->save;
   }

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

   if ( $this->backup_notification->{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 __("Total size: %s\n"),    format_bytes($total_size) );
      $this->log_add_text( sprintf __("Total time: %s\n"),
         strtime( localtime( time - $this->backup_notification->{startTime} ) ) );
   }

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

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

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

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

   #$this->backup_notification->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_notification->{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
#

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

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

   $this->exclude_clear;

   my $folder = abs_path $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 ) = @_;

lib/Applications/BackupAndRestore.pm  view on Meta::CPAN


   $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   = abs_path $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
#

sub on_restore_folder_changed {
   my ($this) = @_;
   printf "on_restore_folder_changed %s\n", abs_path $this->restore_folder->get_filename if $DEBUG > 3;
   $this->gconf( "restore-folder", abs_path $this->restore_folder->get_filename );
   return;
}

sub on_restore_extract_modification_time_changed {
   my ($this) = @_;
   printf "on_restore_extract_modification_time_changed %s\n", $this->restore_extract_modifiction_time->get_active ? 1 : 0
     if $DEBUG > 3;
   $this->gconf( "restore-extract-modification-time", $this->restore_extract_modifiction_time->get_active ? 1 : 0 );
   return;
}

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

   my $selected = $this->tree_view->get_selection->get_selected;
   my ( $hdate, $time ) = $this->tree_view->get_model->get( $selected, COL_HDATE, COL_TIME );

   $this->restore_backup_from_label->set_text("$hdate $time");

   $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_notification->show;

   $this->restore_backup;

   $this->fill_tree;

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

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

   my $restore_to_folder = abs_path $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 Starting restore . . .\n"), strftime( "%F %X", localtime ) );

   $this->restore_progress(0);
   $this->restore_notification->{startTime} = time;

   my $store = $this->get_store_folder;
   my $utf8  = Unicode::UTF8simple->new;

   my $backup      = 0;
   my $counter     = 0;
   my $numFiles    = 0;
   my $size        = 0;
   my $elapsedSize = 0;
   my $totalSize   = 0;
   foreach my $filename (@files) {
      my $infofile = "$store/" . basename( $filename, ".tar.bz2" ) . ".info.txt";
      my $info = $this->get_backup_info( $filename, $infofile );
      $numFiles  += @{ $info->{files} };
      $totalSize += $info->{size};

      $this->log_add_text( sprintf "%s %d\n", $infofile, $info->{size} );
   }

   $this->log_add_text( sprintf "total size %d\n", $totalSize );

   printf "***restore_backup to folder: %s %d\n", $restore_to_folder, $totalSize
     if $DEBUG > 3;

   foreach my $filename (@files) {
      printf "file: %s\n", $filename if $DEBUG > 3;

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

      $this->log_add_text( sprintf __("restoring backup from %s\n"), basename( $filename, ".tar.bz2" ) );
      $this->restore_process_backup_label->set_text( sprintf __("%d / %d"), ++$backup, scalar @files );
      Gtk2->main_iteration while Gtk2->events_pending;

      my $touch =
        $this->restore_extract_modifiction_time->get_active
        ? ""
        : "--touch";

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

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

         print $_ if $DEBUG > 0;

         chomp;
         $_ = $utf8->fromUTF8( "iso-8859-1", $_ );
         my $path = "$restore_to_folder/$_";

         if ( -d $path ) {
         }
         elsif ( -f $path ) {
            $size = -s $path;
            $elapsedSize += $size;

            my @times =
              map { strtime($_) } estimated_time( $this->restore_notification->{startTime}, $elapsedSize, $totalSize );

            $this->restore_elapsed_time_label->set_text( sprintf "%s", $times[0] );
            $this->restore_estimated_time_label->set_text( sprintf "%s / %s", @times[ 1, 2 ] );

            $this->restore_file_label->set_text( sprintf "%s [%sB]", $_, format_bytes($size) );

            $this->restore_progress( $elapsedSize / $totalSize );

            Gtk2->main_iteration while Gtk2->events_pending;
         }
      }
      close TAR;
      printf "tar returned %s\n", $? if $DEBUG > 3;

      if ($?) {    # cancel / error ...

         if ( $? == 512 ) {

            #$this->log_add_text();
            $? = FALSE;
         }
         else {
            $this->log_add_text( sprintf __("Tar exited with status %s\n"), $? );
            last;
         }
      }
      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;
}

sub on_cancel_restore {
   my ($this) = @_;
   printf "on_cancel_restore %s\n", $this->{tarpid} if $DEBUG > 3;
   system "pkill", "-P", $this->{tarpid};

   $this->restore_notification->{startTime} = 0;
   $this->restore_progressbar->set_fraction(1);
   $this->restore_progressbar->set_text( __ "Canceling Restore ..." );

   $this->log_add_text( sprintf __("%s Restore canceled.\n"), strftime( "%F %X", localtime ) );
   return 1;
}

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

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

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

   return;
}

# remove backup
#

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

   my $selected = $this->tree_view->get_selection->get_selected;
   my ( $hdate, $time ) = $this->tree_view->get_model->get( $selected, COL_HDATE, COL_TIME );

   $this->backup_remove_from_label->set_text("$hdate $time");

   $this->window->set_sensitive(FALSE);
   $this->remove_dialog->show;
   return;
}

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

sub on_backup_remove_dialog_ok {
   my ( $this, $widget ) = @_;
   $this->remove_dialog->hide;
   $this->remove_backup;
   $this->fill_tree;
   $this->window->set_sensitive(TRUE);
   $this->backup_remove_button(FALSE);
   return;
}

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

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

   my $selected = $this->tree_view->get_selection->get_selected;
   my ( $hdate, $date, $time, $file ) = $this->tree_view->get_model->get( $selected, COL_HDATE, COL_DATE, COL_TIME, COL_PATH );
   my $folder = dirname $file;
   my $store  = $this->get_store_folder;

   $this->log_add_text( sprintf "\n%s\n", "*" x 42 );
   $this->log_add_text( sprintf __("%s removing backup from %s %s\n"), strftime( "%F %X", localtime ), $hdate, $time );

   {
      my $archive  = "$store/$date $time.tar.bz2";
      my $tardat   = "$store/$date $time.dat.bz2";
      my $infofile = "$folder/$date $time.info.txt";

      print "$archive\n";
      print "$tardat\n";
      print "$infofile\n";

      unlink( $archive, $tardat, $infofile );
      pop @files;
   }

   {
      my $current_dat = "$store/$CurrentDat";
      if (@files) {
         my $date = basename( $files[$#files], ".tar.bz2" );
         my $tardat = "$store/$date.dat.bz2";

         # print "bzip2 -c -d '$tardat' >'$current_dat'\n";
         system "bzip2 -c -d '$tardat' >'$current_dat'";

         $this->store_restore_date( $files[$#files] );
      }
      else {
         my $excludes = "$store/$ExcludesFile";
         my $dateTxt  = "$store/$DateTxt";

         unlink $current_dat, $excludes, $dateTxt;
         rmdir_p $store;
      }
   }

   $this->log_add_text( sprintf __("%s remove done . . .\n"), strftime( "%F %X", localtime ) );
}

# schedule
#

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
#

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

   my $store = abs_path $this->get_main_store_folder;

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



( run in 0.581 second using v1.01-cache-2.11-cpan-39bf76dae61 )