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 Copyright (C) 2006 Marc Cousin and Eric Bollengier
36 This library is free software; you can redistribute it and/or
37 modify it under the terms of the GNU Lesser General Public
38 License as published by the Free Software Foundation; either
39 version 2 of the License, or (at your option) any later version.
41 This library is distributed in the hope that it will be useful,
42 but WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 Lesser General Public License for more details.
46 You should have received a copy of the GNU Lesser General Public
47 License along with this library; if not, write to the
48 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
49 Boston, MA 02111-1307, USA.
51 Base 64 functions from Karl Hakimian <hakimian@aha.com>
52 Integrally copied from recover.pl from bacula source distribution.
56 use Gtk2; # auto-initialize Gtk2
58 use Gtk2::SimpleList; # easy wrapper for list views
59 use Gtk2::Gdk::Keysyms; # keyboard code constants
60 use Data::Dumper qw/Dumper/;
62 my $debug=0; # can be on brestore.conf
64 ################################################################
66 package DlgFileVersion;
68 sub on_versions_close_clicked
70 my ($self, $widget)=@_;
71 $self->{version}->destroy();
74 sub on_selection_button_press_event
76 print STDERR "on_selection_button_press_event()\n";
81 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
83 DlgResto::drag_set_info($widget,
90 my ($class, $dbh, $client, $path, $file) = @_;
93 version => undef, # main window
96 # we load version widget of $glade_file
97 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
99 # Connect signals magically
100 $glade_box->signal_autoconnect_from_package($self);
102 $glade_box->get_widget("version_label")
103 ->set_markup("<b>File revisions : $client:$path/$file</b>");
105 my $widget = $glade_box->get_widget('version_fileview');
106 my $fileview = Gtk2::SimpleList->new_from_treeview(
108 'h_name' => 'hidden',
109 'h_jobid' => 'hidden',
110 'h_type' => 'hidden',
112 'InChanger' => 'pixbuf',
119 DlgResto::init_drag_drop($fileview);
121 my @v = DlgResto::get_all_file_versions($dbh,
127 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
129 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
131 DlgResto::listview_push($fileview,
132 $file, $jobid, 'file',
133 $icon, $volname, $jobid,DlgResto::human($size),
134 scalar(localtime($mtime)), $md5);
137 $self->{version} = $glade_box->get_widget('dlg_version');
138 $self->{version}->show();
143 sub on_forward_keypress
149 ################################################################
154 my ($package, $text) = @_;
158 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
160 # Connect signals magically
161 $glade->signal_autoconnect_from_package($self);
162 $glade->get_widget('label_warn')->set_text($text);
164 print STDERR "$text\n";
166 $self->{window} = $glade->get_widget('dlg_warn');
167 $self->{window}->show_all();
174 $self->{window}->destroy();
178 ################################################################
181 use HTTP::Request::Common;
185 my ($class, %arg) = @_;
188 pref => $arg{pref}, # Pref object
189 timeout => $arg{timeout} || 20,
190 debug => $arg{debug} || 0,
193 'list_fileset' => '',
194 'list_storage' => '',
203 my ($self, @what) = @_;
204 my $ua = LWP::UserAgent->new();
205 $ua->agent("Brestore ");
206 my $req = POST($self->{pref}->{bconsole},
207 Content_Type => 'form-data',
208 Content => [ map { (action => $_) } @what ]);
209 #$req->authorization_basic('eric', 'test');
211 my $res = $ua->request($req);
213 if ($res->is_success) {
214 foreach my $l (split(/\n/, $res->content)) {
215 my ($k, $c) = split(/=/,$l,2);
219 $self->{error} = "Can't connect to bweb : " . $res->status_line;
220 new DlgWarn($self->{error});
226 my ($self, %arg) = @_;
228 my $ua = LWP::UserAgent->new();
229 $ua->agent("Brestore ");
230 my $req = POST($self->{pref}->{bconsole},
231 Content_Type => 'form-data',
232 Content => [ job => $arg{job},
233 client => $arg{client},
234 storage => $arg{storage} || '',
235 fileset => $arg{fileset} || '',
236 where => $arg{where},
237 replace => $arg{replace},
238 priority=> $arg{prio} || '',
241 bootstrap => [$arg{bootstrap}],
243 #$req->authorization_basic('eric', 'test');
245 my $res = $ua->request($req);
247 if ($res->is_success) {
248 foreach my $l (split(/\n/, $res->content)) {
249 my ($k, $c) = split(/=/,$l,2);
255 unlink($arg{bootstrap});
256 new DlgWarn("Can't connect to bweb : " . $res->status_line);
265 return sort split(/;/, $self->{'list_job'});
271 return sort split(/;/, $self->{'list_fileset'});
277 return sort split(/;/, $self->{'list_storage'});
282 return sort split(/;/, $self->{'list_client'});
287 ################################################################
293 # %arg = (bsr_file => '/path/to/bsr', # on director
294 # volumes => [ '00001', '00004']
302 if ($pref->{bconsole} =~ /^http/) {
303 return new BwebConsole(pref => $pref);
305 if (eval { require Bconsole; }) {
306 return new Bconsole(pref => $pref);
308 new DlgWarn("Can't use bconsole, verify your setup");
316 my ($class, %arg) = @_;
319 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
320 pref => $arg{pref}, # Pref ref
321 glade => undef, # GladeXML ref
322 bconsole => undef, # Bconsole ref
325 my $console = $self->{bconsole} = get_bconsole($arg{pref});
330 # we load launch widget of $glade_file
331 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
334 # Connect signals magically
335 $glade->signal_autoconnect_from_package($self);
337 my $widget = $glade->get_widget('volumeview');
338 my $volview = Gtk2::SimpleList->new_from_treeview(
340 'InChanger' => 'pixbuf',
344 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
346 # we replace 0 and 1 by $noicon and $yesicon
347 for my $i (@{$infos}) {
349 $i->[0] = $DlgResto::noicon;
351 $i->[0] = $DlgResto::yesicon;
356 push @{ $volview->{data} }, @{$infos} ;
358 $console->prepare(qw/list_client list_job list_fileset list_storage/);
360 # fill client combobox (with director defined clients
361 my @clients = $console->list_client() ; # get from bconsole
362 if ($console->{error}) {
363 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
365 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
366 $self->{list_client} = DlgResto::init_combo($w, 'text');
367 DlgResto::fill_combo($self->{list_client},
368 $DlgResto::client_list_empty,
372 # fill fileset combobox
373 my @fileset = $console->list_fileset() ;
374 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
375 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
376 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
379 my @job = $console->list_job() ;
380 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
381 $self->{list_job} = DlgResto::init_combo($w, 'text');
382 DlgResto::fill_combo($self->{list_job}, '', @job);
384 # find default_restore_job in jobs list
385 my $default_restore_job = $arg{pref}->{default_restore_job} ;
389 if ($j =~ /$default_restore_job/io) {
395 $w->set_active($index);
397 # fill storage combobox
398 my @storage = $console->list_storage() ;
399 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
400 $self->{list_storage} = DlgResto::init_combo($w, 'text');
401 DlgResto::fill_combo($self->{list_storage}, '', @storage);
403 $glade->get_widget('dlg_launch')->show_all();
410 my ($self, $client, $jobid) = @_;
412 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
415 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
416 "Your job have been submited to bacula.
417 To follow it, you must use bconsole (or install/configure bweb)");
422 $self->on_cancel_resto_clicked();
425 sub on_cancel_resto_clicked
428 $self->{glade}->get_widget('dlg_launch')->destroy();
431 sub on_submit_resto_clicked
434 my $glade = $self->{glade};
436 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
439 new DlgWarn("Can't copy bsr file to director ($self->{error})");
443 my $fileset = $glade->get_widget('combo_launch_fileset')
446 my $storage = $glade->get_widget('combo_launch_storage')
449 my $where = $glade->get_widget('entry_launch_where')->get_text();
451 my $job = $glade->get_widget('combo_launch_job')
455 new DlgWarn("Can't use this job");
459 my $client = $glade->get_widget('combo_launch_client')
462 if (! $client or $client eq $DlgResto::client_list_empty) {
463 new DlgWarn("Can't use this client ($client)");
467 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
469 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
470 $replace=($replace)?'always':'never';
472 my $jobid = $self->{bconsole}->run(job => $job,
481 $self->show_job($client, $jobid);
484 sub on_combo_storage_button_press_event
487 print "on_combo_storage_button_press_event()\n";
490 sub on_combo_fileset_button_press_event
493 print "on_combo_fileset_button_press_event()\n";
497 sub on_combo_job_button_press_event
500 print "on_combo_job_button_press_event()\n";
503 sub get_volume_inchanger
505 my ($dbh, $vols) = @_;
507 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
509 my $rq = "SELECT InChanger, VolumeName
511 WHERE VolumeName IN ($lst)
514 my $res = $dbh->selectall_arrayref($rq);
515 return $res; # [ [ 1, VolName].. ]
519 use File::Copy qw/copy/;
520 use File::Basename qw/basename/;
522 # We must kown the path+filename destination
523 # $self->{error} contains error message
524 # it return 0/1 if fail/success
527 my ($self, $src, $dst) = @_ ;
528 print "$src => $dst\n"
535 if ($dst =~ m!file:/(/.+)!) {
536 $ret = copy($src, $1);
538 $dstfile = "$1/" . basename($src) ;
540 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
541 $err = `scp $src $1 2>&1` ;
543 $dstfile = "$2/" . basename($src) ;
547 $err = "$dst not implemented yet";
548 File::Copy::copy($src, \*STDOUT);
551 $self->{error} = $err;
554 $self->{error} = $err;
563 ################################################################
571 unless ($about_widget) {
572 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
573 $about_widget = $glade_box->get_widget("dlg_about") ;
574 $glade_box->signal_autoconnect_from_package('DlgAbout');
576 $about_widget->show() ;
579 sub on_about_okbutton_clicked
581 $about_widget->hide() ;
586 ################################################################
592 my ($class, $config_file) = @_;
595 config_file => $config_file,
596 password => '', # db passwd
597 username => '', # db username
598 connection_string => '',# db connection string
599 bconsole => 'bconsole', # path and arg to bconsole
600 bsr_dest => '', # destination url for bsr files
601 debug => 0, # debug level 0|1
602 use_ok_bkp_only => 1, # dont use bad backup
603 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
604 glade_file => $glade_file,
605 see_all_versions => 0, # display all file versions in FileInfo
606 mozilla => 'mozilla', # mozilla bin
607 default_restore_job => 'restore', # regular expression to select default
610 # keywords that are used to fill DlgPref
611 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
612 entry_keyword => [ qw/username password bweb mozilla
613 connection_string default_restore_job
614 bconsole bsr_dest glade_file/],
617 $self->read_config();
626 # We read the parameters. They come from the configuration files
627 my $cfgfile ; my $tmpbuffer;
628 if (open FICCFG, $self->{config_file})
630 while(read FICCFG,$tmpbuffer,4096)
632 $cfgfile .= $tmpbuffer;
636 no strict; # I have no idea of the contents of the file
637 eval '$refparams' . " = $cfgfile";
640 for my $p (keys %{$refparams}) {
641 $self->{$p} = $refparams->{$p};
644 if (defined $self->{debug}) {
645 $debug = $self->{debug} ;
648 # TODO : Force dumb default values and display a message
658 for my $k (@{ $self->{entry_keyword} }) {
659 $parameters{$k} = $self->{$k};
662 for my $k (@{ $self->{chk_keyword} }) {
663 $parameters{$k} = $self->{$k};
666 if (open FICCFG,">$self->{config_file}")
668 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
673 # TODO : Display a message
682 $self->{dbh}->disconnect() ;
686 delete $self->{error};
688 if (not $self->{connection_string})
690 # The parameters have not been set. Maybe the conf
691 # file is empty for now
692 $self->{error} = "No configuration found for database connection. " .
693 "Please set this up.";
698 $self->{dbh} = DBI->connect($self->{connection_string},
703 $self->{error} = "Can't open bacula database. " .
704 "Database connect string '" .
705 $self->{connection_string} ."' $!";
708 $self->{dbh}->{RowCacheSize}=100;
714 my ($self, $url, $msg) = @_;
716 unless ($self->{mozilla} and $self->{bweb}) {
717 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
721 if ($^O eq 'MSWin32') {
722 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
725 system("$self->{mozilla} -remote 'Ping()'");
726 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
728 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
738 ################################################################
742 # my $pref = new Pref(config_file => 'brestore.conf');
743 # my $dlg = new DlgPref($pref);
744 # my $dlg_resto = new DlgResto($pref);
745 # $dlg->display($dlg_resto);
748 my ($class, $pref) = @_;
751 pref => $pref, # Pref ref
752 dlgresto => undef, # DlgResto ref
760 my ($self, $dlgresto) = @_ ;
762 unless ($self->{glade}) {
763 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
764 $self->{glade}->signal_autoconnect_from_package($self);
767 $self->{dlgresto} = $dlgresto;
769 my $g = $self->{glade};
770 my $p = $self->{pref};
772 for my $k (@{ $p->{entry_keyword} }) {
773 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
776 for my $k (@{ $p->{chk_keyword} }) {
777 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
780 $g->get_widget("dlg_pref")->show_all() ;
783 sub on_applybutton_clicked
786 my $glade = $self->{glade};
787 my $pref = $self->{pref};
789 for my $k (@{ $pref->{entry_keyword} }) {
790 my $w = $glade->get_widget("entry_$k") ;
791 $pref->{$k} = $w->get_text();
794 for my $k (@{ $pref->{chk_keyword} }) {
795 my $w = $glade->get_widget("chkbp_$k") ;
796 $pref->{$k} = $w->get_active();
799 $pref->write_config();
800 if ($pref->connect_db()) {
801 $self->{dlgresto}->set_dbh($pref->{dbh});
802 $self->{dlgresto}->set_status('Preferences updated');
803 $self->{dlgresto}->init_server_backup_combobox();
804 $self->{dlgresto}->create_brestore_tables();
805 $self->{dlgresto}->set_status($pref->{error});
807 $self->{dlgresto}->set_status($pref->{error});
811 # Handle prefs ok click (apply/dismiss dialog)
812 sub on_okbutton_clicked
815 $self->on_applybutton_clicked();
817 unless ($self->{pref}->{error}) {
818 $self->on_cancelbutton_clicked();
821 sub on_dialog_delete_event
824 $self->on_cancelbutton_clicked();
828 sub on_cancelbutton_clicked
831 $self->{glade}->get_widget('dlg_pref')->hide();
832 delete $self->{dlgresto};
836 ################################################################
846 # Kept as is from the perl-gtk example. Draws the pretty icons
852 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
853 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
854 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
855 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
859 # init combo (and create ListStore object)
862 my ($widget, @type) = @_ ;
863 my %type_info = ('text' => 'Glib::String',
864 'markup' => 'Glib::String',
867 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
869 $widget->set_model($lst);
873 if ($t eq 'text' or $t eq 'markup') {
874 $cell = new Gtk2::CellRendererText();
876 $widget->pack_start($cell, 1);
877 $widget->add_attribute($cell, $t, $i++);
882 # fill simple combo (one element per row)
885 my ($list, @what) = @_;
889 foreach my $w (@what)
892 my $i = $list->append();
893 $list->set($i, 0, $w);
900 my @unit = qw(b Kb Mb Gb Tb);
903 my $format = '%i %s';
904 while ($val / 1024 > 1) {
908 $format = ($i>0)?'%0.1f %s':'%i %s';
909 return sprintf($format, $val, $unit[$i]);
914 my ($self, $dbh) = @_;
920 my ($fileview) = shift;
921 my $fileview_target_entry = {target => 'STRING',
922 flags => ['GTK_TARGET_SAME_APP'],
925 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
926 ['copy'],$fileview_target_entry);
927 $fileview->get_selection->set_mode('multiple');
929 # set some useful SimpleList properties
930 $fileview->set_headers_clickable(0);
931 foreach ($fileview->get_columns())
933 $_->set_resizable(1);
934 $_->set_sizing('grow-only');
940 my ($self, $what) = @_;
944 print Data::Dumper::Dumper($what);
945 } elsif (defined $what) {
953 my ($self, $query) = @_;
954 $self->debug($query);
955 return $self->{dbh}->prepare($query);
960 my ($self, $query) = @_;
961 $self->debug($query);
962 return $self->{dbh}->do($query);
965 sub dbh_selectall_arrayref
967 my ($self, $query) = @_;
968 $self->debug($query);
969 return $self->{dbh}->selectall_arrayref($query);
972 sub dbh_selectrow_arrayref
974 my ($self, $query) = @_;
975 $self->debug($query);
976 return $self->{dbh}->selectrow_arrayref($query);
981 my ($class, $pref) = @_;
986 location => undef, # location entry widget
987 mainwin => undef, # mainwin widget
988 filelist_file_menu => undef, # file menu widget
989 filelist_dir_menu => undef, # dir menu widget
990 glade => undef, # glade object
991 status => undef, # status bar widget
992 dlg_pref => undef, # DlgPref object
993 fileattrib => {}, # cache file
994 fileview => undef, # fileview widget SimpleList
995 fileinfo => undef, # fileinfo widget SimpleList
997 client_combobox => undef, # client_combobox widget
998 restore_backup_combobox => undef, # date combobox widget
999 list_client => undef, # Gtk2::ListStore
1000 list_backup => undef, # Gtk2::ListStore
1001 cache_ppathid => {}, #
1004 # load menu (to use handler with self reference)
1005 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1006 $glade->signal_autoconnect_from_package($self);
1007 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1009 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1010 $glade->signal_autoconnect_from_package($self);
1011 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1013 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1014 $glade->signal_autoconnect_from_package($self);
1016 $self->{status} = $glade->get_widget('statusbar');
1017 $self->{mainwin} = $glade->get_widget('dlg_resto');
1018 $self->{location} = $glade->get_widget('entry_location');
1019 $self->render_icons();
1021 $self->{dlg_pref} = new DlgPref($pref);
1023 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1024 $self->{list_client} = init_combo($c, 'text');
1026 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1027 $self->{list_backup} = init_combo($c, 'text', 'markup');
1029 # Connect glade-fileview to Gtk2::SimpleList
1030 # and set up drag n drop between $fileview and $restore_list
1032 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1033 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1035 my $widget = $glade->get_widget('fileview');
1036 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1038 'h_name' => 'hidden',
1039 'h_jobid' => 'hidden',
1040 'h_type' => 'hidden',
1043 'File Name' => 'text',
1046 init_drag_drop($fileview);
1047 $fileview->set_search_column(4); # search on File Name
1049 # Connect glade-restore_list to Gtk2::SimpleList
1050 $widget = $glade->get_widget('restorelist');
1051 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1053 'h_name' => 'hidden',
1054 'h_jobid' => 'hidden',
1055 'h_type' => 'hidden',
1056 'h_curjobid' => 'hidden',
1059 'File Name' => 'text',
1061 'FileIndex' => 'text',
1063 'Nb Files' => 'text', #8
1064 'Size' => 'text', #9
1065 'size_b' => 'hidden', #10
1068 my @restore_list_target_table = ({'target' => 'STRING',
1072 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1073 $restore_list->get_selection->set_mode('multiple');
1075 $widget = $glade->get_widget('infoview');
1076 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1078 'h_name' => 'hidden',
1079 'h_jobid' => 'hidden',
1080 'h_type' => 'hidden',
1082 'InChanger' => 'pixbuf',
1089 init_drag_drop($infoview);
1091 $pref->connect_db() || $self->{dlg_pref}->display($self);
1094 $self->{dbh} = $pref->{dbh};
1095 $self->init_server_backup_combobox();
1096 $self->create_brestore_tables();
1099 $self->set_status($pref->{error});
1102 # set status bar informations
1105 my ($self, $string) = @_;
1106 return unless ($string);
1108 my $context = $self->{status}->get_context_id('Main');
1109 $self->{status}->push($context, $string);
1112 sub on_time_select_changed
1120 my $c = $self->{glade}->get_widget('combo_time');
1121 return $c->get_active_text;
1124 # This sub returns all clients declared in DB
1128 my $query = "SELECT Name FROM Client ORDER BY Name";
1129 print STDERR $query,"\n" if $debug;
1131 my $result = $dbh->selectall_arrayref($query);
1133 return map { $_->[0] } @$result;
1136 sub get_wanted_job_status
1143 return "'T', 'A', 'E'";
1147 # This sub gives a full list of the EndTimes for a ClientId
1148 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
1149 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
1150 sub get_all_endtimes_for_job
1152 my ($dbh, $client, $ok_only)=@_;
1153 my $status = get_wanted_job_status($ok_only);
1155 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
1156 FROM Job,Client,FileSet
1157 WHERE Job.ClientId=Client.ClientId
1158 AND Client.Name = '$client'
1160 AND JobStatus IN ($status)
1161 AND Job.FileSetId = FileSet.FileSetId
1162 ORDER BY EndTime desc";
1163 print STDERR $query,"\n" if $debug;
1164 my $result = $dbh->selectall_arrayref($query);
1170 # init infoview widget
1174 @{$self->{fileinfo}->{data}} = ();
1178 sub on_clear_clicked
1181 @{$self->{restore_list}->{data}} = ();
1184 sub on_estimate_clicked
1191 # TODO : If we get here, things could get lenghty ... draw a popup window .
1192 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1193 'destroy-with-parent',
1195 'Computing size...');
1199 my $title = "Computing size...\n";
1201 foreach my $entry (@{$self->{restore_list}->{data}})
1203 unless ($entry->[9]) {
1204 my ($size, $nb) = $self->estimate_restore_size($entry);
1205 $entry->[10] = $size;
1206 $entry->[9] = human($size);
1210 my $name = unpack('u', $entry->[0]);
1212 $txt .= "\n<i>$name</i> : " . $entry->[8] . " file(s)/" . $entry->[9] ;
1213 $widget->set_markup($title . $txt);
1215 $size_total+=$entry->[10];
1216 $nb_total+=$entry->[8];
1220 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1221 $widget->set_markup("Size estimation :\n" . $txt);
1222 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1227 sub on_gen_bsr_clicked
1231 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1232 'gtk-save','ok', 'gtk-cancel', 'cancel');
1235 my $w = new Gtk2::FileChooserDialog ( @options );
1240 if ($a eq 'cancel') {
1245 my $f = $w->get_filename();
1247 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1248 'destroy-with-parent',
1249 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1250 if ($dlg->run() eq 'ok') {
1264 if (open(FP, ">$save")) {
1265 my $bsr = $self->create_filelist();
1268 $self->set_status("Dumping BSR to $save ok");
1270 $self->set_status("Can't dump BSR to $save: $!");
1275 use File::Temp qw/tempfile/;
1277 sub on_go_button_clicked
1280 my $bsr = $self->create_filelist();
1281 my ($fh, $filename) = tempfile();
1284 chmod(0644, $filename);
1286 print "Dumping BSR info to $filename\n"
1289 # we get Volume list
1290 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1291 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1293 new DlgLaunch(pref => $self->{pref},
1295 bsr_file => $filename,
1300 our $client_list_empty = 'Clients list';
1301 our %type_markup = ('F' => '<b>$label F</b>',
1304 'B' => '<b>$label B</b>',
1306 'A' => '<span foreground=\"red\">$label</span>',
1308 'E' => '<span foreground=\"red\">$label</span>',
1311 sub on_list_client_changed
1313 my ($self, $widget) = @_;
1314 return 0 unless defined $self->{fileview};
1315 my $dbh = $self->{dbh};
1317 $self->{list_backup}->clear();
1319 if ($self->current_client eq $client_list_empty) {
1323 my @endtimes=get_all_endtimes_for_job($dbh,
1324 $self->current_client,
1325 $self->{pref}->{use_ok_bkp_only});
1326 foreach my $endtime (@endtimes)
1328 my $i = $self->{list_backup}->append();
1330 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1331 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1332 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1334 $self->{list_backup}->set($i,
1339 $self->{restore_backup_combobox}->set_active(0);
1341 $self->{CurrentJobIds} = [
1342 set_job_ids_for_date($dbh,
1343 $self->current_client,
1344 $self->current_date,
1345 $self->{pref}->{use_ok_bkp_only})
1348 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1351 $self->refresh_fileview();
1355 sub fill_server_list
1357 my ($dbh, $combo, $list) = @_;
1359 my @clients=get_all_clients($dbh);
1363 my $i = $list->append();
1364 $list->set($i, 0, $client_list_empty);
1366 foreach my $client (@clients)
1368 $i = $list->append();
1369 $list->set($i, 0, $client);
1371 $combo->set_active(0);
1374 sub init_server_backup_combobox
1377 fill_server_list($self->{dbh},
1378 $self->{client_combobox},
1379 $self->{list_client}) ;
1382 #----------------------------------------------------------------------
1383 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1384 #data isn't. There is additionnal complexity for dirs (visibility problems),
1385 #so the @CurrentJobIds is not sufficient.
1386 sub refresh_fileview
1389 my $fileview = $self->{fileview};
1390 my $client_combobox = $self->{client_combobox};
1391 my $cwd = $self->{cwd};
1393 @{$fileview->{data}} = ();
1395 $self->clear_infoview();
1397 my $client_name = $self->current_client;
1399 if (!$client_name or ($client_name eq $client_list_empty)) {
1400 $self->set_status("Client list empty");
1404 my @list_dirs = $self->list_dirs($cwd,$client_name);
1405 # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1406 my $files = $self->list_files($cwd);
1407 print "CWD : $cwd\n" if ($debug);
1409 my $file_count = 0 ;
1410 my $total_bytes = 0;
1412 # Add directories to view
1413 foreach my $dir_entry (@list_dirs) {
1414 #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
1415 my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime'));
1416 my $dir = $dir_entry->[0];
1417 $total_bytes += 4096;
1420 listview_push($fileview,
1422 $self->dir_attrib("$cwd/$dir",'jobid'),
1432 foreach my $file (@$files)
1434 my $size = file_attrib($file,'st_size');
1435 my $time = localtime(file_attrib($file,'st_mtime'));
1436 $total_bytes += $size;
1438 # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
1440 listview_push($fileview,
1447 human($size), $time);
1450 $self->set_status("$file_count files/" . human($total_bytes));
1452 # set a decent default selection (makes keyboard nav easy)
1453 $fileview->select(0);
1457 sub on_about_activate
1459 DlgAbout::display();
1464 my ($tree, $path, $data) = @_;
1466 my @items = listview_get_all($tree) ;
1468 foreach my $i (@items)
1470 my @file_info = @{$i};
1473 # Ok, we have a corner case :
1478 $file = pack("u", $file_info[0]);
1482 $file = pack("u", $path . '/' . $file_info[0]);
1484 push @ret, join(" ; ", $file,
1485 $file_info[1], # $jobid
1486 $file_info[2], # $type
1490 my $data_get = join(" :: ", @ret);
1492 $data->set_text($data_get,-1);
1495 sub fileview_data_get
1497 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1498 drag_set_info($widget, $self->{cwd}, $data);
1501 sub fileinfo_data_get
1503 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1504 drag_set_info($widget, $self->{cwd}, $data);
1507 sub restore_list_data_received
1509 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1512 if ($info eq 40 || $info eq 0) # patch for display!=:0
1514 foreach my $elt (split(/ :: /, $data->data()))
1517 my ($file, $jobid, $type) =
1519 $file = unpack("u", $file);
1521 $self->add_selected_file_to_list($file, $jobid, $type);
1526 sub on_back_button_clicked {
1530 sub on_location_go_button_clicked
1533 $self->ch_dir($self->{location}->get_text());
1535 sub on_quit_activate {Gtk2->main_quit;}
1536 sub on_preferences_activate
1539 $self->{dlg_pref}->display($self) ;
1541 sub on_main_delete_event {Gtk2->main_quit;}
1542 sub on_bweb_activate
1545 $self->set_status("Open bweb on your browser");
1546 $self->{pref}->go_bweb('', "go on bweb");
1549 # Change to parent directory
1553 if ($self->{cwd} eq '/')
1557 my @dirs = split(/\//, $self->{cwd});
1559 $self->ch_dir(join('/', @dirs));
1562 # Change the current working directory
1563 # * Updates fileview, location, and selection
1568 $self->{cwd} = shift;
1570 $self->refresh_fileview();
1571 $self->{location}->set_text($self->{cwd});
1576 # Handle dialog 'close' (window-decoration induced close)
1577 # * Just hide the dialog, and tell Gtk not to do anything else
1581 my ($self, $w) = @_;
1584 1; # consume this event!
1587 # Handle key presses in location text edit control
1588 # * Translate a Return/Enter key into a 'Go' command
1589 # * All other key presses left for GTK
1591 sub on_location_entry_key_release_event
1597 my $keypress = $event->keyval;
1598 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1599 $keypress == $Gtk2::Gdk::Keysyms{Return})
1601 $self->ch_dir($widget->get_text());
1603 return 1; # consume keypress
1606 return 0; # let gtk have the keypress
1609 sub on_fileview_key_press_event
1611 my ($self, $widget, $event) = @_;
1615 sub listview_get_first
1618 my @selected = $list->get_selected_indices();
1619 if (@selected > 0) {
1620 my ($name, @other) = @{$list->{data}->[$selected[0]]};
1621 return (unpack('u', $name), @other);
1627 sub listview_get_all
1631 my @selected = $list->get_selected_indices();
1633 for my $i (@selected) {
1634 my ($name, @other) = @{$list->{data}->[$i]};
1635 push @ret, [unpack('u', $name), @other];
1643 my ($list, $name, @other) = @_;
1644 push @{$list->{data}}, [pack('u', $name), @other];
1647 #----------------------------------------------------------------------
1648 # Handle keypress in file-view
1649 # * Translates backspace into a 'cd ..' command
1650 # * All other key presses left for GTK
1652 sub on_fileview_key_release_event
1654 my ($self, $widget, $event) = @_;
1655 if (not $event->keyval)
1659 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1661 return 1; # eat keypress
1664 return 0; # let gtk have keypress
1667 sub on_forward_keypress
1672 #----------------------------------------------------------------------
1673 # Handle double-click (or enter) on file-view
1674 # * Translates into a 'cd <dir>' command
1676 sub on_fileview_row_activated
1678 my ($self, $widget) = @_;
1680 my ($name, undef, $type, undef) = listview_get_first($widget);
1684 if ($self->{cwd} eq '')
1686 $self->ch_dir($name);
1688 elsif ($self->{cwd} eq '/')
1690 $self->ch_dir('/' . $name);
1694 $self->ch_dir($self->{cwd} . '/' . $name);
1698 $self->fill_infoview($self->{cwd}, $name);
1701 return 1; # consume event
1706 my ($self, $path, $file) = @_;
1707 $self->clear_infoview();
1708 my @v = get_all_file_versions($self->{dbh},
1711 $self->current_client,
1712 $self->{pref}->{see_all_versions});
1714 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
1716 my $icon = ($inchanger)?$yesicon:$noicon;
1718 $mtime = localtime($mtime) ;
1720 listview_push($self->{fileinfo},
1721 $file, $jobid, 'file',
1722 $icon, $volname, $jobid, human($size), $mtime, $md5);
1729 return $self->{restore_backup_combobox}->get_active_text;
1735 return $self->{client_combobox}->get_active_text;
1738 sub on_list_backups_changed
1740 my ($self, $widget) = @_;
1741 return 0 unless defined $self->{fileview};
1743 $self->{CurrentJobIds} = [
1744 set_job_ids_for_date($self->{dbh},
1745 $self->current_client,
1746 $self->current_date,
1747 $self->{pref}->{use_ok_bkp_only})
1749 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1751 $self->refresh_fileview();
1755 sub on_restore_list_keypress
1757 my ($self, $widget, $event) = @_;
1758 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1760 my @sel = $widget->get_selected_indices;
1761 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1763 splice @{$self->{restore_list}->{data}},$elt,1;
1768 sub on_fileview_button_press_event
1770 my ($self,$widget,$event) = @_;
1771 if ($event->button == 3)
1773 $self->on_right_click_filelist($widget,$event);
1777 if ($event->button == 2)
1779 $self->on_see_all_version();
1786 sub on_see_all_version
1790 my @lst = listview_get_all($self->{fileview});
1793 my ($name, undef) = @{$i};
1795 new DlgFileVersion($self->{dbh},
1796 $self->current_client,
1797 $self->{cwd}, $name);
1801 sub on_right_click_filelist
1803 my ($self,$widget,$event) = @_;
1804 # I need to know what's selected
1805 my @sel = listview_get_all($self->{fileview});
1810 $type = $sel[0]->[2]; # $type
1815 if (@sel >=2 or $type eq 'dir')
1817 # We have selected more than one or it is a directories
1818 $w = $self->{filelist_dir_menu};
1822 $w = $self->{filelist_file_menu};
1828 $event->button, $event->time);
1831 sub context_add_to_filelist
1835 my @sel = listview_get_all($self->{fileview});
1837 foreach my $i (@sel)
1839 my ($file, $jobid, $type, undef) = @{$i};
1840 $file = $self->{cwd} . '/' . $file;
1841 $self->add_selected_file_to_list($file, $jobid, $type);
1845 # Adds a file to the filelist
1846 sub add_selected_file_to_list
1848 my ($self, $name, $jobid, $type)=@_;
1850 my $dbh = $self->{dbh};
1851 my $restore_list = $self->{restore_list};
1853 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1860 if ($name and substr $name,-1 ne '/')
1862 $name .= '/'; # For bacula
1864 my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
1865 listview_push($restore_list,
1866 $name, $jobid, 'dir', $curjobids,
1867 $diricon, $name,$curjobids,$dirfileindex);
1869 elsif ($type eq 'file')
1871 my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
1873 listview_push($restore_list,
1874 $name, $jobid, 'file', $curjobids,
1875 $fileicon, $name, $jobid, $fileindex );
1879 # TODO : we want be able to restore files from a bad ended backup
1880 # we have JobStatus IN ('T', 'A', 'E') and we must
1882 # Data acces subs from here. Interaction with SGBD and caching
1884 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1885 # GUI and stores them in @CurrentJobIds
1886 sub set_job_ids_for_date
1888 my ($dbh, $client, $date, $only_ok)=@_;
1890 if (!$client or !$date) {
1894 my $status = get_wanted_job_status($only_ok);
1896 # The algorithm : for a client, we get all the backups for each
1897 # fileset, in reverse order Then, for each fileset, we store the 'good'
1898 # incrementals and differentials until we have found a full so it goes
1899 # like this : store all incrementals until we have found a differential
1900 # or a full, then find the full #
1902 my $query = "SELECT JobId, FileSet, Level, JobStatus
1903 FROM Job, Client, FileSet
1904 WHERE Job.ClientId = Client.ClientId
1905 AND FileSet.FileSetId = Job.FileSetId
1906 AND EndTime <= '$date'
1907 AND Client.Name = '$client'
1909 AND JobStatus IN ($status)
1910 ORDER BY FileSet, JobTDate DESC";
1912 print STDERR $query,"\n" if $debug;
1914 my $result = $dbh->selectall_arrayref($query);
1916 foreach my $refrow (@$result)
1918 my $jobid = $refrow->[0];
1919 my $fileset = $refrow->[1];
1920 my $level = $refrow->[2];
1922 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1924 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1928 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1929 push @CurrentJobIds,($jobid);
1931 elsif ($level eq 'D')
1933 next if $progress{$fileset} eq 'D'; # We allready have a differential
1934 push @CurrentJobIds,($jobid);
1936 elsif ($level eq 'F')
1938 push @CurrentJobIds,($jobid);
1941 my $status = $refrow->[3] ;
1942 if ($status eq 'T') { # good end of job
1943 $progress{$fileset} = $level;
1946 print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
1948 return @CurrentJobIds;
1951 # Lists all directories contained inside a directory.
1952 # Uses the current dir, the client name, and CurrentJobIds for visibility.
1953 # Returns an array of dirs
1956 my ($self,$dir,$client)=@_;
1958 print "list_dirs(<$dir>, <$client>)\n" if $debug;
1960 if ($dir ne '' and substr $dir,-1 ne '/')
1962 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1965 my $dbh = $self->{dbh};
1966 my $query = "SELECT PathId FROM Path WHERE Path = ?
1967 UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?";
1968 my $sth = $dbh->prepare($query);
1969 $sth->execute($dir,$dir);
1970 my $result = $sth->fetchrow_arrayref();
1972 my $pathid = $result->[0];
1973 my @jobids = @{$self->{CurrentJobIds}};
1974 my $jobclause = join (',', @jobids);
1975 # Let's retrieve the list of the visible dirs in this dir ...
1976 # First, I need the empty filenameid to locate efficiently the dirs in the file table
1977 $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
1978 $sth = $dbh->prepare($query);
1980 $result = $sth->fetchrow_arrayref();
1982 my $dir_filenameid = $result->[0];
1984 # Then we get all the dir entries from File ...
1985 # It's ugly because there are records in brestore_missing_path ...
1987 SELECT Path, JobId, Lstat FROM(
1989 SELECT Path.Path, lower(Path.Path),
1990 listfile.JobId, listfile.Lstat
1992 SELECT DISTINCT brestore_pathhierarchy.PathId
1993 FROM brestore_pathhierarchy
1995 ON (brestore_pathhierarchy.PathId = Path.PathId)
1996 JOIN brestore_pathvisibility
1997 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
1998 WHERE brestore_pathhierarchy.PPathId = $pathid
1999 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2000 JOIN Path ON (listpath.PathId = Path.PathId)
2002 SELECT File.PathId, File.JobId, File.Lstat FROM File
2003 WHERE File.FilenameId = $dir_filenameid
2004 AND File.JobId IN ($jobclause)) AS listfile
2005 ON (listpath.PathId = listfile.PathId)
2007 SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path),
2008 listfile.JobId, listfile.Lstat
2010 SELECT DISTINCT brestore_pathhierarchy.PathId
2011 FROM brestore_pathhierarchy
2012 JOIN brestore_missing_path
2013 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2014 JOIN brestore_pathvisibility
2015 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2016 WHERE brestore_pathhierarchy.PPathId = $pathid
2017 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2018 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2020 SELECT File.PathId, File.JobId, File.Lstat FROM File
2021 WHERE File.FilenameId = $dir_filenameid
2022 AND File.JobId IN ($jobclause)) AS listfile
2023 ON (listpath.PathId = listfile.PathId))
2024 ORDER BY 2,3 DESC ) As a";
2025 print STDERR "$query\n" if $debug;
2026 $sth=$dbh->prepare($query);
2028 $result = $sth->fetchall_arrayref();
2031 foreach my $refrow (@{$result})
2033 my $dir = $refrow->[0];
2034 my $jobid = $refrow->[1];
2035 my $lstat = $refrow->[2];
2036 next if ($dir eq $prev_dir);
2037 # We have to clean up this dirname ... we only want it's 'basename'
2041 my @temp = split ('/',$dir);
2042 $return_value = pop @temp;
2046 $return_value = '/';
2048 my @return_array = ($return_value,$lstat);
2049 push @return_list,(\@return_array);
2052 return @return_list;
2056 # List all files in a directory. dir as parameter, CurrentJobIds for visibility
2057 # Returns an array of dirs
2060 my ($self, $dir)=@_;
2061 my $dbh = $self->{dbh};
2065 print "list_files($dir)\n" if $debug;
2067 if ($dir ne '' and substr $dir,-1 ne '/')
2069 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
2072 my $query = "SELECT Path.PathId
2074 WHERE Path.Path = '$dir'
2076 SELECT brestore_missing_path.PathId
2077 FROM brestore_missing_path
2078 WHERE brestore_missing_path.Path = '$dir'";
2079 print $query,"\n" if $debug;
2081 my $result = $dbh->selectall_arrayref($query);
2082 foreach my $refrow (@$result)
2084 push @list_pathid,($refrow->[0]);
2087 if (@list_pathid == 0)
2089 print "No pathid found for $dir\n" if $debug;
2093 my $inlistpath = join (',', @list_pathid);
2094 my $inclause = join (',', @{$self->{CurrentJobIds}});
2095 if ($inclause eq '')
2101 "SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
2103 (SELECT Filename.Name, max(File.FileId) as id
2105 WHERE File.FilenameId = Filename.FilenameId
2106 AND Filename.Name != ''
2107 AND File.PathId IN ($inlistpath)
2108 AND File.JobId IN ($inclause)
2109 GROUP BY Filename.Name
2110 ORDER BY Filename.Name) AS listfiles,
2112 WHERE File.FileId = listfiles.id";
2114 print STDERR $query,"\n" if $debug;
2115 $result = $dbh->selectall_arrayref($query);
2122 Gtk2->main_iteration while (Gtk2->events_pending);
2125 sub create_brestore_tables
2129 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2131 unless ($self->dbh_do($verif)) {
2132 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2134 $self->{error} = "Creating internal brestore tables";
2136 CREATE TABLE brestore_knownjobid
2138 JobId int4 NOT NULL,
2139 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2141 $self->dbh_do($req);
2144 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2145 unless ($self->dbh_do($verif)) {
2147 CREATE TABLE brestore_pathhierarchy
2149 PathId int4 NOT NULL,
2150 PPathId int4 NOT NULL,
2151 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2153 $self->dbh_do($req);
2156 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2157 ON brestore_pathhierarchy (PPathId)";
2158 $self->dbh_do($req);
2161 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2162 unless ($self->dbh_do($verif)) {
2164 CREATE TABLE brestore_pathvisibility
2166 PathId int4 NOT NULL,
2167 JobId int4 NOT NULL,
2168 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2170 $self->dbh_do($req);
2172 $req = "CREATE INDEX brestore_pathvisibility_jobid
2173 ON brestore_pathvisibility (JobId)";
2174 $self->dbh_do($req);
2177 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2178 unless ($self->dbh_do($verif)) {
2180 CREATE TABLE brestore_missing_path
2182 PathId int4 NOT NULL,
2184 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2186 $self->dbh_do($req);
2188 $req = "CREATE INDEX brestore_missing_path_path
2189 ON brestore_missing_path (Path)";
2190 $self->dbh_do($req);
2194 # Recursive function to calculate the visibility of each directory in the cache
2195 # tree Working with references to save time and memory
2196 # For each directory, we want to propagate it's visible jobids onto it's
2197 # parents directory.
2198 # A tree is visible if
2199 # - it's been in a backup pointed by the CurrentJobIds
2200 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2201 # In the second case, the directory is visible but has no metadata.
2202 # We symbolize this with lstat = 1 for this jobid in the cache.
2204 # Input : reference directory
2205 # Output : visibility of this dir. Has to know visibility of all subdirs
2206 # to know it's visibility, hence the recursing.
2212 # Get the subdirs array references list
2213 my @list_ref_subdirs;
2214 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2216 push @list_ref_subdirs,($ref_subdir);
2219 # Now lets recurse over these subdirs and retrieve the reference of a hash
2220 # containing the jobs where they are visible
2221 foreach my $ref_subdir (@list_ref_subdirs)
2223 my $ref_list_jobs = list_visible($ref_subdir);
2224 foreach my $jobid (keys %$ref_list_jobs)
2226 $visibility{$jobid}=1;
2230 # Ok. Now, we've got the list of those jobs. We are going to update our
2231 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2232 # the lstat for the known jobids. Put 1 in the new elements... But first,
2233 # let's store the current jobids
2235 foreach my $jobid (keys %{$refdir->[1]})
2237 push @known_jobids,($jobid);
2241 foreach my $jobid (keys %visibility)
2243 next if ($refdir->[1]->{$jobid});
2244 $refdir->[1]->{$jobid} = 1;
2246 # Add the known_jobids to %visibility
2247 foreach my $jobid (@known_jobids)
2249 $visibility{$jobid}=1;
2251 return \%visibility;
2254 # Returns the list of media required for a list of jobids.
2255 # Input : dbh, jobid1, jobid2...
2256 # Output : reference to array of (joibd, inchanger)
2257 sub get_required_media_from_jobid
2259 my ($dbh, @jobids)=@_;
2260 my $inclause = join(',',@jobids);
2262 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2263 FROM JobMedia, Media
2264 WHERE JobMedia.MediaId=Media.MediaId
2265 AND JobId In ($inclause)
2267 my $result = $dbh->selectall_arrayref($query);
2271 # Returns the fileindex from dirname and jobid.
2272 # Input : dbh, dirname, jobid
2273 # Output : fileindex
2274 sub get_fileindex_from_dir_jobid
2276 my ($dbh, $dirname, $jobid)=@_;
2278 $query = "SELECT File.FileIndex
2279 FROM File, Filename, Path
2280 WHERE File.FilenameId = Filename.FilenameId
2281 AND File.PathId = Path.PathId
2282 AND Filename.Name = ''
2283 AND Path.Path = '$dirname'
2284 AND File.JobId = '$jobid'
2287 print STDERR $query,"\n" if $debug;
2288 my $result = $dbh->selectall_arrayref($query);
2289 return $result->[0]->[0];
2292 # Returns the fileindex from filename and jobid.
2293 # Input : dbh, filename, jobid
2294 # Output : fileindex
2295 sub get_fileindex_from_file_jobid
2297 my ($dbh, $filename, $jobid)=@_;
2299 my @dirs = split(/\//, $filename);
2300 $filename=pop(@dirs);
2301 my $dirname = join('/', @dirs) . '/';
2306 "SELECT File.FileIndex
2307 FROM File, Filename, Path
2308 WHERE File.FilenameId = Filename.FilenameId
2309 AND File.PathId = Path.PathId
2310 AND Filename.Name = '$filename'
2311 AND Path.Path = '$dirname'
2312 AND File.JobId = '$jobid'";
2314 print STDERR $query,"\n" if $debug;
2315 my $result = $dbh->selectall_arrayref($query);
2316 return $result->[0]->[0];
2320 # Returns list of versions of a file that could be restored
2321 # returns an array of
2322 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2323 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2324 # and volname and md5
2325 # and of course, there will be only one jobid in the array of jobids...
2326 sub get_all_file_versions
2328 my ($dbh,$path,$file,$client,$see_all)=@_;
2330 defined $see_all or $see_all=0;
2335 "SELECT File.JobId, File.FileIndex, File.Lstat,
2336 File.Md5, Media.VolumeName, Media.InChanger
2337 FROM File, Filename, Path, Job, Client, JobMedia, Media
2338 WHERE File.FilenameId = Filename.FilenameId
2339 AND File.PathId=Path.PathId
2340 AND File.JobId = Job.JobId
2341 AND Job.ClientId = Client.ClientId
2342 AND Job.JobId = JobMedia.JobId
2343 AND File.FileIndex >= JobMedia.FirstIndex
2344 AND File.FileIndex <= JobMedia.LastIndex
2345 AND JobMedia.MediaId = Media.MediaId
2346 AND Path.Path = '$path'
2347 AND Filename.Name = '$file'
2348 AND Client.Name = '$client'";
2350 print STDERR $query if $debug;
2352 my $result = $dbh->selectall_arrayref($query);
2354 foreach my $refrow (@$result)
2356 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2357 my @attribs = parse_lstat($lstat);
2358 my $mtime = array_attrib('st_mtime',\@attribs);
2359 my $size = array_attrib('st_size',\@attribs);
2361 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2362 $inchanger, $md5, $volname);
2363 push @versions, (\@list);
2366 # We have the list of all versions of this file.
2367 # We'll sort it by mtime desc, size, md5, inchanger desc
2368 # the rest of the algorithm will be simpler
2369 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2370 @versions = sort { $b->[4] <=> $a->[4]
2371 || $a->[5] <=> $b->[5]
2372 || $a->[7] cmp $a->[7]
2373 || $b->[6] <=> $a->[6]} @versions;
2376 my %allready_seen_by_mtime;
2377 my %allready_seen_by_md5;
2378 # Now we should create a new array with only the interesting records
2379 foreach my $ref (@versions)
2383 # The file has a md5. We compare his md5 to other known md5...
2384 # We take size into account. It may happen that 2 files
2385 # have the same md5sum and are different. size is a supplementary
2388 # If we allready have a (better) version
2389 next if ( (not $see_all)
2390 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2392 # we never met this one before...
2393 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2395 # Even if it has a md5, we should also work with mtimes
2396 # We allready have a (better) version
2397 next if ( (not $see_all)
2398 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2399 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2401 # We reached there. The file hasn't been seen.
2402 push @good_versions,($ref);
2405 # To be nice with the user, we re-sort good_versions by
2406 # inchanger desc, mtime desc
2407 @good_versions = sort { $b->[4] <=> $a->[4]
2408 || $b->[2] <=> $a->[2]} @good_versions;
2410 return @good_versions;
2413 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2414 # This sub creates a BSR from the information in the restore_list
2415 # Returns the BSR as a string
2420 # This query gets all jobid/jobmedia/media combination.
2422 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2423 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2424 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2425 Media.Volumename, Media.MediaType
2426 FROM Job, JobMedia, Media
2427 WHERE Job.JobId = JobMedia.JobId
2428 AND JobMedia.MediaId = Media.MediaId
2429 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2432 my $result = $self->dbh_selectall_arrayref($query);
2434 # We will store everything hashed by jobid.
2436 foreach my $refrow (@$result)
2438 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2439 $firstindex, $lastindex, $startblock, $endblock,
2440 $volindex, $volumename, $mediatype) = @{$refrow};
2442 # We just have to deal with the case where starfile != endfile
2443 # In this case, we concatenate both, for the bsr
2444 if ($startfile != $endfile) {
2445 $startfile = $startfile . '-' . $endfile;
2449 ($jobid, $volsessionid, $volsessiontime, $startfile,
2450 $firstindex, $lastindex, $startblock .'-'. $endblock,
2451 $volindex, $volumename, $mediatype);
2453 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2457 # reminder : restore_list looks like this :
2458 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2460 # Here, we retrieve every file/dir that could be in the restore
2461 # We do as simple as possible for the SQL engine (no crazy joins,
2462 # no pseudo join (>= FirstIndex ...), etc ...
2463 # We do a SQL union of all the files/dirs specified in the restore_list
2465 foreach my $entry (@{$self->{restore_list}->{data}})
2467 if ($entry->[2] eq 'dir')
2469 my $dir = unpack('u', $entry->[0]);
2470 my $inclause = $entry->[3]; #curjobids
2473 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2474 FROM File, Path, Filename
2475 WHERE Path.PathId = File.PathId
2476 AND File.FilenameId = Filename.FilenameId
2477 AND Path.Path LIKE '$dir%'
2478 AND File.JobId IN ($inclause) )";
2479 push @select_queries,($query);
2483 # It's a file. Great, we allready have most
2484 # of what is needed. Simple and efficient query
2485 my $file = unpack('u', $entry->[0]);
2486 my @file = split '/',$file;
2488 my $dir = join('/',@file);
2490 my $jobid = $entry->[1];
2491 my $fileindex = $entry->[7];
2492 my $inclause = $entry->[3]; # curjobids
2494 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2495 FROM File, Path, Filename
2496 WHERE Path.PathId = File.PathId
2497 AND File.FilenameId = Filename.FilenameId
2498 AND Path.Path = '$dir/'
2499 AND Filename.Name = '$file'
2500 AND File.JobId = $jobid)";
2501 push @select_queries,($query);
2504 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2506 print STDERR $query,"\n" if $debug;
2508 #Now we run the query and parse the result...
2509 # there may be a lot of records, so we better be efficient
2510 # We use the bind column method, working with references...
2512 my $sth = $self->dbh_prepare($query);
2515 my ($path,$name,$fileindex,$jobid);
2516 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2518 # The temp place we're going to save all file
2519 # list to before the real list
2523 while ($sth->fetchrow_arrayref())
2525 # This may look dumb, but we're going to do a join by ourselves,
2526 # to save memory and avoid sending a complex query to mysql
2527 my $complete_path = $path . $name;
2535 # Remove trailing slash (normalize file and dir name)
2536 $complete_path =~ s/\/$//;
2538 # Let's find the ref(s) for the %mediainfo element(s)
2539 # containing the data for this file
2540 # There can be several matches. It is the pseudo join.
2542 my $max_elt=@{$mediainfos{$jobid}}-1;
2544 while($med_idx <= $max_elt)
2546 my $ref = $mediainfos{$jobid}->[$med_idx];
2547 # First, can we get rid of the first elements of the
2548 # array ? (if they don't contain valuable records
2550 if ($fileindex > $ref->[5])
2552 # It seems we don't need anymore
2553 # this entry in %mediainfo (the input data
2556 shift @{$mediainfos{$jobid}};
2560 # We will do work on this elt. We can ++
2561 # $med_idx for next loop
2564 # %mediainfo row looks like :
2565 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2566 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2569 # We are in range. We store and continue looping
2571 if ($fileindex >= $ref->[4])
2573 my @data = ($complete_path,$is_dir,
2575 push @temp_list,(\@data);
2579 # We are not in range. No point in continuing looping
2580 # We go to next record.
2584 # Now we have the array.
2585 # We're going to sort it, by
2586 # path, volsessiontime DESC (get the most recent file...)
2587 # The array rows look like this :
2588 # complete_path,is_dir,fileindex,
2589 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2590 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2591 @temp_list = sort {$a->[0] cmp $b->[0]
2592 || $b->[3]->[2] <=> $a->[3]->[2]
2596 my $prev_complete_path='////'; # Sure not to match
2600 while (my $refrow = shift @temp_list)
2602 # For the sake of readability, we load $refrow
2603 # contents in real scalars
2604 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2605 my $jobid= $refother->[0]; # We don't need the rest...
2607 # We skip this entry.
2608 # We allready have a newer one and this
2609 # isn't a continuation of the same file
2610 next if ($complete_path eq $prev_complete_path
2611 and $jobid != $prev_jobid);
2615 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2617 # We would be recursing inside a file.
2618 # Just what we don't want (dir replaced by file
2619 # between two backups
2625 push @restore_list,($refrow);
2627 $prev_complete_path = $complete_path;
2628 $prev_jobid = $jobid;
2634 push @restore_list,($refrow);
2636 $prev_complete_path = $complete_path;
2637 $prev_jobid = $jobid;
2641 # We get rid of @temp_list... save memory
2644 # Ok everything is in the list. Let's sort it again in another way.
2645 # This time it will be in the bsr file order
2647 # we sort the results by
2648 # volsessiontime, volsessionid, volindex, fileindex
2649 # to get all files in right order...
2650 # Reminder : The array rows look like this :
2651 # complete_path,is_dir,fileindex,
2652 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2653 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2655 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2656 || $a->[3]->[1] <=> $b->[3]->[1]
2657 || $a->[3]->[7] <=> $b->[3]->[7]
2658 || $a->[2] <=> $b->[2] }
2661 # Now that everything is ready, we create the bsr
2662 my $prev_fileindex=-1;
2663 my $prev_volsessionid=-1;
2664 my $prev_volsessiontime=-1;
2665 my $prev_volumename=-1;
2666 my $prev_volfile=-1;
2670 my $first_of_current_range=0;
2671 my @fileindex_ranges;
2674 foreach my $refrow (@restore_list)
2676 my (undef,undef,$fileindex,$refother)=@{$refrow};
2677 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2678 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2680 # We can specifiy the number of files in each section of the
2681 # bsr to speedup restore (bacula can then jump over the
2682 # end of tape files.
2686 if ($prev_volumename eq '-1')
2688 # We only have to start the new range...
2689 $first_of_current_range=$fileindex;
2691 elsif ($prev_volsessionid != $volsessionid
2692 or $prev_volsessiontime != $volsessiontime
2693 or $prev_volumename ne $volumename
2694 or $prev_volfile ne $volfile)
2696 # We have to create a new section in the bsr...
2697 # We print the previous one ...
2698 # (before that, save the current range ...)
2699 if ($first_of_current_range != $prev_fileindex)
2702 push @fileindex_ranges,
2703 ("$first_of_current_range-$prev_fileindex");
2707 # We are out of a range,
2708 # but there is only one element in the range
2709 push @fileindex_ranges,
2710 ("$first_of_current_range");
2713 $bsr.=print_bsr_section(\@fileindex_ranges,
2715 $prev_volsessiontime,
2722 # Reset for next loop
2723 @fileindex_ranges=();
2724 $first_of_current_range=$fileindex;
2726 elsif ($fileindex-1 != $prev_fileindex)
2728 # End of a range of fileindexes
2729 if ($first_of_current_range != $prev_fileindex)
2732 push @fileindex_ranges,
2733 ("$first_of_current_range-$prev_fileindex");
2737 # We are out of a range,
2738 # but there is only one element in the range
2739 push @fileindex_ranges,
2740 ("$first_of_current_range");
2742 $first_of_current_range=$fileindex;
2744 $prev_fileindex=$fileindex;
2745 $prev_volsessionid = $volsessionid;
2746 $prev_volsessiontime = $volsessiontime;
2747 $prev_volumename = $volumename;
2748 $prev_volfile=$volfile;
2749 $prev_mediatype=$mediatype;
2750 $prev_volblocks=$volblocks;
2754 # Ok, we're out of the loop. Alas, there's still the last record ...
2755 if ($first_of_current_range != $prev_fileindex)
2758 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2763 # We are out of a range,
2764 # but there is only one element in the range
2765 push @fileindex_ranges,("$first_of_current_range");
2768 $bsr.=print_bsr_section(\@fileindex_ranges,
2770 $prev_volsessiontime,
2780 sub print_bsr_section
2782 my ($ref_fileindex_ranges,$volsessionid,
2783 $volsessiontime,$volumename,$volfile,
2784 $mediatype,$volblocks,$count)=@_;
2787 $bsr .= "Volume=\"$volumename\"\n";
2788 $bsr .= "MediaType=\"$mediatype\"\n";
2789 $bsr .= "VolSessionId=$volsessionid\n";
2790 $bsr .= "VolSessionTime=$volsessiontime\n";
2791 $bsr .= "VolFile=$volfile\n";
2792 $bsr .= "VolBlock=$volblocks\n";
2794 foreach my $range (@{$ref_fileindex_ranges})
2796 $bsr .= "FileIndex=$range\n";
2799 $bsr .= "Count=$count\n";
2803 # This function estimates the size to be restored for an entry of the restore
2805 # In : self,reference to the entry
2806 # Out : size in bytes, number of files
2807 sub estimate_restore_size
2809 # reminder : restore_list looks like this :
2810 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2814 if ($entry->[2] eq 'dir')
2816 my $dir = unpack('u', $entry->[0]);
2817 my $inclause = $entry->[3]; #curjobids
2819 "SELECT Path.Path, File.FilenameId, File.LStat
2820 FROM File, Path, Job
2821 WHERE Path.PathId = File.PathId
2822 AND File.JobId = Job.JobId
2823 AND Path.Path LIKE '$dir%'
2824 AND File.JobId IN ($inclause)
2825 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2829 # It's a file. Great, we allready have most
2830 # of what is needed. Simple and efficient query
2831 my $file = unpack('u', $entry->[0]);
2832 my @file = split '/',$file;
2834 my $dir = join('/',@file);
2836 my $jobid = $entry->[1];
2837 my $fileindex = $entry->[7];
2838 my $inclause = $entry->[3]; # curjobids
2840 "SELECT Path.Path, File.FilenameId, File.Lstat
2841 FROM File, Path, Filename
2842 WHERE Path.PathId = File.PathId
2843 AND Path.Path = '$dir/'
2844 AND Filename.Name = '$file'
2845 AND File.JobId = $jobid
2846 AND Filename.FilenameId = File.FilenameId";
2849 print STDERR $query,"\n" if $debug;
2850 my ($path,$nameid,$lstat);
2851 my $sth = $self->dbh_prepare($query);
2853 $sth->bind_columns(\$path,\$nameid,\$lstat);
2863 while ($sth->fetchrow_arrayref())
2865 # Only the latest version of a file
2866 next if ($nameid eq $old_nameid and $path eq $old_path);
2868 if ($rcount > 15000) {
2875 # We get the size of this file
2876 my $size=lstat_attrib($lstat,'st_size');
2877 $total_size += $size;
2880 $old_nameid=$nameid;
2882 return ($total_size,$total_files);
2885 sub update_brestore_table
2887 my ($self, @jobs) = @_;
2888 my $dbh = $self->{dbh};
2890 foreach my $job (sort {$a <=> $b} @jobs)
2892 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2893 my $retour = $self->dbh_selectrow_arrayref($query);
2894 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2896 print STDERR "Inserting path records for JobId $job\n";
2897 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2898 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2900 $self->dbh_do($query);
2902 # Now we have to do the directory recursion stuff to determine missing visibility
2903 # We try to avoid recursion, to be as fast as possible
2904 # We also only work on not allready hierarchised directories...
2906 print STDERR "Creating missing recursion paths for $job\n";
2908 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2909 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2910 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2911 WHERE brestore_pathvisibility.JobId = $job
2912 AND brestore_pathhierarchy.PathId IS NULL
2915 my $sth = $self->dbh_prepare($query);
2917 my $pathid; my $path;
2918 $sth->bind_columns(\$pathid,\$path);
2922 $self->build_path_hierarchy($path,$pathid);
2926 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2927 # This query gives all parent pathids for a given jobid that aren't stored.
2928 # It has to be called until no record is updated ...
2930 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2931 SELECT a.PathId,$job
2933 (SELECT DISTINCT h.PPathId AS PathId
2934 FROM brestore_pathhierarchy AS h
2935 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2936 WHERE p.JobId=$job) AS a
2939 FROM brestore_pathvisibility
2940 WHERE JobId=$job) AS b
2941 ON (a.PathId = b.PathId)
2942 WHERE b.PathId IS NULL)";
2943 print STDERR $query,"\n" if ($debug);
2945 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2947 print STDERR "Recursively adding $rows_affected records from $job\n";
2950 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2955 sub cleanup_brestore_table
2958 my $dbh = $self->{dbh};
2960 my $query = "SELECT JobId from brestore_knownjobid";
2961 my @jobs = @{$dbh->selectall_arrayref($query)};
2963 foreach my $jobentry (@jobs)
2965 my $job = $jobentry->[0];
2966 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2967 my $result = $dbh->selectall_arrayref($query);
2968 if (scalar(@{$result}))
2970 # There are still files for this jobid
2971 print STDERR "$job still exists. Not cleaning...\n";
2974 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2976 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2982 sub build_path_hierarchy
2984 my ($self, $path,$pathid)=@_;
2985 # Does the ppathid exist for this ? we use a memory cache...
2986 # In order to avoid the full loop, we consider that if a dir is allready in the
2987 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2990 #print STDERR "$path\n" if $debug;
2991 if (! $self->{cache_ppathid}->{$pathid})
2993 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2994 my $sth2 = $self->{dbh}->prepare_cached($query);
2995 $sth2->execute($pathid);
2996 # Do we have a result ?
2997 if (my $refrow = $sth2->fetchrow_arrayref)
2999 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
3001 # This dir was in the db ...
3002 # It means we can leave, the tree has allready been built for
3007 # We have to create the record ...
3008 # What's the current p_path ?
3009 my $ppath = parent_dir($path);
3010 my $ppathid = $self->return_pathid_from_path($ppath);
3011 $self->{cache_ppathid}->{$pathid}= $ppathid;
3013 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
3014 $sth2 = $self->{dbh}->prepare_cached($query);
3015 $sth2->execute($pathid,$ppathid);
3021 # It's allready in the cache.
3022 # We can leave, no time to waste here, all the parent dirs have allready
3030 sub return_pathid_from_path
3032 my ($self, $path) = @_;
3033 my $query = "SELECT PathId FROM Path WHERE Path = ?
3035 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3036 #print STDERR $query,"\n" if $debug;
3037 my $sth = $self->{dbh}->prepare_cached($query);
3038 $sth->execute($path,$path);
3039 my $result =$sth->fetchrow_arrayref();
3041 if (defined $result)
3043 return $result->[0];
3046 # A bit dirty : we insert into path AND missing_path, to be sure
3047 # we aren't deleted by a purge. We still need to insert into path to get
3048 # the pathid, because of mysql
3049 $query = "INSERT INTO Path (Path) VALUES (?)";
3050 #print STDERR $query,"\n" if $debug;
3051 $sth = $self->{dbh}->prepare_cached($query);
3052 $sth->execute($path);
3055 $query = " INSERT INTO brestore_missing_path (PathId,Path)
3056 SELECT PathId,Path FROM Path WHERE Path = ?";
3057 #print STDERR $query,"\n" if $debug;
3058 $sth = $self->{dbh}->prepare_cached($query);
3059 $sth->execute($path);
3061 $query = " DELETE FROM Path WHERE Path = ?";
3062 #print STDERR $query,"\n" if $debug;
3063 $sth = $self->{dbh}->prepare_cached($query);
3064 $sth->execute($path);
3066 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3067 #print STDERR $query,"\n" if $debug;
3068 $sth = $self->{dbh}->prepare_cached($query);
3069 $sth->execute($path);
3070 $result = $sth->fetchrow_arrayref();
3072 return $result->[0];
3084 # Root Windows case :
3085 if ($path =~ /^[a-z]+:\/$/i)
3090 my @tmp = split('/',$path);
3091 # We remove the last ...
3093 my $tmp = join ('/',@tmp) . '/';
3099 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3100 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3101 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3102 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3103 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3104 'data_stream' => 15);;
3107 my ($attrib,$ref_attrib)=@_;
3108 return $ref_attrib->[$attrib_name_id{$attrib}];
3112 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
3114 my ($file, $attrib)=@_;
3116 if (defined $attrib_name_id{$attrib}) {
3118 my @d = split(' ', $file->[2]) ; # TODO : cache this
3120 return from_base64($d[$attrib_name_id{$attrib}]);
3122 } elsif ($attrib eq 'jobid') {
3126 } elsif ($attrib eq 'name') {
3131 die "Attribute not known : $attrib.\n";
3135 # Return the jobid or attribute asked for a dir
3138 my ($self,$dir,$attrib)=@_;
3140 my @dir = split('/',$dir,-1);
3141 my $refdir=$self->{dirtree}->{$self->current_client};
3143 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3145 die "Attribute not known : $attrib.\n";
3148 foreach my $subdir (@dir)
3150 $refdir = $refdir->[0]->{$subdir};
3153 # $refdir is now the reference to the dir's array
3154 # Is the a jobid in @CurrentJobIds where the lstat is
3155 # defined (we'll search in reverse order)
3156 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3158 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3160 if ($attrib eq 'jobid')
3166 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3167 return $attribs[$attrib_name_id{$attrib}+1];
3172 return 0; # We cannot get a good attribute.
3173 # This directory is here for the sake of visibility
3178 my ($lstat,$attrib)=@_;
3179 if ($lstat and defined $attrib_name_id{$attrib})
3181 my @d = split(' ', $lstat) ; # TODO : cache this
3182 return from_base64($d[$attrib_name_id{$attrib}]);
3189 # Base 64 functions, directly from recover.pl.
3191 # Karl Hakimian <hakimian@aha.com>
3192 # This section is also under GPL v2 or later.
3199 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3200 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3201 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3202 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3203 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3205 @base64_map = (0) x 128;
3207 for (my $i=0; $i<64; $i++) {
3208 $base64_map[ord($base64_digits[$i])] = $i;
3223 if (substr($where, 0, 1) eq '-') {
3225 $where = substr($where, 1);
3228 while ($where ne '') {
3230 my $d = substr($where, 0, 1);
3231 $val += $base64_map[ord(substr($where, 0, 1))];
3232 $where = substr($where, 1);
3240 my @attribs = split(' ',$lstat);
3241 foreach my $element (@attribs)
3243 $element = from_base64($element);
3252 ################################################################
3255 use base qw/DlgResto/;
3259 my ($class, $conf) = @_;
3260 my $self = bless {info => $conf}, $class;
3262 $self->{dbh} = $conf->{dbh};
3271 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3272 my $jobs = $self->dbh_selectall_arrayref($query);
3274 $self->update_brestore_table(map { $_->[0] } @$jobs);
3285 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3289 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3292 GetOptions("conf=s" => \$file_conf,
3293 "batch" => \$batch_mod,
3295 "help" => \&HELP_MESSAGE) ;
3297 my $p = new Pref($file_conf);
3299 if (! -f $file_conf) {
3304 my $b = new Batch($p);
3305 if ($p->connect_db()) {
3306 $b->set_dbh($p->{dbh});
3312 $glade_file = $p->{glade_file};
3314 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3315 if (-f "$path/$glade_file") {
3316 $glade_file = "$path/$glade_file" ;
3321 # gtk have lots of warning on stderr
3322 if ($^O eq 'MSWin32')
3325 open(STDERR, ">stderr.log");
3330 if ( -f $glade_file) {
3331 my $w = new DlgResto($p);
3334 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3335 "Can't find your brestore.glade (glade_file => '$glade_file')
3336 Please, edit your $file_conf to setup it." );
3338 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3343 Gtk2->main; # Start Gtk2 main loop
3355 # Code pour trier les colonnes
3356 my $mod = $fileview->get_model();
3357 $mod->set_default_sort_func(sub {
3358 my ($model, $item1, $item2) = @_;
3359 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3360 my $b = $model->get($item2, 1); # colonne (indice 1)
3365 $fileview->set_headers_clickable(1);
3366 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3367 $col->signal_connect('clicked', sub {
3368 my ($colonne, $model) = @_;
3369 $model->set_sort_column_id (1, 'ascending');