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 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2186 $self->dbh_do($req);
2188 $req = "CREATE INDEX brestore_pathvisibility_jobid
2189 ON brestore_pathvisibility (JobId)";
2190 $self->dbh_do($req);
2193 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2194 unless ($self->dbh_do($verif)) {
2196 CREATE TABLE brestore_missing_path
2198 PathId int4 NOT NULL,
2200 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2202 $self->dbh_do($req);
2204 $req = "CREATE INDEX brestore_missing_path_path
2205 ON brestore_missing_path (Path)";
2206 $self->dbh_do($req);
2210 # Recursive function to calculate the visibility of each directory in the cache
2211 # tree Working with references to save time and memory
2212 # For each directory, we want to propagate it's visible jobids onto it's
2213 # parents directory.
2214 # A tree is visible if
2215 # - it's been in a backup pointed by the CurrentJobIds
2216 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2217 # In the second case, the directory is visible but has no metadata.
2218 # We symbolize this with lstat = 1 for this jobid in the cache.
2220 # Input : reference directory
2221 # Output : visibility of this dir. Has to know visibility of all subdirs
2222 # to know it's visibility, hence the recursing.
2228 # Get the subdirs array references list
2229 my @list_ref_subdirs;
2230 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2232 push @list_ref_subdirs,($ref_subdir);
2235 # Now lets recurse over these subdirs and retrieve the reference of a hash
2236 # containing the jobs where they are visible
2237 foreach my $ref_subdir (@list_ref_subdirs)
2239 my $ref_list_jobs = list_visible($ref_subdir);
2240 foreach my $jobid (keys %$ref_list_jobs)
2242 $visibility{$jobid}=1;
2246 # Ok. Now, we've got the list of those jobs. We are going to update our
2247 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2248 # the lstat for the known jobids. Put 1 in the new elements... But first,
2249 # let's store the current jobids
2251 foreach my $jobid (keys %{$refdir->[1]})
2253 push @known_jobids,($jobid);
2257 foreach my $jobid (keys %visibility)
2259 next if ($refdir->[1]->{$jobid});
2260 $refdir->[1]->{$jobid} = 1;
2262 # Add the known_jobids to %visibility
2263 foreach my $jobid (@known_jobids)
2265 $visibility{$jobid}=1;
2267 return \%visibility;
2270 # Returns the list of media required for a list of jobids.
2271 # Input : dbh, jobid1, jobid2...
2272 # Output : reference to array of (joibd, inchanger)
2273 sub get_required_media_from_jobid
2275 my ($dbh, @jobids)=@_;
2276 my $inclause = join(',',@jobids);
2278 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2279 FROM JobMedia, Media
2280 WHERE JobMedia.MediaId=Media.MediaId
2281 AND JobId In ($inclause)
2283 my $result = $dbh->selectall_arrayref($query);
2287 # Returns the fileindex from dirname and jobid.
2288 # Input : dbh, dirname, jobid
2289 # Output : fileindex
2290 sub get_fileindex_from_dir_jobid
2292 my ($dbh, $dirname, $jobid)=@_;
2294 $query = "SELECT File.FileIndex
2295 FROM File, Filename, Path
2296 WHERE File.FilenameId = Filename.FilenameId
2297 AND File.PathId = Path.PathId
2298 AND Filename.Name = ''
2299 AND Path.Path = '$dirname'
2300 AND File.JobId = '$jobid'
2303 print STDERR $query,"\n" if $debug;
2304 my $result = $dbh->selectall_arrayref($query);
2305 return $result->[0]->[0];
2308 # Returns the fileindex from filename and jobid.
2309 # Input : dbh, filename, jobid
2310 # Output : fileindex
2311 sub get_fileindex_from_file_jobid
2313 my ($dbh, $filename, $jobid)=@_;
2315 my @dirs = split(/\//, $filename);
2316 $filename=pop(@dirs);
2317 my $dirname = join('/', @dirs) . '/';
2322 "SELECT File.FileIndex
2323 FROM File, Filename, Path
2324 WHERE File.FilenameId = Filename.FilenameId
2325 AND File.PathId = Path.PathId
2326 AND Filename.Name = '$filename'
2327 AND Path.Path = '$dirname'
2328 AND File.JobId = '$jobid'";
2330 print STDERR $query,"\n" if $debug;
2331 my $result = $dbh->selectall_arrayref($query);
2332 return $result->[0]->[0];
2336 # Returns list of versions of a file that could be restored
2337 # returns an array of
2338 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2339 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2340 # and volname and md5
2341 # and of course, there will be only one jobid in the array of jobids...
2342 sub get_all_file_versions
2344 my ($dbh,$path,$file,$client,$see_all)=@_;
2346 defined $see_all or $see_all=0;
2351 "SELECT File.JobId, File.FileIndex, File.Lstat,
2352 File.Md5, Media.VolumeName, Media.InChanger
2353 FROM File, Filename, Path, Job, Client, JobMedia, Media
2354 WHERE File.FilenameId = Filename.FilenameId
2355 AND File.PathId=Path.PathId
2356 AND File.JobId = Job.JobId
2357 AND Job.ClientId = Client.ClientId
2358 AND Job.JobId = JobMedia.JobId
2359 AND File.FileIndex >= JobMedia.FirstIndex
2360 AND File.FileIndex <= JobMedia.LastIndex
2361 AND JobMedia.MediaId = Media.MediaId
2362 AND Path.Path = '$path'
2363 AND Filename.Name = '$file'
2364 AND Client.Name = '$client'";
2366 print STDERR $query if $debug;
2368 my $result = $dbh->selectall_arrayref($query);
2370 foreach my $refrow (@$result)
2372 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2373 my @attribs = parse_lstat($lstat);
2374 my $mtime = array_attrib('st_mtime',\@attribs);
2375 my $size = array_attrib('st_size',\@attribs);
2377 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2378 $inchanger, $md5, $volname);
2379 push @versions, (\@list);
2382 # We have the list of all versions of this file.
2383 # We'll sort it by mtime desc, size, md5, inchanger desc
2384 # the rest of the algorithm will be simpler
2385 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2386 @versions = sort { $b->[4] <=> $a->[4]
2387 || $a->[5] <=> $b->[5]
2388 || $a->[7] cmp $a->[7]
2389 || $b->[6] <=> $a->[6]} @versions;
2392 my %allready_seen_by_mtime;
2393 my %allready_seen_by_md5;
2394 # Now we should create a new array with only the interesting records
2395 foreach my $ref (@versions)
2399 # The file has a md5. We compare his md5 to other known md5...
2400 # We take size into account. It may happen that 2 files
2401 # have the same md5sum and are different. size is a supplementary
2404 # If we allready have a (better) version
2405 next if ( (not $see_all)
2406 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2408 # we never met this one before...
2409 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2411 # Even if it has a md5, we should also work with mtimes
2412 # We allready have a (better) version
2413 next if ( (not $see_all)
2414 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2415 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2417 # We reached there. The file hasn't been seen.
2418 push @good_versions,($ref);
2421 # To be nice with the user, we re-sort good_versions by
2422 # inchanger desc, mtime desc
2423 @good_versions = sort { $b->[4] <=> $a->[4]
2424 || $b->[2] <=> $a->[2]} @good_versions;
2426 return @good_versions;
2429 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2430 # This sub creates a BSR from the information in the restore_list
2431 # Returns the BSR as a string
2436 # This query gets all jobid/jobmedia/media combination.
2438 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2439 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2440 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2441 Media.Volumename, Media.MediaType
2442 FROM Job, JobMedia, Media
2443 WHERE Job.JobId = JobMedia.JobId
2444 AND JobMedia.MediaId = Media.MediaId
2445 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2448 my $result = $self->dbh_selectall_arrayref($query);
2450 # We will store everything hashed by jobid.
2452 foreach my $refrow (@$result)
2454 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2455 $firstindex, $lastindex, $startblock, $endblock,
2456 $volindex, $volumename, $mediatype) = @{$refrow};
2458 # We just have to deal with the case where starfile != endfile
2459 # In this case, we concatenate both, for the bsr
2460 if ($startfile != $endfile) {
2461 $startfile = $startfile . '-' . $endfile;
2465 ($jobid, $volsessionid, $volsessiontime, $startfile,
2466 $firstindex, $lastindex, $startblock .'-'. $endblock,
2467 $volindex, $volumename, $mediatype);
2469 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2473 # reminder : restore_list looks like this :
2474 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2476 # Here, we retrieve every file/dir that could be in the restore
2477 # We do as simple as possible for the SQL engine (no crazy joins,
2478 # no pseudo join (>= FirstIndex ...), etc ...
2479 # We do a SQL union of all the files/dirs specified in the restore_list
2481 foreach my $entry (@{$self->{restore_list}->{data}})
2483 if ($entry->[2] eq 'dir')
2485 my $dir = unpack('u', $entry->[0]);
2486 my $inclause = $entry->[3]; #curjobids
2489 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2490 FROM File, Path, Filename
2491 WHERE Path.PathId = File.PathId
2492 AND File.FilenameId = Filename.FilenameId
2493 AND Path.Path LIKE '$dir%'
2494 AND File.JobId IN ($inclause) )";
2495 push @select_queries,($query);
2499 # It's a file. Great, we allready have most
2500 # of what is needed. Simple and efficient query
2501 my $file = unpack('u', $entry->[0]);
2502 my @file = split '/',$file;
2504 my $dir = join('/',@file);
2506 my $jobid = $entry->[1];
2507 my $fileindex = $entry->[7];
2508 my $inclause = $entry->[3]; # curjobids
2510 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2511 FROM File, Path, Filename
2512 WHERE Path.PathId = File.PathId
2513 AND File.FilenameId = Filename.FilenameId
2514 AND Path.Path = '$dir/'
2515 AND Filename.Name = '$file'
2516 AND File.JobId = $jobid)";
2517 push @select_queries,($query);
2520 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2522 print STDERR $query,"\n" if $debug;
2524 #Now we run the query and parse the result...
2525 # there may be a lot of records, so we better be efficient
2526 # We use the bind column method, working with references...
2528 my $sth = $self->dbh_prepare($query);
2531 my ($path,$name,$fileindex,$jobid);
2532 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2534 # The temp place we're going to save all file
2535 # list to before the real list
2539 while ($sth->fetchrow_arrayref())
2541 # This may look dumb, but we're going to do a join by ourselves,
2542 # to save memory and avoid sending a complex query to mysql
2543 my $complete_path = $path . $name;
2551 # Remove trailing slash (normalize file and dir name)
2552 $complete_path =~ s/\/$//;
2554 # Let's find the ref(s) for the %mediainfo element(s)
2555 # containing the data for this file
2556 # There can be several matches. It is the pseudo join.
2558 my $max_elt=@{$mediainfos{$jobid}}-1;
2560 while($med_idx <= $max_elt)
2562 my $ref = $mediainfos{$jobid}->[$med_idx];
2563 # First, can we get rid of the first elements of the
2564 # array ? (if they don't contain valuable records
2566 if ($fileindex > $ref->[5])
2568 # It seems we don't need anymore
2569 # this entry in %mediainfo (the input data
2572 shift @{$mediainfos{$jobid}};
2576 # We will do work on this elt. We can ++
2577 # $med_idx for next loop
2580 # %mediainfo row looks like :
2581 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2582 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2585 # We are in range. We store and continue looping
2587 if ($fileindex >= $ref->[4])
2589 my @data = ($complete_path,$is_dir,
2591 push @temp_list,(\@data);
2595 # We are not in range. No point in continuing looping
2596 # We go to next record.
2600 # Now we have the array.
2601 # We're going to sort it, by
2602 # path, volsessiontime DESC (get the most recent file...)
2603 # The array rows look like this :
2604 # complete_path,is_dir,fileindex,
2605 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2606 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2607 @temp_list = sort {$a->[0] cmp $b->[0]
2608 || $b->[3]->[2] <=> $a->[3]->[2]
2612 my $prev_complete_path='////'; # Sure not to match
2616 while (my $refrow = shift @temp_list)
2618 # For the sake of readability, we load $refrow
2619 # contents in real scalars
2620 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2621 my $jobid= $refother->[0]; # We don't need the rest...
2623 # We skip this entry.
2624 # We allready have a newer one and this
2625 # isn't a continuation of the same file
2626 next if ($complete_path eq $prev_complete_path
2627 and $jobid != $prev_jobid);
2631 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2633 # We would be recursing inside a file.
2634 # Just what we don't want (dir replaced by file
2635 # between two backups
2641 push @restore_list,($refrow);
2643 $prev_complete_path = $complete_path;
2644 $prev_jobid = $jobid;
2650 push @restore_list,($refrow);
2652 $prev_complete_path = $complete_path;
2653 $prev_jobid = $jobid;
2657 # We get rid of @temp_list... save memory
2660 # Ok everything is in the list. Let's sort it again in another way.
2661 # This time it will be in the bsr file order
2663 # we sort the results by
2664 # volsessiontime, volsessionid, volindex, fileindex
2665 # to get all files in right order...
2666 # Reminder : The array rows look like this :
2667 # complete_path,is_dir,fileindex,
2668 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2669 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2671 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2672 || $a->[3]->[1] <=> $b->[3]->[1]
2673 || $a->[3]->[7] <=> $b->[3]->[7]
2674 || $a->[2] <=> $b->[2] }
2677 # Now that everything is ready, we create the bsr
2678 my $prev_fileindex=-1;
2679 my $prev_volsessionid=-1;
2680 my $prev_volsessiontime=-1;
2681 my $prev_volumename=-1;
2682 my $prev_volfile=-1;
2686 my $first_of_current_range=0;
2687 my @fileindex_ranges;
2690 foreach my $refrow (@restore_list)
2692 my (undef,undef,$fileindex,$refother)=@{$refrow};
2693 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2694 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2696 # We can specifiy the number of files in each section of the
2697 # bsr to speedup restore (bacula can then jump over the
2698 # end of tape files.
2702 if ($prev_volumename eq '-1')
2704 # We only have to start the new range...
2705 $first_of_current_range=$fileindex;
2707 elsif ($prev_volsessionid != $volsessionid
2708 or $prev_volsessiontime != $volsessiontime
2709 or $prev_volumename ne $volumename
2710 or $prev_volfile ne $volfile)
2712 # We have to create a new section in the bsr...
2713 # We print the previous one ...
2714 # (before that, save the current range ...)
2715 if ($first_of_current_range != $prev_fileindex)
2718 push @fileindex_ranges,
2719 ("$first_of_current_range-$prev_fileindex");
2723 # We are out of a range,
2724 # but there is only one element in the range
2725 push @fileindex_ranges,
2726 ("$first_of_current_range");
2729 $bsr.=print_bsr_section(\@fileindex_ranges,
2731 $prev_volsessiontime,
2738 # Reset for next loop
2739 @fileindex_ranges=();
2740 $first_of_current_range=$fileindex;
2742 elsif ($fileindex-1 != $prev_fileindex)
2744 # End of a range of fileindexes
2745 if ($first_of_current_range != $prev_fileindex)
2748 push @fileindex_ranges,
2749 ("$first_of_current_range-$prev_fileindex");
2753 # We are out of a range,
2754 # but there is only one element in the range
2755 push @fileindex_ranges,
2756 ("$first_of_current_range");
2758 $first_of_current_range=$fileindex;
2760 $prev_fileindex=$fileindex;
2761 $prev_volsessionid = $volsessionid;
2762 $prev_volsessiontime = $volsessiontime;
2763 $prev_volumename = $volumename;
2764 $prev_volfile=$volfile;
2765 $prev_mediatype=$mediatype;
2766 $prev_volblocks=$volblocks;
2770 # Ok, we're out of the loop. Alas, there's still the last record ...
2771 if ($first_of_current_range != $prev_fileindex)
2774 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2779 # We are out of a range,
2780 # but there is only one element in the range
2781 push @fileindex_ranges,("$first_of_current_range");
2784 $bsr.=print_bsr_section(\@fileindex_ranges,
2786 $prev_volsessiontime,
2796 sub print_bsr_section
2798 my ($ref_fileindex_ranges,$volsessionid,
2799 $volsessiontime,$volumename,$volfile,
2800 $mediatype,$volblocks,$count)=@_;
2803 $bsr .= "Volume=\"$volumename\"\n";
2804 $bsr .= "MediaType=\"$mediatype\"\n";
2805 $bsr .= "VolSessionId=$volsessionid\n";
2806 $bsr .= "VolSessionTime=$volsessiontime\n";
2807 $bsr .= "VolFile=$volfile\n";
2808 $bsr .= "VolBlock=$volblocks\n";
2810 foreach my $range (@{$ref_fileindex_ranges})
2812 $bsr .= "FileIndex=$range\n";
2815 $bsr .= "Count=$count\n";
2819 # This function estimates the size to be restored for an entry of the restore
2821 # In : self,reference to the entry
2822 # Out : size in bytes, number of files
2823 sub estimate_restore_size
2825 # reminder : restore_list looks like this :
2826 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2830 if ($entry->[2] eq 'dir')
2832 my $dir = unpack('u', $entry->[0]);
2833 my $inclause = $entry->[3]; #curjobids
2835 "SELECT Path.Path, File.FilenameId, File.LStat
2836 FROM File, Path, Job
2837 WHERE Path.PathId = File.PathId
2838 AND File.JobId = Job.JobId
2839 AND Path.Path LIKE '$dir%'
2840 AND File.JobId IN ($inclause)
2841 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2845 # It's a file. Great, we allready have most
2846 # of what is needed. Simple and efficient query
2847 my $file = unpack('u', $entry->[0]);
2848 my @file = split '/',$file;
2850 my $dir = join('/',@file);
2852 my $jobid = $entry->[1];
2853 my $fileindex = $entry->[7];
2854 my $inclause = $entry->[3]; # curjobids
2856 "SELECT Path.Path, File.FilenameId, File.Lstat
2857 FROM File, Path, Filename
2858 WHERE Path.PathId = File.PathId
2859 AND Path.Path = '$dir/'
2860 AND Filename.Name = '$file'
2861 AND File.JobId = $jobid
2862 AND Filename.FilenameId = File.FilenameId";
2865 print STDERR $query,"\n" if $debug;
2866 my ($path,$nameid,$lstat);
2867 my $sth = $self->dbh_prepare($query);
2869 $sth->bind_columns(\$path,\$nameid,\$lstat);
2879 while ($sth->fetchrow_arrayref())
2881 # Only the latest version of a file
2882 next if ($nameid eq $old_nameid and $path eq $old_path);
2884 if ($rcount > 15000) {
2891 # We get the size of this file
2892 my $size=lstat_attrib($lstat,'st_size');
2893 $total_size += $size;
2896 $old_nameid=$nameid;
2898 return ($total_size,$total_files);
2901 sub update_brestore_table
2903 my ($self, @jobs) = @_;
2904 my $dbh = $self->{dbh};
2906 foreach my $job (sort {$a <=> $b} @jobs)
2908 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2909 my $retour = $self->dbh_selectrow_arrayref($query);
2910 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2912 print STDERR "Inserting path records for JobId $job\n";
2913 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2914 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2916 $self->dbh_do($query);
2918 # Now we have to do the directory recursion stuff to determine missing visibility
2919 # We try to avoid recursion, to be as fast as possible
2920 # We also only work on not allready hierarchised directories...
2922 print STDERR "Creating missing recursion paths for $job\n";
2924 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2925 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2926 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2927 WHERE brestore_pathvisibility.JobId = $job
2928 AND brestore_pathhierarchy.PathId IS NULL
2931 my $sth = $self->dbh_prepare($query);
2933 my $pathid; my $path;
2934 $sth->bind_columns(\$pathid,\$path);
2938 $self->build_path_hierarchy($path,$pathid);
2942 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2943 # This query gives all parent pathids for a given jobid that aren't stored.
2944 # It has to be called until no record is updated ...
2946 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2947 SELECT a.PathId,$job
2949 (SELECT DISTINCT h.PPathId AS PathId
2950 FROM brestore_pathhierarchy AS h
2951 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2952 WHERE p.JobId=$job) AS a
2955 FROM brestore_pathvisibility
2956 WHERE JobId=$job) AS b
2957 ON (a.PathId = b.PathId)
2958 WHERE b.PathId IS NULL)";
2959 print STDERR $query,"\n" if ($debug);
2961 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2963 print STDERR "Recursively adding $rows_affected records from $job\n";
2966 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2971 sub cleanup_brestore_table
2974 my $dbh = $self->{dbh};
2976 my $query = "SELECT JobId from brestore_knownjobid";
2977 my @jobs = @{$dbh->selectall_arrayref($query)};
2979 foreach my $jobentry (@jobs)
2981 my $job = $jobentry->[0];
2982 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2983 my $result = $dbh->selectall_arrayref($query);
2984 if (scalar(@{$result}))
2986 # There are still files for this jobid
2987 print STDERR "$job still exists. Not cleaning...\n";
2990 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2992 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2998 sub build_path_hierarchy
3000 my ($self, $path,$pathid)=@_;
3001 # Does the ppathid exist for this ? we use a memory cache...
3002 # In order to avoid the full loop, we consider that if a dir is allready in the
3003 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
3006 #print STDERR "$path\n" if $debug;
3007 if (! $self->{cache_ppathid}->{$pathid})
3009 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
3010 my $sth2 = $self->{dbh}->prepare_cached($query);
3011 $sth2->execute($pathid);
3012 # Do we have a result ?
3013 if (my $refrow = $sth2->fetchrow_arrayref)
3015 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
3017 # This dir was in the db ...
3018 # It means we can leave, the tree has allready been built for
3023 # We have to create the record ...
3024 # What's the current p_path ?
3025 my $ppath = parent_dir($path);
3026 my $ppathid = $self->return_pathid_from_path($ppath);
3027 $self->{cache_ppathid}->{$pathid}= $ppathid;
3029 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
3030 $sth2 = $self->{dbh}->prepare_cached($query);
3031 $sth2->execute($pathid,$ppathid);
3037 # It's allready in the cache.
3038 # We can leave, no time to waste here, all the parent dirs have allready
3046 sub return_pathid_from_path
3048 my ($self, $path) = @_;
3049 my $query = "SELECT PathId FROM Path WHERE Path = ?
3051 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3052 #print STDERR $query,"\n" if $debug;
3053 my $sth = $self->{dbh}->prepare_cached($query);
3054 $sth->execute($path,$path);
3055 my $result =$sth->fetchrow_arrayref();
3057 if (defined $result)
3059 return $result->[0];
3062 # A bit dirty : we insert into path AND missing_path, to be sure
3063 # we aren't deleted by a purge. We still need to insert into path to get
3064 # the pathid, because of mysql
3065 $query = "INSERT INTO Path (Path) VALUES (?)";
3066 #print STDERR $query,"\n" if $debug;
3067 $sth = $self->{dbh}->prepare_cached($query);
3068 $sth->execute($path);
3071 $query = " INSERT INTO brestore_missing_path (PathId,Path)
3072 SELECT PathId,Path FROM Path WHERE Path = ?";
3073 #print STDERR $query,"\n" if $debug;
3074 $sth = $self->{dbh}->prepare_cached($query);
3075 $sth->execute($path);
3077 $query = " DELETE FROM Path WHERE Path = ?";
3078 #print STDERR $query,"\n" if $debug;
3079 $sth = $self->{dbh}->prepare_cached($query);
3080 $sth->execute($path);
3082 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3083 #print STDERR $query,"\n" if $debug;
3084 $sth = $self->{dbh}->prepare_cached($query);
3085 $sth->execute($path);
3086 $result = $sth->fetchrow_arrayref();
3088 return $result->[0];
3100 # Root Windows case :
3101 if ($path =~ /^[a-z]+:\/$/i)
3106 my @tmp = split('/',$path);
3107 # We remove the last ...
3109 my $tmp = join ('/',@tmp) . '/';
3115 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3116 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3117 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3118 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3119 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3120 'data_stream' => 15);;
3123 my ($attrib,$ref_attrib)=@_;
3124 return $ref_attrib->[$attrib_name_id{$attrib}];
3128 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
3130 my ($file, $attrib)=@_;
3132 if (defined $attrib_name_id{$attrib}) {
3134 my @d = split(' ', $file->[2]) ; # TODO : cache this
3136 return from_base64($d[$attrib_name_id{$attrib}]);
3138 } elsif ($attrib eq 'jobid') {
3142 } elsif ($attrib eq 'name') {
3147 die "Attribute not known : $attrib.\n";
3151 # Return the jobid or attribute asked for a dir
3154 my ($self,$dir,$attrib)=@_;
3156 my @dir = split('/',$dir,-1);
3157 my $refdir=$self->{dirtree}->{$self->current_client};
3159 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3161 die "Attribute not known : $attrib.\n";
3164 foreach my $subdir (@dir)
3166 $refdir = $refdir->[0]->{$subdir};
3169 # $refdir is now the reference to the dir's array
3170 # Is the a jobid in @CurrentJobIds where the lstat is
3171 # defined (we'll search in reverse order)
3172 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3174 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3176 if ($attrib eq 'jobid')
3182 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3183 return $attribs[$attrib_name_id{$attrib}+1];
3188 return 0; # We cannot get a good attribute.
3189 # This directory is here for the sake of visibility
3194 my ($lstat,$attrib)=@_;
3195 if ($lstat and defined $attrib_name_id{$attrib})
3197 my @d = split(' ', $lstat) ; # TODO : cache this
3198 return from_base64($d[$attrib_name_id{$attrib}]);
3205 # Base 64 functions, directly from recover.pl.
3207 # Karl Hakimian <hakimian@aha.com>
3208 # This section is also under GPL v2 or later.
3215 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3216 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3217 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3218 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3219 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3221 @base64_map = (0) x 128;
3223 for (my $i=0; $i<64; $i++) {
3224 $base64_map[ord($base64_digits[$i])] = $i;
3239 if (substr($where, 0, 1) eq '-') {
3241 $where = substr($where, 1);
3244 while ($where ne '') {
3246 my $d = substr($where, 0, 1);
3247 $val += $base64_map[ord(substr($where, 0, 1))];
3248 $where = substr($where, 1);
3256 my @attribs = split(' ',$lstat);
3257 foreach my $element (@attribs)
3259 $element = from_base64($element);
3268 ################################################################
3271 use base qw/DlgResto/;
3275 my ($class, $conf) = @_;
3276 my $self = bless {info => $conf}, $class;
3278 $self->{dbh} = $conf->{dbh};
3287 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3288 my $jobs = $self->dbh_selectall_arrayref($query);
3290 $self->update_brestore_table(map { $_->[0] } @$jobs);
3301 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3305 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3308 GetOptions("conf=s" => \$file_conf,
3309 "batch" => \$batch_mod,
3311 "help" => \&HELP_MESSAGE) ;
3313 my $p = new Pref($file_conf);
3315 if (! -f $file_conf) {
3320 my $b = new Batch($p);
3321 if ($p->connect_db()) {
3322 $b->set_dbh($p->{dbh});
3328 $glade_file = $p->{glade_file};
3330 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3331 if (-f "$path/$glade_file") {
3332 $glade_file = "$path/$glade_file" ;
3337 # gtk have lots of warning on stderr
3338 if ($^O eq 'MSWin32')
3341 open(STDERR, ">stderr.log");
3346 if ( -f $glade_file) {
3347 my $w = new DlgResto($p);
3350 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3351 "Can't find your brestore.glade (glade_file => '$glade_file')
3352 Please, edit your $file_conf to setup it." );
3354 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3359 Gtk2->main; # Start Gtk2 main loop
3371 # Code pour trier les colonnes
3372 my $mod = $fileview->get_model();
3373 $mod->set_default_sort_func(sub {
3374 my ($model, $item1, $item2) = @_;
3375 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3376 my $b = $model->get($item2, 1); # colonne (indice 1)
3381 $fileview->set_headers_clickable(1);
3382 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3383 $col->signal_connect('clicked', sub {
3384 my ($colonne, $model) = @_;
3385 $model->set_sort_column_id (1, 'ascending');