X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;ds=sidebyside;f=gui%2Fbrestore%2Fbrestore.pl;h=bfcbeaba63c3454302aa8a646baa5f7c497cd1b9;hb=d3274117ad14dba7550c289e353405dc1821dfdd;hp=80fac3dd58c7ff6ef99d1882446bdd89c1acd40e;hpb=3118193980a10b5a41b4df193ad720fb5cfd5f81;p=bacula%2Fbacula diff --git a/gui/brestore/brestore.pl b/gui/brestore/brestore.pl index 80fac3dd58..bfcbeaba63 100755 --- a/gui/brestore/brestore.pl +++ b/gui/brestore/brestore.pl @@ -1,5 +1,6 @@ #!/usr/bin/perl -w -use strict ; + +use strict; # path to your brestore.glade my $glade_file = 'brestore.glade' ; @@ -21,6 +22,8 @@ my $glade_file = 'brestore.glade' ; - libdbd-mysql-perl or libdbd-pg-perl - libexpect-perl + You have to add brestore_xxx tables to your catalog. + To speed up database query you have to create theses indexes - CREATE INDEX file_pathid on File(PathId); - ... @@ -29,192 +32,558 @@ my $glade_file = 'brestore.glade' ; =head1 COPYRIGHT - Copyright (C) 2006 Marc Cousin and Eric Bollengier + Bacula® - The Network Backup Solution - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. - - Base 64 functions from Karl Hakimian - Integrally copied from recover.pl from bacula source distribution. + Copyright (C) 2000-2006 Free Software Foundation Europe e.V. + + Brestore authors are Marc Cousin and Eric Bollengier. + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version two of the GNU General Public + License as published by the Free Software Foundation plus additions + that are listed in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of John Walker. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich, + Switzerland, email:ftf@fsfeurope.org. + + Base 64 functions from Karl Hakimian + Integrally copied from recover.pl from bacula source distribution. =cut -use File::Spec; # portable path manipulations -use Gtk2 '-init'; # auto-initialize Gtk2 +use Gtk2; # auto-initialize Gtk2 use Gtk2::GladeXML; use Gtk2::SimpleList; # easy wrapper for list views use Gtk2::Gdk::Keysyms; # keyboard code constants use Data::Dumper qw/Dumper/; -use DBI; my $debug=0; # can be on brestore.conf +our ($VERSION) = ('$Revision$' =~ /(\d+)/); -################################################################ +package Pref; +use DBI; +sub new +{ + my ($class, $config_file) = @_; + + my $self = bless { + config_file => $config_file, + password => '', # db passwd + username => '', # db username + connection_string => '',# db connection string + bconsole => 'bconsole', # path and arg to bconsole + bsr_dest => '', # destination url for bsr files + debug => 0, # debug level 0|1 + use_ok_bkp_only => 1, # dont use bad backup + bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url + see_all_versions => 0, # display all file versions in FileInfo + mozilla => 'mozilla', # mozilla bin + default_restore_job => 'restore', # regular expression to select default + # restore job -package DlgFileVersion; + # keywords that are used to fill DlgPref + chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ], + entry_keyword => [ qw/username password bweb mozilla + connection_string default_restore_job + bconsole bsr_dest glade_file/], + }; -sub on_versions_close_clicked -{ - my ($self, $widget)=@_; - $self->{version}->destroy(); -} + $self->read_config(); -sub on_selection_button_press_event -{ - print "on_selection_button_press_event()\n"; + return $self; } -sub fileview_data_get +sub read_config { - my ($self, $widget, $context, $data, $info, $time,$string) = @_; + my ($self) = @_; - DlgResto::drag_set_info($widget, - $self->{cwd}, - $data); + # We read the parameters. They come from the configuration files + my $cfgfile ; my $tmpbuffer; + if (open FICCFG, $self->{config_file}) + { + while(read FICCFG,$tmpbuffer,4096) + { + $cfgfile .= $tmpbuffer; + } + close FICCFG; + my $refparams; + no strict; # I have no idea of the contents of the file + eval '$refparams' . " = $cfgfile"; + use strict; + + for my $p (keys %{$refparams}) { + $self->{$p} = $refparams->{$p}; + } + + } else { + # TODO : Force dumb default values and display a message + } } -sub new +sub write_config { - my ($class, $dbh, $client, $path, $file) = @_; - my $self = bless { - cwd => $path, - version => undef, # main window - }; + my ($self) = @_; + + $self->{error} = ''; + my %parameters; - # we load version widget of $glade_file - my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version"); + for my $k (@{ $self->{entry_keyword} }) { + $parameters{$k} = $self->{$k}; + } - # Connect signals magically - $glade_box->signal_autoconnect_from_package($self); + for my $k (@{ $self->{chk_keyword} }) { + $parameters{$k} = $self->{$k}; + } - $glade_box->get_widget("version_label") - ->set_markup("File revisions : $client:$path/$file"); + if (open FICCFG,">$self->{config_file}") + { + print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]); + close FICCFG; + } + else + { + $self->{error} = "Can't write configuration $!"; + } + return $self->{error}; +} - my $widget = $glade_box->get_widget('version_fileview'); - my $fileview = Gtk2::SimpleList->new_from_treeview( - $widget, - 'h_name' => 'hidden', - 'h_jobid' => 'hidden', - 'h_type' => 'hidden', +sub connect_db +{ + my $self = shift ; - 'InChanger' => 'pixbuf', - 'Volume' => 'text', - 'JobId' => 'text', - 'Size' => 'text', - 'Date' => 'text', - 'MD5' => 'text', - ); - DlgResto::init_drag_drop($fileview); + if ($self->{dbh}) { + $self->{dbh}->disconnect() ; + } - my @v = DlgResto::get_all_file_versions($dbh, - "$path/", - $file, - $client, - 1); - for my $ver (@v) { - my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname) - = @{$ver}; - my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon; + delete $self->{dbh}; + delete $self->{error}; - DlgResto::listview_push($fileview, - $file, $jobid, 'file', - $icon, $volname, $jobid, $size, - scalar(localtime($mtime)), $md5); + if (not $self->{connection_string}) + { + # The parameters have not been set. Maybe the conf + # file is empty for now + $self->{error} = "No configuration found for database connection. " . + "Please set this up."; + return 0; } - - $self->{version} = $glade_box->get_widget('dlg_version'); - $self->{version}->show(); - return $self; + if (not eval { + $self->{dbh} = DBI->connect($self->{connection_string}, + $self->{username}, + $self->{password}) + }) + { + $self->{error} = "Can't open bacula database. " . + "Database connect string '" . + $self->{connection_string} ."' $!"; + return 0; + } + $self->{is_mysql} = ($self->{connection_string} =~ m/dbi:mysql/i); + $self->{dbh}->{RowCacheSize}=100; + return 1; } -sub on_forward_keypress -{ - return 0; +sub go_bweb +{ + my ($self, $url, $msg) = @_; + + unless ($self->{mozilla} and $self->{bweb}) { + new DlgWarn("You must install Bweb and set your mozilla bin to $msg"); + return -1; + } + + if ($^O eq 'MSWin32') { + system("start /B $self->{mozilla} \"$self->{bweb}$url\""); + + } elsif (!fork()) { + system("$self->{mozilla} -remote 'Ping()'"); + my $cmd = "$self->{mozilla} '$self->{bweb}$url'"; + if ($? == 0) { + $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ; + } + exec($cmd); + exit 1; + } + return ($? == 0); } 1; + ################################################################ -package DlgWarn; + +package Bbase; sub new { - my ($package, $text) = @_; + my ($class, %arg) = @_; - my $self = bless {}; + my $self = bless { + conf => $arg{conf}, + }, $class; - my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn"); + return $self; +} - # Connect signals magically - $glade->signal_autoconnect_from_package($self); - $glade->get_widget('label_warn')->set_text($text); +use Data::Dumper; - print "$text\n"; +sub debug +{ + my ($self, $what, %arg) = @_; + + if ($self->{conf}->{debug} and defined $what) { + my $level=0; + if ($arg{up}) { + $level++; + } + my $line = (caller($level))[2]; + my $func = (caller($level+1))[3] || 'main'; + print "$func:$line\t"; + if (ref $what) { + print Data::Dumper::Dumper($what); + } elsif ($arg{md5}) { + print "MD5=", md5_base64($what), " str=", $what,"\n"; + } else { + print $what, "\n"; + } + } +} - $self->{window} = $glade->get_widget('dlg_warn'); - $self->{window}->show_all(); - return $self; +sub dbh_strcat +{ + my ($self, @what) = @_; + if ($self->{conf}->{connection_string} =~ /dbi:pg/i) { + return join(' || ', @what); + } else { + return 'CONCAT(' . join(',', @what) . ')' ; + } } -sub on_close_clicked +sub dbh_prepare { - my ($self) = @_; - $self->{window}->destroy(); + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->prepare($query); } -1; -################################################################ +sub dbh_do +{ + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->do($query); +} -package DlgLaunch; +sub dbh_selectall_arrayref +{ + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->selectall_arrayref($query); +} -use Bconsole; +sub dbh_selectrow_arrayref +{ + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->selectrow_arrayref($query); +} -# %arg = (bsr_file => '/path/to/bsr', # on director -# volumes => [ '00001', '00004'] -# pref => ref Pref -# ); +sub dbh +{ + my ($self) = @_; + return $self->{conf}->{dbh}; +} + +1; +################################################################ +# Manage preference +package DlgPref; + +# my $pref = new Pref(config_file => 'brestore.conf'); +# my $dlg = new DlgPref($pref); +# my $dlg_resto = new DlgResto($pref); +# $dlg->display($dlg_resto); sub new { - my ($class, %arg) = @_; + my ($class, $pref) = @_; my $self = bless { - bsr_file => $arg{bsr_file}, # /path/to/bsr on director - pref => $arg{pref}, # Pref ref - glade => undef, # GladeXML ref - bconsole => undef, # Bconsole ref - }; + pref => $pref, # Pref ref + dlgresto => undef, # DlgResto ref + }; - # we load launch widget of $glade_file - my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, - "dlg_launch"); + return $self; +} - # Connect signals magically - $glade->signal_autoconnect_from_package($self); +sub display +{ + my ($self, $dlgresto) = @_ ; - my $widget = $glade->get_widget('volumeview'); - my $volview = Gtk2::SimpleList->new_from_treeview( - $widget, - 'InChanger' => 'pixbuf', - 'Volume' => 'text', - ); + unless ($self->{glade}) { + $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ; + $self->{glade}->signal_autoconnect_from_package($self); + } - my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ; - - # we replace 0 and 1 by $noicon and $yesicon - for my $i (@{$infos}) { - if ($i->[0] == 0) { + $self->{dlgresto} = $dlgresto; + + my $g = $self->{glade}; + my $p = $self->{pref}; + + for my $k (@{ $p->{entry_keyword} }) { + $g->get_widget("entry_$k")->set_text($p->{$k}) ; + } + + for my $k (@{ $p->{chk_keyword} }) { + $g->get_widget("chkbp_$k")->set_active($p->{$k}) ; + } + + $g->get_widget("dlg_pref")->show_all() ; +} + +sub on_applybutton_clicked +{ + my ($self) = @_; + my $glade = $self->{glade}; + my $pref = $self->{pref}; + + for my $k (@{ $pref->{entry_keyword} }) { + my $w = $glade->get_widget("entry_$k") ; + $pref->{$k} = $w->get_text(); + } + + for my $k (@{ $pref->{chk_keyword} }) { + my $w = $glade->get_widget("chkbp_$k") ; + $pref->{$k} = $w->get_active(); + } + + if (!$pref->write_config() && $pref->connect_db()) { + $self->{dlgresto}->set_status('Preferences updated'); + $self->{dlgresto}->init_server_backup_combobox(); + $self->{dlgresto}->set_status($pref->{error}); + + } else { + $self->{dlgresto}->set_status($pref->{error}); + } +} + +# Handle prefs ok click (apply/dismiss dialog) +sub on_okbutton_clicked +{ + my ($self) = @_; + $self->on_applybutton_clicked(); + + unless ($self->{pref}->{error}) { + $self->on_cancelbutton_clicked(); + } +} +sub on_dialog_delete_event +{ + my ($self) = @_; + $self->on_cancelbutton_clicked(); + 1; +} + +sub on_cancelbutton_clicked +{ + my ($self) = @_; + $self->{glade}->get_widget('dlg_pref')->hide(); + delete $self->{dlgresto}; +} +1; + +################################################################ +# Display all file revision in a separated window +package DlgFileVersion; + +sub on_versions_close_clicked +{ + my ($self, $widget)=@_; + $self->{version}->destroy(); +} + +sub on_selection_button_press_event +{ + print STDERR "on_selection_button_press_event()\n"; +} + +sub fileview_data_get +{ + my ($self, $widget, $context, $data, $info, $time,$string) = @_; + + DlgResto::drag_set_info($widget, + $self->{pwd}, + $data); +} + +# +# new DlgFileVersion(Bvfs, "client", pathid, fileid, "/path/to/", "filename"); +# +sub new +{ + my ($class, $bvfs, $client, $pathid, $fileid, $path, $fn) = @_; + my $self = bless { + pwd => $path, + version => undef, # main window + }; + + # we load version widget of $glade_file + my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version"); + + # Connect signals magically + $glade_box->signal_autoconnect_from_package($self); + + $glade_box->get_widget("version_label") + ->set_markup("File revisions : $client:$path$fn"); + + my $widget = $glade_box->get_widget('version_fileview'); + my $fileview = Gtk2::SimpleList->new_from_treeview( + $widget, + 'h_pathid' => 'hidden', + 'h_filenameid' => 'hidden', + 'h_name' => 'hidden', + 'h_jobid' => 'hidden', + 'h_type' => 'hidden', + + 'InChanger' => 'pixbuf', + 'Volume' => 'text', + 'JobId' => 'text', + 'Size' => 'text', + 'Date' => 'text', + 'MD5' => 'text', + ); + DlgResto::init_drag_drop($fileview); + + my @v = $bvfs->get_all_file_versions($pathid, + $fileid, + $client, + 1); + for my $ver (@v) { + my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size, + $inchanger,$md5,$volname) = @{$ver}; + my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon; + + DlgResto::listview_push($fileview,$pid,$fid, + $fn, $jobid, 'file', + $icon, $volname, $jobid,DlgResto::human($size), + scalar(localtime($mtime)), $md5); + } + + $self->{version} = $glade_box->get_widget('dlg_version'); + $self->{version}->show(); + + return $self; +} + +sub on_forward_keypress +{ + return 0; +} + +1; +################################################################ +# Display a warning message +package DlgWarn; + +sub new +{ + my ($package, $text) = @_; + + my $self = bless {}; + + my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn"); + + # Connect signals magically + $glade->signal_autoconnect_from_package($self); + $glade->get_widget('label_warn')->set_text($text); + + print STDERR "$text\n"; + + $self->{window} = $glade->get_widget('dlg_warn'); + $self->{window}->show_all(); + return $self; +} + +sub on_close_clicked +{ + my ($self) = @_; + $self->{window}->destroy(); +} +1; + +################################################################ + +package DlgLaunch; + +#use Bconsole; + +# %arg = (bsr_file => '/path/to/bsr', # on director +# volumes => [ '00001', '00004'] +# pref => ref Pref +# ); + +sub get_bconsole +{ + my ($pref) = (@_); + + if ($pref->{bconsole} =~ /^http/) { + return new BwebConsole(pref => $pref); + } else { + if (eval { require Bconsole; }) { + return new Bconsole(pref => $pref); + } else { + new DlgWarn("Can't use bconsole, verify your setup"); + return undef; + } + } +} + +sub new +{ + my ($class, %arg) = @_; + + my $self = bless { + bsr_file => $arg{bsr_file}, # /path/to/bsr on director + pref => $arg{pref}, # Pref ref + glade => undef, # GladeXML ref + bconsole => undef, # Bconsole ref + }; + + my $console = $self->{bconsole} = get_bconsole($arg{pref}); + unless ($console) { + return undef; + } + + # we load launch widget of $glade_file + my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, + "dlg_launch"); + + # Connect signals magically + $glade->signal_autoconnect_from_package($self); + + my $widget = $glade->get_widget('volumeview'); + my $volview = Gtk2::SimpleList->new_from_treeview( + $widget, + 'InChanger' => 'pixbuf', + 'Volume' => 'text', + ); + + my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ; + + # we replace 0 and 1 by $noicon and $yesicon + for my $i (@{$infos}) { + if ($i->[0] == 0) { $i->[0] = $DlgResto::noicon; } else { $i->[0] = $DlgResto::yesicon; @@ -223,8 +592,8 @@ sub new # fill volume view push @{ $volview->{data} }, @{$infos} ; - - my $console = $self->{bconsole} = new Bconsole(pref => $arg{pref}); + + $console->prepare(qw/list_client list_job list_fileset list_storage/); # fill client combobox (with director defined clients my @clients = $console->list_client() ; # get from bconsole @@ -280,6 +649,8 @@ sub show_job my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status"); + $self->on_cancel_resto_clicked(); + if ($ret == -1) { my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close', "Your job have been submited to bacula. @@ -287,8 +658,44 @@ To follow it, you must use bconsole (or install/configure bweb)"); $widget->run; $widget->destroy(); } +} - $self->on_cancel_resto_clicked(); +sub on_use_regexp_toggled +{ + my ($self,$widget) = @_; + my $act = $widget->get_active(); + + foreach my $w ('entry_launch_where') { + $self->{glade}->get_widget($w)->set_sensitive(!$act); + } + + foreach my $w ('entry_add_prefix', 'entry_strip_prefix', + 'entry_add_suffix','entry_rwhere','chk_use_regexp') + { + $self->{glade}->get_widget($w)->set_sensitive($act); + } + + if ($act) { # if we activate file relocation, we reset use_regexp + $self->{glade}->get_widget('entry_rwhere')->set_sensitive(0); + $self->{glade}->get_widget('chk_use_regexp')->set_active(0); + } +} + + +sub on_use_rwhere_toggled +{ + my ($self,$widget) = @_; + my $act = $widget->get_active(); + + foreach my $w ('entry_rwhere') { + $self->{glade}->get_widget($w)->set_sensitive($act); + } + + foreach my $w ('entry_add_prefix', 'entry_strip_prefix', + 'entry_add_suffix') + { + $self->{glade}->get_widget($w)->set_sensitive(!$act); + } } sub on_cancel_resto_clicked @@ -297,6 +704,45 @@ sub on_cancel_resto_clicked $self->{glade}->get_widget('dlg_launch')->destroy(); } +sub get_where +{ + my ($self) = @_ ; + + if ($self->{glade}->get_widget('chk_file_relocation')->get_active()) { + # using regexp + if ($self->{glade}->get_widget('chk_use_regexp')->get_active()) { + + return ('regexwhere', + $self->{glade}->get_widget('entry_rwhere')->get_active()); + } + + # using regexp utils + my @ret; + my ($strip_prefix, $add_prefix, $add_suffix) = + ($self->{glade}->get_widget('entry_strip_prefix')->get_text(), + $self->{glade}->get_widget('entry_add_prefix')->get_text(), + $self->{glade}->get_widget('entry_add_suffix')->get_text()); + + if ($strip_prefix) { + push @ret,"!$strip_prefix!!i"; + } + + if ($add_prefix) { + push @ret,"!^!$add_prefix!"; + } + + if ($add_suffix) { + push @ret,"!([^/])\$!\$1$add_suffix!"; + } + + return ('regexwhere', join(',', @ret)); + + } else { # using where + return ('where', + $self->{glade}->get_widget('entry_launch_where')->get_text()); + } +} + sub on_submit_resto_clicked { my ($self) = @_ ; @@ -315,7 +761,8 @@ sub on_submit_resto_clicked my $storage = $glade->get_widget('combo_launch_storage') ->get_active_text(); - my $where = $glade->get_widget('entry_launch_where')->get_text(); + my ($where_cmd, $where) = $self->get_where(); + print "$where_cmd => $where\n"; my $job = $glade->get_widget('combo_launch_job') ->get_active_text(); @@ -342,7 +789,7 @@ sub on_submit_resto_clicked client => $client, storage => $storage, fileset => $fileset, - where => $where, + $where_cmd => $where, replace => $replace, priority=> $prio, bootstrap => $r); @@ -397,6 +844,10 @@ sub copy_bsr print "$src => $dst\n" if ($debug); + if (!$dst) { + return $src; + } + my $ret=0 ; my $err ; my $dstfile; @@ -453,254 +904,9 @@ sub on_about_okbutton_clicked 1; ################################################################ -# preference reader -package Pref; - -sub new -{ - my ($class, $config_file) = @_; - - my $self = bless { - config_file => $config_file, - password => '', # db passwd - username => '', # db username - connection_string => '',# db connection string - bconsole => 'bconsole', # path and arg to bconsole - bsr_dest => '', # destination url for bsr files - debug => 0, # debug level 0|1 - use_ok_bkp_only => 1, # dont use bad backup - bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url - glade_file => $glade_file, - see_all_versions => 0, # display all file versions in FileInfo - mozilla => 'mozilla', # mozilla bin - default_restore_job => 'restore', # regular expression to select default - # restore job - - # keywords that are used to fill DlgPref - chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ], - entry_keyword => [ qw/username password bweb mozilla - connection_string default_restore_job - bconsole bsr_dest glade_file/], - }; - - $self->read_config(); - - return $self; -} - -sub read_config -{ - my ($self) = @_; - - # We read the parameters. They come from the configuration files - my $cfgfile ; my $tmpbuffer; - if (open FICCFG, $self->{config_file}) - { - while(read FICCFG,$tmpbuffer,4096) - { - $cfgfile .= $tmpbuffer; - } - close FICCFG; - my $refparams; - no strict; # I have no idea of the contents of the file - eval '$refparams' . " = $cfgfile"; - use strict; - - for my $p (keys %{$refparams}) { - $self->{$p} = $refparams->{$p}; - } - - if (defined $self->{debug}) { - $debug = $self->{debug} ; - } - } else { - # TODO : Force dumb default values and display a message - } -} - -sub write_config -{ - my ($self) = @_; - - my %parameters; - - for my $k (@{ $self->{entry_keyword} }) { - $parameters{$k} = $self->{$k}; - } - - for my $k (@{ $self->{chk_keyword} }) { - $parameters{$k} = $self->{$k}; - } - - if (open FICCFG,">$self->{config_file}") - { - print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]); - close FICCFG; - } - else - { - # TODO : Display a message - } -} - -sub connect_db -{ - my $self = shift ; - - if ($self->{dbh}) { - $self->{dbh}->disconnect() ; - } - - delete $self->{dbh}; - delete $self->{error}; - - if (not $self->{connection_string}) - { - # The parameters have not been set. Maybe the conf - # file is empty for now - $self->{error} = "No configuration found for database connection. " . - "Please set this up."; - return 0; - } - - if (not eval { - $self->{dbh} = DBI->connect($self->{connection_string}, - $self->{username}, - $self->{password}) - }) - { - $self->{error} = "Can't open bacula database. " . - "Database connect string '" . - $self->{connection_string} ."' $!"; - return 0; - } - $self->{dbh}->{RowCacheSize}=100; - return 1; -} - -sub go_bweb -{ - my ($self, $url, $msg) = @_; - - unless ($self->{mozilla} and $self->{bweb}) { - new DlgWarn("You must install Bweb and set your mozilla bin to $msg"); - return -1; - } - - system("$self->{mozilla} -remote 'Ping()'"); - if ($? != 0) { - new DlgWarn("Warning, you must have a running $self->{mozilla} to $msg"); - return 0; - } - - my $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ; - print "$cmd\n"; - system($cmd); - return ($? == 0); -} - -1; - -################################################################ -# Manage preference -package DlgPref; - -# my $pref = new Pref(config_file => 'brestore.conf'); -# my $dlg = new DlgPref($pref); -# my $dlg_resto = new DlgResto($pref); -# $dlg->display($dlg_resto); -sub new -{ - my ($class, $pref) = @_; - - my $self = bless { - pref => $pref, # Pref ref - dlgresto => undef, # DlgResto ref - }; - - return $self; -} - -sub display -{ - my ($self, $dlgresto) = @_ ; - - unless ($self->{glade}) { - $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ; - $self->{glade}->signal_autoconnect_from_package($self); - } - - $self->{dlgresto} = $dlgresto; - - my $g = $self->{glade}; - my $p = $self->{pref}; - - for my $k (@{ $p->{entry_keyword} }) { - $g->get_widget("entry_$k")->set_text($p->{$k}) ; - } - - for my $k (@{ $p->{chk_keyword} }) { - $g->get_widget("chkbp_$k")->set_active($p->{$k}) ; - } - - $g->get_widget("dlg_pref")->show_all() ; -} - -sub on_applybutton_clicked -{ - my ($self) = @_; - my $glade = $self->{glade}; - my $pref = $self->{pref}; - - for my $k (@{ $pref->{entry_keyword} }) { - my $w = $glade->get_widget("entry_$k") ; - $pref->{$k} = $w->get_text(); - } - - for my $k (@{ $pref->{chk_keyword} }) { - my $w = $glade->get_widget("chkbp_$k") ; - $pref->{$k} = $w->get_active(); - } - - $pref->write_config(); - if ($pref->connect_db()) { - $self->{dlgresto}->set_dbh($pref->{dbh}); - $self->{dlgresto}->set_status('Preferences updated'); - $self->{dlgresto}->init_server_backup_combobox(); - } else { - $self->{dlgresto}->set_status($pref->{error}); - } -} - -# Handle prefs ok click (apply/dismiss dialog) -sub on_okbutton_clicked -{ - my ($self) = @_; - $self->on_applybutton_clicked(); - - unless ($self->{pref}->{error}) { - $self->on_cancelbutton_clicked(); - } -} -sub on_dialog_delete_event -{ - my ($self) = @_; - $self->on_cancelbutton_clicked(); - 1; -} - -sub on_cancelbutton_clicked -{ - my ($self) = @_; - $self->{glade}->get_widget('dlg_pref')->hide(); - delete $self->{dlgresto}; -} -1; - -################################################################ -# Main Interface package DlgResto; +use base qw/Bbase/; our $diricon; our $fileicon; @@ -719,7 +925,6 @@ sub render_icons $noicon = $self->{mainwin}->render_icon('gtk-no', $size); } } - # init combo (and create ListStore object) sub init_combo { @@ -761,7 +966,7 @@ sub fill_combo # display Mb/Gb/Kb sub human { - my @unit = qw(b Kb Mb Gb Tb); + my @unit = qw(B KB MB GB TB); my $val = shift; my $i=0; my $format = '%i %s'; @@ -773,37 +978,63 @@ sub human return sprintf($format, $val, $unit[$i]); } -sub set_dbh -{ - my ($self, $dbh) = @_; - $self->{dbh} = $dbh; -} - -sub init_drag_drop +sub get_wanted_job_status { - my ($fileview) = shift; - my $fileview_target_entry = {target => 'STRING', - flags => ['GTK_TARGET_SAME_APP'], - info => 40 }; - - $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'], - ['copy'],$fileview_target_entry); - $fileview->get_selection->set_mode('multiple'); + my ($ok_only) = @_; - # set some useful SimpleList properties - $fileview->set_headers_clickable(0); - foreach ($fileview->get_columns()) - { - $_->set_resizable(1); - $_->set_sizing('grow-only'); + if ($ok_only) { + return "'T'"; + } else { + return "'T', 'A', 'E'"; } } -sub new +# This sub gives a full list of the EndTimes for a ClientId +# ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'], +# ['Date', 'FileSet', 'Type', 'Status', 'JobId']..) +sub get_all_endtimes_for_job +{ + my ($self, $client, $ok_only)=@_; + my $status = get_wanted_job_status($ok_only); + my $query = " + SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId + FROM Job,Client,FileSet + WHERE Job.ClientId=Client.ClientId + AND Client.Name = '$client' + AND Job.Type = 'B' + AND JobStatus IN ($status) + AND Job.FileSetId = FileSet.FileSetId + ORDER BY EndTime desc"; + my $result = $self->dbh_selectall_arrayref($query); + + return @$result; +} + +sub init_drag_drop +{ + my ($fileview) = shift; + my $fileview_target_entry = {target => 'STRING', + flags => ['GTK_TARGET_SAME_APP'], + info => 40 }; + + $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'], + ['copy'],$fileview_target_entry); + $fileview->get_selection->set_mode('multiple'); + + # set some useful SimpleList properties + $fileview->set_headers_clickable(0); + foreach ($fileview->get_columns()) + { + $_->set_resizable(1); + $_->set_sizing('grow-only'); + } +} + +sub new { my ($class, $pref) = @_; my $self = bless { - pref => $pref, + conf => $pref, dirtree => undef, CurrentJobIds => [], location => undef, # location entry widget @@ -816,13 +1047,17 @@ sub new fileattrib => {}, # cache file fileview => undef, # fileview widget SimpleList fileinfo => undef, # fileinfo widget SimpleList - cwd => '/', + cwd => '/', client_combobox => undef, # client_combobox widget restore_backup_combobox => undef, # date combobox widget list_client => undef, # Gtk2::ListStore list_backup => undef, # Gtk2::ListStore + cache_ppathid => {}, # + bvfs => undef, # Bfvs object }; + $self->{bvfs} = new Bvfs(conf => $pref); + # load menu (to use handler with self reference) my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu"); $glade->signal_autoconnect_from_package($self); @@ -857,6 +1092,8 @@ sub new my $widget = $glade->get_widget('fileview'); my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview( $widget, + 'h_pathid' => 'hidden', + 'h_filenameid' => 'hidden', 'h_name' => 'hidden', 'h_jobid' => 'hidden', 'h_type' => 'hidden', @@ -866,21 +1103,28 @@ sub new 'Size' => 'text', 'Date' => 'text'); init_drag_drop($fileview); - $fileview->set_search_column(4); # search on File Name + $fileview->set_search_column(6); # search on File Name # Connect glade-restore_list to Gtk2::SimpleList $widget = $glade->get_widget('restorelist'); my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview( $widget, - 'h_name' => 'hidden', - 'h_jobid' => 'hidden', - 'h_type' => 'hidden', - 'h_curjobid' => 'hidden', - - '' => 'pixbuf', - 'File Name' => 'text', - 'JobId' => 'text', - 'FileIndex' => 'text'); + 'h_pathid' => 'hidden', #0 + 'h_filenameid' => 'hidden', + 'h_name' => 'hidden', + 'h_jobid' => 'hidden', + 'h_type' => 'hidden', + 'h_curjobid' => 'hidden', #5 + + '' => 'pixbuf', + 'File Name' => 'text', + 'JobId' => 'text', + 'FileIndex' => 'text', + + 'Nb Files' => 'text', #10 + 'Size' => 'text', #11 + 'size_b' => 'hidden', #12 + ); my @restore_list_target_table = ({'target' => 'STRING', 'flags' => [], @@ -892,6 +1136,8 @@ sub new $widget = $glade->get_widget('infoview'); my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview( $widget, + 'h_pathid' => 'hidden', + 'h_filenameid' => 'hidden', 'h_name' => 'hidden', 'h_jobid' => 'hidden', 'h_type' => 'hidden', @@ -908,15 +1154,21 @@ sub new $pref->connect_db() || $self->{dlg_pref}->display($self); if ($pref->{dbh}) { - $self->{dbh} = $pref->{dbh}; $self->init_server_backup_combobox(); + if ($self->{bvfs}->create_brestore_tables()) { + new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them."); + } } + + $self->set_status($pref->{error}); } # set status bar informations sub set_status { my ($self, $string) = @_; + return unless ($string); + my $context = $self->{status}->get_context_id('Main'); $self->{status}->push($context, $string); } @@ -938,50 +1190,13 @@ sub get_all_clients { my $dbh = shift; my $query = "SELECT Name FROM Client ORDER BY Name"; - print $query,"\n" if $debug; - my $result = $dbh->selectall_arrayref($query); - my @return_array; - foreach my $refrow (@$result) - { - push @return_array,($refrow->[0]); - } - return @return_array; -} - -sub get_wanted_job_status -{ - my ($ok_only) = @_; + print STDERR $query,"\n" if $debug; - if ($ok_only) { - return "'T'"; - } else { - return "'T', 'A', 'E'"; - } -} - -# This sub gives a full list of the EndTimes for a ClientId -# ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'], -# ['Date', 'FileSet', 'Type', 'Status', 'JobId']..) -sub get_all_endtimes_for_job -{ - my ($dbh, $client, $ok_only)=@_; - my $status = get_wanted_job_status($ok_only); - my $query = " - SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId - FROM Job,Client,FileSet - WHERE Job.ClientId=Client.ClientId - AND Client.Name = '$client' - AND Job.Type = 'B' - AND JobStatus IN ($status) - AND Job.FileSetId = FileSet.FileSetId - ORDER BY EndTime desc"; - print $query,"\n" if $debug; my $result = $dbh->selectall_arrayref($query); - return @$result; + return map { $_->[0] } @$result; } - # init infoview widget sub clear_infoview { @@ -996,11 +1211,113 @@ sub on_clear_clicked @{$self->{restore_list}->{data}} = (); } +sub on_estimate_clicked +{ + my ($self) = @_; + + my $size_total=0; + my $nb_total=0; + + # TODO : If we get here, things could get lenghty ... draw a popup window . + my $widget = Gtk2::MessageDialog->new($self->{mainwin}, + 'destroy-with-parent', + 'info', 'close', + 'Computing size...'); + $widget->show; + refresh_screen(); + + my $title = "Computing size...\n"; + my $txt=""; + + # ($pid,$fid,$name,$jobid,'file',$curjobids, + # undef, undef, undef, $dirfileindex); + foreach my $entry (@{$self->{restore_list}->{data}}) + { + unless ($entry->[11]) { + my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen); + $entry->[12] = $size; + $entry->[11] = human($size); + $entry->[10] = $nb; + } + + my $name = unpack('u', $entry->[2]); + + $txt .= "\n$name : ". $entry->[10] ." file(s)/". $entry->[11] ; + $self->debug($title . $txt); + $widget->set_markup($title . $txt); + + $size_total+=$entry->[12]; + $nb_total+=$entry->[10]; + refresh_screen(); + } + + $txt .= "\n\nTotal : $nb_total file(s)/" . human($size_total); + $widget->set_markup("Size estimation :\n" . $txt); + $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();}); + + return 0; +} + + + +sub on_gen_bsr_clicked +{ + my ($self) = @_; + + my @options = ("Choose a bsr file", $self->{mainwin}, 'save', + 'gtk-save','ok', 'gtk-cancel', 'cancel'); + + + my $w = new Gtk2::FileChooserDialog ( @options ); + my $ok = 0; + my $save; + while (!$ok) { + my $a = $w->run(); + if ($a eq 'cancel') { + $ok = 1; + } + + if ($a eq 'ok') { + my $f = $w->get_filename(); + if (-f $f) { + my $dlg = Gtk2::MessageDialog->new($self->{mainwin}, + 'destroy-with-parent', + 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?'); + if ($dlg->run() eq 'ok') { + $save = $f; + } + $dlg->destroy(); + } else { + $save = $f; + } + $ok = 1; + } + } + + $w->destroy(); + + if ($save) { + if (open(FP, ">$save")) { + my $bsr = $self->create_filelist(); + print FP $bsr; + close(FP); + $self->set_status("Dumping BSR to $save ok"); + } else { + $self->set_status("Can't dump BSR to $save: $!"); + } + } +} + + use File::Temp qw/tempfile/; sub on_go_button_clicked { my $self = shift; + unless (scalar(@{$self->{restore_list}->{data}})) { + new DlgWarn("No file to restore"); + return 0; + } my $bsr = $self->create_filelist(); my ($fh, $filename) = tempfile(); $fh->print($bsr); @@ -1014,13 +1331,14 @@ sub on_go_button_clicked my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g); my $vol = [ keys %a ] ; # need only one occurrence of each volume - new DlgLaunch(pref => $self->{pref}, + new DlgLaunch(pref => $self->{conf}, volumes => $vol, bsr_file => $filename, ); } + our $client_list_empty = 'Clients list'; our %type_markup = ('F' => '$label F', 'D' => '$label D', @@ -1036,7 +1354,6 @@ sub on_list_client_changed { my ($self, $widget) = @_; return 0 unless defined $self->{fileview}; - my $dbh = $self->{dbh}; $self->{list_backup}->clear(); @@ -1044,9 +1361,22 @@ sub on_list_client_changed return 0 ; } - my @endtimes=get_all_endtimes_for_job($dbh, - $self->current_client, - $self->{pref}->{use_ok_bkp_only}); + $self->{CurrentJobIds} = [ + set_job_ids_for_date($self->dbh(), + $self->current_client, + $self->current_date, + $self->{conf}->{use_ok_bkp_only}) + ]; + + my $fs = $self->{bvfs}; + $fs->set_curjobids(@{$self->{CurrentJobIds}}); + $fs->ch_dir($fs->get_root()); + # refresh_fileview will be done by list_backup_changed + + + my @endtimes=$self->get_all_endtimes_for_job($self->current_client, + $self->{conf}->{use_ok_bkp_only}); + foreach my $endtime (@endtimes) { my $i = $self->{list_backup}->append(); @@ -1061,21 +1391,10 @@ sub on_list_client_changed ); } $self->{restore_backup_combobox}->set_active(0); - - $self->{CurrentJobIds} = [ - set_job_ids_for_date($dbh, - $self->current_client, - $self->current_date, - $self->{pref}->{use_ok_bkp_only}) - ]; - - $self->ch_dir(''); - -# undef $self->{dirtree}; - $self->refresh_fileview(); 0; } + sub fill_server_list { my ($dbh, $combo, $list) = @_; @@ -1098,7 +1417,7 @@ sub fill_server_list sub init_server_backup_combobox { my $self = shift ; - fill_server_list($self->{dbh}, + fill_server_list($self->{conf}->{dbh}, $self->{client_combobox}, $self->{list_client}) ; } @@ -1112,7 +1431,7 @@ sub refresh_fileview my ($self) = @_; my $fileview = $self->{fileview}; my $client_combobox = $self->{client_combobox}; - my $cwd = $self->{cwd}; + my $bvfs = $self->{bvfs}; @{$fileview->{data}} = (); @@ -1125,27 +1444,30 @@ sub refresh_fileview return; } - my @dirs = $self->list_dirs($cwd,$client_name); - # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..] - my $files = $self->list_files($cwd); - print "CWD : $cwd\n" if ($debug); + # [ [dirid, dir_basename, File.LStat, jobid]..] + my $list_dirs = $bvfs->ls_dirs(); + # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..] + my $files = $bvfs->ls_files(); my $file_count = 0 ; my $total_bytes = 0; # Add directories to view - foreach my $dir (@dirs) { - my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime')); + foreach my $dir_entry (@$list_dirs) { + my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime')); $total_bytes += 4096; $file_count++; listview_push($fileview, - $dir, - $self->dir_attrib("$cwd/$dir",'jobid'), + $dir_entry->[0], + 0, + $dir_entry->[1], + # TODO: voir ce que l'on met la + $dir_entry->[3], 'dir', $diricon, - $dir, + $dir_entry->[1], "4 Kb", $time); } @@ -1153,24 +1475,26 @@ sub refresh_fileview # Add files to view foreach my $file (@$files) { - my $size = file_attrib($file,'st_size'); - my $time = localtime(file_attrib($file,'st_mtime')); + my $size = Bvfs::file_attrib($file,'st_size'); + my $time = localtime(Bvfs::file_attrib($file,'st_mtime')); $total_bytes += $size; $file_count++; - # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId] - + # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId] listview_push($fileview, - $file->[1], - $file->[3], + $bvfs->{cwdid}, + $file->[0], + $file->[2], + $file->[4], 'file', $fileicon, - $file->[1], + $file->[2], human($size), $time); } $self->set_status("$file_count files/" . human($total_bytes)); - + $self->{cwd} = $self->{bvfs}->pwd(); + $self->{location}->set_text($self->{cwd}); # set a decent default selection (makes keyboard nav easy) $fileview->select(0); } @@ -1194,18 +1518,13 @@ sub drag_set_info # doc ligne 93 # Ok, we have a corner case : # path can be empty - my $file; - if ($path eq '') - { - $file = pack("u", $file_info[0]); - } - else - { - $file = pack("u", $path . '/' . $file_info[0]); - } - push @ret, join(" ; ", $file, - $file_info[1], # $jobid - $file_info[2], # $type + my $file = pack("u", $path . $file_info[2]); + + push @ret, join(" ; ", $file, + $file_info[0], # $pathid + $file_info[1], # $filenameid + $file_info[3], # $jobid + $file_info[4], # $type ); } @@ -1214,6 +1533,8 @@ sub drag_set_info $data->set_text($data_get,-1); } + + sub fileview_data_get { my ($self, $widget, $context, $data, $info, $time,$string) = @_; @@ -1230,24 +1551,26 @@ sub restore_list_data_received { my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_; my @ret; - + $self->debug("start\n"); if ($info eq 40 || $info eq 0) # patch for display!=:0 { foreach my $elt (split(/ :: /, $data->data())) { - - my ($file, $jobid, $type) = + my ($file, $pathid, $filenameid, $jobid, $type) = split(/ ; /, $elt); $file = unpack("u", $file); - $self->add_selected_file_to_list($file, $jobid, $type); + $self->add_selected_file_to_list($pathid,$filenameid, + $file, $jobid, $type); } } + $self->debug("end\n"); } sub on_back_button_clicked { my $self = shift; - $self->up_dir(); + $self->{bvfs}->up_dir(); + $self->refresh_fileview(); } sub on_location_go_button_clicked { @@ -1265,20 +1588,7 @@ sub on_bweb_activate { my $self = shift; $self->set_status("Open bweb on your browser"); - $self->{pref}->go_bweb('', "go on bweb"); -} - -# Change to parent directory -sub up_dir -{ - my $self = shift ; - if ($self->{cwd} eq '/') - { - $self->ch_dir(''); - } - my @dirs = File::Spec->splitdir ($self->{cwd}); - pop @dirs; - $self->ch_dir(File::Spec->catdir(@dirs)); + $self->{conf}->go_bweb('', "go on bweb"); } # Change the current working directory @@ -1286,12 +1596,15 @@ sub up_dir # sub ch_dir { - my $self = shift; - $self->{cwd} = shift; - - $self->refresh_fileview(); - $self->{location}->set_text($self->{cwd}); - + my ($self,$l) = @_; + + my $p = $self->{bvfs}->get_pathid($l); + if ($p) { + $self->{bvfs}->ch_dir($p); + $self->refresh_fileview(); + } else { + $self->set_status("Can't find $l"); + } 1; } @@ -1339,8 +1652,8 @@ sub listview_get_first my ($list) = shift; my @selected = $list->get_selected_indices(); if (@selected > 0) { - my ($name, @other) = @{$list->{data}->[$selected[0]]}; - return (unpack('u', $name), @other); + my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]}; + return ($pid,$fid,unpack('u', $name), @other); } else { return undef; } @@ -1353,20 +1666,19 @@ sub listview_get_all my @selected = $list->get_selected_indices(); my @ret; for my $i (@selected) { - my ($name, @other) = @{$list->{data}->[$i]}; - push @ret, [unpack('u', $name), @other]; + my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]}; + push @ret, [$pid,$fid,unpack('u', $name), @other]; } return @ret; } - sub listview_push { - my ($list, $name, @other) = @_; - push @{$list->{data}}, [pack('u', $name), @other]; + my ($list, $pid, $fid, $name, @other) = @_; + push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other]; } -#---------------------------------------------------------------------- +#----------------------------------------------------------------- # Handle keypress in file-view # * Translates backspace into a 'cd ..' command # * All other key presses left for GTK @@ -1379,7 +1691,7 @@ sub on_fileview_key_release_event return 0; } if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) { - $self->up_dir(); + $self->on_back_button_clicked(); return 1; # eat keypress } @@ -1391,7 +1703,7 @@ sub on_forward_keypress return 0; } -#---------------------------------------------------------------------- +#------------------------------------------------------------------- # Handle double-click (or enter) on file-view # * Translates into a 'cd ' command # @@ -1399,25 +1711,14 @@ sub on_fileview_row_activated { my ($self, $widget) = @_; - my ($name, undef, $type, undef) = listview_get_first($widget); + my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget); if ($type eq 'dir') { - if ($self->{cwd} eq '') - { - $self->ch_dir($name); - } - elsif ($self->{cwd} eq '/') - { - $self->ch_dir('/' . $name); - } - else - { - $self->ch_dir($self->{cwd} . '/' . $name); - } - + $self->{bvfs}->ch_dir($pid); + $self->refresh_fileview(); } else { - $self->fill_infoview($self->{cwd}, $name); + $self->fill_infoview($pid,$fid,$name); } return 1; # consume event @@ -1425,22 +1726,21 @@ sub on_fileview_row_activated sub fill_infoview { - my ($self, $path, $file) = @_; + my ($self, $path, $file, $fn) = @_; $self->clear_infoview(); - my @v = get_all_file_versions($self->{dbh}, - "$path/", - $file, - $self->current_client, - $self->{pref}->{see_all_versions}); + my @v = $self->{bvfs}->get_all_file_versions($path, + $file, + $self->current_client, + $self->{conf}->{see_all_versions}); for my $ver (@v) { - my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname) - = @{$ver}; + my (undef,$pid,$fid,$jobid,$fileindex,$mtime, + $size,$inchanger,$md5,$volname) = @{$ver}; my $icon = ($inchanger)?$yesicon:$noicon; $mtime = localtime($mtime) ; - listview_push($self->{fileinfo}, - $file, $jobid, 'file', + listview_push($self->{fileinfo},$pid,$fid, + $fn, $jobid, 'file', $icon, $volname, $jobid, human($size), $mtime, $md5); } } @@ -1463,12 +1763,12 @@ sub on_list_backups_changed return 0 unless defined $self->{fileview}; $self->{CurrentJobIds} = [ - set_job_ids_for_date($self->{dbh}, + set_job_ids_for_date($self->dbh(), $self->current_client, $self->current_date, - $self->{pref}->{use_ok_bkp_only}) + $self->{conf}->{use_ok_bkp_only}) ]; - + $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}}); $self->refresh_fileview(); 0; } @@ -1511,11 +1811,11 @@ sub on_see_all_version my @lst = listview_get_all($self->{fileview}); for my $i (@lst) { - my ($name, undef) = @{$i}; + my ($pid,$fid,$name, undef) = @{$i}; - new DlgFileVersion($self->{dbh}, + new DlgFileVersion($self->{bvfs}, $self->current_client, - $self->{cwd}, $name); + $pid,$fid,$self->{cwd},$name); } } @@ -1528,7 +1828,7 @@ sub on_right_click_filelist my $type = ''; if (@sel == 1) { - $type = $sel[0]->[2]; # $type + $type = $sel[0]->[4]; # $type } my $w; @@ -1557,18 +1857,17 @@ sub context_add_to_filelist foreach my $i (@sel) { - my ($file, $jobid, $type, undef) = @{$i}; - $file = $self->{cwd} . '/' . $file; - $self->add_selected_file_to_list($file, $jobid, $type); + my ($pid, $fid, $file, $jobid, $type, undef) = @{$i}; + $file = $self->{cwd} . $file; + $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type); } } # Adds a file to the filelist sub add_selected_file_to_list { - my ($self, $name, $jobid, $type)=@_; + my ($self, $pid, $fid, $name, $jobid, $type)=@_; - my $dbh = $self->{dbh}; my $restore_list = $self->{restore_list}; my $curjobids=join(',', @{$self->{CurrentJobIds}}); @@ -1582,16 +1881,16 @@ sub add_selected_file_to_list { $name .= '/'; # For bacula } - my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid); - listview_push($restore_list, + my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid); + listview_push($restore_list,$pid,0, $name, $jobid, 'dir', $curjobids, - $diricon, $name,$jobid,$dirfileindex); + $diricon, $name,$curjobids,$dirfileindex); } elsif ($type eq 'file') { - my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid); + my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid); - listview_push($restore_list, + listview_push($restore_list,$pid,$fid, $name, $jobid, 'file', $curjobids, $fileicon, $name, $jobid, $fileindex ); } @@ -1630,7 +1929,6 @@ sub set_job_ids_for_date AND JobStatus IN ($status) ORDER BY FileSet, JobTDate DESC"; - print $query,"\n" if $debug; my @CurrentJobIds; my $result = $dbh->selectall_arrayref($query); my %progress; @@ -1664,414 +1962,630 @@ sub set_job_ids_for_date $progress{$fileset} = $level; } } - print Data::Dumper::Dumper(\@CurrentJobIds) if $debug; return @CurrentJobIds; } -# Lists all directories contained inside a directory. -# Uses the current dir, the client name, and CurrentJobIds for visibility. -# Returns an array of dirs -sub list_dirs +sub refresh_screen { - my ($self,$dir,$client)=@_; - print "list_dirs($dir, $client)\n"; - - # Is data allready cached ? - if (not $self->{dirtree}->{$client}) - { - $self->cache_dirs($client); - } - - if ($dir ne '' and substr $dir,-1 ne '/') - { - $dir .= '/'; # In the db, there is a / at the end of the dirs ... - } - # Here, the tree is cached in ram - my @dir = split('/',$dir,-1); - pop @dir; # We don't need the empty trailing element - - # We have to get the reference of the hash containing $dir contents - # Get to the root - my $refdir=$self->{dirtree}->{$client}; - - # Find the leaf - foreach my $subdir (@dir) - { - if ($subdir eq '') - { - $subdir = '/'; - } - $refdir = $refdir->[0]->{$subdir}; - } - - # We reached the directory - my @return_list; - DIRLOOP: - foreach my $dir (sort(keys %{$refdir->[0]})) - { - # We return the directory's content : only visible directories - foreach my $jobid (reverse(sort(@{$self->{CurrentJobIds}}))) - { - if (defined $refdir->[0]->{$dir}->[1]->{$jobid}) - { - my $dirname = $refdir->[0]->{$dir}->[2]; # The real dirname... - push @return_list,($dirname); - next DIRLOOP; # No need to waste more CPU cycles... - } - } - } - print "LIST DIR : ", Data::Dumper::Dumper(\@return_list),"\n"; - return @return_list; + Gtk2->main_iteration while (Gtk2->events_pending); } - -# List all files in a directory. dir as parameter, CurrentJobIds for visibility -# Returns an array of dirs -sub list_files +# TODO : bsr must use only good backup or not (see use_ok_bkp_only) +# This sub creates a BSR from the information in the restore_list +# Returns the BSR as a string +sub create_filelist { - my ($self, $dir)=@_; - my $dbh = $self->{dbh}; + my $self = shift; + my %mediainfos; + # This query gets all jobid/jobmedia/media combination. + my $query = " +SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile, + JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex, + JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex, + Media.Volumename, Media.MediaType +FROM Job, JobMedia, Media +WHERE Job.JobId = JobMedia.JobId + AND JobMedia.MediaId = Media.MediaId + ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex"; + - my $empty = []; + my $result = $self->dbh_selectall_arrayref($query); - print "list_files($dir)\n"; + # We will store everything hashed by jobid. - if ($dir ne '' and substr $dir,-1 ne '/') - { - $dir .= '/'; # In the db, there is a / at the end of the dirs ... - } + foreach my $refrow (@$result) + { + my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile, + $firstindex, $lastindex, $startblock, $endblock, + $volindex, $volumename, $mediatype) = @{$refrow}; + + # We just have to deal with the case where starfile != endfile + # In this case, we concatenate both, for the bsr + if ($startfile != $endfile) { + $startfile = $startfile . '-' . $endfile; + } + + my @tmparray = + ($jobid, $volsessionid, $volsessiontime, $startfile, + $firstindex, $lastindex, $startblock .'-'. $endblock, + $volindex, $volumename, $mediatype); + + push @{$mediainfos{$refrow->[0]}},(\@tmparray); + } - my $query = "SELECT Path.PathId FROM Path WHERE Path.Path = '$dir'"; - print $query,"\n" if $debug; - my @list_pathid=(); - my $result = $dbh->selectall_arrayref($query); - foreach my $refrow (@$result) - { - push @list_pathid,($refrow->[0]); - } - if (@list_pathid == 0) - { - print "No pathid found for $dir\n" if $debug; - return $empty; - } + # reminder : restore_list looks like this : + # ($pid,$fid,$name,$jobid,'file',$curjobids, + # undef, undef, undef, $dirfileindex); - my $inlistpath = join (',', @list_pathid); - my $inclause = join (',', @{$self->{CurrentJobIds}}); - if ($inclause eq '') - { - return $empty; - } + # Here, we retrieve every file/dir that could be in the restore + # We do as simple as possible for the SQL engine (no crazy joins, + # no pseudo join (>= FirstIndex ...), etc ... + # We do a SQL union of all the files/dirs specified in the restore_list + my @select_queries; + foreach my $entry (@{$self->{restore_list}->{data}}) + { + if ($entry->[4] eq 'dir') + { + my $dirid = $entry->[0]; + my $inclause = $entry->[5]; #curjobids - $query = -"SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId - FROM - (SELECT Filename.Name, max(File.FileId) as id - FROM File, Filename - WHERE File.FilenameId = Filename.FilenameId - AND Filename.Name != '' - AND File.PathId IN ($inlistpath) - AND File.JobId IN ($inclause) - GROUP BY Filename.Name - ORDER BY Filename.Name) AS listfiles, -File -WHERE File.FileId = listfiles.id"; - - print $query,"\n" if $debug; - $result = $dbh->selectall_arrayref($query); - - return $result; -} + my $query = +"(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId + FROM File, Path, Filename + WHERE Path.PathId = File.PathId + AND File.FilenameId = Filename.FilenameId + AND Path.Path LIKE + (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path + WHERE PathId IN ($dirid) + ) + AND File.JobId IN ($inclause) )"; + push @select_queries,($query); + } + else + { + # It's a file. Great, we allready have most + # of what is needed. Simple and efficient query + my $dir = $entry->[0]; + my $file = $entry->[1]; + + my $jobid = $entry->[3]; + my $fileindex = $entry->[9]; + my $inclause = $entry->[5]; # curjobids + my $query = +"(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId + FROM File,Path,Filename + WHERE File.PathId = $dir + AND File.PathId = Path.PathId + AND File.FilenameId = $file + AND File.FilenameId = Filename.FilenameId + AND File.JobId = $jobid + ) +"; + push @select_queries,($query); + } + } + $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n"; -# For the dirs, because of the db schema, it's inefficient to get the -# directories contained inside other directories (regexp match or tossing -# lots of records...). So we load all the tree and cache it. The data is -# stored in a structure of this form : -# Each directory is an array. -# - In this array, the first element is a ref to next dir (hash) -# - The second element is a hash containing all jobids pointing -# on an array containing their lstat (or 1 if this jobid is there because -# of dependencies) -# - The third is the filename itself (it could get mangled because of -# the hashing...) - -# So it looks like this : -# $reftree->[ { 'dir1' => $refdir1 -# 'dir2' => $refdir2 -# ...... -# }, -# { 'jobid1' => 'lstat1', -# 'jobid2' => 'lstat2', -# 'jobid3' => 1 # This one is here for "visibility" -# }, -# 'dirname' -# ] - -# Client as a parameter -# Returns an array of dirs -sub cache_dirs -{ - my ($self, $client) = @_; - print "cache_dirs()\n"; - - $self->{dirtree}->{$client} = []; # reset cache - my $dbh = $self->{dbh}; + #Now we run the query and parse the result... + # there may be a lot of records, so we better be efficient + # We use the bind column method, working with references... - # TODO : If we get here, things could get lenghty ... draw a popup window . - my $widget = Gtk2::MessageDialog->new($self->{mainwin}, - 'destroy-with-parent', - 'info', 'none', - 'Populating cache'); - $widget->show; - Gtk2->main_iteration while (Gtk2->events_pending); - - # We have to build the tree, as it's the first time it is asked... - - - # First, we only need the jobids of the selected server. - # It's not the same as @CurrentJobIds (we need ALL the jobs) - # We get the JobIds first in order to have the best execution - # plan possible for the big query, with an in clause. - my $query; - my $status = get_wanted_job_status($self->{pref}->{use_ok_bkp_only}); - $query = -"SELECT JobId - FROM Job,Client - WHERE Job.ClientId = Client.ClientId - AND Client.Name = '$client' - AND Job.JobStatus IN ($status) - AND Job.Type = 'B'"; + my $sth = $self->dbh_prepare($query); + $sth->execute; + + my ($path,$name,$fileindex,$jobid); + $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid); - print $query,"\n" if $debug; - my $result = $dbh->selectall_arrayref($query); - my @jobids; - foreach my $record (@{$result}) - { - push @jobids,($record->[0]); - } - my $inclause = join(',',@jobids); - if ($inclause eq '') - { - $widget->destroy(); - $self->set_status("No previous backup found for $client"); - return (); - } + # The temp place we're going to save all file + # list to before the real list + my @temp_list; -# Then, still to help dear mysql, we'll retrieve the PathId from empty Path (directory entries...) - my @dirids; - $query = -"SELECT Filename.FilenameId FROM Filename WHERE Filename.Name=''"; + RECORD_LOOP: + while ($sth->fetchrow_arrayref()) + { + # This may look dumb, but we're going to do a join by ourselves, + # to save memory and avoid sending a complex query to mysql + my $complete_path = $path . $name; + my $is_dir = 0; + + if ( $name eq '') + { + $is_dir = 1; + } + + # Remove trailing slash (normalize file and dir name) + $complete_path =~ s/\/$//; + + # Let's find the ref(s) for the %mediainfo element(s) + # containing the data for this file + # There can be several matches. It is the pseudo join. + my $med_idx=0; + my $max_elt=@{$mediainfos{$jobid}}-1; + MEDIA_LOOP: + while($med_idx <= $max_elt) + { + my $ref = $mediainfos{$jobid}->[$med_idx]; + # First, can we get rid of the first elements of the + # array ? (if they don't contain valuable records + # anymore + if ($fileindex > $ref->[5]) + { + # It seems we don't need anymore + # this entry in %mediainfo (the input data + # is sorted...) + # We get rid of it. + shift @{$mediainfos{$jobid}}; + $max_elt--; + next MEDIA_LOOP; + } + # We will do work on this elt. We can ++ + # $med_idx for next loop + $med_idx++; - print $query,"\n" if $debug; - $result = $dbh->selectall_arrayref($query); - foreach my $record (@{$result}) - { - push @dirids,$record->[0]; - } - my $dirinclause = join(',',@dirids); - - # This query is a bit complicated : - # whe need to find all dir entries that should be displayed, even - # if the directory itself has no entry in File table (it means a file - # is explicitely chosen in the backup configuration) - # Here's what I wanted to do : -# $query = -# " -# SELECT T1.Path, T2.Lstat, T2.JobId -# FROM ( SELECT DISTINCT Path.PathId, Path.Path FROM File, Path -# WHERE File.PathId = Path.PathId -# AND File.JobId IN ($inclause)) AS T1 -# LEFT JOIN -# ( SELECT File.Lstat, File.JobId, File.PathId FROM File -# WHERE File.FilenameId IN ($dirinclause) -# AND File.JobId IN ($inclause)) AS T2 -# ON (T1.PathId = T2.PathId) -# "; - # It works perfectly with postgresql, but mysql doesn't seem to be able - # to do the hash join correcty, so the performance sucks. - # So it will be done in 4 steps : - # o create T1 and T2 as temp tables - # o create an index on T2.PathId - # o do the query - # o remove the temp tables - $query = " -CREATE TEMPORARY TABLE T1 AS -SELECT DISTINCT Path.PathId, Path.Path FROM File, Path -WHERE File.PathId = Path.PathId - AND File.JobId IN ($inclause) -"; - print $query,"\n" if $debug; - $dbh->do($query); - $query = " -CREATE TEMPORARY TABLE T2 AS -SELECT File.Lstat, File.JobId, File.PathId FROM File -WHERE File.FilenameId IN ($dirinclause) - AND File.JobId IN ($inclause) -"; - print $query,"\n" if $debug; - $dbh->do($query); - $query = " -CREATE INDEX tmp2 ON T2(PathId) -"; - print $query,"\n" if $debug; - $dbh->do($query); - - $query = " -SELECT T1.Path, T2.Lstat, T2.JobId -FROM T1 LEFT JOIN T2 -ON (T1.PathId = T2.PathId) -"; + # %mediainfo row looks like : + # (jobid,VolsessionId,VolsessionTime,File,FirstIndex, + # LastIndex,StartBlock-EndBlock,VolIndex,Volumename, + # MediaType) + + # We are in range. We store and continue looping + # in the medias + if ($fileindex >= $ref->[4]) + { + my @data = ($complete_path,$is_dir, + $fileindex,$ref); + push @temp_list,(\@data); + next MEDIA_LOOP; + } + + # We are not in range. No point in continuing looping + # We go to next record. + next RECORD_LOOP; + } + } + # Now we have the array. + # We're going to sort it, by + # path, volsessiontime DESC (get the most recent file...) + # The array rows look like this : + # complete_path,is_dir,fileindex, + # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex, + # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType) + @temp_list = sort {$a->[0] cmp $b->[0] + || $b->[3]->[2] <=> $a->[3]->[2] + } @temp_list; - print $query,"\n" if $debug; - $result = $dbh->selectall_arrayref($query); - - foreach my $record (@{$result}) - { - # Dirty hack to force the string encoding on perl... we don't - # want implicit conversions - my $path = pack "U0C*", unpack "C*",$record->[0]; - - my @path = split('/',$path,-1); - pop @path; # we don't need the trailing empty element - my $lstat = $record->[1]; - my $jobid = $record->[2]; - - # We're going to store all the data on the cache tree. - # We find the leaf, then store data there - my $reftree=$self->{dirtree}->{$client}; - foreach my $dir(@path) + my @restore_list; + my $prev_complete_path='////'; # Sure not to match + my $prev_is_file=1; + my $prev_jobid; + + while (my $refrow = shift @temp_list) { - if ($dir eq '') - { - $dir = '/'; - } - if (not defined($reftree->[0]->{$dir})) - { - my @tmparray; - $reftree->[0]->{$dir}=\@tmparray; - } - $reftree=$reftree->[0]->{$dir}; - $reftree->[2]=$dir; + # For the sake of readability, we load $refrow + # contents in real scalars + my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow}; + my $jobid= $refother->[0]; # We don't need the rest... + + # We skip this entry. + # We allready have a newer one and this + # isn't a continuation of the same file + next if ($complete_path eq $prev_complete_path + and $jobid != $prev_jobid); + + + if ($prev_is_file + and $complete_path =~ m|^\Q$prev_complete_path\E/|) + { + # We would be recursing inside a file. + # Just what we don't want (dir replaced by file + # between two backups + next; + } + elsif ($is_dir) + { + # It is a directory + push @restore_list,($refrow); + + $prev_complete_path = $complete_path; + $prev_jobid = $jobid; + $prev_is_file = 0; + } + else + { + # It is a file + push @restore_list,($refrow); + + $prev_complete_path = $complete_path; + $prev_jobid = $jobid; + $prev_is_file = 1; + } } - # We can now add the metadata for this dir ... - -# $result = $dbh->selectall_arrayref($query); - if ($lstat) + # We get rid of @temp_list... save memory + @temp_list=(); + + # Ok everything is in the list. Let's sort it again in another way. + # This time it will be in the bsr file order + + # we sort the results by + # volsessiontime, volsessionid, volindex, fileindex + # to get all files in right order... + # Reminder : The array rows look like this : + # complete_path,is_dir,fileindex, + # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex, + # StartBlock-EndBlock,VolIndex,Volumename,MediaType) + + @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2] + || $a->[3]->[1] <=> $b->[3]->[1] + || $a->[3]->[7] <=> $b->[3]->[7] + || $a->[2] <=> $b->[2] } + @restore_list; + + # Now that everything is ready, we create the bsr + my $prev_fileindex=-1; + my $prev_volsessionid=-1; + my $prev_volsessiontime=-1; + my $prev_volumename=-1; + my $prev_volfile=-1; + my $prev_mediatype; + my $prev_volblocks; + my $count=0; + my $first_of_current_range=0; + my @fileindex_ranges; + my $bsr=''; + + foreach my $refrow (@restore_list) + { + my (undef,undef,$fileindex,$refother)=@{$refrow}; + my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef, + $volblocks,undef,$volumename,$mediatype)=@{$refother}; + + # We can specifiy the number of files in each section of the + # bsr to speedup restore (bacula can then jump over the + # end of tape files. + $count++; + + + if ($prev_volumename eq '-1') + { + # We only have to start the new range... + $first_of_current_range=$fileindex; + } + elsif ($prev_volsessionid != $volsessionid + or $prev_volsessiontime != $volsessiontime + or $prev_volumename ne $volumename + or $prev_volfile ne $volfile) + { + # We have to create a new section in the bsr... + #Â We print the previous one ... + # (before that, save the current range ...) + if ($first_of_current_range != $prev_fileindex) + { + #Â we are in a range + push @fileindex_ranges, + ("$first_of_current_range-$prev_fileindex"); + } + else + { + # We are out of a range, + # but there is only one element in the range + push @fileindex_ranges, + ("$first_of_current_range"); + } + + $bsr.=print_bsr_section(\@fileindex_ranges, + $prev_volsessionid, + $prev_volsessiontime, + $prev_volumename, + $prev_volfile, + $prev_mediatype, + $prev_volblocks, + $count-1); + $count=1; + # Reset for next loop + @fileindex_ranges=(); + $first_of_current_range=$fileindex; + } + elsif ($fileindex-1 != $prev_fileindex) + { + # End of a range of fileindexes + if ($first_of_current_range != $prev_fileindex) + { + #we are in a range + push @fileindex_ranges, + ("$first_of_current_range-$prev_fileindex"); + } + else + { + # We are out of a range, + # but there is only one element in the range + push @fileindex_ranges, + ("$first_of_current_range"); + } + $first_of_current_range=$fileindex; + } + $prev_fileindex=$fileindex; + $prev_volsessionid = $volsessionid; + $prev_volsessiontime = $volsessiontime; + $prev_volumename = $volumename; + $prev_volfile=$volfile; + $prev_mediatype=$mediatype; + $prev_volblocks=$volblocks; + + } + + # Ok, we're out of the loop. Alas, there's still the last record ... + if ($first_of_current_range != $prev_fileindex) { - # contains something - $reftree->[1]->{$jobid}=$lstat; + # we are in a range + push @fileindex_ranges,("$first_of_current_range-$prev_fileindex"); + } else { - # We have a very special case here... - # lstat is not defined. - # it means the directory is there because a file has been - # backuped. so the dir has no entry in File table. - # That's a rare case, so we can afford to determine it's - # visibility with a query - my $select_path=$record->[0]; - $select_path=$dbh->quote($select_path); # gotta be careful - my $query = " -SELECT File.JobId -FROM File, Path -WHERE File.PathId = Path.PathId -AND Path.Path = $select_path -"; - print $query,"\n" if $debug; - my $result2 = $dbh->selectall_arrayref($query); - foreach my $record (@{$result2}) - { - my $jobid=$record->[0]; - $reftree->[1]->{$jobid}=1; - } + # We are out of a range, + # but there is only one element in the range + push @fileindex_ranges,("$first_of_current_range"); + } + $bsr.=print_bsr_section(\@fileindex_ranges, + $prev_volsessionid, + $prev_volsessiontime, + $prev_volumename, + $prev_volfile, + $prev_mediatype, + $prev_volblocks, + $count); + return $bsr; +} + +sub print_bsr_section +{ + my ($ref_fileindex_ranges,$volsessionid, + $volsessiontime,$volumename,$volfile, + $mediatype,$volblocks,$count)=@_; + + my $bsr=''; + $bsr .= "Volume=\"$volumename\"\n"; + $bsr .= "MediaType=\"$mediatype\"\n"; + $bsr .= "VolSessionId=$volsessionid\n"; + $bsr .= "VolSessionTime=$volsessiontime\n"; + $bsr .= "VolFile=$volfile\n"; + $bsr .= "VolBlock=$volblocks\n"; + + foreach my $range (@{$ref_fileindex_ranges}) + { + $bsr .= "FileIndex=$range\n"; } - $query = " -DROP TABLE T1; -"; - print $query,"\n" if $debug; - $dbh->do($query); - $query = " -DROP TABLE T2; -"; - print $query,"\n" if $debug; - $dbh->do($query); + + $bsr .= "Count=$count\n"; + return $bsr; +} +1; + +################################################################ - list_visible($self->{dirtree}->{$client}); - $widget->destroy(); +package Bvfs; +use base qw/Bbase/; -# print Data::Dumper::Dumper($self->{dirtree}); +sub get_pathid +{ + my ($self, $dir) = @_; + my $query = + "SELECT PathId FROM Path WHERE Path = ?"; + my $sth = $self->dbh_prepare($query); + $sth->execute($dir); + my $result = $sth->fetchall_arrayref(); + $sth->finish(); + + return join(',', map { $_->[0] } @$result); } -# Recursive function to calculate the visibility of each directory in the cache -# tree Working with references to save time and memory -# For each directory, we want to propagate it's visible jobids onto it's -# parents directory. -# A tree is visible if -# - it's been in a backup pointed by the CurrentJobIds -# - one of it's subdirs is in a backup pointed by the CurrentJobIds -# In the second case, the directory is visible but has no metadata. -# We symbolize this with lstat = 1 for this jobid in the cache. -# Input : reference directory -# Output : visibility of this dir. Has to know visibility of all subdirs -# to know it's visibility, hence the recursing. -sub list_visible +sub update_cache { - my ($refdir)=@_; - - my %visibility; - # Get the subdirs array references list - my @list_ref_subdirs; - while( my (undef,$ref_subdir) = each (%{$refdir->[0]})) - { - push @list_ref_subdirs,($ref_subdir); - } + my ($self) = @_; - # Now lets recurse over these subdirs and retrieve the reference of a hash - # containing the jobs where they are visible - foreach my $ref_subdir (@list_ref_subdirs) - { - my $ref_list_jobs = list_visible($ref_subdir); - foreach my $jobid (keys %$ref_list_jobs) - { - $visibility{$jobid}=1; - } + $self->{conf}->{dbh}->begin_work(); + + my $query = " + SELECT JobId from Job + WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) AND JobStatus IN ('T', 'f', 'A') ORDER BY JobId"; + my $jobs = $self->dbh_selectall_arrayref($query); + + $self->update_brestore_table(map { $_->[0] } @$jobs); + + $self->{conf}->{dbh}->commit(); + $self->{conf}->{dbh}->begin_work(); + + print STDERR "Cleaning path visibility\n"; + + my $nb = $self->dbh_do(" + DELETE FROM brestore_pathvisibility + WHERE NOT EXISTS + (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)"); + + print STDERR "$nb rows affected\n"; + print STDERR "Cleaning known jobid\n"; + + $nb = $self->dbh_do(" + DELETE FROM brestore_knownjobid + WHERE NOT EXISTS + (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)"); + + print STDERR "$nb rows affected\n"; + + $self->{conf}->{dbh}->commit(); +} + +sub get_root +{ + my ($self, $dir) = @_; + return $self->get_pathid(''); +} + +sub ch_dir +{ + my ($self, $pathid) = @_; + $self->{cwdid} = $pathid; +} + +sub up_dir +{ + my ($self) = @_ ; + my $query = " + SELECT PPathId + FROM brestore_pathhierarchy + WHERE PathId IN ($self->{cwdid}) "; + + my $all = $self->dbh_selectall_arrayref($query); + return unless ($all); # already at root + + my $dir = join(',', map { $_->[0] } @$all); + if ($dir) { + $self->ch_dir($dir); } +} + +sub pwd +{ + my ($self) = @_; + return $self->get_path($self->{cwdid}); +} + +sub get_path +{ + my ($self, $pathid) = @_; + $self->debug("Call with pathid = $pathid"); + my $query = + "SELECT Path FROM Path WHERE PathId IN (?)"; + + my $sth = $self->dbh_prepare($query); + $sth->execute($pathid); + my $result = $sth->fetchrow_arrayref(); + $sth->finish(); + return $result->[0]; +} + +sub set_curjobids +{ + my ($self, @jobids) = @_; + $self->{curjobids} = join(',', @jobids); + $self->update_brestore_table(@jobids); +} + +sub ls_files +{ + my ($self) = @_; + + return undef unless ($self->{curjobids}); + + my $inclause = $self->{curjobids}; + my $inlistpath = $self->{cwdid}; + + my $query = +"SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId + FROM + (SELECT Filename.Name, max(File.FileId) as id + FROM File, Filename + WHERE File.FilenameId = Filename.FilenameId + AND Filename.Name != '' + AND File.PathId IN ($inlistpath) + AND File.JobId IN ($inclause) + GROUP BY Filename.Name + ORDER BY Filename.Name) AS listfiles, +File +WHERE File.FileId = listfiles.id"; + + $self->debug($query); + my $result = $self->dbh_selectall_arrayref($query); + $self->debug($result); + + return $result; +} - # Ok. Now, we've got the list of those jobs. We are going to update our - # hash (element 1 of the dir array) containing our jobs Do NOT overwrite - # the lstat for the known jobids. Put 1 in the new elements... But first, - # let's store the current jobids - my @known_jobids; - foreach my $jobid (keys %{$refdir->[1]}) - { - push @known_jobids,($jobid); - } +# return ($dirid,$dir_basename,$lstat,$jobid) +sub ls_dirs +{ + my ($self) = @_; + + return undef unless ($self->{curjobids}); + + my $pathid = $self->{cwdid}; + my $jobclause = $self->{curjobids}; + + # Let's retrieve the list of the visible dirs in this dir ... + # First, I need the empty filenameid to locate efficiently the dirs in the file table + my $query = "SELECT FilenameId FROM Filename WHERE Name = ''"; + my $sth = $self->dbh_prepare($query); + $sth->execute(); + my $result = $sth->fetchrow_arrayref(); + $sth->finish(); + my $dir_filenameid = $result->[0]; + + # Then we get all the dir entries from File ... + $query = " +SELECT PathId, Path, JobId, Lstat FROM ( - # Add the new jobs - foreach my $jobid (keys %visibility) - { - next if ($refdir->[1]->{$jobid}); - $refdir->[1]->{$jobid} = 1; - } - # Add the known_jobids to %visibility - foreach my $jobid (@known_jobids) + SELECT Path1.PathId, Path1.Path, lower(Path1.Path), + listfile1.JobId, listfile1.Lstat + FROM ( + SELECT DISTINCT brestore_pathhierarchy1.PathId + FROM brestore_pathhierarchy AS brestore_pathhierarchy1 + JOIN Path AS Path2 + ON (brestore_pathhierarchy1.PathId = Path2.PathId) + JOIN brestore_pathvisibility AS brestore_pathvisibility1 + ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId) + WHERE brestore_pathhierarchy1.PPathId = $pathid + AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1 + JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) + LEFT JOIN ( + SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1 + WHERE File1.FilenameId = $dir_filenameid + AND File1.JobId IN ($jobclause)) AS listfile1 + ON (listpath1.PathId = listfile1.PathId) + ) AS A ORDER BY 2,3 DESC +"; + $self->debug($query); + $sth=$self->dbh_prepare($query); + $sth->execute(); + $result = $sth->fetchall_arrayref(); + my @return_list; + my $prev_dir=''; + foreach my $refrow (@{$result}) { - $visibility{$jobid}=1; + my $dirid = $refrow->[0]; + my $dir = $refrow->[1]; + my $lstat = $refrow->[3]; + my $jobid = $refrow->[2] || 0; + next if ($dirid eq $prev_dir); + # We have to clean up this dirname ... we only want it's 'basename' + my $return_value; + if ($dir ne '/') + { + my @temp = split ('/',$dir); + $return_value = pop @temp; + } + else + { + $return_value = '/'; + } + my @return_array = ($dirid,$return_value,$lstat,$jobid); + push @return_list,(\@return_array); + $prev_dir = $dirid; } - return \%visibility; + $self->debug(\@return_list); + return \@return_list; } # Returns the list of media required for a list of jobids. -# Input : dbh, jobid1, jobid2... +# Input : self, jobid1, jobid2... # Output : reference to array of (joibd, inchanger) sub get_required_media_from_jobid { - my ($dbh, @jobids)=@_; + my ($self, @jobids)=@_; my $inclause = join(',',@jobids); my $query = " SELECT DISTINCT JobMedia.MediaId, Media.InChanger @@ -2079,68 +2593,139 @@ FROM JobMedia, Media WHERE JobMedia.MediaId=Media.MediaId AND JobId In ($inclause) ORDER BY MediaId"; - my $result = $dbh->selectall_arrayref($query); + my $result = $self->dbh_selectall_arrayref($query); return $result; } # Returns the fileindex from dirname and jobid. -# Input : dbh, dirname, jobid +# Input : self, dirid, jobid # Output : fileindex sub get_fileindex_from_dir_jobid { - my ($dbh, $dirname, $jobid)=@_; + my ($self, $dirid, $jobid)=@_; my $query; $query = "SELECT File.FileIndex - FROM File, Filename, Path + FROM File, Filename WHERE File.FilenameId = Filename.FilenameId - AND File.PathId = Path.PathId + AND File.PathId = $dirid AND Filename.Name = '' - AND Path.Path = '$dirname' AND File.JobId = '$jobid' "; - print $query,"\n" if $debug; - my $result = $dbh->selectall_arrayref($query); + $self->debug($query); + my $result = $self->dbh_selectall_arrayref($query); return $result->[0]->[0]; } # Returns the fileindex from filename and jobid. -# Input : dbh, filename, jobid +# Input : self, dirid, filenameid, jobid # Output : fileindex sub get_fileindex_from_file_jobid { - my ($dbh, $filename, $jobid)=@_; - - my @dirs = File::Spec->splitdir ($filename); - $filename=pop(@dirs); - my $dirname = File::Spec->catdir(@dirs) . '/'; - + my ($self, $dirid, $filenameid, $jobid)=@_; my $query; $query = "SELECT File.FileIndex - FROM File, Filename, Path - WHERE File.FilenameId = Filename.FilenameId - AND File.PathId = Path.PathId - AND Filename.Name = '$filename' - AND Path.Path = '$dirname' - AND File.JobId = '$jobid'"; + FROM File + WHERE File.PathId = $dirid + AND File.FilenameId = $filenameid + AND File.JobId = $jobid"; - print $query,"\n" if $debug; - my $result = $dbh->selectall_arrayref($query); + $self->debug($query); + my $result = $self->dbh_selectall_arrayref($query); return $result->[0]->[0]; } +# This function estimates the size to be restored for an entry of the restore +# list +# In : self,reference to the entry +# Out : size in bytes, number of files +sub estimate_restore_size +{ + # reminder : restore_list looks like this : + # ($pid,$fid,$name,$jobid,'file',$curjobids, + # undef, undef, undef, $dirfileindex); + my ($self, $entry, $refresh) = @_; + my $query; + if ($entry->[4] eq 'dir') + { + my $dir = $entry->[0]; + + my $inclause = $entry->[5]; #curjobids + $query = +"SELECT Path.Path, File.FilenameId, File.LStat + FROM File, Path, Job + WHERE Path.PathId = File.PathId + AND File.JobId = Job.JobId + AND Path.Path LIKE + (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir) + ) + AND File.JobId IN ($inclause) + ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC"; + } + else + { + # It's a file. Great, we allready have most + # of what is needed. Simple and efficient query + my $dir = $entry->[0]; + my $fileid = $entry->[1]; + + my $jobid = $entry->[3]; + my $fileindex = $entry->[9]; + my $inclause = $entry->[5]; # curjobids + $query = +"SELECT Path.Path, File.FilenameId, File.Lstat + FROM File, Path + WHERE Path.PathId = File.PathId + AND Path.PathId = $dir + AND File.FilenameId = $fileid + AND File.JobId = $jobid"; + } + + my ($path,$nameid,$lstat); + my $sth = $self->dbh_prepare($query); + $sth->execute; + $sth->bind_columns(\$path,\$nameid,\$lstat); + my $old_path=''; + my $old_nameid=''; + my $total_size=0; + my $total_files=0; + + &$refresh(); + + my $rcount=0; + # We fetch all rows + while ($sth->fetchrow_arrayref()) + { + # Only the latest version of a file + next if ($nameid eq $old_nameid and $path eq $old_path); + + if ($rcount > 15000) { + &$refresh(); + $rcount=0; + } else { + $rcount++; + } + + # We get the size of this file + my $size=lstat_attrib($lstat,'st_size'); + $total_size += $size; + $total_files++; + $old_path=$path; + $old_nameid=$nameid; + } + + return ($total_size,$total_files); +} # Returns list of versions of a file that could be restored # returns an array of -# ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname) -# It's the same as entries of restore_list (hidden) + mtime and size and inchanger -# and volname and md5 -# and of course, there will be only one jobid in the array of jobids... +# ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname) +# there will be only one jobid in the array of jobids... sub get_all_file_versions { - my ($dbh,$path,$file,$client,$see_all)=@_; + my ($self,$pathid,$fileid,$client,$see_all)=@_; defined $see_all or $see_all=0; @@ -2149,22 +2734,20 @@ sub get_all_file_versions $query = "SELECT File.JobId, File.FileIndex, File.Lstat, File.Md5, Media.VolumeName, Media.InChanger - FROM File, Filename, Path, Job, Client, JobMedia, Media - WHERE File.FilenameId = Filename.FilenameId - AND File.PathId=Path.PathId + FROM File, Job, Client, JobMedia, Media + WHERE File.FilenameId = $fileid + AND File.PathId=$pathid AND File.JobId = Job.JobId AND Job.ClientId = Client.ClientId AND Job.JobId = JobMedia.JobId AND File.FileIndex >= JobMedia.FirstIndex AND File.FileIndex <= JobMedia.LastIndex AND JobMedia.MediaId = Media.MediaId - AND Path.Path = '$path' - AND Filename.Name = '$file' AND Client.Name = '$client'"; - print $query if $debug; + $self->debug($query); - my $result = $dbh->selectall_arrayref($query); + my $result = $self->dbh_selectall_arrayref($query); foreach my $refrow (@$result) { @@ -2173,8 +2756,9 @@ sub get_all_file_versions my $mtime = array_attrib('st_mtime',\@attribs); my $size = array_attrib('st_size',\@attribs); - my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size, - $inchanger, $md5, $volname); + my @list = ('FILE:',$pathid,$fileid,$jobid, + $fileindex, $mtime, $size, $inchanger, + $md5, $volname); push @versions, (\@list); } @@ -2182,10 +2766,11 @@ sub get_all_file_versions # We'll sort it by mtime desc, size, md5, inchanger desc # the rest of the algorithm will be simpler # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname) - @versions = sort { $b->[4] <=> $a->[4] - || $a->[5] <=> $b->[5] - || $a->[7] cmp $a->[7] - || $b->[6] <=> $a->[6]} @versions; + @versions = sort { $b->[5] <=> $a->[5] + || $a->[6] <=> $b->[6] + || $a->[8] cmp $a->[8] + || $b->[7] <=> $a->[7]} @versions; + my @good_versions; my %allready_seen_by_mtime; @@ -2193,25 +2778,25 @@ sub get_all_file_versions # Now we should create a new array with only the interesting records foreach my $ref (@versions) { - if ($ref->[7]) + if ($ref->[8]) { # The file has a md5. We compare his md5 to other known md5... - # We take size into account. It may happen that 2 files + # We take size into account. It may happen that 2 files # have the same md5sum and are different. size is a supplementary # criterion # If we allready have a (better) version next if ( (not $see_all) - and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}); + and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}); # we never met this one before... - $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1; + $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1; } # Even if it has a md5, we should also work with mtimes # We allready have a (better) version next if ( (not $see_all) - and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]}); - $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1; + and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]}); + $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1; # We reached there. The file hasn't been seen. push @good_versions,($ref); @@ -2219,401 +2804,243 @@ sub get_all_file_versions # To be nice with the user, we re-sort good_versions by # inchanger desc, mtime desc - @good_versions = sort { $b->[4] <=> $a->[4] - || $b->[2] <=> $a->[2]} @good_versions; + @good_versions = sort { $b->[5] <=> $a->[5] + || $b->[3] <=> $a->[3]} @good_versions; return @good_versions; } -# TODO : bsr must use only good backup or not (see use_ok_bkp_only) -# This sub creates a BSR from the information in the restore_list -# Returns the BSR as a string -sub create_filelist -{ - my $self = shift; - my $dbh = $self->{dbh}; - my %mediainfos; - # This query gets all jobid/jobmedia/media combination. - my $query = " -SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile, - JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex, - JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex, - Media.Volumename, Media.MediaType -FROM Job, JobMedia, Media -WHERE Job.JobId = JobMedia.JobId - AND JobMedia.MediaId = Media.MediaId - ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex"; - - - my $result = $dbh->selectall_arrayref($query); - - # We will store everything hashed by jobid. - - foreach my $refrow (@$result) - { - my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile, - $firstindex, $lastindex, $startblock, $endblock, - $volindex, $volumename, $mediatype) = @{$refrow}; - - # We just have to deal with the case where starfile != endfile - # In this case, we concatenate both, for the bsr - if ($startfile != $endfile) { - $startfile = $startfile . '-' . $endfile; - } - - my @tmparray = - ($jobid, $volsessionid, $volsessiontime, $startfile, - $firstindex, $lastindex, $startblock .'-'. $endblock, - $volindex, $volumename, $mediatype); - - push @{$mediainfos{$refrow->[0]}},(\@tmparray); - } - - - # reminder : restore_list looks like this : - # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex); - - # Here, we retrieve every file/dir that could be in the restore - # We do as simple as possible for the SQL engine (no crazy joins, - # no pseudo join (>= FirstIndex ...), etc ... - # We do a SQL union of all the files/dirs specified in the restore_list - my @select_queries; - foreach my $entry (@{$self->{restore_list}->{data}}) - { - if ($entry->[2] eq 'dir') - { - my $dir = unpack('u', $entry->[0]); - my $inclause = $entry->[3]; #curjobids - - my $query = -"(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId - FROM File, Path, Filename - WHERE Path.PathId = File.PathId - AND File.FilenameId = Filename.FilenameId - AND Path.Path LIKE '$dir%' - AND File.JobId IN ($inclause) )"; - push @select_queries,($query); - } - else - { - # It's a file. Great, we allready have most - # of what is needed. Simple and efficient query - my $file = unpack('u', $entry->[0]); - my @file = split '/',$file; - $file = pop @file; - my $dir = join('/',@file); - - my $jobid = $entry->[1]; - my $fileindex = $entry->[7]; - my $inclause = $entry->[3]; # curjobids - my $query = -"(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId - FROM File, Path, Filename - WHERE Path.PathId = File.PathId - AND File.FilenameId = Filename.FilenameId - AND Path.Path = '$dir/' - AND Filename.Name = '$file' - AND File.JobId = $jobid)"; - push @select_queries,($query); - } - } - $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n"; - - print $query,"\n" if $debug; - - #Now we run the query and parse the result... - # there may be a lot of records, so we better be efficient - # We use the bind column method, working with references... - - my $sth = $dbh->prepare($query); - $sth->execute; - - my ($path,$name,$fileindex,$jobid); - $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid); - - # The temp place we're going to save all file - # list to before the real list - my @temp_list; - - RECORD_LOOP: - while ($sth->fetchrow_arrayref()) - { - # This may look dumb, but we're going to do a join by ourselves, - # to save memory and avoid sending a complex query to mysql - my $complete_path = $path . $name; - my $is_dir = 0; - - if ( $name eq '') - { - $is_dir = 1; - } - - # Remove trailing slash (normalize file and dir name) - $complete_path =~ s/\/$//; - - # Let's find the ref(s) for the %mediainfo element(s) - # containing the data for this file - # There can be several matches. It is the pseudo join. - my $med_idx=0; - my $max_elt=@{$mediainfos{$jobid}}-1; - MEDIA_LOOP: - while($med_idx <= $max_elt) - { - my $ref = $mediainfos{$jobid}->[$med_idx]; - # First, can we get rid of the first elements of the - # array ? (if they don't contain valuable records - # anymore - if ($fileindex > $ref->[5]) - { - # It seems we don't need anymore - # this entry in %mediainfo (the input data - # is sorted...) - # We get rid of it. - shift @{$mediainfos{$jobid}}; - $max_elt--; - next MEDIA_LOOP; - } - # We will do work on this elt. We can ++ - # $med_idx for next loop - $med_idx++; - - # %mediainfo row looks like : - # (jobid,VolsessionId,VolsessionTime,File,FirstIndex, - # LastIndex,StartBlock-EndBlock,VolIndex,Volumename, - # MediaType) - - # We are in range. We store and continue looping - # in the medias - if ($fileindex >= $ref->[4]) - { - my @data = ($complete_path,$is_dir, - $fileindex,$ref); - push @temp_list,(\@data); - next MEDIA_LOOP; - } - - # We are not in range. No point in continuing looping - # We go to next record. - next RECORD_LOOP; - } - } - # Now we have the array. - # We're going to sort it, by - # path, volsessiontime DESC (get the most recent file...) - # The array rows look like this : - # complete_path,is_dir,fileindex, - # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex, - # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType) - @temp_list = sort {$a->[0] cmp $b->[0] - || $b->[3]->[2] <=> $a->[3]->[2] - } @temp_list; - my @restore_list; - my $prev_complete_path='////'; # Sure not to match - my $prev_is_file=1; - my $prev_jobid; +sub update_brestore_table +{ + my ($self, @jobs) = @_; - while (my $refrow = shift @temp_list) - { - # For the sake of readability, we load $refrow - # contents in real scalars - my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow}; - my $jobid= $refother->[0]; # We don't need the rest... + $self->debug(\@jobs); - # We skip this entry. - # We allready have a newer one and this - # isn't a continuation of the same file - next if ($complete_path eq $prev_complete_path - and $jobid != $prev_jobid); - - - if ($prev_is_file - and $complete_path =~ m|^\Q$prev_complete_path\E/|) - { - # We would be recursing inside a file. - # Just what we don't want (dir replaced by file - # between two backups - next; - } - elsif ($is_dir) - { - # It is a directory - push @restore_list,($refrow); - - $prev_complete_path = $complete_path; - $prev_jobid = $jobid; - $prev_is_file = 0; - } - else - { - # It is a file - push @restore_list,($refrow); - - $prev_complete_path = $complete_path; - $prev_jobid = $jobid; - $prev_is_file = 1; - } - } - # We get rid of @temp_list... save memory - @temp_list=(); + foreach my $job (sort {$a <=> $b} @jobs) + { + my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job"; + my $retour = $self->dbh_selectrow_arrayref($query); + next if ($retour and ($retour->[0] == 1)); # We have allready done this one ... - # Ok everything is in the list. Let's sort it again in another way. - # This time it will be in the bsr file order + print STDERR "Inserting path records for JobId $job\n"; + $query = "INSERT INTO brestore_pathvisibility (PathId, JobId) + (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)"; - # we sort the results by - # volsessiontime, volsessionid, volindex, fileindex - # to get all files in right order... - # Reminder : The array rows look like this : - # complete_path,is_dir,fileindex, - # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex, - # StartBlock-EndBlock,VolIndex,Volumename,MediaType) + $self->dbh_do($query); - @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2] - || $a->[3]->[1] <=> $b->[3]->[1] - || $a->[3]->[7] <=> $b->[3]->[7] - || $a->[2] <=> $b->[2] } - @restore_list; + # Now we have to do the directory recursion stuff to determine missing visibility + # We try to avoid recursion, to be as fast as possible + # We also only work on not allready hierarchised directories... - # Now that everything is ready, we create the bsr - my $prev_fileindex=-1; - my $prev_volsessionid=-1; - my $prev_volsessiontime=-1; - my $prev_volumename=-1; - my $prev_volfile=-1; - my $prev_mediatype; - my $prev_volblocks; - my $count=0; - my $first_of_current_range=0; - my @fileindex_ranges; - my $bsr=''; + print STDERR "Creating missing recursion paths for $job\n"; - foreach my $refrow (@restore_list) - { - my (undef,undef,$fileindex,$refother)=@{$refrow}; - my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef, - $volblocks,undef,$volumename,$mediatype)=@{$refother}; - - # We can specifiy the number of files in each section of the - # bsr to speedup restore (bacula can then jump over the - # end of tape files. - $count++; - - - if ($prev_volumename eq '-1') - { - # We only have to start the new range... - $first_of_current_range=$fileindex; - } - elsif ($prev_volsessionid != $volsessionid - or $prev_volsessiontime != $volsessiontime - or $prev_volumename ne $volumename - or $prev_volfile != $volfile) - { - # We have to create a new section in the bsr... - # We print the previous one ... - # (before that, save the current range ...) - if ($first_of_current_range != $prev_fileindex) - { - # we are in a range - push @fileindex_ranges, - ("$first_of_current_range-$prev_fileindex"); - } - else - { - # We are out of a range, - # but there is only one element in the range - push @fileindex_ranges, - ("$first_of_current_range"); - } - - $bsr.=print_bsr_section(\@fileindex_ranges, - $prev_volsessionid, - $prev_volsessiontime, - $prev_volumename, - $prev_volfile, - $prev_mediatype, - $prev_volblocks, - $count-1); - $count=1; - # Reset for next loop - @fileindex_ranges=(); - $first_of_current_range=$fileindex; - } - elsif ($fileindex-1 != $prev_fileindex) - { - # End of a range of fileindexes - if ($first_of_current_range != $prev_fileindex) - { - #we are in a range - push @fileindex_ranges, - ("$first_of_current_range-$prev_fileindex"); - } - else - { - # We are out of a range, - # but there is only one element in the range - push @fileindex_ranges, - ("$first_of_current_range"); - } - $first_of_current_range=$fileindex; - } - $prev_fileindex=$fileindex; - $prev_volsessionid = $volsessionid; - $prev_volsessiontime = $volsessiontime; - $prev_volumename = $volumename; - $prev_volfile=$volfile; - $prev_mediatype=$mediatype; - $prev_volblocks=$volblocks; + $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility + JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) + LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) + WHERE brestore_pathvisibility.JobId = $job + AND brestore_pathhierarchy.PathId IS NULL + ORDER BY Path"; + my $sth = $self->dbh_prepare($query); + $sth->execute(); + my $pathid; my $path; + $sth->bind_columns(\$pathid,\$path); + + while ($sth->fetch) + { + $self->build_path_hierarchy($path,$pathid); } - - # Ok, we're out of the loop. Alas, there's still the last record ... - if ($first_of_current_range != $prev_fileindex) + $sth->finish(); + + # Great. We have calculated all dependancies. We can use them to add the missing pathids ... + # This query gives all parent pathids for a given jobid that aren't stored. + # It has to be called until no record is updated ... + $query = " + INSERT INTO brestore_pathvisibility (PathId, JobId) ( + SELECT a.PathId,$job + FROM + (SELECT DISTINCT h.PPathId AS PathId + FROM brestore_pathhierarchy AS h + JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId) + WHERE p.JobId=$job) AS a + LEFT JOIN + (SELECT PathId + FROM brestore_pathvisibility + WHERE JobId=$job) AS b + ON (a.PathId = b.PathId) + WHERE b.PathId IS NULL)"; + + my $rows_affected; + while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/)) { - # we are in a range - push @fileindex_ranges,("$first_of_current_range-$prev_fileindex"); - + print STDERR "Recursively adding $rows_affected records from $job\n"; } - else + # Job's done + $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)"; + $self->dbh_do($query); + } +} + +sub parent_dir +{ + my ($path) = @_; + # Root Unix case : + if ($path eq '/') + { + return ''; + } + # Root Windows case : + if ($path =~ /^[a-z]+:\/$/i) + { + return ''; + } + # Split + my @tmp = split('/',$path); + # We remove the last ... + pop @tmp; + my $tmp = join ('/',@tmp) . '/'; + return $tmp; +} + +sub build_path_hierarchy +{ + my ($self, $path,$pathid)=@_; + # Does the ppathid exist for this ? we use a memory cache... + # In order to avoid the full loop, we consider that if a dir is allready in the + # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy + while ($path ne '') + { + if (! $self->{cache_ppathid}->{$pathid}) { - # We are out of a range, - # but there is only one element in the range - push @fileindex_ranges,("$first_of_current_range"); + my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?"; + my $sth2 = $self->{conf}->{dbh}->prepare_cached($query); + $sth2->execute($pathid); + # Do we have a result ? + if (my $refrow = $sth2->fetchrow_arrayref) + { + $self->{cache_ppathid}->{$pathid}=$refrow->[0]; + $sth2->finish(); + # This dir was in the db ... + # It means we can leave, the tree has allready been built for + # this dir + return 1; + } else { + $sth2->finish(); + # We have to create the record ... + # What's the current p_path ? + my $ppath = parent_dir($path); + my $ppathid = $self->return_pathid_from_path($ppath); + $self->{cache_ppathid}->{$pathid}= $ppathid; + $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)"; + $sth2 = $self->{conf}->{dbh}->prepare_cached($query); + $sth2->execute($pathid,$ppathid); + $sth2->finish(); + $path = $ppath; + $pathid = $ppathid; + } + } else { + # It's allready in the cache. + # We can leave, no time to waste here, all the parent dirs have allready + # been done + return 1; } - $bsr.=print_bsr_section(\@fileindex_ranges, - $prev_volsessionid, - $prev_volsessiontime, - $prev_volumename, - $prev_volfile, - $prev_mediatype, - $prev_volblocks, - $count); - - return $bsr; + } + return 1; } -sub print_bsr_section + +sub return_pathid_from_path { - my ($ref_fileindex_ranges,$volsessionid, - $volsessiontime,$volumename,$volfile, - $mediatype,$volblocks,$count)=@_; - - my $bsr=''; - $bsr .= "Volume=\"$volumename\"\n"; - $bsr .= "MediaType=\"$mediatype\"\n"; - $bsr .= "VolSessionId=$volsessionid\n"; - $bsr .= "VolSessionTime=$volsessiontime\n"; - $bsr .= "VolFile=$volfile\n"; - $bsr .= "VolBlock=$volblocks\n"; - - foreach my $range (@{$ref_fileindex_ranges}) + my ($self, $path) = @_; + my $query = "SELECT PathId FROM Path WHERE Path = ?"; + + #print STDERR $query,"\n" if $debug; + my $sth = $self->{conf}->{dbh}->prepare_cached($query); + $sth->execute($path); + my $result =$sth->fetchrow_arrayref(); + $sth->finish(); + if (defined $result) { - $bsr .= "FileIndex=$range\n"; + return $result->[0]; + + } else { + # A bit dirty : we insert into path, and we have to be sure + # we aren't deleted by a purge. We still need to insert into path to get + # the pathid, because of mysql + $query = "INSERT INTO Path (Path) VALUES (?)"; + #print STDERR $query,"\n" if $debug; + $sth = $self->{conf}->{dbh}->prepare_cached($query); + $sth->execute($path); + $sth->finish(); + + $query = "SELECT PathId FROM Path WHERE Path = ?"; + #print STDERR $query,"\n" if $debug; + $sth = $self->{conf}->{dbh}->prepare_cached($query); + $sth->execute($path); + $result = $sth->fetchrow_arrayref(); + $sth->finish(); + return $result->[0]; + } +} + + +sub create_brestore_tables +{ + my ($self) = @_; + my $ret = 0; + my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1"; + + unless ($self->dbh_do($verif)) { + $ret=1; + + my $req = " + CREATE TABLE brestore_knownjobid + ( + JobId int4 NOT NULL, + CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId) + )"; + $self->dbh_do($req); } - $bsr .= "Count=$count\n"; - return $bsr; + $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1"; + unless ($self->dbh_do($verif)) { + $ret=1; + my $req = " + CREATE TABLE brestore_pathhierarchy + ( + PathId int4 NOT NULL, + PPathId int4 NOT NULL, + CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId) + )"; + $self->dbh_do($req); + + + $req = "CREATE INDEX brestore_pathhierarchy_ppathid + ON brestore_pathhierarchy (PPathId)"; + $self->dbh_do($req); + } + + $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1"; + unless ($self->dbh_do($verif)) { + $ret=1; + my $req = " + CREATE TABLE brestore_pathvisibility + ( + PathId int4 NOT NULL, + JobId int4 NOT NULL, + Size int8 DEFAULT 0, + Files int4 DEFAULT 0, + CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId) + )"; + $self->dbh_do($req); + + $req = "CREATE INDEX brestore_pathvisibility_jobid + ON brestore_pathvisibility (JobId)"; + $self->dbh_do($req); + } + return $ret; } # Get metadata @@ -2631,68 +3058,38 @@ sub print_bsr_section } sub file_attrib - { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId] + { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId] my ($file, $attrib)=@_; if (defined $attrib_name_id{$attrib}) { - my @d = split(' ', $file->[2]) ; # TODO : cache this + my @d = split(' ', $file->[3]) ; # TODO : cache this return from_base64($d[$attrib_name_id{$attrib}]); } elsif ($attrib eq 'jobid') { - return $file->[3]; + return $file->[4]; } elsif ($attrib eq 'name') { - return $file->[1]; + return $file->[2]; } else { die "Attribute not known : $attrib.\n"; } } - - # Return the jobid or attribute asked for a dir - sub dir_attrib + + sub lstat_attrib { - my ($self,$dir,$attrib)=@_; - - my @dir = split('/',$dir,-1); - my $refdir=$self->{dirtree}->{$self->current_client}; - - if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid') - { - die "Attribute not known : $attrib.\n"; - } - # Find the leaf - foreach my $subdir (@dir) - { - $refdir = $refdir->[0]->{$subdir}; - } - - # $refdir is now the reference to the dir's array - # Is the a jobid in @CurrentJobIds where the lstat is - # defined (we'll search in reverse order) - foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}})) - { - if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1') - { - if ($attrib eq 'jobid') - { - return $jobid; - } - else - { - my @attribs = parse_lstat($refdir->[1]->{$jobid}); - return $attribs[$attrib_name_id{$attrib}+1]; - } - } + my ($lstat,$attrib)=@_; + if ($lstat and defined $attrib_name_id{$attrib}) + { + my @d = split(' ', $lstat) ; # TODO : cache this + return from_base64($d[$attrib_name_id{$attrib}]); } - - return 0; # We cannot get a good attribute. - # This directory is here for the sake of visibility + return 0; } } @@ -2737,7 +3134,7 @@ sub print_bsr_section } while ($where ne '') { - $val <<= 6; + $val *= 64; my $d = substr($where, 0, 1); $val += $base64_map[ord(substr($where, 0, 1))]; $where = substr($where, 1); @@ -2756,20 +3153,162 @@ sub print_bsr_section return @attribs; } } + 1; +################################################################ +package BwebConsole; +use LWP::UserAgent; +use HTTP::Request::Common; + +sub new +{ + my ($class, %arg) = @_; + + my $self = bless { + pref => $arg{pref}, # Pref object + timeout => $arg{timeout} || 20, + debug => $arg{debug} || 0, + 'list_job' => '', + 'list_client' => '', + 'list_fileset' => '', + 'list_storage' => '', + 'run' => '', + }; + + return $self; +} + +sub prepare +{ + my ($self, @what) = @_; + my $ua = LWP::UserAgent->new(); + $ua->agent("Brestore/$VERSION"); + my $req = POST($self->{pref}->{bconsole}, + Content_Type => 'form-data', + Content => [ map { (action => $_) } @what ]); + #$req->authorization_basic('eric', 'test'); + + my $res = $ua->request($req); + + if ($res->is_success) { + foreach my $l (split(/\n/, $res->content)) { + my ($k, $c) = split(/=/,$l,2); + $self->{$k} = $c; + } + } else { + $self->{error} = "Can't connect to bweb : " . $res->status_line; + new DlgWarn($self->{error}); + } +} + +sub run +{ + my ($self, %arg) = @_; + + my $ua = LWP::UserAgent->new(); + $ua->agent("Brestore/$VERSION"); + my $req = POST($self->{pref}->{bconsole}, + Content_Type => 'form-data', + Content => [ job => $arg{job}, + client => $arg{client}, + storage => $arg{storage} || '', + fileset => $arg{fileset} || '', + where => $arg{where} || '', + regexwhere => $arg{regexwhere} || '', + priority=> $arg{prio} || '', + replace => $arg{replace}, + action => 'run', + timeout => 10, + bootstrap => [$arg{bootstrap}], + ]); + #$req->authorization_basic('eric', 'test'); + + my $res = $ua->request($req); + + if ($res->is_success) { + foreach my $l (split(/\n/, $res->content)) { + my ($k, $c) = split(/=/,$l,2); + $self->{$k} = $c; + } + } + + if (!$self->{run}) { + new DlgWarn("Can't connect to bweb : " . $res->status_line); + } + + unlink($arg{bootstrap}); + + return $self->{run}; +} + +sub list_job +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_job'}); +} + +sub list_fileset +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_fileset'}); +} + +sub list_storage +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_storage'}); +} +sub list_client +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_client'}); +} + +1; ################################################################ package main; -my $conf = "$ENV{HOME}/.brestore.conf" ; -my $p = new Pref($conf); +use Getopt::Long ; + +sub HELP_MESSAGE +{ + print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n"; + exit 1; +} + +my $file_conf = (exists $ENV{HOME})? "$ENV{HOME}/.brestore.conf" : undef ; +my $batch_mod; + +GetOptions("conf=s" => \$file_conf, + "batch" => \$batch_mod, + "debug" => \$debug, + "help" => \&HELP_MESSAGE) ; + +if (! defined $file_conf) { + print STDERR "Could not detect default config and no config file specified\n"; + HELP_MESSAGE(); +} + +my $p = new Pref($file_conf); -if (! -f $conf) { +if (! -f $file_conf) { $p->write_config(); } -$glade_file = $p->{glade_file}; +if ($batch_mod) { + my $vfs = new Bvfs(conf => $p); + if ($p->connect_db()) { + if ($vfs->create_brestore_tables()) { + print "Creating brestore tables\n"; + } + $vfs->update_cache(); + } + exit (0); +} + +$glade_file = $p->{glade_file} || $glade_file; foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') { if (-f "$path/$glade_file") { @@ -2778,13 +3317,22 @@ foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') { } } +# gtk have lots of warning on stderr +if ($^O eq 'MSWin32') +{ + close(STDERR); + open(STDERR, ">stderr.log"); +} + +Gtk2->init(); + if ( -f $glade_file) { my $w = new DlgResto($p); } else { my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close', "Can't find your brestore.glade (glade_file => '$glade_file') -Please, edit your $conf to setup it." ); +Please, edit your $file_conf to setup it." ); $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; }); $widget->run; @@ -2797,27 +3345,22 @@ Gtk2->main; # Start Gtk2 main loop exit 0; - __END__ +package main; -TODO : - - -# Code pour trier les colonnes - my $mod = $fileview->get_model(); - $mod->set_default_sort_func(sub { - my ($model, $item1, $item2) = @_; - my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème - my $b = $model->get($item2, 1); # colonne (indice 1) - return $a cmp $b; - } - ); - - $fileview->set_headers_clickable(1); - my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2 - $col->signal_connect('clicked', sub { - my ($colonne, $model) = @_; - $model->set_sort_column_id (1, 'ascending'); - }, - $mod - ); +my $p = new Pref("$ENV{HOME}/.brestore.conf"); +$p->{debug} = 1; +$p->connect_db() || print $p->{error}; + +my $bvfs = new Bvfs(conf => $p); + +$bvfs->debug($bvfs->get_root()); +$bvfs->ch_dir($bvfs->get_root()); +$bvfs->up_dir(); +$bvfs->set_curjobids(268,178,282,281,279); +$bvfs->ls_files(); +my $dirs = $bvfs->ls_dirs(); +$bvfs->ch_dir(123496); +$dirs = $bvfs->ls_dirs(); +$bvfs->ls_files(); +map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwdid},312433, "exw3srv3", 1);