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 new DlgWarn("Can't connect to bweb : " . $res->status_line);
269 unlink($arg{bootstrap});
277 return sort split(/;/, $self->{'list_job'});
283 return sort split(/;/, $self->{'list_fileset'});
289 return sort split(/;/, $self->{'list_storage'});
294 return sort split(/;/, $self->{'list_client'});
299 ################################################################
305 # %arg = (bsr_file => '/path/to/bsr', # on director
306 # volumes => [ '00001', '00004']
314 if ($pref->{bconsole} =~ /^http/) {
315 return new BwebConsole(pref => $pref);
317 if (eval { require Bconsole; }) {
318 return new Bconsole(pref => $pref);
320 new DlgWarn("Can't use bconsole, verify your setup");
328 my ($class, %arg) = @_;
331 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
332 pref => $arg{pref}, # Pref ref
333 glade => undef, # GladeXML ref
334 bconsole => undef, # Bconsole ref
337 my $console = $self->{bconsole} = get_bconsole($arg{pref});
342 # we load launch widget of $glade_file
343 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
346 # Connect signals magically
347 $glade->signal_autoconnect_from_package($self);
349 my $widget = $glade->get_widget('volumeview');
350 my $volview = Gtk2::SimpleList->new_from_treeview(
352 'InChanger' => 'pixbuf',
356 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
358 # we replace 0 and 1 by $noicon and $yesicon
359 for my $i (@{$infos}) {
361 $i->[0] = $DlgResto::noicon;
363 $i->[0] = $DlgResto::yesicon;
368 push @{ $volview->{data} }, @{$infos} ;
370 $console->prepare(qw/list_client list_job list_fileset list_storage/);
372 # fill client combobox (with director defined clients
373 my @clients = $console->list_client() ; # get from bconsole
374 if ($console->{error}) {
375 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
377 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
378 $self->{list_client} = DlgResto::init_combo($w, 'text');
379 DlgResto::fill_combo($self->{list_client},
380 $DlgResto::client_list_empty,
384 # fill fileset combobox
385 my @fileset = $console->list_fileset() ;
386 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
387 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
388 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
391 my @job = $console->list_job() ;
392 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
393 $self->{list_job} = DlgResto::init_combo($w, 'text');
394 DlgResto::fill_combo($self->{list_job}, '', @job);
396 # find default_restore_job in jobs list
397 my $default_restore_job = $arg{pref}->{default_restore_job} ;
401 if ($j =~ /$default_restore_job/io) {
407 $w->set_active($index);
409 # fill storage combobox
410 my @storage = $console->list_storage() ;
411 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
412 $self->{list_storage} = DlgResto::init_combo($w, 'text');
413 DlgResto::fill_combo($self->{list_storage}, '', @storage);
415 $glade->get_widget('dlg_launch')->show_all();
422 my ($self, $client, $jobid) = @_;
424 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
427 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
428 "Your job have been submited to bacula.
429 To follow it, you must use bconsole (or install/configure bweb)");
434 $self->on_cancel_resto_clicked();
437 sub on_cancel_resto_clicked
440 $self->{glade}->get_widget('dlg_launch')->destroy();
443 sub on_submit_resto_clicked
446 my $glade = $self->{glade};
448 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
451 new DlgWarn("Can't copy bsr file to director ($self->{error})");
455 my $fileset = $glade->get_widget('combo_launch_fileset')
458 my $storage = $glade->get_widget('combo_launch_storage')
461 my $where = $glade->get_widget('entry_launch_where')->get_text();
463 my $job = $glade->get_widget('combo_launch_job')
467 new DlgWarn("Can't use this job");
471 my $client = $glade->get_widget('combo_launch_client')
474 if (! $client or $client eq $DlgResto::client_list_empty) {
475 new DlgWarn("Can't use this client ($client)");
479 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
481 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
482 $replace=($replace)?'always':'never';
484 my $jobid = $self->{bconsole}->run(job => $job,
493 $self->show_job($client, $jobid);
496 sub on_combo_storage_button_press_event
499 print "on_combo_storage_button_press_event()\n";
502 sub on_combo_fileset_button_press_event
505 print "on_combo_fileset_button_press_event()\n";
509 sub on_combo_job_button_press_event
512 print "on_combo_job_button_press_event()\n";
515 sub get_volume_inchanger
517 my ($dbh, $vols) = @_;
519 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
521 my $rq = "SELECT InChanger, VolumeName
523 WHERE VolumeName IN ($lst)
526 my $res = $dbh->selectall_arrayref($rq);
527 return $res; # [ [ 1, VolName].. ]
531 use File::Copy qw/copy/;
532 use File::Basename qw/basename/;
534 # We must kown the path+filename destination
535 # $self->{error} contains error message
536 # it return 0/1 if fail/success
539 my ($self, $src, $dst) = @_ ;
540 print "$src => $dst\n"
547 if ($dst =~ m!file:/(/.+)!) {
548 $ret = copy($src, $1);
550 $dstfile = "$1/" . basename($src) ;
552 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
553 $err = `scp $src $1 2>&1` ;
555 $dstfile = "$2/" . basename($src) ;
559 $err = "$dst not implemented yet";
560 File::Copy::copy($src, \*STDOUT);
563 $self->{error} = $err;
566 $self->{error} = $err;
575 ################################################################
583 unless ($about_widget) {
584 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
585 $about_widget = $glade_box->get_widget("dlg_about") ;
586 $glade_box->signal_autoconnect_from_package('DlgAbout');
588 $about_widget->show() ;
591 sub on_about_okbutton_clicked
593 $about_widget->hide() ;
598 ################################################################
604 my ($class, $config_file) = @_;
607 config_file => $config_file,
608 password => '', # db passwd
609 username => '', # db username
610 connection_string => '',# db connection string
611 bconsole => 'bconsole', # path and arg to bconsole
612 bsr_dest => '', # destination url for bsr files
613 debug => 0, # debug level 0|1
614 use_ok_bkp_only => 1, # dont use bad backup
615 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
616 glade_file => $glade_file,
617 see_all_versions => 0, # display all file versions in FileInfo
618 mozilla => 'mozilla', # mozilla bin
619 default_restore_job => 'restore', # regular expression to select default
622 # keywords that are used to fill DlgPref
623 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
624 entry_keyword => [ qw/username password bweb mozilla
625 connection_string default_restore_job
626 bconsole bsr_dest glade_file/],
629 $self->read_config();
638 # We read the parameters. They come from the configuration files
639 my $cfgfile ; my $tmpbuffer;
640 if (open FICCFG, $self->{config_file})
642 while(read FICCFG,$tmpbuffer,4096)
644 $cfgfile .= $tmpbuffer;
648 no strict; # I have no idea of the contents of the file
649 eval '$refparams' . " = $cfgfile";
652 for my $p (keys %{$refparams}) {
653 $self->{$p} = $refparams->{$p};
656 if (defined $self->{debug}) {
657 $debug = $self->{debug} ;
660 # TODO : Force dumb default values and display a message
670 for my $k (@{ $self->{entry_keyword} }) {
671 $parameters{$k} = $self->{$k};
674 for my $k (@{ $self->{chk_keyword} }) {
675 $parameters{$k} = $self->{$k};
678 if (open FICCFG,">$self->{config_file}")
680 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
685 # TODO : Display a message
694 $self->{dbh}->disconnect() ;
698 delete $self->{error};
700 if (not $self->{connection_string})
702 # The parameters have not been set. Maybe the conf
703 # file is empty for now
704 $self->{error} = "No configuration found for database connection. " .
705 "Please set this up.";
710 $self->{dbh} = DBI->connect($self->{connection_string},
715 $self->{error} = "Can't open bacula database. " .
716 "Database connect string '" .
717 $self->{connection_string} ."' $!";
720 $self->{dbh}->{RowCacheSize}=100;
726 my ($self, $url, $msg) = @_;
728 unless ($self->{mozilla} and $self->{bweb}) {
729 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
733 if ($^O eq 'MSWin32') {
734 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
737 system("$self->{mozilla} -remote 'Ping()'");
738 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
740 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
750 ################################################################
754 # my $pref = new Pref(config_file => 'brestore.conf');
755 # my $dlg = new DlgPref($pref);
756 # my $dlg_resto = new DlgResto($pref);
757 # $dlg->display($dlg_resto);
760 my ($class, $pref) = @_;
763 pref => $pref, # Pref ref
764 dlgresto => undef, # DlgResto ref
772 my ($self, $dlgresto) = @_ ;
774 unless ($self->{glade}) {
775 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
776 $self->{glade}->signal_autoconnect_from_package($self);
779 $self->{dlgresto} = $dlgresto;
781 my $g = $self->{glade};
782 my $p = $self->{pref};
784 for my $k (@{ $p->{entry_keyword} }) {
785 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
788 for my $k (@{ $p->{chk_keyword} }) {
789 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
792 $g->get_widget("dlg_pref")->show_all() ;
795 sub on_applybutton_clicked
798 my $glade = $self->{glade};
799 my $pref = $self->{pref};
801 for my $k (@{ $pref->{entry_keyword} }) {
802 my $w = $glade->get_widget("entry_$k") ;
803 $pref->{$k} = $w->get_text();
806 for my $k (@{ $pref->{chk_keyword} }) {
807 my $w = $glade->get_widget("chkbp_$k") ;
808 $pref->{$k} = $w->get_active();
811 $pref->write_config();
812 if ($pref->connect_db()) {
813 $self->{dlgresto}->set_dbh($pref->{dbh});
814 $self->{dlgresto}->set_status('Preferences updated');
815 $self->{dlgresto}->init_server_backup_combobox();
816 $self->{dlgresto}->create_brestore_tables();
817 $self->{dlgresto}->set_status($pref->{error});
819 $self->{dlgresto}->set_status($pref->{error});
823 # Handle prefs ok click (apply/dismiss dialog)
824 sub on_okbutton_clicked
827 $self->on_applybutton_clicked();
829 unless ($self->{pref}->{error}) {
830 $self->on_cancelbutton_clicked();
833 sub on_dialog_delete_event
836 $self->on_cancelbutton_clicked();
840 sub on_cancelbutton_clicked
843 $self->{glade}->get_widget('dlg_pref')->hide();
844 delete $self->{dlgresto};
848 ################################################################
858 # Kept as is from the perl-gtk example. Draws the pretty icons
864 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
865 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
866 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
867 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
871 # init combo (and create ListStore object)
874 my ($widget, @type) = @_ ;
875 my %type_info = ('text' => 'Glib::String',
876 'markup' => 'Glib::String',
879 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
881 $widget->set_model($lst);
885 if ($t eq 'text' or $t eq 'markup') {
886 $cell = new Gtk2::CellRendererText();
888 $widget->pack_start($cell, 1);
889 $widget->add_attribute($cell, $t, $i++);
894 # fill simple combo (one element per row)
897 my ($list, @what) = @_;
901 foreach my $w (@what)
904 my $i = $list->append();
905 $list->set($i, 0, $w);
912 my @unit = qw(b Kb Mb Gb Tb);
915 my $format = '%i %s';
916 while ($val / 1024 > 1) {
920 $format = ($i>0)?'%0.1f %s':'%i %s';
921 return sprintf($format, $val, $unit[$i]);
926 my ($self, $dbh) = @_;
932 my ($fileview) = shift;
933 my $fileview_target_entry = {target => 'STRING',
934 flags => ['GTK_TARGET_SAME_APP'],
937 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
938 ['copy'],$fileview_target_entry);
939 $fileview->get_selection->set_mode('multiple');
941 # set some useful SimpleList properties
942 $fileview->set_headers_clickable(0);
943 foreach ($fileview->get_columns())
945 $_->set_resizable(1);
946 $_->set_sizing('grow-only');
952 my ($self, $what) = @_;
956 print Data::Dumper::Dumper($what);
957 } elsif (defined $what) {
965 my ($self, $query) = @_;
966 $self->debug($query);
967 return $self->{dbh}->prepare($query);
972 my ($self, $query) = @_;
973 $self->debug($query);
974 return $self->{dbh}->do($query);
977 sub dbh_selectall_arrayref
979 my ($self, $query) = @_;
980 $self->debug($query);
981 return $self->{dbh}->selectall_arrayref($query);
984 sub dbh_selectrow_arrayref
986 my ($self, $query) = @_;
987 $self->debug($query);
988 return $self->{dbh}->selectrow_arrayref($query);
993 my ($class, $pref) = @_;
998 location => undef, # location entry widget
999 mainwin => undef, # mainwin widget
1000 filelist_file_menu => undef, # file menu widget
1001 filelist_dir_menu => undef, # dir menu widget
1002 glade => undef, # glade object
1003 status => undef, # status bar widget
1004 dlg_pref => undef, # DlgPref object
1005 fileattrib => {}, # cache file
1006 fileview => undef, # fileview widget SimpleList
1007 fileinfo => undef, # fileinfo widget SimpleList
1009 client_combobox => undef, # client_combobox widget
1010 restore_backup_combobox => undef, # date combobox widget
1011 list_client => undef, # Gtk2::ListStore
1012 list_backup => undef, # Gtk2::ListStore
1013 cache_ppathid => {}, #
1016 # load menu (to use handler with self reference)
1017 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1018 $glade->signal_autoconnect_from_package($self);
1019 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1021 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1022 $glade->signal_autoconnect_from_package($self);
1023 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1025 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1026 $glade->signal_autoconnect_from_package($self);
1028 $self->{status} = $glade->get_widget('statusbar');
1029 $self->{mainwin} = $glade->get_widget('dlg_resto');
1030 $self->{location} = $glade->get_widget('entry_location');
1031 $self->render_icons();
1033 $self->{dlg_pref} = new DlgPref($pref);
1035 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1036 $self->{list_client} = init_combo($c, 'text');
1038 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1039 $self->{list_backup} = init_combo($c, 'text', 'markup');
1041 # Connect glade-fileview to Gtk2::SimpleList
1042 # and set up drag n drop between $fileview and $restore_list
1044 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1045 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1047 my $widget = $glade->get_widget('fileview');
1048 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1050 'h_name' => 'hidden',
1051 'h_jobid' => 'hidden',
1052 'h_type' => 'hidden',
1055 'File Name' => 'text',
1058 init_drag_drop($fileview);
1059 $fileview->set_search_column(4); # search on File Name
1061 # Connect glade-restore_list to Gtk2::SimpleList
1062 $widget = $glade->get_widget('restorelist');
1063 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1065 'h_name' => 'hidden',
1066 'h_jobid' => 'hidden',
1067 'h_type' => 'hidden',
1068 'h_curjobid' => 'hidden',
1071 'File Name' => 'text',
1073 'FileIndex' => 'text',
1075 'Nb Files' => 'text', #8
1076 'Size' => 'text', #9
1077 'size_b' => 'hidden', #10
1080 my @restore_list_target_table = ({'target' => 'STRING',
1084 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1085 $restore_list->get_selection->set_mode('multiple');
1087 $widget = $glade->get_widget('infoview');
1088 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1090 'h_name' => 'hidden',
1091 'h_jobid' => 'hidden',
1092 'h_type' => 'hidden',
1094 'InChanger' => 'pixbuf',
1101 init_drag_drop($infoview);
1103 $pref->connect_db() || $self->{dlg_pref}->display($self);
1106 $self->{dbh} = $pref->{dbh};
1107 $self->init_server_backup_combobox();
1108 $self->create_brestore_tables();
1111 $self->set_status($pref->{error});
1114 # set status bar informations
1117 my ($self, $string) = @_;
1118 return unless ($string);
1120 my $context = $self->{status}->get_context_id('Main');
1121 $self->{status}->push($context, $string);
1124 sub on_time_select_changed
1132 my $c = $self->{glade}->get_widget('combo_time');
1133 return $c->get_active_text;
1136 # This sub returns all clients declared in DB
1140 my $query = "SELECT Name FROM Client ORDER BY Name";
1141 print STDERR $query,"\n" if $debug;
1143 my $result = $dbh->selectall_arrayref($query);
1145 return map { $_->[0] } @$result;
1148 sub get_wanted_job_status
1155 return "'T', 'A', 'E'";
1159 # This sub gives a full list of the EndTimes for a ClientId
1160 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
1161 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
1162 sub get_all_endtimes_for_job
1164 my ($dbh, $client, $ok_only)=@_;
1165 my $status = get_wanted_job_status($ok_only);
1167 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
1168 FROM Job,Client,FileSet
1169 WHERE Job.ClientId=Client.ClientId
1170 AND Client.Name = '$client'
1172 AND JobStatus IN ($status)
1173 AND Job.FileSetId = FileSet.FileSetId
1174 ORDER BY EndTime desc";
1175 print STDERR $query,"\n" if $debug;
1176 my $result = $dbh->selectall_arrayref($query);
1182 # init infoview widget
1186 @{$self->{fileinfo}->{data}} = ();
1190 sub on_clear_clicked
1193 @{$self->{restore_list}->{data}} = ();
1196 sub on_estimate_clicked
1203 # TODO : If we get here, things could get lenghty ... draw a popup window .
1204 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1205 'destroy-with-parent',
1207 'Computing size...');
1211 my $title = "Computing size...\n";
1213 foreach my $entry (@{$self->{restore_list}->{data}})
1215 unless ($entry->[9]) {
1216 my ($size, $nb) = $self->estimate_restore_size($entry);
1217 $entry->[10] = $size;
1218 $entry->[9] = human($size);
1222 my $name = unpack('u', $entry->[0]);
1224 $txt .= "\n<i>$name</i> : " . $entry->[8] . " file(s)/" . $entry->[9] ;
1225 $widget->set_markup($title . $txt);
1227 $size_total+=$entry->[10];
1228 $nb_total+=$entry->[8];
1232 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1233 $widget->set_markup("Size estimation :\n" . $txt);
1234 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1239 sub on_gen_bsr_clicked
1243 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1244 'gtk-save','ok', 'gtk-cancel', 'cancel');
1247 my $w = new Gtk2::FileChooserDialog ( @options );
1252 if ($a eq 'cancel') {
1257 my $f = $w->get_filename();
1259 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1260 'destroy-with-parent',
1261 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1262 if ($dlg->run() eq 'ok') {
1276 if (open(FP, ">$save")) {
1277 my $bsr = $self->create_filelist();
1280 $self->set_status("Dumping BSR to $save ok");
1282 $self->set_status("Can't dump BSR to $save: $!");
1287 use File::Temp qw/tempfile/;
1289 sub on_go_button_clicked
1292 unless (scalar(@{$self->{restore_list}->{data}})) {
1293 new DlgWarn("No file to restore");
1296 my $bsr = $self->create_filelist();
1297 my ($fh, $filename) = tempfile();
1300 chmod(0644, $filename);
1302 print "Dumping BSR info to $filename\n"
1305 # we get Volume list
1306 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1307 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1309 new DlgLaunch(pref => $self->{pref},
1311 bsr_file => $filename,
1316 our $client_list_empty = 'Clients list';
1317 our %type_markup = ('F' => '<b>$label F</b>',
1320 'B' => '<b>$label B</b>',
1322 'A' => '<span foreground=\"red\">$label</span>',
1324 'E' => '<span foreground=\"red\">$label</span>',
1327 sub on_list_client_changed
1329 my ($self, $widget) = @_;
1330 return 0 unless defined $self->{fileview};
1331 my $dbh = $self->{dbh};
1333 $self->{list_backup}->clear();
1335 if ($self->current_client eq $client_list_empty) {
1339 my @endtimes=get_all_endtimes_for_job($dbh,
1340 $self->current_client,
1341 $self->{pref}->{use_ok_bkp_only});
1342 foreach my $endtime (@endtimes)
1344 my $i = $self->{list_backup}->append();
1346 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1347 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1348 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1350 $self->{list_backup}->set($i,
1355 $self->{restore_backup_combobox}->set_active(0);
1357 $self->{CurrentJobIds} = [
1358 set_job_ids_for_date($dbh,
1359 $self->current_client,
1360 $self->current_date,
1361 $self->{pref}->{use_ok_bkp_only})
1364 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1367 $self->refresh_fileview();
1371 sub fill_server_list
1373 my ($dbh, $combo, $list) = @_;
1375 my @clients=get_all_clients($dbh);
1379 my $i = $list->append();
1380 $list->set($i, 0, $client_list_empty);
1382 foreach my $client (@clients)
1384 $i = $list->append();
1385 $list->set($i, 0, $client);
1387 $combo->set_active(0);
1390 sub init_server_backup_combobox
1393 fill_server_list($self->{dbh},
1394 $self->{client_combobox},
1395 $self->{list_client}) ;
1398 #----------------------------------------------------------------------
1399 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1400 #data isn't. There is additionnal complexity for dirs (visibility problems),
1401 #so the @CurrentJobIds is not sufficient.
1402 sub refresh_fileview
1405 my $fileview = $self->{fileview};
1406 my $client_combobox = $self->{client_combobox};
1407 my $cwd = $self->{cwd};
1409 @{$fileview->{data}} = ();
1411 $self->clear_infoview();
1413 my $client_name = $self->current_client;
1415 if (!$client_name or ($client_name eq $client_list_empty)) {
1416 $self->set_status("Client list empty");
1420 my @list_dirs = $self->list_dirs($cwd,$client_name);
1421 # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1422 my $files = $self->list_files($cwd);
1423 print "CWD : $cwd\n" if ($debug);
1425 my $file_count = 0 ;
1426 my $total_bytes = 0;
1428 # Add directories to view
1429 foreach my $dir_entry (@list_dirs) {
1430 #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
1431 my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime'));
1432 my $dir = $dir_entry->[0];
1433 $total_bytes += 4096;
1436 listview_push($fileview,
1438 $self->dir_attrib("$cwd/$dir",'jobid'),
1448 foreach my $file (@$files)
1450 my $size = file_attrib($file,'st_size');
1451 my $time = localtime(file_attrib($file,'st_mtime'));
1452 $total_bytes += $size;
1454 # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
1456 listview_push($fileview,
1463 human($size), $time);
1466 $self->set_status("$file_count files/" . human($total_bytes));
1468 # set a decent default selection (makes keyboard nav easy)
1469 $fileview->select(0);
1473 sub on_about_activate
1475 DlgAbout::display();
1480 my ($tree, $path, $data) = @_;
1482 my @items = listview_get_all($tree) ;
1484 foreach my $i (@items)
1486 my @file_info = @{$i};
1489 # Ok, we have a corner case :
1494 $file = pack("u", $file_info[0]);
1498 $file = pack("u", $path . '/' . $file_info[0]);
1500 push @ret, join(" ; ", $file,
1501 $file_info[1], # $jobid
1502 $file_info[2], # $type
1506 my $data_get = join(" :: ", @ret);
1508 $data->set_text($data_get,-1);
1511 sub fileview_data_get
1513 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1514 drag_set_info($widget, $self->{cwd}, $data);
1517 sub fileinfo_data_get
1519 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1520 drag_set_info($widget, $self->{cwd}, $data);
1523 sub restore_list_data_received
1525 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1528 if ($info eq 40 || $info eq 0) # patch for display!=:0
1530 foreach my $elt (split(/ :: /, $data->data()))
1533 my ($file, $jobid, $type) =
1535 $file = unpack("u", $file);
1537 $self->add_selected_file_to_list($file, $jobid, $type);
1542 sub on_back_button_clicked {
1546 sub on_location_go_button_clicked
1549 $self->ch_dir($self->{location}->get_text());
1551 sub on_quit_activate {Gtk2->main_quit;}
1552 sub on_preferences_activate
1555 $self->{dlg_pref}->display($self) ;
1557 sub on_main_delete_event {Gtk2->main_quit;}
1558 sub on_bweb_activate
1561 $self->set_status("Open bweb on your browser");
1562 $self->{pref}->go_bweb('', "go on bweb");
1565 # Change to parent directory
1569 if ($self->{cwd} eq '/')
1573 my @dirs = split(/\//, $self->{cwd});
1575 $self->ch_dir(join('/', @dirs));
1578 # Change the current working directory
1579 # * Updates fileview, location, and selection
1584 $self->{cwd} = shift;
1586 $self->refresh_fileview();
1587 $self->{location}->set_text($self->{cwd});
1592 # Handle dialog 'close' (window-decoration induced close)
1593 # * Just hide the dialog, and tell Gtk not to do anything else
1597 my ($self, $w) = @_;
1600 1; # consume this event!
1603 # Handle key presses in location text edit control
1604 # * Translate a Return/Enter key into a 'Go' command
1605 # * All other key presses left for GTK
1607 sub on_location_entry_key_release_event
1613 my $keypress = $event->keyval;
1614 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1615 $keypress == $Gtk2::Gdk::Keysyms{Return})
1617 $self->ch_dir($widget->get_text());
1619 return 1; # consume keypress
1622 return 0; # let gtk have the keypress
1625 sub on_fileview_key_press_event
1627 my ($self, $widget, $event) = @_;
1631 sub listview_get_first
1634 my @selected = $list->get_selected_indices();
1635 if (@selected > 0) {
1636 my ($name, @other) = @{$list->{data}->[$selected[0]]};
1637 return (unpack('u', $name), @other);
1643 sub listview_get_all
1647 my @selected = $list->get_selected_indices();
1649 for my $i (@selected) {
1650 my ($name, @other) = @{$list->{data}->[$i]};
1651 push @ret, [unpack('u', $name), @other];
1659 my ($list, $name, @other) = @_;
1660 push @{$list->{data}}, [pack('u', $name), @other];
1663 #----------------------------------------------------------------------
1664 # Handle keypress in file-view
1665 # * Translates backspace into a 'cd ..' command
1666 # * All other key presses left for GTK
1668 sub on_fileview_key_release_event
1670 my ($self, $widget, $event) = @_;
1671 if (not $event->keyval)
1675 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1677 return 1; # eat keypress
1680 return 0; # let gtk have keypress
1683 sub on_forward_keypress
1688 #----------------------------------------------------------------------
1689 # Handle double-click (or enter) on file-view
1690 # * Translates into a 'cd <dir>' command
1692 sub on_fileview_row_activated
1694 my ($self, $widget) = @_;
1696 my ($name, undef, $type, undef) = listview_get_first($widget);
1700 if ($self->{cwd} eq '')
1702 $self->ch_dir($name);
1704 elsif ($self->{cwd} eq '/')
1706 $self->ch_dir('/' . $name);
1710 $self->ch_dir($self->{cwd} . '/' . $name);
1714 $self->fill_infoview($self->{cwd}, $name);
1717 return 1; # consume event
1722 my ($self, $path, $file) = @_;
1723 $self->clear_infoview();
1724 my @v = get_all_file_versions($self->{dbh},
1727 $self->current_client,
1728 $self->{pref}->{see_all_versions});
1730 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
1732 my $icon = ($inchanger)?$yesicon:$noicon;
1734 $mtime = localtime($mtime) ;
1736 listview_push($self->{fileinfo},
1737 $file, $jobid, 'file',
1738 $icon, $volname, $jobid, human($size), $mtime, $md5);
1745 return $self->{restore_backup_combobox}->get_active_text;
1751 return $self->{client_combobox}->get_active_text;
1754 sub on_list_backups_changed
1756 my ($self, $widget) = @_;
1757 return 0 unless defined $self->{fileview};
1759 $self->{CurrentJobIds} = [
1760 set_job_ids_for_date($self->{dbh},
1761 $self->current_client,
1762 $self->current_date,
1763 $self->{pref}->{use_ok_bkp_only})
1765 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1767 $self->refresh_fileview();
1771 sub on_restore_list_keypress
1773 my ($self, $widget, $event) = @_;
1774 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1776 my @sel = $widget->get_selected_indices;
1777 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1779 splice @{$self->{restore_list}->{data}},$elt,1;
1784 sub on_fileview_button_press_event
1786 my ($self,$widget,$event) = @_;
1787 if ($event->button == 3)
1789 $self->on_right_click_filelist($widget,$event);
1793 if ($event->button == 2)
1795 $self->on_see_all_version();
1802 sub on_see_all_version
1806 my @lst = listview_get_all($self->{fileview});
1809 my ($name, undef) = @{$i};
1811 new DlgFileVersion($self->{dbh},
1812 $self->current_client,
1813 $self->{cwd}, $name);
1817 sub on_right_click_filelist
1819 my ($self,$widget,$event) = @_;
1820 # I need to know what's selected
1821 my @sel = listview_get_all($self->{fileview});
1826 $type = $sel[0]->[2]; # $type
1831 if (@sel >=2 or $type eq 'dir')
1833 # We have selected more than one or it is a directories
1834 $w = $self->{filelist_dir_menu};
1838 $w = $self->{filelist_file_menu};
1844 $event->button, $event->time);
1847 sub context_add_to_filelist
1851 my @sel = listview_get_all($self->{fileview});
1853 foreach my $i (@sel)
1855 my ($file, $jobid, $type, undef) = @{$i};
1856 $file = $self->{cwd} . '/' . $file;
1857 $self->add_selected_file_to_list($file, $jobid, $type);
1861 # Adds a file to the filelist
1862 sub add_selected_file_to_list
1864 my ($self, $name, $jobid, $type)=@_;
1866 my $dbh = $self->{dbh};
1867 my $restore_list = $self->{restore_list};
1869 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1876 if ($name and substr $name,-1 ne '/')
1878 $name .= '/'; # For bacula
1880 my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
1881 listview_push($restore_list,
1882 $name, $jobid, 'dir', $curjobids,
1883 $diricon, $name,$curjobids,$dirfileindex);
1885 elsif ($type eq 'file')
1887 my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
1889 listview_push($restore_list,
1890 $name, $jobid, 'file', $curjobids,
1891 $fileicon, $name, $jobid, $fileindex );
1895 # TODO : we want be able to restore files from a bad ended backup
1896 # we have JobStatus IN ('T', 'A', 'E') and we must
1898 # Data acces subs from here. Interaction with SGBD and caching
1900 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1901 # GUI and stores them in @CurrentJobIds
1902 sub set_job_ids_for_date
1904 my ($dbh, $client, $date, $only_ok)=@_;
1906 if (!$client or !$date) {
1910 my $status = get_wanted_job_status($only_ok);
1912 # The algorithm : for a client, we get all the backups for each
1913 # fileset, in reverse order Then, for each fileset, we store the 'good'
1914 # incrementals and differentials until we have found a full so it goes
1915 # like this : store all incrementals until we have found a differential
1916 # or a full, then find the full #
1918 my $query = "SELECT JobId, FileSet, Level, JobStatus
1919 FROM Job, Client, FileSet
1920 WHERE Job.ClientId = Client.ClientId
1921 AND FileSet.FileSetId = Job.FileSetId
1922 AND EndTime <= '$date'
1923 AND Client.Name = '$client'
1925 AND JobStatus IN ($status)
1926 ORDER BY FileSet, JobTDate DESC";
1928 print STDERR $query,"\n" if $debug;
1930 my $result = $dbh->selectall_arrayref($query);
1932 foreach my $refrow (@$result)
1934 my $jobid = $refrow->[0];
1935 my $fileset = $refrow->[1];
1936 my $level = $refrow->[2];
1938 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1940 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1944 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1945 push @CurrentJobIds,($jobid);
1947 elsif ($level eq 'D')
1949 next if $progress{$fileset} eq 'D'; # We allready have a differential
1950 push @CurrentJobIds,($jobid);
1952 elsif ($level eq 'F')
1954 push @CurrentJobIds,($jobid);
1957 my $status = $refrow->[3] ;
1958 if ($status eq 'T') { # good end of job
1959 $progress{$fileset} = $level;
1962 print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
1964 return @CurrentJobIds;
1967 # Lists all directories contained inside a directory.
1968 # Uses the current dir, the client name, and CurrentJobIds for visibility.
1969 # Returns an array of dirs
1972 my ($self,$dir,$client)=@_;
1974 print "list_dirs(<$dir>, <$client>)\n" if $debug;
1976 if ($dir ne '' and substr $dir,-1 ne '/')
1978 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1981 my $dbh = $self->{dbh};
1982 my $query = "SELECT PathId FROM Path WHERE Path = ?
1983 UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?";
1984 my $sth = $dbh->prepare($query);
1985 $sth->execute($dir,$dir);
1986 my $result = $sth->fetchrow_arrayref();
1988 my $pathid = $result->[0];
1989 my @jobids = @{$self->{CurrentJobIds}};
1990 my $jobclause = join (',', @jobids);
1991 # Let's retrieve the list of the visible dirs in this dir ...
1992 # First, I need the empty filenameid to locate efficiently the dirs in the file table
1993 $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
1994 $sth = $dbh->prepare($query);
1996 $result = $sth->fetchrow_arrayref();
1998 my $dir_filenameid = $result->[0];
2000 # Then we get all the dir entries from File ...
2001 # It's ugly because there are records in brestore_missing_path ...
2003 SELECT Path, JobId, Lstat FROM(
2005 SELECT Path.Path, lower(Path.Path),
2006 listfile.JobId, listfile.Lstat
2008 SELECT DISTINCT brestore_pathhierarchy.PathId
2009 FROM brestore_pathhierarchy
2011 ON (brestore_pathhierarchy.PathId = Path.PathId)
2012 JOIN brestore_pathvisibility
2013 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2014 WHERE brestore_pathhierarchy.PPathId = $pathid
2015 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2016 JOIN Path ON (listpath.PathId = Path.PathId)
2018 SELECT File.PathId, File.JobId, File.Lstat FROM File
2019 WHERE File.FilenameId = $dir_filenameid
2020 AND File.JobId IN ($jobclause)) AS listfile
2021 ON (listpath.PathId = listfile.PathId)
2023 SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path),
2024 listfile.JobId, listfile.Lstat
2026 SELECT DISTINCT brestore_pathhierarchy.PathId
2027 FROM brestore_pathhierarchy
2028 JOIN brestore_missing_path
2029 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2030 JOIN brestore_pathvisibility
2031 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2032 WHERE brestore_pathhierarchy.PPathId = $pathid
2033 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2034 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2036 SELECT File.PathId, File.JobId, File.Lstat FROM File
2037 WHERE File.FilenameId = $dir_filenameid
2038 AND File.JobId IN ($jobclause)) AS listfile
2039 ON (listpath.PathId = listfile.PathId))
2040 ORDER BY 2,3 DESC ) As a";
2041 print STDERR "$query\n" if $debug;
2042 $sth=$dbh->prepare($query);
2044 $result = $sth->fetchall_arrayref();
2047 foreach my $refrow (@{$result})
2049 my $dir = $refrow->[0];
2050 my $jobid = $refrow->[1];
2051 my $lstat = $refrow->[2];
2052 next if ($dir eq $prev_dir);
2053 # We have to clean up this dirname ... we only want it's 'basename'
2057 my @temp = split ('/',$dir);
2058 $return_value = pop @temp;
2062 $return_value = '/';
2064 my @return_array = ($return_value,$lstat);
2065 push @return_list,(\@return_array);
2068 return @return_list;
2072 # List all files in a directory. dir as parameter, CurrentJobIds for visibility
2073 # Returns an array of dirs
2076 my ($self, $dir)=@_;
2077 my $dbh = $self->{dbh};
2081 print "list_files($dir)\n" if $debug;
2083 if ($dir ne '' and substr $dir,-1 ne '/')
2085 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
2088 my $query = "SELECT Path.PathId
2090 WHERE Path.Path = '$dir'
2092 SELECT brestore_missing_path.PathId
2093 FROM brestore_missing_path
2094 WHERE brestore_missing_path.Path = '$dir'";
2095 print $query,"\n" if $debug;
2097 my $result = $dbh->selectall_arrayref($query);
2098 foreach my $refrow (@$result)
2100 push @list_pathid,($refrow->[0]);
2103 if (@list_pathid == 0)
2105 print "No pathid found for $dir\n" if $debug;
2109 my $inlistpath = join (',', @list_pathid);
2110 my $inclause = join (',', @{$self->{CurrentJobIds}});
2111 if ($inclause eq '')
2117 "SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
2119 (SELECT Filename.Name, max(File.FileId) as id
2121 WHERE File.FilenameId = Filename.FilenameId
2122 AND Filename.Name != ''
2123 AND File.PathId IN ($inlistpath)
2124 AND File.JobId IN ($inclause)
2125 GROUP BY Filename.Name
2126 ORDER BY Filename.Name) AS listfiles,
2128 WHERE File.FileId = listfiles.id";
2130 print STDERR $query,"\n" if $debug;
2131 $result = $dbh->selectall_arrayref($query);
2138 Gtk2->main_iteration while (Gtk2->events_pending);
2141 sub create_brestore_tables
2145 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2147 unless ($self->dbh_do($verif)) {
2148 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2150 $self->{error} = "Creating internal brestore tables";
2152 CREATE TABLE brestore_knownjobid
2154 JobId int4 NOT NULL,
2155 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2157 $self->dbh_do($req);
2160 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2161 unless ($self->dbh_do($verif)) {
2163 CREATE TABLE brestore_pathhierarchy
2165 PathId int4 NOT NULL,
2166 PPathId int4 NOT NULL,
2167 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2169 $self->dbh_do($req);
2172 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2173 ON brestore_pathhierarchy (PPathId)";
2174 $self->dbh_do($req);
2177 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2178 unless ($self->dbh_do($verif)) {
2180 CREATE TABLE brestore_pathvisibility
2182 PathId int4 NOT NULL,
2183 JobId int4 NOT NULL,
2184 Size int8 DEFAULT 0,
2185 Files int4 DEFAULT 0,
2186 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2188 $self->dbh_do($req);
2190 $req = "CREATE INDEX brestore_pathvisibility_jobid
2191 ON brestore_pathvisibility (JobId)";
2192 $self->dbh_do($req);
2195 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2196 unless ($self->dbh_do($verif)) {
2198 CREATE TABLE brestore_missing_path
2200 PathId int4 NOT NULL,
2202 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2204 $self->dbh_do($req);
2206 $req = "CREATE INDEX brestore_missing_path_path
2207 ON brestore_missing_path (Path)";
2208 $self->dbh_do($req);
2212 # Recursive function to calculate the visibility of each directory in the cache
2213 # tree Working with references to save time and memory
2214 # For each directory, we want to propagate it's visible jobids onto it's
2215 # parents directory.
2216 # A tree is visible if
2217 # - it's been in a backup pointed by the CurrentJobIds
2218 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2219 # In the second case, the directory is visible but has no metadata.
2220 # We symbolize this with lstat = 1 for this jobid in the cache.
2222 # Input : reference directory
2223 # Output : visibility of this dir. Has to know visibility of all subdirs
2224 # to know it's visibility, hence the recursing.
2230 # Get the subdirs array references list
2231 my @list_ref_subdirs;
2232 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2234 push @list_ref_subdirs,($ref_subdir);
2237 # Now lets recurse over these subdirs and retrieve the reference of a hash
2238 # containing the jobs where they are visible
2239 foreach my $ref_subdir (@list_ref_subdirs)
2241 my $ref_list_jobs = list_visible($ref_subdir);
2242 foreach my $jobid (keys %$ref_list_jobs)
2244 $visibility{$jobid}=1;
2248 # Ok. Now, we've got the list of those jobs. We are going to update our
2249 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2250 # the lstat for the known jobids. Put 1 in the new elements... But first,
2251 # let's store the current jobids
2253 foreach my $jobid (keys %{$refdir->[1]})
2255 push @known_jobids,($jobid);
2259 foreach my $jobid (keys %visibility)
2261 next if ($refdir->[1]->{$jobid});
2262 $refdir->[1]->{$jobid} = 1;
2264 # Add the known_jobids to %visibility
2265 foreach my $jobid (@known_jobids)
2267 $visibility{$jobid}=1;
2269 return \%visibility;
2272 # Returns the list of media required for a list of jobids.
2273 # Input : dbh, jobid1, jobid2...
2274 # Output : reference to array of (joibd, inchanger)
2275 sub get_required_media_from_jobid
2277 my ($dbh, @jobids)=@_;
2278 my $inclause = join(',',@jobids);
2280 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2281 FROM JobMedia, Media
2282 WHERE JobMedia.MediaId=Media.MediaId
2283 AND JobId In ($inclause)
2285 my $result = $dbh->selectall_arrayref($query);
2289 # Returns the fileindex from dirname and jobid.
2290 # Input : dbh, dirname, jobid
2291 # Output : fileindex
2292 sub get_fileindex_from_dir_jobid
2294 my ($dbh, $dirname, $jobid)=@_;
2296 $query = "SELECT File.FileIndex
2297 FROM File, Filename, Path
2298 WHERE File.FilenameId = Filename.FilenameId
2299 AND File.PathId = Path.PathId
2300 AND Filename.Name = ''
2301 AND Path.Path = '$dirname'
2302 AND File.JobId = '$jobid'
2305 print STDERR $query,"\n" if $debug;
2306 my $result = $dbh->selectall_arrayref($query);
2307 return $result->[0]->[0];
2310 # Returns the fileindex from filename and jobid.
2311 # Input : dbh, filename, jobid
2312 # Output : fileindex
2313 sub get_fileindex_from_file_jobid
2315 my ($dbh, $filename, $jobid)=@_;
2317 my @dirs = split(/\//, $filename);
2318 $filename=pop(@dirs);
2319 my $dirname = join('/', @dirs) . '/';
2324 "SELECT File.FileIndex
2325 FROM File, Filename, Path
2326 WHERE File.FilenameId = Filename.FilenameId
2327 AND File.PathId = Path.PathId
2328 AND Filename.Name = '$filename'
2329 AND Path.Path = '$dirname'
2330 AND File.JobId = '$jobid'";
2332 print STDERR $query,"\n" if $debug;
2333 my $result = $dbh->selectall_arrayref($query);
2334 return $result->[0]->[0];
2338 # Returns list of versions of a file that could be restored
2339 # returns an array of
2340 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2341 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2342 # and volname and md5
2343 # and of course, there will be only one jobid in the array of jobids...
2344 sub get_all_file_versions
2346 my ($dbh,$path,$file,$client,$see_all)=@_;
2348 defined $see_all or $see_all=0;
2353 "SELECT File.JobId, File.FileIndex, File.Lstat,
2354 File.Md5, Media.VolumeName, Media.InChanger
2355 FROM File, Filename, Path, Job, Client, JobMedia, Media
2356 WHERE File.FilenameId = Filename.FilenameId
2357 AND File.PathId=Path.PathId
2358 AND File.JobId = Job.JobId
2359 AND Job.ClientId = Client.ClientId
2360 AND Job.JobId = JobMedia.JobId
2361 AND File.FileIndex >= JobMedia.FirstIndex
2362 AND File.FileIndex <= JobMedia.LastIndex
2363 AND JobMedia.MediaId = Media.MediaId
2364 AND Path.Path = '$path'
2365 AND Filename.Name = '$file'
2366 AND Client.Name = '$client'";
2368 print STDERR $query if $debug;
2370 my $result = $dbh->selectall_arrayref($query);
2372 foreach my $refrow (@$result)
2374 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2375 my @attribs = parse_lstat($lstat);
2376 my $mtime = array_attrib('st_mtime',\@attribs);
2377 my $size = array_attrib('st_size',\@attribs);
2379 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2380 $inchanger, $md5, $volname);
2381 push @versions, (\@list);
2384 # We have the list of all versions of this file.
2385 # We'll sort it by mtime desc, size, md5, inchanger desc
2386 # the rest of the algorithm will be simpler
2387 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2388 @versions = sort { $b->[4] <=> $a->[4]
2389 || $a->[5] <=> $b->[5]
2390 || $a->[7] cmp $a->[7]
2391 || $b->[6] <=> $a->[6]} @versions;
2394 my %allready_seen_by_mtime;
2395 my %allready_seen_by_md5;
2396 # Now we should create a new array with only the interesting records
2397 foreach my $ref (@versions)
2401 # The file has a md5. We compare his md5 to other known md5...
2402 # We take size into account. It may happen that 2 files
2403 # have the same md5sum and are different. size is a supplementary
2406 # If we allready have a (better) version
2407 next if ( (not $see_all)
2408 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2410 # we never met this one before...
2411 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2413 # Even if it has a md5, we should also work with mtimes
2414 # We allready have a (better) version
2415 next if ( (not $see_all)
2416 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2417 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2419 # We reached there. The file hasn't been seen.
2420 push @good_versions,($ref);
2423 # To be nice with the user, we re-sort good_versions by
2424 # inchanger desc, mtime desc
2425 @good_versions = sort { $b->[4] <=> $a->[4]
2426 || $b->[2] <=> $a->[2]} @good_versions;
2428 return @good_versions;
2431 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2432 # This sub creates a BSR from the information in the restore_list
2433 # Returns the BSR as a string
2438 # This query gets all jobid/jobmedia/media combination.
2440 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2441 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2442 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2443 Media.Volumename, Media.MediaType
2444 FROM Job, JobMedia, Media
2445 WHERE Job.JobId = JobMedia.JobId
2446 AND JobMedia.MediaId = Media.MediaId
2447 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2450 my $result = $self->dbh_selectall_arrayref($query);
2452 # We will store everything hashed by jobid.
2454 foreach my $refrow (@$result)
2456 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2457 $firstindex, $lastindex, $startblock, $endblock,
2458 $volindex, $volumename, $mediatype) = @{$refrow};
2460 # We just have to deal with the case where starfile != endfile
2461 # In this case, we concatenate both, for the bsr
2462 if ($startfile != $endfile) {
2463 $startfile = $startfile . '-' . $endfile;
2467 ($jobid, $volsessionid, $volsessiontime, $startfile,
2468 $firstindex, $lastindex, $startblock .'-'. $endblock,
2469 $volindex, $volumename, $mediatype);
2471 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2475 # reminder : restore_list looks like this :
2476 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2478 # Here, we retrieve every file/dir that could be in the restore
2479 # We do as simple as possible for the SQL engine (no crazy joins,
2480 # no pseudo join (>= FirstIndex ...), etc ...
2481 # We do a SQL union of all the files/dirs specified in the restore_list
2483 foreach my $entry (@{$self->{restore_list}->{data}})
2485 if ($entry->[2] eq 'dir')
2487 my $dir = unpack('u', $entry->[0]);
2488 my $inclause = $entry->[3]; #curjobids
2491 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2492 FROM File, Path, Filename
2493 WHERE Path.PathId = File.PathId
2494 AND File.FilenameId = Filename.FilenameId
2495 AND Path.Path LIKE '$dir%'
2496 AND File.JobId IN ($inclause) )";
2497 push @select_queries,($query);
2501 # It's a file. Great, we allready have most
2502 # of what is needed. Simple and efficient query
2503 my $file = unpack('u', $entry->[0]);
2504 my @file = split '/',$file;
2506 my $dir = join('/',@file);
2508 my $jobid = $entry->[1];
2509 my $fileindex = $entry->[7];
2510 my $inclause = $entry->[3]; # curjobids
2512 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2513 FROM File, Path, Filename
2514 WHERE Path.PathId = File.PathId
2515 AND File.FilenameId = Filename.FilenameId
2516 AND Path.Path = '$dir/'
2517 AND Filename.Name = '$file'
2518 AND File.JobId = $jobid)";
2519 push @select_queries,($query);
2522 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2524 print STDERR $query,"\n" if $debug;
2526 #Now we run the query and parse the result...
2527 # there may be a lot of records, so we better be efficient
2528 # We use the bind column method, working with references...
2530 my $sth = $self->dbh_prepare($query);
2533 my ($path,$name,$fileindex,$jobid);
2534 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2536 # The temp place we're going to save all file
2537 # list to before the real list
2541 while ($sth->fetchrow_arrayref())
2543 # This may look dumb, but we're going to do a join by ourselves,
2544 # to save memory and avoid sending a complex query to mysql
2545 my $complete_path = $path . $name;
2553 # Remove trailing slash (normalize file and dir name)
2554 $complete_path =~ s/\/$//;
2556 # Let's find the ref(s) for the %mediainfo element(s)
2557 # containing the data for this file
2558 # There can be several matches. It is the pseudo join.
2560 my $max_elt=@{$mediainfos{$jobid}}-1;
2562 while($med_idx <= $max_elt)
2564 my $ref = $mediainfos{$jobid}->[$med_idx];
2565 # First, can we get rid of the first elements of the
2566 # array ? (if they don't contain valuable records
2568 if ($fileindex > $ref->[5])
2570 # It seems we don't need anymore
2571 # this entry in %mediainfo (the input data
2574 shift @{$mediainfos{$jobid}};
2578 # We will do work on this elt. We can ++
2579 # $med_idx for next loop
2582 # %mediainfo row looks like :
2583 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2584 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2587 # We are in range. We store and continue looping
2589 if ($fileindex >= $ref->[4])
2591 my @data = ($complete_path,$is_dir,
2593 push @temp_list,(\@data);
2597 # We are not in range. No point in continuing looping
2598 # We go to next record.
2602 # Now we have the array.
2603 # We're going to sort it, by
2604 # path, volsessiontime DESC (get the most recent file...)
2605 # The array rows look like this :
2606 # complete_path,is_dir,fileindex,
2607 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2608 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2609 @temp_list = sort {$a->[0] cmp $b->[0]
2610 || $b->[3]->[2] <=> $a->[3]->[2]
2614 my $prev_complete_path='////'; # Sure not to match
2618 while (my $refrow = shift @temp_list)
2620 # For the sake of readability, we load $refrow
2621 # contents in real scalars
2622 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2623 my $jobid= $refother->[0]; # We don't need the rest...
2625 # We skip this entry.
2626 # We allready have a newer one and this
2627 # isn't a continuation of the same file
2628 next if ($complete_path eq $prev_complete_path
2629 and $jobid != $prev_jobid);
2633 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2635 # We would be recursing inside a file.
2636 # Just what we don't want (dir replaced by file
2637 # between two backups
2643 push @restore_list,($refrow);
2645 $prev_complete_path = $complete_path;
2646 $prev_jobid = $jobid;
2652 push @restore_list,($refrow);
2654 $prev_complete_path = $complete_path;
2655 $prev_jobid = $jobid;
2659 # We get rid of @temp_list... save memory
2662 # Ok everything is in the list. Let's sort it again in another way.
2663 # This time it will be in the bsr file order
2665 # we sort the results by
2666 # volsessiontime, volsessionid, volindex, fileindex
2667 # to get all files in right order...
2668 # Reminder : The array rows look like this :
2669 # complete_path,is_dir,fileindex,
2670 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2671 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2673 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2674 || $a->[3]->[1] <=> $b->[3]->[1]
2675 || $a->[3]->[7] <=> $b->[3]->[7]
2676 || $a->[2] <=> $b->[2] }
2679 # Now that everything is ready, we create the bsr
2680 my $prev_fileindex=-1;
2681 my $prev_volsessionid=-1;
2682 my $prev_volsessiontime=-1;
2683 my $prev_volumename=-1;
2684 my $prev_volfile=-1;
2688 my $first_of_current_range=0;
2689 my @fileindex_ranges;
2692 foreach my $refrow (@restore_list)
2694 my (undef,undef,$fileindex,$refother)=@{$refrow};
2695 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2696 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2698 # We can specifiy the number of files in each section of the
2699 # bsr to speedup restore (bacula can then jump over the
2700 # end of tape files.
2704 if ($prev_volumename eq '-1')
2706 # We only have to start the new range...
2707 $first_of_current_range=$fileindex;
2709 elsif ($prev_volsessionid != $volsessionid
2710 or $prev_volsessiontime != $volsessiontime
2711 or $prev_volumename ne $volumename
2712 or $prev_volfile ne $volfile)
2714 # We have to create a new section in the bsr...
2715 # We print the previous one ...
2716 # (before that, save the current range ...)
2717 if ($first_of_current_range != $prev_fileindex)
2720 push @fileindex_ranges,
2721 ("$first_of_current_range-$prev_fileindex");
2725 # We are out of a range,
2726 # but there is only one element in the range
2727 push @fileindex_ranges,
2728 ("$first_of_current_range");
2731 $bsr.=print_bsr_section(\@fileindex_ranges,
2733 $prev_volsessiontime,
2740 # Reset for next loop
2741 @fileindex_ranges=();
2742 $first_of_current_range=$fileindex;
2744 elsif ($fileindex-1 != $prev_fileindex)
2746 # End of a range of fileindexes
2747 if ($first_of_current_range != $prev_fileindex)
2750 push @fileindex_ranges,
2751 ("$first_of_current_range-$prev_fileindex");
2755 # We are out of a range,
2756 # but there is only one element in the range
2757 push @fileindex_ranges,
2758 ("$first_of_current_range");
2760 $first_of_current_range=$fileindex;
2762 $prev_fileindex=$fileindex;
2763 $prev_volsessionid = $volsessionid;
2764 $prev_volsessiontime = $volsessiontime;
2765 $prev_volumename = $volumename;
2766 $prev_volfile=$volfile;
2767 $prev_mediatype=$mediatype;
2768 $prev_volblocks=$volblocks;
2772 # Ok, we're out of the loop. Alas, there's still the last record ...
2773 if ($first_of_current_range != $prev_fileindex)
2776 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2781 # We are out of a range,
2782 # but there is only one element in the range
2783 push @fileindex_ranges,("$first_of_current_range");
2786 $bsr.=print_bsr_section(\@fileindex_ranges,
2788 $prev_volsessiontime,
2798 sub print_bsr_section
2800 my ($ref_fileindex_ranges,$volsessionid,
2801 $volsessiontime,$volumename,$volfile,
2802 $mediatype,$volblocks,$count)=@_;
2805 $bsr .= "Volume=\"$volumename\"\n";
2806 $bsr .= "MediaType=\"$mediatype\"\n";
2807 $bsr .= "VolSessionId=$volsessionid\n";
2808 $bsr .= "VolSessionTime=$volsessiontime\n";
2809 $bsr .= "VolFile=$volfile\n";
2810 $bsr .= "VolBlock=$volblocks\n";
2812 foreach my $range (@{$ref_fileindex_ranges})
2814 $bsr .= "FileIndex=$range\n";
2817 $bsr .= "Count=$count\n";
2821 # This function estimates the size to be restored for an entry of the restore
2823 # In : self,reference to the entry
2824 # Out : size in bytes, number of files
2825 sub estimate_restore_size
2827 # reminder : restore_list looks like this :
2828 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2832 if ($entry->[2] eq 'dir')
2834 my $dir = unpack('u', $entry->[0]);
2835 my $inclause = $entry->[3]; #curjobids
2837 "SELECT Path.Path, File.FilenameId, File.LStat
2838 FROM File, Path, Job
2839 WHERE Path.PathId = File.PathId
2840 AND File.JobId = Job.JobId
2841 AND Path.Path LIKE '$dir%'
2842 AND File.JobId IN ($inclause)
2843 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2847 # It's a file. Great, we allready have most
2848 # of what is needed. Simple and efficient query
2849 my $file = unpack('u', $entry->[0]);
2850 my @file = split '/',$file;
2852 my $dir = join('/',@file);
2854 my $jobid = $entry->[1];
2855 my $fileindex = $entry->[7];
2856 my $inclause = $entry->[3]; # curjobids
2858 "SELECT Path.Path, File.FilenameId, File.Lstat
2859 FROM File, Path, Filename
2860 WHERE Path.PathId = File.PathId
2861 AND Path.Path = '$dir/'
2862 AND Filename.Name = '$file'
2863 AND File.JobId = $jobid
2864 AND Filename.FilenameId = File.FilenameId";
2867 print STDERR $query,"\n" if $debug;
2868 my ($path,$nameid,$lstat);
2869 my $sth = $self->dbh_prepare($query);
2871 $sth->bind_columns(\$path,\$nameid,\$lstat);
2881 while ($sth->fetchrow_arrayref())
2883 # Only the latest version of a file
2884 next if ($nameid eq $old_nameid and $path eq $old_path);
2886 if ($rcount > 15000) {
2893 # We get the size of this file
2894 my $size=lstat_attrib($lstat,'st_size');
2895 $total_size += $size;
2898 $old_nameid=$nameid;
2900 return ($total_size,$total_files);
2903 sub update_brestore_table
2905 my ($self, @jobs) = @_;
2906 my $dbh = $self->{dbh};
2908 foreach my $job (sort {$a <=> $b} @jobs)
2910 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2911 my $retour = $self->dbh_selectrow_arrayref($query);
2912 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2914 print STDERR "Inserting path records for JobId $job\n";
2915 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2916 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2918 $self->dbh_do($query);
2920 # Now we have to do the directory recursion stuff to determine missing visibility
2921 # We try to avoid recursion, to be as fast as possible
2922 # We also only work on not allready hierarchised directories...
2924 print STDERR "Creating missing recursion paths for $job\n";
2926 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2927 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2928 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2929 WHERE brestore_pathvisibility.JobId = $job
2930 AND brestore_pathhierarchy.PathId IS NULL
2933 my $sth = $self->dbh_prepare($query);
2935 my $pathid; my $path;
2936 $sth->bind_columns(\$pathid,\$path);
2940 $self->build_path_hierarchy($path,$pathid);
2944 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2945 # This query gives all parent pathids for a given jobid that aren't stored.
2946 # It has to be called until no record is updated ...
2948 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2949 SELECT a.PathId,$job
2951 (SELECT DISTINCT h.PPathId AS PathId
2952 FROM brestore_pathhierarchy AS h
2953 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2954 WHERE p.JobId=$job) AS a
2957 FROM brestore_pathvisibility
2958 WHERE JobId=$job) AS b
2959 ON (a.PathId = b.PathId)
2960 WHERE b.PathId IS NULL)";
2961 print STDERR $query,"\n" if ($debug);
2963 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2965 print STDERR "Recursively adding $rows_affected records from $job\n";
2968 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2973 sub cleanup_brestore_table
2976 my $dbh = $self->{dbh};
2978 my $query = "SELECT JobId from brestore_knownjobid";
2979 my @jobs = @{$dbh->selectall_arrayref($query)};
2981 foreach my $jobentry (@jobs)
2983 my $job = $jobentry->[0];
2984 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2985 my $result = $dbh->selectall_arrayref($query);
2986 if (scalar(@{$result}))
2988 # There are still files for this jobid
2989 print STDERR "$job still exists. Not cleaning...\n";
2992 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2994 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
3000 sub build_path_hierarchy
3002 my ($self, $path,$pathid)=@_;
3003 # Does the ppathid exist for this ? we use a memory cache...
3004 # In order to avoid the full loop, we consider that if a dir is allready in the
3005 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
3008 #print STDERR "$path\n" if $debug;
3009 if (! $self->{cache_ppathid}->{$pathid})
3011 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
3012 my $sth2 = $self->{dbh}->prepare_cached($query);
3013 $sth2->execute($pathid);
3014 # Do we have a result ?
3015 if (my $refrow = $sth2->fetchrow_arrayref)
3017 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
3019 # This dir was in the db ...
3020 # It means we can leave, the tree has allready been built for
3025 # We have to create the record ...
3026 # What's the current p_path ?
3027 my $ppath = parent_dir($path);
3028 my $ppathid = $self->return_pathid_from_path($ppath);
3029 $self->{cache_ppathid}->{$pathid}= $ppathid;
3031 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
3032 $sth2 = $self->{dbh}->prepare_cached($query);
3033 $sth2->execute($pathid,$ppathid);
3039 # It's allready in the cache.
3040 # We can leave, no time to waste here, all the parent dirs have allready
3048 sub return_pathid_from_path
3050 my ($self, $path) = @_;
3051 my $query = "SELECT PathId FROM Path WHERE Path = ?
3053 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3054 #print STDERR $query,"\n" if $debug;
3055 my $sth = $self->{dbh}->prepare_cached($query);
3056 $sth->execute($path,$path);
3057 my $result =$sth->fetchrow_arrayref();
3059 if (defined $result)
3061 return $result->[0];
3064 # A bit dirty : we insert into path AND missing_path, to be sure
3065 # we aren't deleted by a purge. We still need to insert into path to get
3066 # the pathid, because of mysql
3067 $query = "INSERT INTO Path (Path) VALUES (?)";
3068 #print STDERR $query,"\n" if $debug;
3069 $sth = $self->{dbh}->prepare_cached($query);
3070 $sth->execute($path);
3073 $query = " INSERT INTO brestore_missing_path (PathId,Path)
3074 SELECT PathId,Path FROM Path WHERE Path = ?";
3075 #print STDERR $query,"\n" if $debug;
3076 $sth = $self->{dbh}->prepare_cached($query);
3077 $sth->execute($path);
3079 $query = " DELETE FROM Path WHERE Path = ?";
3080 #print STDERR $query,"\n" if $debug;
3081 $sth = $self->{dbh}->prepare_cached($query);
3082 $sth->execute($path);
3084 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3085 #print STDERR $query,"\n" if $debug;
3086 $sth = $self->{dbh}->prepare_cached($query);
3087 $sth->execute($path);
3088 $result = $sth->fetchrow_arrayref();
3090 return $result->[0];
3102 # Root Windows case :
3103 if ($path =~ /^[a-z]+:\/$/i)
3108 my @tmp = split('/',$path);
3109 # We remove the last ...
3111 my $tmp = join ('/',@tmp) . '/';
3117 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3118 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3119 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3120 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3121 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3122 'data_stream' => 15);;
3125 my ($attrib,$ref_attrib)=@_;
3126 return $ref_attrib->[$attrib_name_id{$attrib}];
3130 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
3132 my ($file, $attrib)=@_;
3134 if (defined $attrib_name_id{$attrib}) {
3136 my @d = split(' ', $file->[2]) ; # TODO : cache this
3138 return from_base64($d[$attrib_name_id{$attrib}]);
3140 } elsif ($attrib eq 'jobid') {
3144 } elsif ($attrib eq 'name') {
3149 die "Attribute not known : $attrib.\n";
3153 # Return the jobid or attribute asked for a dir
3156 my ($self,$dir,$attrib)=@_;
3158 my @dir = split('/',$dir,-1);
3159 my $refdir=$self->{dirtree}->{$self->current_client};
3161 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3163 die "Attribute not known : $attrib.\n";
3166 foreach my $subdir (@dir)
3168 $refdir = $refdir->[0]->{$subdir};
3171 # $refdir is now the reference to the dir's array
3172 # Is the a jobid in @CurrentJobIds where the lstat is
3173 # defined (we'll search in reverse order)
3174 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3176 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3178 if ($attrib eq 'jobid')
3184 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3185 return $attribs[$attrib_name_id{$attrib}+1];
3190 return 0; # We cannot get a good attribute.
3191 # This directory is here for the sake of visibility
3196 my ($lstat,$attrib)=@_;
3197 if ($lstat and defined $attrib_name_id{$attrib})
3199 my @d = split(' ', $lstat) ; # TODO : cache this
3200 return from_base64($d[$attrib_name_id{$attrib}]);
3207 # Base 64 functions, directly from recover.pl.
3209 # Karl Hakimian <hakimian@aha.com>
3210 # This section is also under GPL v2 or later.
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 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3220 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3221 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3223 @base64_map = (0) x 128;
3225 for (my $i=0; $i<64; $i++) {
3226 $base64_map[ord($base64_digits[$i])] = $i;
3241 if (substr($where, 0, 1) eq '-') {
3243 $where = substr($where, 1);
3246 while ($where ne '') {
3248 my $d = substr($where, 0, 1);
3249 $val += $base64_map[ord(substr($where, 0, 1))];
3250 $where = substr($where, 1);
3258 my @attribs = split(' ',$lstat);
3259 foreach my $element (@attribs)
3261 $element = from_base64($element);
3270 ################################################################
3273 use base qw/DlgResto/;
3277 my ($class, $conf) = @_;
3278 my $self = bless {info => $conf}, $class;
3280 $self->{dbh} = $conf->{dbh};
3289 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3290 my $jobs = $self->dbh_selectall_arrayref($query);
3292 $self->update_brestore_table(map { $_->[0] } @$jobs);
3303 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3307 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3310 GetOptions("conf=s" => \$file_conf,
3311 "batch" => \$batch_mod,
3313 "help" => \&HELP_MESSAGE) ;
3315 my $p = new Pref($file_conf);
3317 if (! -f $file_conf) {
3322 my $b = new Batch($p);
3323 if ($p->connect_db()) {
3324 $b->set_dbh($p->{dbh});
3330 $glade_file = $p->{glade_file};
3332 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3333 if (-f "$path/$glade_file") {
3334 $glade_file = "$path/$glade_file" ;
3339 # gtk have lots of warning on stderr
3340 if ($^O eq 'MSWin32')
3343 open(STDERR, ">stderr.log");
3348 if ( -f $glade_file) {
3349 my $w = new DlgResto($p);
3352 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3353 "Can't find your brestore.glade (glade_file => '$glade_file')
3354 Please, edit your $file_conf to setup it." );
3356 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3361 Gtk2->main; # Start Gtk2 main loop
3373 # Code pour trier les colonnes
3374 my $mod = $fileview->get_model();
3375 $mod->set_default_sort_func(sub {
3376 my ($model, $item1, $item2) = @_;
3377 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3378 my $b = $model->get($item2, 1); # colonne (indice 1)
3383 $fileview->set_headers_clickable(1);
3384 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3385 $col->signal_connect('clicked', sub {
3386 my ($colonne, $model) = @_;
3387 $model->set_sort_column_id (1, 'ascending');