4 # path to your brestore.glade
5 my $glade_file = 'brestore.glade' ;
9 brestore.pl - A Perl/Gtk console for Bacula
17 Setup ~/.brestore.conf to find your brestore.glade
19 On debian like system, you need :
20 - libgtk2-gladexml-perl
21 - libdbd-mysql-perl or libdbd-pg-perl
24 You have to add brestore_xxx tables to your catalog.
26 To speed up database query you have to create theses indexes
27 - CREATE INDEX file_pathid on File(PathId);
30 To follow restore job, you must have a running Bweb installation.
34 Bacula® - The Network Backup Solution
36 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
38 Brestore authors are Marc Cousin and Eric Bollengier.
39 The main author of Bacula is Kern Sibbald, with contributions from
40 many others, a complete list can be found in the file AUTHORS.
42 This program is Free Software; you can redistribute it and/or
43 modify it under the terms of version two of the GNU General Public
44 License as published by the Free Software Foundation plus additions
45 that are listed in the file LICENSE.
47 This program is distributed in the hope that it will be useful, but
48 WITHOUT ANY WARRANTY; without even the implied warranty of
49 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
50 General Public License for more details.
52 You should have received a copy of the GNU General Public License
53 along with this program; if not, write to the Free Software
54 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
57 Bacula® is a registered trademark of John Walker.
58 The licensor of Bacula is the Free Software Foundation Europe
59 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
60 Switzerland, email:ftf@fsfeurope.org.
62 Base 64 functions from Karl Hakimian <hakimian@aha.com>
63 Integrally copied from recover.pl from bacula source distribution.
67 use Gtk2; # auto-initialize Gtk2
69 use Gtk2::SimpleList; # easy wrapper for list views
70 use Gtk2::Gdk::Keysyms; # keyboard code constants
71 use Data::Dumper qw/Dumper/;
73 my $debug=0; # can be on brestore.conf
75 ################################################################
77 package DlgFileVersion;
79 sub on_versions_close_clicked
81 my ($self, $widget)=@_;
82 $self->{version}->destroy();
85 sub on_selection_button_press_event
87 print STDERR "on_selection_button_press_event()\n";
92 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
94 DlgResto::drag_set_info($widget,
101 my ($class, $dbh, $client, $path, $file) = @_;
104 version => undef, # main window
107 # we load version widget of $glade_file
108 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
110 # Connect signals magically
111 $glade_box->signal_autoconnect_from_package($self);
113 $glade_box->get_widget("version_label")
114 ->set_markup("<b>File revisions : $client:$path/$file</b>");
116 my $widget = $glade_box->get_widget('version_fileview');
117 my $fileview = Gtk2::SimpleList->new_from_treeview(
119 'h_name' => 'hidden',
120 'h_jobid' => 'hidden',
121 'h_type' => 'hidden',
123 'InChanger' => 'pixbuf',
130 DlgResto::init_drag_drop($fileview);
132 my @v = DlgResto::get_all_file_versions($dbh,
138 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
140 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
142 DlgResto::listview_push($fileview,
143 $file, $jobid, 'file',
144 $icon, $volname, $jobid,DlgResto::human($size),
145 scalar(localtime($mtime)), $md5);
148 $self->{version} = $glade_box->get_widget('dlg_version');
149 $self->{version}->show();
154 sub on_forward_keypress
160 ################################################################
165 my ($package, $text) = @_;
169 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
171 # Connect signals magically
172 $glade->signal_autoconnect_from_package($self);
173 $glade->get_widget('label_warn')->set_text($text);
175 print STDERR "$text\n";
177 $self->{window} = $glade->get_widget('dlg_warn');
178 $self->{window}->show_all();
185 $self->{window}->destroy();
189 ################################################################
192 use HTTP::Request::Common;
196 my ($class, %arg) = @_;
199 pref => $arg{pref}, # Pref object
200 timeout => $arg{timeout} || 20,
201 debug => $arg{debug} || 0,
204 'list_fileset' => '',
205 'list_storage' => '',
214 my ($self, @what) = @_;
215 my $ua = LWP::UserAgent->new();
216 $ua->agent("Brestore ");
217 my $req = POST($self->{pref}->{bconsole},
218 Content_Type => 'form-data',
219 Content => [ map { (action => $_) } @what ]);
220 #$req->authorization_basic('eric', 'test');
222 my $res = $ua->request($req);
224 if ($res->is_success) {
225 foreach my $l (split(/\n/, $res->content)) {
226 my ($k, $c) = split(/=/,$l,2);
230 $self->{error} = "Can't connect to bweb : " . $res->status_line;
231 new DlgWarn($self->{error});
237 my ($self, %arg) = @_;
239 my $ua = LWP::UserAgent->new();
240 $ua->agent("Brestore ");
241 my $req = POST($self->{pref}->{bconsole},
242 Content_Type => 'form-data',
243 Content => [ job => $arg{job},
244 client => $arg{client},
245 storage => $arg{storage} || '',
246 fileset => $arg{fileset} || '',
247 where => $arg{where},
248 replace => $arg{replace},
249 priority=> $arg{prio} || '',
252 bootstrap => [$arg{bootstrap}],
254 #$req->authorization_basic('eric', 'test');
256 my $res = $ua->request($req);
258 if ($res->is_success) {
259 foreach my $l (split(/\n/, $res->content)) {
260 my ($k, $c) = split(/=/,$l,2);
266 unlink($arg{bootstrap});
267 new DlgWarn("Can't connect to bweb : " . $res->status_line);
276 return sort split(/;/, $self->{'list_job'});
282 return sort split(/;/, $self->{'list_fileset'});
288 return sort split(/;/, $self->{'list_storage'});
293 return sort split(/;/, $self->{'list_client'});
298 ################################################################
304 # %arg = (bsr_file => '/path/to/bsr', # on director
305 # volumes => [ '00001', '00004']
313 if ($pref->{bconsole} =~ /^http/) {
314 return new BwebConsole(pref => $pref);
316 if (eval { require Bconsole; }) {
317 return new Bconsole(pref => $pref);
319 new DlgWarn("Can't use bconsole, verify your setup");
327 my ($class, %arg) = @_;
330 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
331 pref => $arg{pref}, # Pref ref
332 glade => undef, # GladeXML ref
333 bconsole => undef, # Bconsole ref
336 my $console = $self->{bconsole} = get_bconsole($arg{pref});
341 # we load launch widget of $glade_file
342 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
345 # Connect signals magically
346 $glade->signal_autoconnect_from_package($self);
348 my $widget = $glade->get_widget('volumeview');
349 my $volview = Gtk2::SimpleList->new_from_treeview(
351 'InChanger' => 'pixbuf',
355 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
357 # we replace 0 and 1 by $noicon and $yesicon
358 for my $i (@{$infos}) {
360 $i->[0] = $DlgResto::noicon;
362 $i->[0] = $DlgResto::yesicon;
367 push @{ $volview->{data} }, @{$infos} ;
369 $console->prepare(qw/list_client list_job list_fileset list_storage/);
371 # fill client combobox (with director defined clients
372 my @clients = $console->list_client() ; # get from bconsole
373 if ($console->{error}) {
374 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
376 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
377 $self->{list_client} = DlgResto::init_combo($w, 'text');
378 DlgResto::fill_combo($self->{list_client},
379 $DlgResto::client_list_empty,
383 # fill fileset combobox
384 my @fileset = $console->list_fileset() ;
385 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
386 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
387 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
390 my @job = $console->list_job() ;
391 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
392 $self->{list_job} = DlgResto::init_combo($w, 'text');
393 DlgResto::fill_combo($self->{list_job}, '', @job);
395 # find default_restore_job in jobs list
396 my $default_restore_job = $arg{pref}->{default_restore_job} ;
400 if ($j =~ /$default_restore_job/io) {
406 $w->set_active($index);
408 # fill storage combobox
409 my @storage = $console->list_storage() ;
410 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
411 $self->{list_storage} = DlgResto::init_combo($w, 'text');
412 DlgResto::fill_combo($self->{list_storage}, '', @storage);
414 $glade->get_widget('dlg_launch')->show_all();
421 my ($self, $client, $jobid) = @_;
423 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
426 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
427 "Your job have been submited to bacula.
428 To follow it, you must use bconsole (or install/configure bweb)");
433 $self->on_cancel_resto_clicked();
436 sub on_cancel_resto_clicked
439 $self->{glade}->get_widget('dlg_launch')->destroy();
442 sub on_submit_resto_clicked
445 my $glade = $self->{glade};
447 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
450 new DlgWarn("Can't copy bsr file to director ($self->{error})");
454 my $fileset = $glade->get_widget('combo_launch_fileset')
457 my $storage = $glade->get_widget('combo_launch_storage')
460 my $where = $glade->get_widget('entry_launch_where')->get_text();
462 my $job = $glade->get_widget('combo_launch_job')
466 new DlgWarn("Can't use this job");
470 my $client = $glade->get_widget('combo_launch_client')
473 if (! $client or $client eq $DlgResto::client_list_empty) {
474 new DlgWarn("Can't use this client ($client)");
478 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
480 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
481 $replace=($replace)?'always':'never';
483 my $jobid = $self->{bconsole}->run(job => $job,
492 $self->show_job($client, $jobid);
495 sub on_combo_storage_button_press_event
498 print "on_combo_storage_button_press_event()\n";
501 sub on_combo_fileset_button_press_event
504 print "on_combo_fileset_button_press_event()\n";
508 sub on_combo_job_button_press_event
511 print "on_combo_job_button_press_event()\n";
514 sub get_volume_inchanger
516 my ($dbh, $vols) = @_;
518 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
520 my $rq = "SELECT InChanger, VolumeName
522 WHERE VolumeName IN ($lst)
525 my $res = $dbh->selectall_arrayref($rq);
526 return $res; # [ [ 1, VolName].. ]
530 use File::Copy qw/copy/;
531 use File::Basename qw/basename/;
533 # We must kown the path+filename destination
534 # $self->{error} contains error message
535 # it return 0/1 if fail/success
538 my ($self, $src, $dst) = @_ ;
539 print "$src => $dst\n"
546 if ($dst =~ m!file:/(/.+)!) {
547 $ret = copy($src, $1);
549 $dstfile = "$1/" . basename($src) ;
551 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
552 $err = `scp $src $1 2>&1` ;
554 $dstfile = "$2/" . basename($src) ;
558 $err = "$dst not implemented yet";
559 File::Copy::copy($src, \*STDOUT);
562 $self->{error} = $err;
565 $self->{error} = $err;
574 ################################################################
582 unless ($about_widget) {
583 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
584 $about_widget = $glade_box->get_widget("dlg_about") ;
585 $glade_box->signal_autoconnect_from_package('DlgAbout');
587 $about_widget->show() ;
590 sub on_about_okbutton_clicked
592 $about_widget->hide() ;
597 ################################################################
603 my ($class, $config_file) = @_;
606 config_file => $config_file,
607 password => '', # db passwd
608 username => '', # db username
609 connection_string => '',# db connection string
610 bconsole => 'bconsole', # path and arg to bconsole
611 bsr_dest => '', # destination url for bsr files
612 debug => 0, # debug level 0|1
613 use_ok_bkp_only => 1, # dont use bad backup
614 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
615 glade_file => $glade_file,
616 see_all_versions => 0, # display all file versions in FileInfo
617 mozilla => 'mozilla', # mozilla bin
618 default_restore_job => 'restore', # regular expression to select default
621 # keywords that are used to fill DlgPref
622 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
623 entry_keyword => [ qw/username password bweb mozilla
624 connection_string default_restore_job
625 bconsole bsr_dest glade_file/],
628 $self->read_config();
637 # We read the parameters. They come from the configuration files
638 my $cfgfile ; my $tmpbuffer;
639 if (open FICCFG, $self->{config_file})
641 while(read FICCFG,$tmpbuffer,4096)
643 $cfgfile .= $tmpbuffer;
647 no strict; # I have no idea of the contents of the file
648 eval '$refparams' . " = $cfgfile";
651 for my $p (keys %{$refparams}) {
652 $self->{$p} = $refparams->{$p};
655 if (defined $self->{debug}) {
656 $debug = $self->{debug} ;
659 # TODO : Force dumb default values and display a message
669 for my $k (@{ $self->{entry_keyword} }) {
670 $parameters{$k} = $self->{$k};
673 for my $k (@{ $self->{chk_keyword} }) {
674 $parameters{$k} = $self->{$k};
677 if (open FICCFG,">$self->{config_file}")
679 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
684 # TODO : Display a message
693 $self->{dbh}->disconnect() ;
697 delete $self->{error};
699 if (not $self->{connection_string})
701 # The parameters have not been set. Maybe the conf
702 # file is empty for now
703 $self->{error} = "No configuration found for database connection. " .
704 "Please set this up.";
709 $self->{dbh} = DBI->connect($self->{connection_string},
714 $self->{error} = "Can't open bacula database. " .
715 "Database connect string '" .
716 $self->{connection_string} ."' $!";
719 $self->{dbh}->{RowCacheSize}=100;
725 my ($self, $url, $msg) = @_;
727 unless ($self->{mozilla} and $self->{bweb}) {
728 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
732 if ($^O eq 'MSWin32') {
733 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
736 system("$self->{mozilla} -remote 'Ping()'");
737 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
739 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
749 ################################################################
753 # my $pref = new Pref(config_file => 'brestore.conf');
754 # my $dlg = new DlgPref($pref);
755 # my $dlg_resto = new DlgResto($pref);
756 # $dlg->display($dlg_resto);
759 my ($class, $pref) = @_;
762 pref => $pref, # Pref ref
763 dlgresto => undef, # DlgResto ref
771 my ($self, $dlgresto) = @_ ;
773 unless ($self->{glade}) {
774 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
775 $self->{glade}->signal_autoconnect_from_package($self);
778 $self->{dlgresto} = $dlgresto;
780 my $g = $self->{glade};
781 my $p = $self->{pref};
783 for my $k (@{ $p->{entry_keyword} }) {
784 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
787 for my $k (@{ $p->{chk_keyword} }) {
788 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
791 $g->get_widget("dlg_pref")->show_all() ;
794 sub on_applybutton_clicked
797 my $glade = $self->{glade};
798 my $pref = $self->{pref};
800 for my $k (@{ $pref->{entry_keyword} }) {
801 my $w = $glade->get_widget("entry_$k") ;
802 $pref->{$k} = $w->get_text();
805 for my $k (@{ $pref->{chk_keyword} }) {
806 my $w = $glade->get_widget("chkbp_$k") ;
807 $pref->{$k} = $w->get_active();
810 $pref->write_config();
811 if ($pref->connect_db()) {
812 $self->{dlgresto}->set_dbh($pref->{dbh});
813 $self->{dlgresto}->set_status('Preferences updated');
814 $self->{dlgresto}->init_server_backup_combobox();
815 $self->{dlgresto}->create_brestore_tables();
816 $self->{dlgresto}->set_status($pref->{error});
818 $self->{dlgresto}->set_status($pref->{error});
822 # Handle prefs ok click (apply/dismiss dialog)
823 sub on_okbutton_clicked
826 $self->on_applybutton_clicked();
828 unless ($self->{pref}->{error}) {
829 $self->on_cancelbutton_clicked();
832 sub on_dialog_delete_event
835 $self->on_cancelbutton_clicked();
839 sub on_cancelbutton_clicked
842 $self->{glade}->get_widget('dlg_pref')->hide();
843 delete $self->{dlgresto};
847 ################################################################
857 # Kept as is from the perl-gtk example. Draws the pretty icons
863 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
864 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
865 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
866 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
870 # init combo (and create ListStore object)
873 my ($widget, @type) = @_ ;
874 my %type_info = ('text' => 'Glib::String',
875 'markup' => 'Glib::String',
878 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
880 $widget->set_model($lst);
884 if ($t eq 'text' or $t eq 'markup') {
885 $cell = new Gtk2::CellRendererText();
887 $widget->pack_start($cell, 1);
888 $widget->add_attribute($cell, $t, $i++);
893 # fill simple combo (one element per row)
896 my ($list, @what) = @_;
900 foreach my $w (@what)
903 my $i = $list->append();
904 $list->set($i, 0, $w);
911 my @unit = qw(b Kb Mb Gb Tb);
914 my $format = '%i %s';
915 while ($val / 1024 > 1) {
919 $format = ($i>0)?'%0.1f %s':'%i %s';
920 return sprintf($format, $val, $unit[$i]);
925 my ($self, $dbh) = @_;
931 my ($fileview) = shift;
932 my $fileview_target_entry = {target => 'STRING',
933 flags => ['GTK_TARGET_SAME_APP'],
936 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
937 ['copy'],$fileview_target_entry);
938 $fileview->get_selection->set_mode('multiple');
940 # set some useful SimpleList properties
941 $fileview->set_headers_clickable(0);
942 foreach ($fileview->get_columns())
944 $_->set_resizable(1);
945 $_->set_sizing('grow-only');
951 my ($self, $what) = @_;
955 print Data::Dumper::Dumper($what);
956 } elsif (defined $what) {
964 my ($self, $query) = @_;
965 $self->debug($query);
966 return $self->{dbh}->prepare($query);
971 my ($self, $query) = @_;
972 $self->debug($query);
973 return $self->{dbh}->do($query);
976 sub dbh_selectall_arrayref
978 my ($self, $query) = @_;
979 $self->debug($query);
980 return $self->{dbh}->selectall_arrayref($query);
983 sub dbh_selectrow_arrayref
985 my ($self, $query) = @_;
986 $self->debug($query);
987 return $self->{dbh}->selectrow_arrayref($query);
992 my ($class, $pref) = @_;
997 location => undef, # location entry widget
998 mainwin => undef, # mainwin widget
999 filelist_file_menu => undef, # file menu widget
1000 filelist_dir_menu => undef, # dir menu widget
1001 glade => undef, # glade object
1002 status => undef, # status bar widget
1003 dlg_pref => undef, # DlgPref object
1004 fileattrib => {}, # cache file
1005 fileview => undef, # fileview widget SimpleList
1006 fileinfo => undef, # fileinfo widget SimpleList
1008 client_combobox => undef, # client_combobox widget
1009 restore_backup_combobox => undef, # date combobox widget
1010 list_client => undef, # Gtk2::ListStore
1011 list_backup => undef, # Gtk2::ListStore
1012 cache_ppathid => {}, #
1015 # load menu (to use handler with self reference)
1016 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1017 $glade->signal_autoconnect_from_package($self);
1018 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1020 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1021 $glade->signal_autoconnect_from_package($self);
1022 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1024 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1025 $glade->signal_autoconnect_from_package($self);
1027 $self->{status} = $glade->get_widget('statusbar');
1028 $self->{mainwin} = $glade->get_widget('dlg_resto');
1029 $self->{location} = $glade->get_widget('entry_location');
1030 $self->render_icons();
1032 $self->{dlg_pref} = new DlgPref($pref);
1034 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1035 $self->{list_client} = init_combo($c, 'text');
1037 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1038 $self->{list_backup} = init_combo($c, 'text', 'markup');
1040 # Connect glade-fileview to Gtk2::SimpleList
1041 # and set up drag n drop between $fileview and $restore_list
1043 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1044 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1046 my $widget = $glade->get_widget('fileview');
1047 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1049 'h_name' => 'hidden',
1050 'h_jobid' => 'hidden',
1051 'h_type' => 'hidden',
1054 'File Name' => 'text',
1057 init_drag_drop($fileview);
1058 $fileview->set_search_column(4); # search on File Name
1060 # Connect glade-restore_list to Gtk2::SimpleList
1061 $widget = $glade->get_widget('restorelist');
1062 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1064 'h_name' => 'hidden',
1065 'h_jobid' => 'hidden',
1066 'h_type' => 'hidden',
1067 'h_curjobid' => 'hidden',
1070 'File Name' => 'text',
1072 'FileIndex' => 'text',
1074 'Nb Files' => 'text', #8
1075 'Size' => 'text', #9
1076 'size_b' => 'hidden', #10
1079 my @restore_list_target_table = ({'target' => 'STRING',
1083 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1084 $restore_list->get_selection->set_mode('multiple');
1086 $widget = $glade->get_widget('infoview');
1087 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1089 'h_name' => 'hidden',
1090 'h_jobid' => 'hidden',
1091 'h_type' => 'hidden',
1093 'InChanger' => 'pixbuf',
1100 init_drag_drop($infoview);
1102 $pref->connect_db() || $self->{dlg_pref}->display($self);
1105 $self->{dbh} = $pref->{dbh};
1106 $self->init_server_backup_combobox();
1107 $self->create_brestore_tables();
1110 $self->set_status($pref->{error});
1113 # set status bar informations
1116 my ($self, $string) = @_;
1117 return unless ($string);
1119 my $context = $self->{status}->get_context_id('Main');
1120 $self->{status}->push($context, $string);
1123 sub on_time_select_changed
1131 my $c = $self->{glade}->get_widget('combo_time');
1132 return $c->get_active_text;
1135 # This sub returns all clients declared in DB
1139 my $query = "SELECT Name FROM Client ORDER BY Name";
1140 print STDERR $query,"\n" if $debug;
1142 my $result = $dbh->selectall_arrayref($query);
1144 return map { $_->[0] } @$result;
1147 sub get_wanted_job_status
1154 return "'T', 'A', 'E'";
1158 # This sub gives a full list of the EndTimes for a ClientId
1159 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
1160 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
1161 sub get_all_endtimes_for_job
1163 my ($dbh, $client, $ok_only)=@_;
1164 my $status = get_wanted_job_status($ok_only);
1166 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
1167 FROM Job,Client,FileSet
1168 WHERE Job.ClientId=Client.ClientId
1169 AND Client.Name = '$client'
1171 AND JobStatus IN ($status)
1172 AND Job.FileSetId = FileSet.FileSetId
1173 ORDER BY EndTime desc";
1174 print STDERR $query,"\n" if $debug;
1175 my $result = $dbh->selectall_arrayref($query);
1181 # init infoview widget
1185 @{$self->{fileinfo}->{data}} = ();
1189 sub on_clear_clicked
1192 @{$self->{restore_list}->{data}} = ();
1195 sub on_estimate_clicked
1202 # TODO : If we get here, things could get lenghty ... draw a popup window .
1203 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1204 'destroy-with-parent',
1206 'Computing size...');
1210 my $title = "Computing size...\n";
1212 foreach my $entry (@{$self->{restore_list}->{data}})
1214 unless ($entry->[9]) {
1215 my ($size, $nb) = $self->estimate_restore_size($entry);
1216 $entry->[10] = $size;
1217 $entry->[9] = human($size);
1221 my $name = unpack('u', $entry->[0]);
1223 $txt .= "\n<i>$name</i> : " . $entry->[8] . " file(s)/" . $entry->[9] ;
1224 $widget->set_markup($title . $txt);
1226 $size_total+=$entry->[10];
1227 $nb_total+=$entry->[8];
1231 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1232 $widget->set_markup("Size estimation :\n" . $txt);
1233 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1238 sub on_gen_bsr_clicked
1242 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1243 'gtk-save','ok', 'gtk-cancel', 'cancel');
1246 my $w = new Gtk2::FileChooserDialog ( @options );
1251 if ($a eq 'cancel') {
1256 my $f = $w->get_filename();
1258 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1259 'destroy-with-parent',
1260 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1261 if ($dlg->run() eq 'ok') {
1275 if (open(FP, ">$save")) {
1276 my $bsr = $self->create_filelist();
1279 $self->set_status("Dumping BSR to $save ok");
1281 $self->set_status("Can't dump BSR to $save: $!");
1286 use File::Temp qw/tempfile/;
1288 sub on_go_button_clicked
1291 unless (scalar(@{$self->{restore_list}->{data}})) {
1292 new DlgWarn("No file to restore");
1295 my $bsr = $self->create_filelist();
1296 my ($fh, $filename) = tempfile();
1299 chmod(0644, $filename);
1301 print "Dumping BSR info to $filename\n"
1304 # we get Volume list
1305 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1306 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1308 new DlgLaunch(pref => $self->{pref},
1310 bsr_file => $filename,
1315 our $client_list_empty = 'Clients list';
1316 our %type_markup = ('F' => '<b>$label F</b>',
1319 'B' => '<b>$label B</b>',
1321 'A' => '<span foreground=\"red\">$label</span>',
1323 'E' => '<span foreground=\"red\">$label</span>',
1326 sub on_list_client_changed
1328 my ($self, $widget) = @_;
1329 return 0 unless defined $self->{fileview};
1330 my $dbh = $self->{dbh};
1332 $self->{list_backup}->clear();
1334 if ($self->current_client eq $client_list_empty) {
1338 my @endtimes=get_all_endtimes_for_job($dbh,
1339 $self->current_client,
1340 $self->{pref}->{use_ok_bkp_only});
1341 foreach my $endtime (@endtimes)
1343 my $i = $self->{list_backup}->append();
1345 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1346 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1347 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1349 $self->{list_backup}->set($i,
1354 $self->{restore_backup_combobox}->set_active(0);
1356 $self->{CurrentJobIds} = [
1357 set_job_ids_for_date($dbh,
1358 $self->current_client,
1359 $self->current_date,
1360 $self->{pref}->{use_ok_bkp_only})
1363 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1366 $self->refresh_fileview();
1370 sub fill_server_list
1372 my ($dbh, $combo, $list) = @_;
1374 my @clients=get_all_clients($dbh);
1378 my $i = $list->append();
1379 $list->set($i, 0, $client_list_empty);
1381 foreach my $client (@clients)
1383 $i = $list->append();
1384 $list->set($i, 0, $client);
1386 $combo->set_active(0);
1389 sub init_server_backup_combobox
1392 fill_server_list($self->{dbh},
1393 $self->{client_combobox},
1394 $self->{list_client}) ;
1397 #----------------------------------------------------------------------
1398 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1399 #data isn't. There is additionnal complexity for dirs (visibility problems),
1400 #so the @CurrentJobIds is not sufficient.
1401 sub refresh_fileview
1404 my $fileview = $self->{fileview};
1405 my $client_combobox = $self->{client_combobox};
1406 my $cwd = $self->{cwd};
1408 @{$fileview->{data}} = ();
1410 $self->clear_infoview();
1412 my $client_name = $self->current_client;
1414 if (!$client_name or ($client_name eq $client_list_empty)) {
1415 $self->set_status("Client list empty");
1419 my @list_dirs = $self->list_dirs($cwd,$client_name);
1420 # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1421 my $files = $self->list_files($cwd);
1422 print "CWD : $cwd\n" if ($debug);
1424 my $file_count = 0 ;
1425 my $total_bytes = 0;
1427 # Add directories to view
1428 foreach my $dir_entry (@list_dirs) {
1429 #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
1430 my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime'));
1431 my $dir = $dir_entry->[0];
1432 $total_bytes += 4096;
1435 listview_push($fileview,
1437 $self->dir_attrib("$cwd/$dir",'jobid'),
1447 foreach my $file (@$files)
1449 my $size = file_attrib($file,'st_size');
1450 my $time = localtime(file_attrib($file,'st_mtime'));
1451 $total_bytes += $size;
1453 # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
1455 listview_push($fileview,
1462 human($size), $time);
1465 $self->set_status("$file_count files/" . human($total_bytes));
1467 # set a decent default selection (makes keyboard nav easy)
1468 $fileview->select(0);
1472 sub on_about_activate
1474 DlgAbout::display();
1479 my ($tree, $path, $data) = @_;
1481 my @items = listview_get_all($tree) ;
1483 foreach my $i (@items)
1485 my @file_info = @{$i};
1488 # Ok, we have a corner case :
1493 $file = pack("u", $file_info[0]);
1497 $file = pack("u", $path . '/' . $file_info[0]);
1499 push @ret, join(" ; ", $file,
1500 $file_info[1], # $jobid
1501 $file_info[2], # $type
1505 my $data_get = join(" :: ", @ret);
1507 $data->set_text($data_get,-1);
1510 sub fileview_data_get
1512 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1513 drag_set_info($widget, $self->{cwd}, $data);
1516 sub fileinfo_data_get
1518 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1519 drag_set_info($widget, $self->{cwd}, $data);
1522 sub restore_list_data_received
1524 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1527 if ($info eq 40 || $info eq 0) # patch for display!=:0
1529 foreach my $elt (split(/ :: /, $data->data()))
1532 my ($file, $jobid, $type) =
1534 $file = unpack("u", $file);
1536 $self->add_selected_file_to_list($file, $jobid, $type);
1541 sub on_back_button_clicked {
1545 sub on_location_go_button_clicked
1548 $self->ch_dir($self->{location}->get_text());
1550 sub on_quit_activate {Gtk2->main_quit;}
1551 sub on_preferences_activate
1554 $self->{dlg_pref}->display($self) ;
1556 sub on_main_delete_event {Gtk2->main_quit;}
1557 sub on_bweb_activate
1560 $self->set_status("Open bweb on your browser");
1561 $self->{pref}->go_bweb('', "go on bweb");
1564 # Change to parent directory
1568 if ($self->{cwd} eq '/')
1572 my @dirs = split(/\//, $self->{cwd});
1574 $self->ch_dir(join('/', @dirs));
1577 # Change the current working directory
1578 # * Updates fileview, location, and selection
1583 $self->{cwd} = shift;
1585 $self->refresh_fileview();
1586 $self->{location}->set_text($self->{cwd});
1591 # Handle dialog 'close' (window-decoration induced close)
1592 # * Just hide the dialog, and tell Gtk not to do anything else
1596 my ($self, $w) = @_;
1599 1; # consume this event!
1602 # Handle key presses in location text edit control
1603 # * Translate a Return/Enter key into a 'Go' command
1604 # * All other key presses left for GTK
1606 sub on_location_entry_key_release_event
1612 my $keypress = $event->keyval;
1613 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1614 $keypress == $Gtk2::Gdk::Keysyms{Return})
1616 $self->ch_dir($widget->get_text());
1618 return 1; # consume keypress
1621 return 0; # let gtk have the keypress
1624 sub on_fileview_key_press_event
1626 my ($self, $widget, $event) = @_;
1630 sub listview_get_first
1633 my @selected = $list->get_selected_indices();
1634 if (@selected > 0) {
1635 my ($name, @other) = @{$list->{data}->[$selected[0]]};
1636 return (unpack('u', $name), @other);
1642 sub listview_get_all
1646 my @selected = $list->get_selected_indices();
1648 for my $i (@selected) {
1649 my ($name, @other) = @{$list->{data}->[$i]};
1650 push @ret, [unpack('u', $name), @other];
1658 my ($list, $name, @other) = @_;
1659 push @{$list->{data}}, [pack('u', $name), @other];
1662 #----------------------------------------------------------------------
1663 # Handle keypress in file-view
1664 # * Translates backspace into a 'cd ..' command
1665 # * All other key presses left for GTK
1667 sub on_fileview_key_release_event
1669 my ($self, $widget, $event) = @_;
1670 if (not $event->keyval)
1674 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1676 return 1; # eat keypress
1679 return 0; # let gtk have keypress
1682 sub on_forward_keypress
1687 #----------------------------------------------------------------------
1688 # Handle double-click (or enter) on file-view
1689 # * Translates into a 'cd <dir>' command
1691 sub on_fileview_row_activated
1693 my ($self, $widget) = @_;
1695 my ($name, undef, $type, undef) = listview_get_first($widget);
1699 if ($self->{cwd} eq '')
1701 $self->ch_dir($name);
1703 elsif ($self->{cwd} eq '/')
1705 $self->ch_dir('/' . $name);
1709 $self->ch_dir($self->{cwd} . '/' . $name);
1713 $self->fill_infoview($self->{cwd}, $name);
1716 return 1; # consume event
1721 my ($self, $path, $file) = @_;
1722 $self->clear_infoview();
1723 my @v = get_all_file_versions($self->{dbh},
1726 $self->current_client,
1727 $self->{pref}->{see_all_versions});
1729 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
1731 my $icon = ($inchanger)?$yesicon:$noicon;
1733 $mtime = localtime($mtime) ;
1735 listview_push($self->{fileinfo},
1736 $file, $jobid, 'file',
1737 $icon, $volname, $jobid, human($size), $mtime, $md5);
1744 return $self->{restore_backup_combobox}->get_active_text;
1750 return $self->{client_combobox}->get_active_text;
1753 sub on_list_backups_changed
1755 my ($self, $widget) = @_;
1756 return 0 unless defined $self->{fileview};
1758 $self->{CurrentJobIds} = [
1759 set_job_ids_for_date($self->{dbh},
1760 $self->current_client,
1761 $self->current_date,
1762 $self->{pref}->{use_ok_bkp_only})
1764 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1766 $self->refresh_fileview();
1770 sub on_restore_list_keypress
1772 my ($self, $widget, $event) = @_;
1773 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1775 my @sel = $widget->get_selected_indices;
1776 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1778 splice @{$self->{restore_list}->{data}},$elt,1;
1783 sub on_fileview_button_press_event
1785 my ($self,$widget,$event) = @_;
1786 if ($event->button == 3)
1788 $self->on_right_click_filelist($widget,$event);
1792 if ($event->button == 2)
1794 $self->on_see_all_version();
1801 sub on_see_all_version
1805 my @lst = listview_get_all($self->{fileview});
1808 my ($name, undef) = @{$i};
1810 new DlgFileVersion($self->{dbh},
1811 $self->current_client,
1812 $self->{cwd}, $name);
1816 sub on_right_click_filelist
1818 my ($self,$widget,$event) = @_;
1819 # I need to know what's selected
1820 my @sel = listview_get_all($self->{fileview});
1825 $type = $sel[0]->[2]; # $type
1830 if (@sel >=2 or $type eq 'dir')
1832 # We have selected more than one or it is a directories
1833 $w = $self->{filelist_dir_menu};
1837 $w = $self->{filelist_file_menu};
1843 $event->button, $event->time);
1846 sub context_add_to_filelist
1850 my @sel = listview_get_all($self->{fileview});
1852 foreach my $i (@sel)
1854 my ($file, $jobid, $type, undef) = @{$i};
1855 $file = $self->{cwd} . '/' . $file;
1856 $self->add_selected_file_to_list($file, $jobid, $type);
1860 # Adds a file to the filelist
1861 sub add_selected_file_to_list
1863 my ($self, $name, $jobid, $type)=@_;
1865 my $dbh = $self->{dbh};
1866 my $restore_list = $self->{restore_list};
1868 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1875 if ($name and substr $name,-1 ne '/')
1877 $name .= '/'; # For bacula
1879 my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
1880 listview_push($restore_list,
1881 $name, $jobid, 'dir', $curjobids,
1882 $diricon, $name,$curjobids,$dirfileindex);
1884 elsif ($type eq 'file')
1886 my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
1888 listview_push($restore_list,
1889 $name, $jobid, 'file', $curjobids,
1890 $fileicon, $name, $jobid, $fileindex );
1894 # TODO : we want be able to restore files from a bad ended backup
1895 # we have JobStatus IN ('T', 'A', 'E') and we must
1897 # Data acces subs from here. Interaction with SGBD and caching
1899 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1900 # GUI and stores them in @CurrentJobIds
1901 sub set_job_ids_for_date
1903 my ($dbh, $client, $date, $only_ok)=@_;
1905 if (!$client or !$date) {
1909 my $status = get_wanted_job_status($only_ok);
1911 # The algorithm : for a client, we get all the backups for each
1912 # fileset, in reverse order Then, for each fileset, we store the 'good'
1913 # incrementals and differentials until we have found a full so it goes
1914 # like this : store all incrementals until we have found a differential
1915 # or a full, then find the full #
1917 my $query = "SELECT JobId, FileSet, Level, JobStatus
1918 FROM Job, Client, FileSet
1919 WHERE Job.ClientId = Client.ClientId
1920 AND FileSet.FileSetId = Job.FileSetId
1921 AND EndTime <= '$date'
1922 AND Client.Name = '$client'
1924 AND JobStatus IN ($status)
1925 ORDER BY FileSet, JobTDate DESC";
1927 print STDERR $query,"\n" if $debug;
1929 my $result = $dbh->selectall_arrayref($query);
1931 foreach my $refrow (@$result)
1933 my $jobid = $refrow->[0];
1934 my $fileset = $refrow->[1];
1935 my $level = $refrow->[2];
1937 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1939 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1943 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1944 push @CurrentJobIds,($jobid);
1946 elsif ($level eq 'D')
1948 next if $progress{$fileset} eq 'D'; # We allready have a differential
1949 push @CurrentJobIds,($jobid);
1951 elsif ($level eq 'F')
1953 push @CurrentJobIds,($jobid);
1956 my $status = $refrow->[3] ;
1957 if ($status eq 'T') { # good end of job
1958 $progress{$fileset} = $level;
1961 print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
1963 return @CurrentJobIds;
1966 # Lists all directories contained inside a directory.
1967 # Uses the current dir, the client name, and CurrentJobIds for visibility.
1968 # Returns an array of dirs
1971 my ($self,$dir,$client)=@_;
1973 print "list_dirs(<$dir>, <$client>)\n" if $debug;
1975 if ($dir ne '' and substr $dir,-1 ne '/')
1977 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1980 my $dbh = $self->{dbh};
1981 my $query = "SELECT PathId FROM Path WHERE Path = ?
1982 UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?";
1983 my $sth = $dbh->prepare($query);
1984 $sth->execute($dir,$dir);
1985 my $result = $sth->fetchrow_arrayref();
1987 my $pathid = $result->[0];
1988 my @jobids = @{$self->{CurrentJobIds}};
1989 my $jobclause = join (',', @jobids);
1990 # Let's retrieve the list of the visible dirs in this dir ...
1991 # First, I need the empty filenameid to locate efficiently the dirs in the file table
1992 $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
1993 $sth = $dbh->prepare($query);
1995 $result = $sth->fetchrow_arrayref();
1997 my $dir_filenameid = $result->[0];
1999 # Then we get all the dir entries from File ...
2000 # It's ugly because there are records in brestore_missing_path ...
2002 SELECT Path, JobId, Lstat FROM(
2004 SELECT Path.Path, lower(Path.Path),
2005 listfile.JobId, listfile.Lstat
2007 SELECT DISTINCT brestore_pathhierarchy.PathId
2008 FROM brestore_pathhierarchy
2010 ON (brestore_pathhierarchy.PathId = Path.PathId)
2011 JOIN brestore_pathvisibility
2012 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2013 WHERE brestore_pathhierarchy.PPathId = $pathid
2014 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2015 JOIN Path ON (listpath.PathId = Path.PathId)
2017 SELECT File.PathId, File.JobId, File.Lstat FROM File
2018 WHERE File.FilenameId = $dir_filenameid
2019 AND File.JobId IN ($jobclause)) AS listfile
2020 ON (listpath.PathId = listfile.PathId)
2022 SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path),
2023 listfile.JobId, listfile.Lstat
2025 SELECT DISTINCT brestore_pathhierarchy.PathId
2026 FROM brestore_pathhierarchy
2027 JOIN brestore_missing_path
2028 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2029 JOIN brestore_pathvisibility
2030 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2031 WHERE brestore_pathhierarchy.PPathId = $pathid
2032 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2033 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2035 SELECT File.PathId, File.JobId, File.Lstat FROM File
2036 WHERE File.FilenameId = $dir_filenameid
2037 AND File.JobId IN ($jobclause)) AS listfile
2038 ON (listpath.PathId = listfile.PathId))
2039 ORDER BY 2,3 DESC ) As a";
2040 print STDERR "$query\n" if $debug;
2041 $sth=$dbh->prepare($query);
2043 $result = $sth->fetchall_arrayref();
2046 foreach my $refrow (@{$result})
2048 my $dir = $refrow->[0];
2049 my $jobid = $refrow->[1];
2050 my $lstat = $refrow->[2];
2051 next if ($dir eq $prev_dir);
2052 # We have to clean up this dirname ... we only want it's 'basename'
2056 my @temp = split ('/',$dir);
2057 $return_value = pop @temp;
2061 $return_value = '/';
2063 my @return_array = ($return_value,$lstat);
2064 push @return_list,(\@return_array);
2067 return @return_list;
2071 # List all files in a directory. dir as parameter, CurrentJobIds for visibility
2072 # Returns an array of dirs
2075 my ($self, $dir)=@_;
2076 my $dbh = $self->{dbh};
2080 print "list_files($dir)\n" if $debug;
2082 if ($dir ne '' and substr $dir,-1 ne '/')
2084 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
2087 my $query = "SELECT Path.PathId
2089 WHERE Path.Path = '$dir'
2091 SELECT brestore_missing_path.PathId
2092 FROM brestore_missing_path
2093 WHERE brestore_missing_path.Path = '$dir'";
2094 print $query,"\n" if $debug;
2096 my $result = $dbh->selectall_arrayref($query);
2097 foreach my $refrow (@$result)
2099 push @list_pathid,($refrow->[0]);
2102 if (@list_pathid == 0)
2104 print "No pathid found for $dir\n" if $debug;
2108 my $inlistpath = join (',', @list_pathid);
2109 my $inclause = join (',', @{$self->{CurrentJobIds}});
2110 if ($inclause eq '')
2116 "SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
2118 (SELECT Filename.Name, max(File.FileId) as id
2120 WHERE File.FilenameId = Filename.FilenameId
2121 AND Filename.Name != ''
2122 AND File.PathId IN ($inlistpath)
2123 AND File.JobId IN ($inclause)
2124 GROUP BY Filename.Name
2125 ORDER BY Filename.Name) AS listfiles,
2127 WHERE File.FileId = listfiles.id";
2129 print STDERR $query,"\n" if $debug;
2130 $result = $dbh->selectall_arrayref($query);
2137 Gtk2->main_iteration while (Gtk2->events_pending);
2140 sub create_brestore_tables
2144 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2146 unless ($self->dbh_do($verif)) {
2147 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2149 $self->{error} = "Creating internal brestore tables";
2151 CREATE TABLE brestore_knownjobid
2153 JobId int4 NOT NULL,
2154 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2156 $self->dbh_do($req);
2159 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2160 unless ($self->dbh_do($verif)) {
2162 CREATE TABLE brestore_pathhierarchy
2164 PathId int4 NOT NULL,
2165 PPathId int4 NOT NULL,
2166 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2168 $self->dbh_do($req);
2171 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2172 ON brestore_pathhierarchy (PPathId)";
2173 $self->dbh_do($req);
2176 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2177 unless ($self->dbh_do($verif)) {
2179 CREATE TABLE brestore_pathvisibility
2181 PathId int4 NOT NULL,
2182 JobId int4 NOT NULL,
2183 Size int8 DEFAULT 0,
2184 Files int4 DEFAULT 0,
2185 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2187 $self->dbh_do($req);
2189 $req = "CREATE INDEX brestore_pathvisibility_jobid
2190 ON brestore_pathvisibility (JobId)";
2191 $self->dbh_do($req);
2194 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2195 unless ($self->dbh_do($verif)) {
2197 CREATE TABLE brestore_missing_path
2199 PathId int4 NOT NULL,
2201 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2203 $self->dbh_do($req);
2205 $req = "CREATE INDEX brestore_missing_path_path
2206 ON brestore_missing_path (Path)";
2207 $self->dbh_do($req);
2211 # Recursive function to calculate the visibility of each directory in the cache
2212 # tree Working with references to save time and memory
2213 # For each directory, we want to propagate it's visible jobids onto it's
2214 # parents directory.
2215 # A tree is visible if
2216 # - it's been in a backup pointed by the CurrentJobIds
2217 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2218 # In the second case, the directory is visible but has no metadata.
2219 # We symbolize this with lstat = 1 for this jobid in the cache.
2221 # Input : reference directory
2222 # Output : visibility of this dir. Has to know visibility of all subdirs
2223 # to know it's visibility, hence the recursing.
2229 # Get the subdirs array references list
2230 my @list_ref_subdirs;
2231 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2233 push @list_ref_subdirs,($ref_subdir);
2236 # Now lets recurse over these subdirs and retrieve the reference of a hash
2237 # containing the jobs where they are visible
2238 foreach my $ref_subdir (@list_ref_subdirs)
2240 my $ref_list_jobs = list_visible($ref_subdir);
2241 foreach my $jobid (keys %$ref_list_jobs)
2243 $visibility{$jobid}=1;
2247 # Ok. Now, we've got the list of those jobs. We are going to update our
2248 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2249 # the lstat for the known jobids. Put 1 in the new elements... But first,
2250 # let's store the current jobids
2252 foreach my $jobid (keys %{$refdir->[1]})
2254 push @known_jobids,($jobid);
2258 foreach my $jobid (keys %visibility)
2260 next if ($refdir->[1]->{$jobid});
2261 $refdir->[1]->{$jobid} = 1;
2263 # Add the known_jobids to %visibility
2264 foreach my $jobid (@known_jobids)
2266 $visibility{$jobid}=1;
2268 return \%visibility;
2271 # Returns the list of media required for a list of jobids.
2272 # Input : dbh, jobid1, jobid2...
2273 # Output : reference to array of (joibd, inchanger)
2274 sub get_required_media_from_jobid
2276 my ($dbh, @jobids)=@_;
2277 my $inclause = join(',',@jobids);
2279 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2280 FROM JobMedia, Media
2281 WHERE JobMedia.MediaId=Media.MediaId
2282 AND JobId In ($inclause)
2284 my $result = $dbh->selectall_arrayref($query);
2288 # Returns the fileindex from dirname and jobid.
2289 # Input : dbh, dirname, jobid
2290 # Output : fileindex
2291 sub get_fileindex_from_dir_jobid
2293 my ($dbh, $dirname, $jobid)=@_;
2295 $query = "SELECT File.FileIndex
2296 FROM File, Filename, Path
2297 WHERE File.FilenameId = Filename.FilenameId
2298 AND File.PathId = Path.PathId
2299 AND Filename.Name = ''
2300 AND Path.Path = '$dirname'
2301 AND File.JobId = '$jobid'
2304 print STDERR $query,"\n" if $debug;
2305 my $result = $dbh->selectall_arrayref($query);
2306 return $result->[0]->[0];
2309 # Returns the fileindex from filename and jobid.
2310 # Input : dbh, filename, jobid
2311 # Output : fileindex
2312 sub get_fileindex_from_file_jobid
2314 my ($dbh, $filename, $jobid)=@_;
2316 my @dirs = split(/\//, $filename);
2317 $filename=pop(@dirs);
2318 my $dirname = join('/', @dirs) . '/';
2323 "SELECT File.FileIndex
2324 FROM File, Filename, Path
2325 WHERE File.FilenameId = Filename.FilenameId
2326 AND File.PathId = Path.PathId
2327 AND Filename.Name = '$filename'
2328 AND Path.Path = '$dirname'
2329 AND File.JobId = '$jobid'";
2331 print STDERR $query,"\n" if $debug;
2332 my $result = $dbh->selectall_arrayref($query);
2333 return $result->[0]->[0];
2337 # Returns list of versions of a file that could be restored
2338 # returns an array of
2339 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2340 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2341 # and volname and md5
2342 # and of course, there will be only one jobid in the array of jobids...
2343 sub get_all_file_versions
2345 my ($dbh,$path,$file,$client,$see_all)=@_;
2347 defined $see_all or $see_all=0;
2352 "SELECT File.JobId, File.FileIndex, File.Lstat,
2353 File.Md5, Media.VolumeName, Media.InChanger
2354 FROM File, Filename, Path, Job, Client, JobMedia, Media
2355 WHERE File.FilenameId = Filename.FilenameId
2356 AND File.PathId=Path.PathId
2357 AND File.JobId = Job.JobId
2358 AND Job.ClientId = Client.ClientId
2359 AND Job.JobId = JobMedia.JobId
2360 AND File.FileIndex >= JobMedia.FirstIndex
2361 AND File.FileIndex <= JobMedia.LastIndex
2362 AND JobMedia.MediaId = Media.MediaId
2363 AND Path.Path = '$path'
2364 AND Filename.Name = '$file'
2365 AND Client.Name = '$client'";
2367 print STDERR $query if $debug;
2369 my $result = $dbh->selectall_arrayref($query);
2371 foreach my $refrow (@$result)
2373 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2374 my @attribs = parse_lstat($lstat);
2375 my $mtime = array_attrib('st_mtime',\@attribs);
2376 my $size = array_attrib('st_size',\@attribs);
2378 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2379 $inchanger, $md5, $volname);
2380 push @versions, (\@list);
2383 # We have the list of all versions of this file.
2384 # We'll sort it by mtime desc, size, md5, inchanger desc
2385 # the rest of the algorithm will be simpler
2386 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2387 @versions = sort { $b->[4] <=> $a->[4]
2388 || $a->[5] <=> $b->[5]
2389 || $a->[7] cmp $a->[7]
2390 || $b->[6] <=> $a->[6]} @versions;
2393 my %allready_seen_by_mtime;
2394 my %allready_seen_by_md5;
2395 # Now we should create a new array with only the interesting records
2396 foreach my $ref (@versions)
2400 # The file has a md5. We compare his md5 to other known md5...
2401 # We take size into account. It may happen that 2 files
2402 # have the same md5sum and are different. size is a supplementary
2405 # If we allready have a (better) version
2406 next if ( (not $see_all)
2407 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2409 # we never met this one before...
2410 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2412 # Even if it has a md5, we should also work with mtimes
2413 # We allready have a (better) version
2414 next if ( (not $see_all)
2415 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2416 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2418 # We reached there. The file hasn't been seen.
2419 push @good_versions,($ref);
2422 # To be nice with the user, we re-sort good_versions by
2423 # inchanger desc, mtime desc
2424 @good_versions = sort { $b->[4] <=> $a->[4]
2425 || $b->[2] <=> $a->[2]} @good_versions;
2427 return @good_versions;
2430 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2431 # This sub creates a BSR from the information in the restore_list
2432 # Returns the BSR as a string
2437 # This query gets all jobid/jobmedia/media combination.
2439 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2440 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2441 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2442 Media.Volumename, Media.MediaType
2443 FROM Job, JobMedia, Media
2444 WHERE Job.JobId = JobMedia.JobId
2445 AND JobMedia.MediaId = Media.MediaId
2446 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2449 my $result = $self->dbh_selectall_arrayref($query);
2451 # We will store everything hashed by jobid.
2453 foreach my $refrow (@$result)
2455 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2456 $firstindex, $lastindex, $startblock, $endblock,
2457 $volindex, $volumename, $mediatype) = @{$refrow};
2459 # We just have to deal with the case where starfile != endfile
2460 # In this case, we concatenate both, for the bsr
2461 if ($startfile != $endfile) {
2462 $startfile = $startfile . '-' . $endfile;
2466 ($jobid, $volsessionid, $volsessiontime, $startfile,
2467 $firstindex, $lastindex, $startblock .'-'. $endblock,
2468 $volindex, $volumename, $mediatype);
2470 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2474 # reminder : restore_list looks like this :
2475 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2477 # Here, we retrieve every file/dir that could be in the restore
2478 # We do as simple as possible for the SQL engine (no crazy joins,
2479 # no pseudo join (>= FirstIndex ...), etc ...
2480 # We do a SQL union of all the files/dirs specified in the restore_list
2482 foreach my $entry (@{$self->{restore_list}->{data}})
2484 if ($entry->[2] eq 'dir')
2486 my $dir = unpack('u', $entry->[0]);
2487 my $inclause = $entry->[3]; #curjobids
2490 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2491 FROM File, Path, Filename
2492 WHERE Path.PathId = File.PathId
2493 AND File.FilenameId = Filename.FilenameId
2494 AND Path.Path LIKE '$dir%'
2495 AND File.JobId IN ($inclause) )";
2496 push @select_queries,($query);
2500 # It's a file. Great, we allready have most
2501 # of what is needed. Simple and efficient query
2502 my $file = unpack('u', $entry->[0]);
2503 my @file = split '/',$file;
2505 my $dir = join('/',@file);
2507 my $jobid = $entry->[1];
2508 my $fileindex = $entry->[7];
2509 my $inclause = $entry->[3]; # curjobids
2511 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2512 FROM File, Path, Filename
2513 WHERE Path.PathId = File.PathId
2514 AND File.FilenameId = Filename.FilenameId
2515 AND Path.Path = '$dir/'
2516 AND Filename.Name = '$file'
2517 AND File.JobId = $jobid)";
2518 push @select_queries,($query);
2521 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2523 print STDERR $query,"\n" if $debug;
2525 #Now we run the query and parse the result...
2526 # there may be a lot of records, so we better be efficient
2527 # We use the bind column method, working with references...
2529 my $sth = $self->dbh_prepare($query);
2532 my ($path,$name,$fileindex,$jobid);
2533 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2535 # The temp place we're going to save all file
2536 # list to before the real list
2540 while ($sth->fetchrow_arrayref())
2542 # This may look dumb, but we're going to do a join by ourselves,
2543 # to save memory and avoid sending a complex query to mysql
2544 my $complete_path = $path . $name;
2552 # Remove trailing slash (normalize file and dir name)
2553 $complete_path =~ s/\/$//;
2555 # Let's find the ref(s) for the %mediainfo element(s)
2556 # containing the data for this file
2557 # There can be several matches. It is the pseudo join.
2559 my $max_elt=@{$mediainfos{$jobid}}-1;
2561 while($med_idx <= $max_elt)
2563 my $ref = $mediainfos{$jobid}->[$med_idx];
2564 # First, can we get rid of the first elements of the
2565 # array ? (if they don't contain valuable records
2567 if ($fileindex > $ref->[5])
2569 # It seems we don't need anymore
2570 # this entry in %mediainfo (the input data
2573 shift @{$mediainfos{$jobid}};
2577 # We will do work on this elt. We can ++
2578 # $med_idx for next loop
2581 # %mediainfo row looks like :
2582 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2583 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2586 # We are in range. We store and continue looping
2588 if ($fileindex >= $ref->[4])
2590 my @data = ($complete_path,$is_dir,
2592 push @temp_list,(\@data);
2596 # We are not in range. No point in continuing looping
2597 # We go to next record.
2601 # Now we have the array.
2602 # We're going to sort it, by
2603 # path, volsessiontime DESC (get the most recent file...)
2604 # The array rows look like this :
2605 # complete_path,is_dir,fileindex,
2606 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2607 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2608 @temp_list = sort {$a->[0] cmp $b->[0]
2609 || $b->[3]->[2] <=> $a->[3]->[2]
2613 my $prev_complete_path='////'; # Sure not to match
2617 while (my $refrow = shift @temp_list)
2619 # For the sake of readability, we load $refrow
2620 # contents in real scalars
2621 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2622 my $jobid= $refother->[0]; # We don't need the rest...
2624 # We skip this entry.
2625 # We allready have a newer one and this
2626 # isn't a continuation of the same file
2627 next if ($complete_path eq $prev_complete_path
2628 and $jobid != $prev_jobid);
2632 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2634 # We would be recursing inside a file.
2635 # Just what we don't want (dir replaced by file
2636 # between two backups
2642 push @restore_list,($refrow);
2644 $prev_complete_path = $complete_path;
2645 $prev_jobid = $jobid;
2651 push @restore_list,($refrow);
2653 $prev_complete_path = $complete_path;
2654 $prev_jobid = $jobid;
2658 # We get rid of @temp_list... save memory
2661 # Ok everything is in the list. Let's sort it again in another way.
2662 # This time it will be in the bsr file order
2664 # we sort the results by
2665 # volsessiontime, volsessionid, volindex, fileindex
2666 # to get all files in right order...
2667 # Reminder : The array rows look like this :
2668 # complete_path,is_dir,fileindex,
2669 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2670 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2672 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2673 || $a->[3]->[1] <=> $b->[3]->[1]
2674 || $a->[3]->[7] <=> $b->[3]->[7]
2675 || $a->[2] <=> $b->[2] }
2678 # Now that everything is ready, we create the bsr
2679 my $prev_fileindex=-1;
2680 my $prev_volsessionid=-1;
2681 my $prev_volsessiontime=-1;
2682 my $prev_volumename=-1;
2683 my $prev_volfile=-1;
2687 my $first_of_current_range=0;
2688 my @fileindex_ranges;
2691 foreach my $refrow (@restore_list)
2693 my (undef,undef,$fileindex,$refother)=@{$refrow};
2694 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2695 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2697 # We can specifiy the number of files in each section of the
2698 # bsr to speedup restore (bacula can then jump over the
2699 # end of tape files.
2703 if ($prev_volumename eq '-1')
2705 # We only have to start the new range...
2706 $first_of_current_range=$fileindex;
2708 elsif ($prev_volsessionid != $volsessionid
2709 or $prev_volsessiontime != $volsessiontime
2710 or $prev_volumename ne $volumename
2711 or $prev_volfile ne $volfile)
2713 # We have to create a new section in the bsr...
2714 # We print the previous one ...
2715 # (before that, save the current range ...)
2716 if ($first_of_current_range != $prev_fileindex)
2719 push @fileindex_ranges,
2720 ("$first_of_current_range-$prev_fileindex");
2724 # We are out of a range,
2725 # but there is only one element in the range
2726 push @fileindex_ranges,
2727 ("$first_of_current_range");
2730 $bsr.=print_bsr_section(\@fileindex_ranges,
2732 $prev_volsessiontime,
2739 # Reset for next loop
2740 @fileindex_ranges=();
2741 $first_of_current_range=$fileindex;
2743 elsif ($fileindex-1 != $prev_fileindex)
2745 # End of a range of fileindexes
2746 if ($first_of_current_range != $prev_fileindex)
2749 push @fileindex_ranges,
2750 ("$first_of_current_range-$prev_fileindex");
2754 # We are out of a range,
2755 # but there is only one element in the range
2756 push @fileindex_ranges,
2757 ("$first_of_current_range");
2759 $first_of_current_range=$fileindex;
2761 $prev_fileindex=$fileindex;
2762 $prev_volsessionid = $volsessionid;
2763 $prev_volsessiontime = $volsessiontime;
2764 $prev_volumename = $volumename;
2765 $prev_volfile=$volfile;
2766 $prev_mediatype=$mediatype;
2767 $prev_volblocks=$volblocks;
2771 # Ok, we're out of the loop. Alas, there's still the last record ...
2772 if ($first_of_current_range != $prev_fileindex)
2775 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2780 # We are out of a range,
2781 # but there is only one element in the range
2782 push @fileindex_ranges,("$first_of_current_range");
2785 $bsr.=print_bsr_section(\@fileindex_ranges,
2787 $prev_volsessiontime,
2797 sub print_bsr_section
2799 my ($ref_fileindex_ranges,$volsessionid,
2800 $volsessiontime,$volumename,$volfile,
2801 $mediatype,$volblocks,$count)=@_;
2804 $bsr .= "Volume=\"$volumename\"\n";
2805 $bsr .= "MediaType=\"$mediatype\"\n";
2806 $bsr .= "VolSessionId=$volsessionid\n";
2807 $bsr .= "VolSessionTime=$volsessiontime\n";
2808 $bsr .= "VolFile=$volfile\n";
2809 $bsr .= "VolBlock=$volblocks\n";
2811 foreach my $range (@{$ref_fileindex_ranges})
2813 $bsr .= "FileIndex=$range\n";
2816 $bsr .= "Count=$count\n";
2820 # This function estimates the size to be restored for an entry of the restore
2822 # In : self,reference to the entry
2823 # Out : size in bytes, number of files
2824 sub estimate_restore_size
2826 # reminder : restore_list looks like this :
2827 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2831 if ($entry->[2] eq 'dir')
2833 my $dir = unpack('u', $entry->[0]);
2834 my $inclause = $entry->[3]; #curjobids
2836 "SELECT Path.Path, File.FilenameId, File.LStat
2837 FROM File, Path, Job
2838 WHERE Path.PathId = File.PathId
2839 AND File.JobId = Job.JobId
2840 AND Path.Path LIKE '$dir%'
2841 AND File.JobId IN ($inclause)
2842 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2846 # It's a file. Great, we allready have most
2847 # of what is needed. Simple and efficient query
2848 my $file = unpack('u', $entry->[0]);
2849 my @file = split '/',$file;
2851 my $dir = join('/',@file);
2853 my $jobid = $entry->[1];
2854 my $fileindex = $entry->[7];
2855 my $inclause = $entry->[3]; # curjobids
2857 "SELECT Path.Path, File.FilenameId, File.Lstat
2858 FROM File, Path, Filename
2859 WHERE Path.PathId = File.PathId
2860 AND Path.Path = '$dir/'
2861 AND Filename.Name = '$file'
2862 AND File.JobId = $jobid
2863 AND Filename.FilenameId = File.FilenameId";
2866 print STDERR $query,"\n" if $debug;
2867 my ($path,$nameid,$lstat);
2868 my $sth = $self->dbh_prepare($query);
2870 $sth->bind_columns(\$path,\$nameid,\$lstat);
2880 while ($sth->fetchrow_arrayref())
2882 # Only the latest version of a file
2883 next if ($nameid eq $old_nameid and $path eq $old_path);
2885 if ($rcount > 15000) {
2892 # We get the size of this file
2893 my $size=lstat_attrib($lstat,'st_size');
2894 $total_size += $size;
2897 $old_nameid=$nameid;
2899 return ($total_size,$total_files);
2902 sub update_brestore_table
2904 my ($self, @jobs) = @_;
2905 my $dbh = $self->{dbh};
2907 foreach my $job (sort {$a <=> $b} @jobs)
2909 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2910 my $retour = $self->dbh_selectrow_arrayref($query);
2911 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2913 print STDERR "Inserting path records for JobId $job\n";
2914 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2915 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2917 $self->dbh_do($query);
2919 # Now we have to do the directory recursion stuff to determine missing visibility
2920 # We try to avoid recursion, to be as fast as possible
2921 # We also only work on not allready hierarchised directories...
2923 print STDERR "Creating missing recursion paths for $job\n";
2925 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2926 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2927 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2928 WHERE brestore_pathvisibility.JobId = $job
2929 AND brestore_pathhierarchy.PathId IS NULL
2932 my $sth = $self->dbh_prepare($query);
2934 my $pathid; my $path;
2935 $sth->bind_columns(\$pathid,\$path);
2939 $self->build_path_hierarchy($path,$pathid);
2943 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2944 # This query gives all parent pathids for a given jobid that aren't stored.
2945 # It has to be called until no record is updated ...
2947 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2948 SELECT a.PathId,$job
2950 (SELECT DISTINCT h.PPathId AS PathId
2951 FROM brestore_pathhierarchy AS h
2952 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2953 WHERE p.JobId=$job) AS a
2956 FROM brestore_pathvisibility
2957 WHERE JobId=$job) AS b
2958 ON (a.PathId = b.PathId)
2959 WHERE b.PathId IS NULL)";
2960 print STDERR $query,"\n" if ($debug);
2962 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2964 print STDERR "Recursively adding $rows_affected records from $job\n";
2967 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2972 sub cleanup_brestore_table
2975 my $dbh = $self->{dbh};
2977 my $query = "SELECT JobId from brestore_knownjobid";
2978 my @jobs = @{$dbh->selectall_arrayref($query)};
2980 foreach my $jobentry (@jobs)
2982 my $job = $jobentry->[0];
2983 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2984 my $result = $dbh->selectall_arrayref($query);
2985 if (scalar(@{$result}))
2987 # There are still files for this jobid
2988 print STDERR "$job still exists. Not cleaning...\n";
2991 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2993 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2999 sub build_path_hierarchy
3001 my ($self, $path,$pathid)=@_;
3002 # Does the ppathid exist for this ? we use a memory cache...
3003 # In order to avoid the full loop, we consider that if a dir is allready in the
3004 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
3007 #print STDERR "$path\n" if $debug;
3008 if (! $self->{cache_ppathid}->{$pathid})
3010 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
3011 my $sth2 = $self->{dbh}->prepare_cached($query);
3012 $sth2->execute($pathid);
3013 # Do we have a result ?
3014 if (my $refrow = $sth2->fetchrow_arrayref)
3016 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
3018 # This dir was in the db ...
3019 # It means we can leave, the tree has allready been built for
3024 # We have to create the record ...
3025 # What's the current p_path ?
3026 my $ppath = parent_dir($path);
3027 my $ppathid = $self->return_pathid_from_path($ppath);
3028 $self->{cache_ppathid}->{$pathid}= $ppathid;
3030 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
3031 $sth2 = $self->{dbh}->prepare_cached($query);
3032 $sth2->execute($pathid,$ppathid);
3038 # It's allready in the cache.
3039 # We can leave, no time to waste here, all the parent dirs have allready
3047 sub return_pathid_from_path
3049 my ($self, $path) = @_;
3050 my $query = "SELECT PathId FROM Path WHERE Path = ?
3052 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3053 #print STDERR $query,"\n" if $debug;
3054 my $sth = $self->{dbh}->prepare_cached($query);
3055 $sth->execute($path,$path);
3056 my $result =$sth->fetchrow_arrayref();
3058 if (defined $result)
3060 return $result->[0];
3063 # A bit dirty : we insert into path AND missing_path, to be sure
3064 # we aren't deleted by a purge. We still need to insert into path to get
3065 # the pathid, because of mysql
3066 $query = "INSERT INTO Path (Path) VALUES (?)";
3067 #print STDERR $query,"\n" if $debug;
3068 $sth = $self->{dbh}->prepare_cached($query);
3069 $sth->execute($path);
3072 $query = " INSERT INTO brestore_missing_path (PathId,Path)
3073 SELECT PathId,Path FROM Path WHERE Path = ?";
3074 #print STDERR $query,"\n" if $debug;
3075 $sth = $self->{dbh}->prepare_cached($query);
3076 $sth->execute($path);
3078 $query = " DELETE FROM Path WHERE Path = ?";
3079 #print STDERR $query,"\n" if $debug;
3080 $sth = $self->{dbh}->prepare_cached($query);
3081 $sth->execute($path);
3083 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3084 #print STDERR $query,"\n" if $debug;
3085 $sth = $self->{dbh}->prepare_cached($query);
3086 $sth->execute($path);
3087 $result = $sth->fetchrow_arrayref();
3089 return $result->[0];
3101 # Root Windows case :
3102 if ($path =~ /^[a-z]+:\/$/i)
3107 my @tmp = split('/',$path);
3108 # We remove the last ...
3110 my $tmp = join ('/',@tmp) . '/';
3116 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3117 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3118 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3119 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3120 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3121 'data_stream' => 15);;
3124 my ($attrib,$ref_attrib)=@_;
3125 return $ref_attrib->[$attrib_name_id{$attrib}];
3129 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
3131 my ($file, $attrib)=@_;
3133 if (defined $attrib_name_id{$attrib}) {
3135 my @d = split(' ', $file->[2]) ; # TODO : cache this
3137 return from_base64($d[$attrib_name_id{$attrib}]);
3139 } elsif ($attrib eq 'jobid') {
3143 } elsif ($attrib eq 'name') {
3148 die "Attribute not known : $attrib.\n";
3152 # Return the jobid or attribute asked for a dir
3155 my ($self,$dir,$attrib)=@_;
3157 my @dir = split('/',$dir,-1);
3158 my $refdir=$self->{dirtree}->{$self->current_client};
3160 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3162 die "Attribute not known : $attrib.\n";
3165 foreach my $subdir (@dir)
3167 $refdir = $refdir->[0]->{$subdir};
3170 # $refdir is now the reference to the dir's array
3171 # Is the a jobid in @CurrentJobIds where the lstat is
3172 # defined (we'll search in reverse order)
3173 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3175 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3177 if ($attrib eq 'jobid')
3183 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3184 return $attribs[$attrib_name_id{$attrib}+1];
3189 return 0; # We cannot get a good attribute.
3190 # This directory is here for the sake of visibility
3195 my ($lstat,$attrib)=@_;
3196 if ($lstat and defined $attrib_name_id{$attrib})
3198 my @d = split(' ', $lstat) ; # TODO : cache this
3199 return from_base64($d[$attrib_name_id{$attrib}]);
3206 # Base 64 functions, directly from recover.pl.
3208 # Karl Hakimian <hakimian@aha.com>
3209 # This section is also under GPL v2 or later.
3216 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3217 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3218 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3219 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3220 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3222 @base64_map = (0) x 128;
3224 for (my $i=0; $i<64; $i++) {
3225 $base64_map[ord($base64_digits[$i])] = $i;
3240 if (substr($where, 0, 1) eq '-') {
3242 $where = substr($where, 1);
3245 while ($where ne '') {
3247 my $d = substr($where, 0, 1);
3248 $val += $base64_map[ord(substr($where, 0, 1))];
3249 $where = substr($where, 1);
3257 my @attribs = split(' ',$lstat);
3258 foreach my $element (@attribs)
3260 $element = from_base64($element);
3269 ################################################################
3272 use base qw/DlgResto/;
3276 my ($class, $conf) = @_;
3277 my $self = bless {info => $conf}, $class;
3279 $self->{dbh} = $conf->{dbh};
3288 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3289 my $jobs = $self->dbh_selectall_arrayref($query);
3291 $self->update_brestore_table(map { $_->[0] } @$jobs);
3302 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3306 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3309 GetOptions("conf=s" => \$file_conf,
3310 "batch" => \$batch_mod,
3312 "help" => \&HELP_MESSAGE) ;
3314 my $p = new Pref($file_conf);
3316 if (! -f $file_conf) {
3321 my $b = new Batch($p);
3322 if ($p->connect_db()) {
3323 $b->set_dbh($p->{dbh});
3329 $glade_file = $p->{glade_file};
3331 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3332 if (-f "$path/$glade_file") {
3333 $glade_file = "$path/$glade_file" ;
3338 # gtk have lots of warning on stderr
3339 if ($^O eq 'MSWin32')
3342 open(STDERR, ">stderr.log");
3347 if ( -f $glade_file) {
3348 my $w = new DlgResto($p);
3351 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3352 "Can't find your brestore.glade (glade_file => '$glade_file')
3353 Please, edit your $file_conf to setup it." );
3355 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3360 Gtk2->main; # Start Gtk2 main loop
3372 # Code pour trier les colonnes
3373 my $mod = $fileview->get_model();
3374 $mod->set_default_sort_func(sub {
3375 my ($model, $item1, $item2) = @_;
3376 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3377 my $b = $model->get($item2, 1); # colonne (indice 1)
3382 $fileview->set_headers_clickable(1);
3383 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3384 $col->signal_connect('clicked', sub {
3385 my ($colonne, $model) = @_;
3386 $model->set_sort_column_id (1, 'ascending');