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 unless (scalar(@{$self->{restore_list}->{data}})) {
1281 new DlgWarn("No file to restore");
1284 my $bsr = $self->create_filelist();
1285 my ($fh, $filename) = tempfile();
1288 chmod(0644, $filename);
1290 print "Dumping BSR info to $filename\n"
1293 # we get Volume list
1294 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1295 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1297 new DlgLaunch(pref => $self->{pref},
1299 bsr_file => $filename,
1304 our $client_list_empty = 'Clients list';
1305 our %type_markup = ('F' => '<b>$label F</b>',
1308 'B' => '<b>$label B</b>',
1310 'A' => '<span foreground=\"red\">$label</span>',
1312 'E' => '<span foreground=\"red\">$label</span>',
1315 sub on_list_client_changed
1317 my ($self, $widget) = @_;
1318 return 0 unless defined $self->{fileview};
1319 my $dbh = $self->{dbh};
1321 $self->{list_backup}->clear();
1323 if ($self->current_client eq $client_list_empty) {
1327 my @endtimes=get_all_endtimes_for_job($dbh,
1328 $self->current_client,
1329 $self->{pref}->{use_ok_bkp_only});
1330 foreach my $endtime (@endtimes)
1332 my $i = $self->{list_backup}->append();
1334 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1335 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1336 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1338 $self->{list_backup}->set($i,
1343 $self->{restore_backup_combobox}->set_active(0);
1345 $self->{CurrentJobIds} = [
1346 set_job_ids_for_date($dbh,
1347 $self->current_client,
1348 $self->current_date,
1349 $self->{pref}->{use_ok_bkp_only})
1352 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1355 $self->refresh_fileview();
1359 sub fill_server_list
1361 my ($dbh, $combo, $list) = @_;
1363 my @clients=get_all_clients($dbh);
1367 my $i = $list->append();
1368 $list->set($i, 0, $client_list_empty);
1370 foreach my $client (@clients)
1372 $i = $list->append();
1373 $list->set($i, 0, $client);
1375 $combo->set_active(0);
1378 sub init_server_backup_combobox
1381 fill_server_list($self->{dbh},
1382 $self->{client_combobox},
1383 $self->{list_client}) ;
1386 #----------------------------------------------------------------------
1387 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1388 #data isn't. There is additionnal complexity for dirs (visibility problems),
1389 #so the @CurrentJobIds is not sufficient.
1390 sub refresh_fileview
1393 my $fileview = $self->{fileview};
1394 my $client_combobox = $self->{client_combobox};
1395 my $cwd = $self->{cwd};
1397 @{$fileview->{data}} = ();
1399 $self->clear_infoview();
1401 my $client_name = $self->current_client;
1403 if (!$client_name or ($client_name eq $client_list_empty)) {
1404 $self->set_status("Client list empty");
1408 my @list_dirs = $self->list_dirs($cwd,$client_name);
1409 # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1410 my $files = $self->list_files($cwd);
1411 print "CWD : $cwd\n" if ($debug);
1413 my $file_count = 0 ;
1414 my $total_bytes = 0;
1416 # Add directories to view
1417 foreach my $dir_entry (@list_dirs) {
1418 #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
1419 my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime'));
1420 my $dir = $dir_entry->[0];
1421 $total_bytes += 4096;
1424 listview_push($fileview,
1426 $self->dir_attrib("$cwd/$dir",'jobid'),
1436 foreach my $file (@$files)
1438 my $size = file_attrib($file,'st_size');
1439 my $time = localtime(file_attrib($file,'st_mtime'));
1440 $total_bytes += $size;
1442 # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
1444 listview_push($fileview,
1451 human($size), $time);
1454 $self->set_status("$file_count files/" . human($total_bytes));
1456 # set a decent default selection (makes keyboard nav easy)
1457 $fileview->select(0);
1461 sub on_about_activate
1463 DlgAbout::display();
1468 my ($tree, $path, $data) = @_;
1470 my @items = listview_get_all($tree) ;
1472 foreach my $i (@items)
1474 my @file_info = @{$i};
1477 # Ok, we have a corner case :
1482 $file = pack("u", $file_info[0]);
1486 $file = pack("u", $path . '/' . $file_info[0]);
1488 push @ret, join(" ; ", $file,
1489 $file_info[1], # $jobid
1490 $file_info[2], # $type
1494 my $data_get = join(" :: ", @ret);
1496 $data->set_text($data_get,-1);
1499 sub fileview_data_get
1501 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1502 drag_set_info($widget, $self->{cwd}, $data);
1505 sub fileinfo_data_get
1507 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1508 drag_set_info($widget, $self->{cwd}, $data);
1511 sub restore_list_data_received
1513 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1516 if ($info eq 40 || $info eq 0) # patch for display!=:0
1518 foreach my $elt (split(/ :: /, $data->data()))
1521 my ($file, $jobid, $type) =
1523 $file = unpack("u", $file);
1525 $self->add_selected_file_to_list($file, $jobid, $type);
1530 sub on_back_button_clicked {
1534 sub on_location_go_button_clicked
1537 $self->ch_dir($self->{location}->get_text());
1539 sub on_quit_activate {Gtk2->main_quit;}
1540 sub on_preferences_activate
1543 $self->{dlg_pref}->display($self) ;
1545 sub on_main_delete_event {Gtk2->main_quit;}
1546 sub on_bweb_activate
1549 $self->set_status("Open bweb on your browser");
1550 $self->{pref}->go_bweb('', "go on bweb");
1553 # Change to parent directory
1557 if ($self->{cwd} eq '/')
1561 my @dirs = split(/\//, $self->{cwd});
1563 $self->ch_dir(join('/', @dirs));
1566 # Change the current working directory
1567 # * Updates fileview, location, and selection
1572 $self->{cwd} = shift;
1574 $self->refresh_fileview();
1575 $self->{location}->set_text($self->{cwd});
1580 # Handle dialog 'close' (window-decoration induced close)
1581 # * Just hide the dialog, and tell Gtk not to do anything else
1585 my ($self, $w) = @_;
1588 1; # consume this event!
1591 # Handle key presses in location text edit control
1592 # * Translate a Return/Enter key into a 'Go' command
1593 # * All other key presses left for GTK
1595 sub on_location_entry_key_release_event
1601 my $keypress = $event->keyval;
1602 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1603 $keypress == $Gtk2::Gdk::Keysyms{Return})
1605 $self->ch_dir($widget->get_text());
1607 return 1; # consume keypress
1610 return 0; # let gtk have the keypress
1613 sub on_fileview_key_press_event
1615 my ($self, $widget, $event) = @_;
1619 sub listview_get_first
1622 my @selected = $list->get_selected_indices();
1623 if (@selected > 0) {
1624 my ($name, @other) = @{$list->{data}->[$selected[0]]};
1625 return (unpack('u', $name), @other);
1631 sub listview_get_all
1635 my @selected = $list->get_selected_indices();
1637 for my $i (@selected) {
1638 my ($name, @other) = @{$list->{data}->[$i]};
1639 push @ret, [unpack('u', $name), @other];
1647 my ($list, $name, @other) = @_;
1648 push @{$list->{data}}, [pack('u', $name), @other];
1651 #----------------------------------------------------------------------
1652 # Handle keypress in file-view
1653 # * Translates backspace into a 'cd ..' command
1654 # * All other key presses left for GTK
1656 sub on_fileview_key_release_event
1658 my ($self, $widget, $event) = @_;
1659 if (not $event->keyval)
1663 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1665 return 1; # eat keypress
1668 return 0; # let gtk have keypress
1671 sub on_forward_keypress
1676 #----------------------------------------------------------------------
1677 # Handle double-click (or enter) on file-view
1678 # * Translates into a 'cd <dir>' command
1680 sub on_fileview_row_activated
1682 my ($self, $widget) = @_;
1684 my ($name, undef, $type, undef) = listview_get_first($widget);
1688 if ($self->{cwd} eq '')
1690 $self->ch_dir($name);
1692 elsif ($self->{cwd} eq '/')
1694 $self->ch_dir('/' . $name);
1698 $self->ch_dir($self->{cwd} . '/' . $name);
1702 $self->fill_infoview($self->{cwd}, $name);
1705 return 1; # consume event
1710 my ($self, $path, $file) = @_;
1711 $self->clear_infoview();
1712 my @v = get_all_file_versions($self->{dbh},
1715 $self->current_client,
1716 $self->{pref}->{see_all_versions});
1718 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
1720 my $icon = ($inchanger)?$yesicon:$noicon;
1722 $mtime = localtime($mtime) ;
1724 listview_push($self->{fileinfo},
1725 $file, $jobid, 'file',
1726 $icon, $volname, $jobid, human($size), $mtime, $md5);
1733 return $self->{restore_backup_combobox}->get_active_text;
1739 return $self->{client_combobox}->get_active_text;
1742 sub on_list_backups_changed
1744 my ($self, $widget) = @_;
1745 return 0 unless defined $self->{fileview};
1747 $self->{CurrentJobIds} = [
1748 set_job_ids_for_date($self->{dbh},
1749 $self->current_client,
1750 $self->current_date,
1751 $self->{pref}->{use_ok_bkp_only})
1753 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1755 $self->refresh_fileview();
1759 sub on_restore_list_keypress
1761 my ($self, $widget, $event) = @_;
1762 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1764 my @sel = $widget->get_selected_indices;
1765 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1767 splice @{$self->{restore_list}->{data}},$elt,1;
1772 sub on_fileview_button_press_event
1774 my ($self,$widget,$event) = @_;
1775 if ($event->button == 3)
1777 $self->on_right_click_filelist($widget,$event);
1781 if ($event->button == 2)
1783 $self->on_see_all_version();
1790 sub on_see_all_version
1794 my @lst = listview_get_all($self->{fileview});
1797 my ($name, undef) = @{$i};
1799 new DlgFileVersion($self->{dbh},
1800 $self->current_client,
1801 $self->{cwd}, $name);
1805 sub on_right_click_filelist
1807 my ($self,$widget,$event) = @_;
1808 # I need to know what's selected
1809 my @sel = listview_get_all($self->{fileview});
1814 $type = $sel[0]->[2]; # $type
1819 if (@sel >=2 or $type eq 'dir')
1821 # We have selected more than one or it is a directories
1822 $w = $self->{filelist_dir_menu};
1826 $w = $self->{filelist_file_menu};
1832 $event->button, $event->time);
1835 sub context_add_to_filelist
1839 my @sel = listview_get_all($self->{fileview});
1841 foreach my $i (@sel)
1843 my ($file, $jobid, $type, undef) = @{$i};
1844 $file = $self->{cwd} . '/' . $file;
1845 $self->add_selected_file_to_list($file, $jobid, $type);
1849 # Adds a file to the filelist
1850 sub add_selected_file_to_list
1852 my ($self, $name, $jobid, $type)=@_;
1854 my $dbh = $self->{dbh};
1855 my $restore_list = $self->{restore_list};
1857 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1864 if ($name and substr $name,-1 ne '/')
1866 $name .= '/'; # For bacula
1868 my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
1869 listview_push($restore_list,
1870 $name, $jobid, 'dir', $curjobids,
1871 $diricon, $name,$curjobids,$dirfileindex);
1873 elsif ($type eq 'file')
1875 my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
1877 listview_push($restore_list,
1878 $name, $jobid, 'file', $curjobids,
1879 $fileicon, $name, $jobid, $fileindex );
1883 # TODO : we want be able to restore files from a bad ended backup
1884 # we have JobStatus IN ('T', 'A', 'E') and we must
1886 # Data acces subs from here. Interaction with SGBD and caching
1888 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1889 # GUI and stores them in @CurrentJobIds
1890 sub set_job_ids_for_date
1892 my ($dbh, $client, $date, $only_ok)=@_;
1894 if (!$client or !$date) {
1898 my $status = get_wanted_job_status($only_ok);
1900 # The algorithm : for a client, we get all the backups for each
1901 # fileset, in reverse order Then, for each fileset, we store the 'good'
1902 # incrementals and differentials until we have found a full so it goes
1903 # like this : store all incrementals until we have found a differential
1904 # or a full, then find the full #
1906 my $query = "SELECT JobId, FileSet, Level, JobStatus
1907 FROM Job, Client, FileSet
1908 WHERE Job.ClientId = Client.ClientId
1909 AND FileSet.FileSetId = Job.FileSetId
1910 AND EndTime <= '$date'
1911 AND Client.Name = '$client'
1913 AND JobStatus IN ($status)
1914 ORDER BY FileSet, JobTDate DESC";
1916 print STDERR $query,"\n" if $debug;
1918 my $result = $dbh->selectall_arrayref($query);
1920 foreach my $refrow (@$result)
1922 my $jobid = $refrow->[0];
1923 my $fileset = $refrow->[1];
1924 my $level = $refrow->[2];
1926 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1928 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1932 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1933 push @CurrentJobIds,($jobid);
1935 elsif ($level eq 'D')
1937 next if $progress{$fileset} eq 'D'; # We allready have a differential
1938 push @CurrentJobIds,($jobid);
1940 elsif ($level eq 'F')
1942 push @CurrentJobIds,($jobid);
1945 my $status = $refrow->[3] ;
1946 if ($status eq 'T') { # good end of job
1947 $progress{$fileset} = $level;
1950 print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
1952 return @CurrentJobIds;
1955 # Lists all directories contained inside a directory.
1956 # Uses the current dir, the client name, and CurrentJobIds for visibility.
1957 # Returns an array of dirs
1960 my ($self,$dir,$client)=@_;
1962 print "list_dirs(<$dir>, <$client>)\n" if $debug;
1964 if ($dir ne '' and substr $dir,-1 ne '/')
1966 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1969 my $dbh = $self->{dbh};
1970 my $query = "SELECT PathId FROM Path WHERE Path = ?
1971 UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?";
1972 my $sth = $dbh->prepare($query);
1973 $sth->execute($dir,$dir);
1974 my $result = $sth->fetchrow_arrayref();
1976 my $pathid = $result->[0];
1977 my @jobids = @{$self->{CurrentJobIds}};
1978 my $jobclause = join (',', @jobids);
1979 # Let's retrieve the list of the visible dirs in this dir ...
1980 # First, I need the empty filenameid to locate efficiently the dirs in the file table
1981 $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
1982 $sth = $dbh->prepare($query);
1984 $result = $sth->fetchrow_arrayref();
1986 my $dir_filenameid = $result->[0];
1988 # Then we get all the dir entries from File ...
1989 # It's ugly because there are records in brestore_missing_path ...
1991 SELECT Path, JobId, Lstat FROM(
1993 SELECT Path.Path, lower(Path.Path),
1994 listfile.JobId, listfile.Lstat
1996 SELECT DISTINCT brestore_pathhierarchy.PathId
1997 FROM brestore_pathhierarchy
1999 ON (brestore_pathhierarchy.PathId = Path.PathId)
2000 JOIN brestore_pathvisibility
2001 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2002 WHERE brestore_pathhierarchy.PPathId = $pathid
2003 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2004 JOIN Path ON (listpath.PathId = Path.PathId)
2006 SELECT File.PathId, File.JobId, File.Lstat FROM File
2007 WHERE File.FilenameId = $dir_filenameid
2008 AND File.JobId IN ($jobclause)) AS listfile
2009 ON (listpath.PathId = listfile.PathId)
2011 SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path),
2012 listfile.JobId, listfile.Lstat
2014 SELECT DISTINCT brestore_pathhierarchy.PathId
2015 FROM brestore_pathhierarchy
2016 JOIN brestore_missing_path
2017 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2018 JOIN brestore_pathvisibility
2019 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2020 WHERE brestore_pathhierarchy.PPathId = $pathid
2021 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2022 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2024 SELECT File.PathId, File.JobId, File.Lstat FROM File
2025 WHERE File.FilenameId = $dir_filenameid
2026 AND File.JobId IN ($jobclause)) AS listfile
2027 ON (listpath.PathId = listfile.PathId))
2028 ORDER BY 2,3 DESC ) As a";
2029 print STDERR "$query\n" if $debug;
2030 $sth=$dbh->prepare($query);
2032 $result = $sth->fetchall_arrayref();
2035 foreach my $refrow (@{$result})
2037 my $dir = $refrow->[0];
2038 my $jobid = $refrow->[1];
2039 my $lstat = $refrow->[2];
2040 next if ($dir eq $prev_dir);
2041 # We have to clean up this dirname ... we only want it's 'basename'
2045 my @temp = split ('/',$dir);
2046 $return_value = pop @temp;
2050 $return_value = '/';
2052 my @return_array = ($return_value,$lstat);
2053 push @return_list,(\@return_array);
2056 return @return_list;
2060 # List all files in a directory. dir as parameter, CurrentJobIds for visibility
2061 # Returns an array of dirs
2064 my ($self, $dir)=@_;
2065 my $dbh = $self->{dbh};
2069 print "list_files($dir)\n" if $debug;
2071 if ($dir ne '' and substr $dir,-1 ne '/')
2073 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
2076 my $query = "SELECT Path.PathId
2078 WHERE Path.Path = '$dir'
2080 SELECT brestore_missing_path.PathId
2081 FROM brestore_missing_path
2082 WHERE brestore_missing_path.Path = '$dir'";
2083 print $query,"\n" if $debug;
2085 my $result = $dbh->selectall_arrayref($query);
2086 foreach my $refrow (@$result)
2088 push @list_pathid,($refrow->[0]);
2091 if (@list_pathid == 0)
2093 print "No pathid found for $dir\n" if $debug;
2097 my $inlistpath = join (',', @list_pathid);
2098 my $inclause = join (',', @{$self->{CurrentJobIds}});
2099 if ($inclause eq '')
2105 "SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
2107 (SELECT Filename.Name, max(File.FileId) as id
2109 WHERE File.FilenameId = Filename.FilenameId
2110 AND Filename.Name != ''
2111 AND File.PathId IN ($inlistpath)
2112 AND File.JobId IN ($inclause)
2113 GROUP BY Filename.Name
2114 ORDER BY Filename.Name) AS listfiles,
2116 WHERE File.FileId = listfiles.id";
2118 print STDERR $query,"\n" if $debug;
2119 $result = $dbh->selectall_arrayref($query);
2126 Gtk2->main_iteration while (Gtk2->events_pending);
2129 sub create_brestore_tables
2133 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2135 unless ($self->dbh_do($verif)) {
2136 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2138 $self->{error} = "Creating internal brestore tables";
2140 CREATE TABLE brestore_knownjobid
2142 JobId int4 NOT NULL,
2143 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2145 $self->dbh_do($req);
2148 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2149 unless ($self->dbh_do($verif)) {
2151 CREATE TABLE brestore_pathhierarchy
2153 PathId int4 NOT NULL,
2154 PPathId int4 NOT NULL,
2155 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2157 $self->dbh_do($req);
2160 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2161 ON brestore_pathhierarchy (PPathId)";
2162 $self->dbh_do($req);
2165 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2166 unless ($self->dbh_do($verif)) {
2168 CREATE TABLE brestore_pathvisibility
2170 PathId int4 NOT NULL,
2171 JobId int4 NOT NULL,
2172 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2174 $self->dbh_do($req);
2176 $req = "CREATE INDEX brestore_pathvisibility_jobid
2177 ON brestore_pathvisibility (JobId)";
2178 $self->dbh_do($req);
2181 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2182 unless ($self->dbh_do($verif)) {
2184 CREATE TABLE brestore_missing_path
2186 PathId int4 NOT NULL,
2188 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2190 $self->dbh_do($req);
2192 $req = "CREATE INDEX brestore_missing_path_path
2193 ON brestore_missing_path (Path)";
2194 $self->dbh_do($req);
2198 # Recursive function to calculate the visibility of each directory in the cache
2199 # tree Working with references to save time and memory
2200 # For each directory, we want to propagate it's visible jobids onto it's
2201 # parents directory.
2202 # A tree is visible if
2203 # - it's been in a backup pointed by the CurrentJobIds
2204 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2205 # In the second case, the directory is visible but has no metadata.
2206 # We symbolize this with lstat = 1 for this jobid in the cache.
2208 # Input : reference directory
2209 # Output : visibility of this dir. Has to know visibility of all subdirs
2210 # to know it's visibility, hence the recursing.
2216 # Get the subdirs array references list
2217 my @list_ref_subdirs;
2218 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2220 push @list_ref_subdirs,($ref_subdir);
2223 # Now lets recurse over these subdirs and retrieve the reference of a hash
2224 # containing the jobs where they are visible
2225 foreach my $ref_subdir (@list_ref_subdirs)
2227 my $ref_list_jobs = list_visible($ref_subdir);
2228 foreach my $jobid (keys %$ref_list_jobs)
2230 $visibility{$jobid}=1;
2234 # Ok. Now, we've got the list of those jobs. We are going to update our
2235 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2236 # the lstat for the known jobids. Put 1 in the new elements... But first,
2237 # let's store the current jobids
2239 foreach my $jobid (keys %{$refdir->[1]})
2241 push @known_jobids,($jobid);
2245 foreach my $jobid (keys %visibility)
2247 next if ($refdir->[1]->{$jobid});
2248 $refdir->[1]->{$jobid} = 1;
2250 # Add the known_jobids to %visibility
2251 foreach my $jobid (@known_jobids)
2253 $visibility{$jobid}=1;
2255 return \%visibility;
2258 # Returns the list of media required for a list of jobids.
2259 # Input : dbh, jobid1, jobid2...
2260 # Output : reference to array of (joibd, inchanger)
2261 sub get_required_media_from_jobid
2263 my ($dbh, @jobids)=@_;
2264 my $inclause = join(',',@jobids);
2266 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2267 FROM JobMedia, Media
2268 WHERE JobMedia.MediaId=Media.MediaId
2269 AND JobId In ($inclause)
2271 my $result = $dbh->selectall_arrayref($query);
2275 # Returns the fileindex from dirname and jobid.
2276 # Input : dbh, dirname, jobid
2277 # Output : fileindex
2278 sub get_fileindex_from_dir_jobid
2280 my ($dbh, $dirname, $jobid)=@_;
2282 $query = "SELECT File.FileIndex
2283 FROM File, Filename, Path
2284 WHERE File.FilenameId = Filename.FilenameId
2285 AND File.PathId = Path.PathId
2286 AND Filename.Name = ''
2287 AND Path.Path = '$dirname'
2288 AND File.JobId = '$jobid'
2291 print STDERR $query,"\n" if $debug;
2292 my $result = $dbh->selectall_arrayref($query);
2293 return $result->[0]->[0];
2296 # Returns the fileindex from filename and jobid.
2297 # Input : dbh, filename, jobid
2298 # Output : fileindex
2299 sub get_fileindex_from_file_jobid
2301 my ($dbh, $filename, $jobid)=@_;
2303 my @dirs = split(/\//, $filename);
2304 $filename=pop(@dirs);
2305 my $dirname = join('/', @dirs) . '/';
2310 "SELECT File.FileIndex
2311 FROM File, Filename, Path
2312 WHERE File.FilenameId = Filename.FilenameId
2313 AND File.PathId = Path.PathId
2314 AND Filename.Name = '$filename'
2315 AND Path.Path = '$dirname'
2316 AND File.JobId = '$jobid'";
2318 print STDERR $query,"\n" if $debug;
2319 my $result = $dbh->selectall_arrayref($query);
2320 return $result->[0]->[0];
2324 # Returns list of versions of a file that could be restored
2325 # returns an array of
2326 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2327 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2328 # and volname and md5
2329 # and of course, there will be only one jobid in the array of jobids...
2330 sub get_all_file_versions
2332 my ($dbh,$path,$file,$client,$see_all)=@_;
2334 defined $see_all or $see_all=0;
2339 "SELECT File.JobId, File.FileIndex, File.Lstat,
2340 File.Md5, Media.VolumeName, Media.InChanger
2341 FROM File, Filename, Path, Job, Client, JobMedia, Media
2342 WHERE File.FilenameId = Filename.FilenameId
2343 AND File.PathId=Path.PathId
2344 AND File.JobId = Job.JobId
2345 AND Job.ClientId = Client.ClientId
2346 AND Job.JobId = JobMedia.JobId
2347 AND File.FileIndex >= JobMedia.FirstIndex
2348 AND File.FileIndex <= JobMedia.LastIndex
2349 AND JobMedia.MediaId = Media.MediaId
2350 AND Path.Path = '$path'
2351 AND Filename.Name = '$file'
2352 AND Client.Name = '$client'";
2354 print STDERR $query if $debug;
2356 my $result = $dbh->selectall_arrayref($query);
2358 foreach my $refrow (@$result)
2360 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2361 my @attribs = parse_lstat($lstat);
2362 my $mtime = array_attrib('st_mtime',\@attribs);
2363 my $size = array_attrib('st_size',\@attribs);
2365 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2366 $inchanger, $md5, $volname);
2367 push @versions, (\@list);
2370 # We have the list of all versions of this file.
2371 # We'll sort it by mtime desc, size, md5, inchanger desc
2372 # the rest of the algorithm will be simpler
2373 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2374 @versions = sort { $b->[4] <=> $a->[4]
2375 || $a->[5] <=> $b->[5]
2376 || $a->[7] cmp $a->[7]
2377 || $b->[6] <=> $a->[6]} @versions;
2380 my %allready_seen_by_mtime;
2381 my %allready_seen_by_md5;
2382 # Now we should create a new array with only the interesting records
2383 foreach my $ref (@versions)
2387 # The file has a md5. We compare his md5 to other known md5...
2388 # We take size into account. It may happen that 2 files
2389 # have the same md5sum and are different. size is a supplementary
2392 # If we allready have a (better) version
2393 next if ( (not $see_all)
2394 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2396 # we never met this one before...
2397 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2399 # Even if it has a md5, we should also work with mtimes
2400 # We allready have a (better) version
2401 next if ( (not $see_all)
2402 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2403 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2405 # We reached there. The file hasn't been seen.
2406 push @good_versions,($ref);
2409 # To be nice with the user, we re-sort good_versions by
2410 # inchanger desc, mtime desc
2411 @good_versions = sort { $b->[4] <=> $a->[4]
2412 || $b->[2] <=> $a->[2]} @good_versions;
2414 return @good_versions;
2417 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2418 # This sub creates a BSR from the information in the restore_list
2419 # Returns the BSR as a string
2424 # This query gets all jobid/jobmedia/media combination.
2426 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2427 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2428 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2429 Media.Volumename, Media.MediaType
2430 FROM Job, JobMedia, Media
2431 WHERE Job.JobId = JobMedia.JobId
2432 AND JobMedia.MediaId = Media.MediaId
2433 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2436 my $result = $self->dbh_selectall_arrayref($query);
2438 # We will store everything hashed by jobid.
2440 foreach my $refrow (@$result)
2442 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2443 $firstindex, $lastindex, $startblock, $endblock,
2444 $volindex, $volumename, $mediatype) = @{$refrow};
2446 # We just have to deal with the case where starfile != endfile
2447 # In this case, we concatenate both, for the bsr
2448 if ($startfile != $endfile) {
2449 $startfile = $startfile . '-' . $endfile;
2453 ($jobid, $volsessionid, $volsessiontime, $startfile,
2454 $firstindex, $lastindex, $startblock .'-'. $endblock,
2455 $volindex, $volumename, $mediatype);
2457 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2461 # reminder : restore_list looks like this :
2462 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2464 # Here, we retrieve every file/dir that could be in the restore
2465 # We do as simple as possible for the SQL engine (no crazy joins,
2466 # no pseudo join (>= FirstIndex ...), etc ...
2467 # We do a SQL union of all the files/dirs specified in the restore_list
2469 foreach my $entry (@{$self->{restore_list}->{data}})
2471 if ($entry->[2] eq 'dir')
2473 my $dir = unpack('u', $entry->[0]);
2474 my $inclause = $entry->[3]; #curjobids
2477 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2478 FROM File, Path, Filename
2479 WHERE Path.PathId = File.PathId
2480 AND File.FilenameId = Filename.FilenameId
2481 AND Path.Path LIKE '$dir%'
2482 AND File.JobId IN ($inclause) )";
2483 push @select_queries,($query);
2487 # It's a file. Great, we allready have most
2488 # of what is needed. Simple and efficient query
2489 my $file = unpack('u', $entry->[0]);
2490 my @file = split '/',$file;
2492 my $dir = join('/',@file);
2494 my $jobid = $entry->[1];
2495 my $fileindex = $entry->[7];
2496 my $inclause = $entry->[3]; # curjobids
2498 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2499 FROM File, Path, Filename
2500 WHERE Path.PathId = File.PathId
2501 AND File.FilenameId = Filename.FilenameId
2502 AND Path.Path = '$dir/'
2503 AND Filename.Name = '$file'
2504 AND File.JobId = $jobid)";
2505 push @select_queries,($query);
2508 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2510 print STDERR $query,"\n" if $debug;
2512 #Now we run the query and parse the result...
2513 # there may be a lot of records, so we better be efficient
2514 # We use the bind column method, working with references...
2516 my $sth = $self->dbh_prepare($query);
2519 my ($path,$name,$fileindex,$jobid);
2520 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2522 # The temp place we're going to save all file
2523 # list to before the real list
2527 while ($sth->fetchrow_arrayref())
2529 # This may look dumb, but we're going to do a join by ourselves,
2530 # to save memory and avoid sending a complex query to mysql
2531 my $complete_path = $path . $name;
2539 # Remove trailing slash (normalize file and dir name)
2540 $complete_path =~ s/\/$//;
2542 # Let's find the ref(s) for the %mediainfo element(s)
2543 # containing the data for this file
2544 # There can be several matches. It is the pseudo join.
2546 my $max_elt=@{$mediainfos{$jobid}}-1;
2548 while($med_idx <= $max_elt)
2550 my $ref = $mediainfos{$jobid}->[$med_idx];
2551 # First, can we get rid of the first elements of the
2552 # array ? (if they don't contain valuable records
2554 if ($fileindex > $ref->[5])
2556 # It seems we don't need anymore
2557 # this entry in %mediainfo (the input data
2560 shift @{$mediainfos{$jobid}};
2564 # We will do work on this elt. We can ++
2565 # $med_idx for next loop
2568 # %mediainfo row looks like :
2569 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2570 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2573 # We are in range. We store and continue looping
2575 if ($fileindex >= $ref->[4])
2577 my @data = ($complete_path,$is_dir,
2579 push @temp_list,(\@data);
2583 # We are not in range. No point in continuing looping
2584 # We go to next record.
2588 # Now we have the array.
2589 # We're going to sort it, by
2590 # path, volsessiontime DESC (get the most recent file...)
2591 # The array rows look like this :
2592 # complete_path,is_dir,fileindex,
2593 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2594 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2595 @temp_list = sort {$a->[0] cmp $b->[0]
2596 || $b->[3]->[2] <=> $a->[3]->[2]
2600 my $prev_complete_path='////'; # Sure not to match
2604 while (my $refrow = shift @temp_list)
2606 # For the sake of readability, we load $refrow
2607 # contents in real scalars
2608 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2609 my $jobid= $refother->[0]; # We don't need the rest...
2611 # We skip this entry.
2612 # We allready have a newer one and this
2613 # isn't a continuation of the same file
2614 next if ($complete_path eq $prev_complete_path
2615 and $jobid != $prev_jobid);
2619 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2621 # We would be recursing inside a file.
2622 # Just what we don't want (dir replaced by file
2623 # between two backups
2629 push @restore_list,($refrow);
2631 $prev_complete_path = $complete_path;
2632 $prev_jobid = $jobid;
2638 push @restore_list,($refrow);
2640 $prev_complete_path = $complete_path;
2641 $prev_jobid = $jobid;
2645 # We get rid of @temp_list... save memory
2648 # Ok everything is in the list. Let's sort it again in another way.
2649 # This time it will be in the bsr file order
2651 # we sort the results by
2652 # volsessiontime, volsessionid, volindex, fileindex
2653 # to get all files in right order...
2654 # Reminder : The array rows look like this :
2655 # complete_path,is_dir,fileindex,
2656 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2657 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2659 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2660 || $a->[3]->[1] <=> $b->[3]->[1]
2661 || $a->[3]->[7] <=> $b->[3]->[7]
2662 || $a->[2] <=> $b->[2] }
2665 # Now that everything is ready, we create the bsr
2666 my $prev_fileindex=-1;
2667 my $prev_volsessionid=-1;
2668 my $prev_volsessiontime=-1;
2669 my $prev_volumename=-1;
2670 my $prev_volfile=-1;
2674 my $first_of_current_range=0;
2675 my @fileindex_ranges;
2678 foreach my $refrow (@restore_list)
2680 my (undef,undef,$fileindex,$refother)=@{$refrow};
2681 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2682 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2684 # We can specifiy the number of files in each section of the
2685 # bsr to speedup restore (bacula can then jump over the
2686 # end of tape files.
2690 if ($prev_volumename eq '-1')
2692 # We only have to start the new range...
2693 $first_of_current_range=$fileindex;
2695 elsif ($prev_volsessionid != $volsessionid
2696 or $prev_volsessiontime != $volsessiontime
2697 or $prev_volumename ne $volumename
2698 or $prev_volfile ne $volfile)
2700 # We have to create a new section in the bsr...
2701 # We print the previous one ...
2702 # (before that, save the current range ...)
2703 if ($first_of_current_range != $prev_fileindex)
2706 push @fileindex_ranges,
2707 ("$first_of_current_range-$prev_fileindex");
2711 # We are out of a range,
2712 # but there is only one element in the range
2713 push @fileindex_ranges,
2714 ("$first_of_current_range");
2717 $bsr.=print_bsr_section(\@fileindex_ranges,
2719 $prev_volsessiontime,
2726 # Reset for next loop
2727 @fileindex_ranges=();
2728 $first_of_current_range=$fileindex;
2730 elsif ($fileindex-1 != $prev_fileindex)
2732 # End of a range of fileindexes
2733 if ($first_of_current_range != $prev_fileindex)
2736 push @fileindex_ranges,
2737 ("$first_of_current_range-$prev_fileindex");
2741 # We are out of a range,
2742 # but there is only one element in the range
2743 push @fileindex_ranges,
2744 ("$first_of_current_range");
2746 $first_of_current_range=$fileindex;
2748 $prev_fileindex=$fileindex;
2749 $prev_volsessionid = $volsessionid;
2750 $prev_volsessiontime = $volsessiontime;
2751 $prev_volumename = $volumename;
2752 $prev_volfile=$volfile;
2753 $prev_mediatype=$mediatype;
2754 $prev_volblocks=$volblocks;
2758 # Ok, we're out of the loop. Alas, there's still the last record ...
2759 if ($first_of_current_range != $prev_fileindex)
2762 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2767 # We are out of a range,
2768 # but there is only one element in the range
2769 push @fileindex_ranges,("$first_of_current_range");
2772 $bsr.=print_bsr_section(\@fileindex_ranges,
2774 $prev_volsessiontime,
2784 sub print_bsr_section
2786 my ($ref_fileindex_ranges,$volsessionid,
2787 $volsessiontime,$volumename,$volfile,
2788 $mediatype,$volblocks,$count)=@_;
2791 $bsr .= "Volume=\"$volumename\"\n";
2792 $bsr .= "MediaType=\"$mediatype\"\n";
2793 $bsr .= "VolSessionId=$volsessionid\n";
2794 $bsr .= "VolSessionTime=$volsessiontime\n";
2795 $bsr .= "VolFile=$volfile\n";
2796 $bsr .= "VolBlock=$volblocks\n";
2798 foreach my $range (@{$ref_fileindex_ranges})
2800 $bsr .= "FileIndex=$range\n";
2803 $bsr .= "Count=$count\n";
2807 # This function estimates the size to be restored for an entry of the restore
2809 # In : self,reference to the entry
2810 # Out : size in bytes, number of files
2811 sub estimate_restore_size
2813 # reminder : restore_list looks like this :
2814 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2818 if ($entry->[2] eq 'dir')
2820 my $dir = unpack('u', $entry->[0]);
2821 my $inclause = $entry->[3]; #curjobids
2823 "SELECT Path.Path, File.FilenameId, File.LStat
2824 FROM File, Path, Job
2825 WHERE Path.PathId = File.PathId
2826 AND File.JobId = Job.JobId
2827 AND Path.Path LIKE '$dir%'
2828 AND File.JobId IN ($inclause)
2829 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2833 # It's a file. Great, we allready have most
2834 # of what is needed. Simple and efficient query
2835 my $file = unpack('u', $entry->[0]);
2836 my @file = split '/',$file;
2838 my $dir = join('/',@file);
2840 my $jobid = $entry->[1];
2841 my $fileindex = $entry->[7];
2842 my $inclause = $entry->[3]; # curjobids
2844 "SELECT Path.Path, File.FilenameId, File.Lstat
2845 FROM File, Path, Filename
2846 WHERE Path.PathId = File.PathId
2847 AND Path.Path = '$dir/'
2848 AND Filename.Name = '$file'
2849 AND File.JobId = $jobid
2850 AND Filename.FilenameId = File.FilenameId";
2853 print STDERR $query,"\n" if $debug;
2854 my ($path,$nameid,$lstat);
2855 my $sth = $self->dbh_prepare($query);
2857 $sth->bind_columns(\$path,\$nameid,\$lstat);
2867 while ($sth->fetchrow_arrayref())
2869 # Only the latest version of a file
2870 next if ($nameid eq $old_nameid and $path eq $old_path);
2872 if ($rcount > 15000) {
2879 # We get the size of this file
2880 my $size=lstat_attrib($lstat,'st_size');
2881 $total_size += $size;
2884 $old_nameid=$nameid;
2886 return ($total_size,$total_files);
2889 sub update_brestore_table
2891 my ($self, @jobs) = @_;
2892 my $dbh = $self->{dbh};
2894 foreach my $job (sort {$a <=> $b} @jobs)
2896 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2897 my $retour = $self->dbh_selectrow_arrayref($query);
2898 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2900 print STDERR "Inserting path records for JobId $job\n";
2901 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2902 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2904 $self->dbh_do($query);
2906 # Now we have to do the directory recursion stuff to determine missing visibility
2907 # We try to avoid recursion, to be as fast as possible
2908 # We also only work on not allready hierarchised directories...
2910 print STDERR "Creating missing recursion paths for $job\n";
2912 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2913 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2914 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2915 WHERE brestore_pathvisibility.JobId = $job
2916 AND brestore_pathhierarchy.PathId IS NULL
2919 my $sth = $self->dbh_prepare($query);
2921 my $pathid; my $path;
2922 $sth->bind_columns(\$pathid,\$path);
2926 $self->build_path_hierarchy($path,$pathid);
2930 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2931 # This query gives all parent pathids for a given jobid that aren't stored.
2932 # It has to be called until no record is updated ...
2934 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2935 SELECT a.PathId,$job
2937 (SELECT DISTINCT h.PPathId AS PathId
2938 FROM brestore_pathhierarchy AS h
2939 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2940 WHERE p.JobId=$job) AS a
2943 FROM brestore_pathvisibility
2944 WHERE JobId=$job) AS b
2945 ON (a.PathId = b.PathId)
2946 WHERE b.PathId IS NULL)";
2947 print STDERR $query,"\n" if ($debug);
2949 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2951 print STDERR "Recursively adding $rows_affected records from $job\n";
2954 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2959 sub cleanup_brestore_table
2962 my $dbh = $self->{dbh};
2964 my $query = "SELECT JobId from brestore_knownjobid";
2965 my @jobs = @{$dbh->selectall_arrayref($query)};
2967 foreach my $jobentry (@jobs)
2969 my $job = $jobentry->[0];
2970 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2971 my $result = $dbh->selectall_arrayref($query);
2972 if (scalar(@{$result}))
2974 # There are still files for this jobid
2975 print STDERR "$job still exists. Not cleaning...\n";
2978 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2980 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2986 sub build_path_hierarchy
2988 my ($self, $path,$pathid)=@_;
2989 # Does the ppathid exist for this ? we use a memory cache...
2990 # In order to avoid the full loop, we consider that if a dir is allready in the
2991 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2994 #print STDERR "$path\n" if $debug;
2995 if (! $self->{cache_ppathid}->{$pathid})
2997 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2998 my $sth2 = $self->{dbh}->prepare_cached($query);
2999 $sth2->execute($pathid);
3000 # Do we have a result ?
3001 if (my $refrow = $sth2->fetchrow_arrayref)
3003 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
3005 # This dir was in the db ...
3006 # It means we can leave, the tree has allready been built for
3011 # We have to create the record ...
3012 # What's the current p_path ?
3013 my $ppath = parent_dir($path);
3014 my $ppathid = $self->return_pathid_from_path($ppath);
3015 $self->{cache_ppathid}->{$pathid}= $ppathid;
3017 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
3018 $sth2 = $self->{dbh}->prepare_cached($query);
3019 $sth2->execute($pathid,$ppathid);
3025 # It's allready in the cache.
3026 # We can leave, no time to waste here, all the parent dirs have allready
3034 sub return_pathid_from_path
3036 my ($self, $path) = @_;
3037 my $query = "SELECT PathId FROM Path WHERE Path = ?
3039 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3040 #print STDERR $query,"\n" if $debug;
3041 my $sth = $self->{dbh}->prepare_cached($query);
3042 $sth->execute($path,$path);
3043 my $result =$sth->fetchrow_arrayref();
3045 if (defined $result)
3047 return $result->[0];
3050 # A bit dirty : we insert into path AND missing_path, to be sure
3051 # we aren't deleted by a purge. We still need to insert into path to get
3052 # the pathid, because of mysql
3053 $query = "INSERT INTO Path (Path) VALUES (?)";
3054 #print STDERR $query,"\n" if $debug;
3055 $sth = $self->{dbh}->prepare_cached($query);
3056 $sth->execute($path);
3059 $query = " INSERT INTO brestore_missing_path (PathId,Path)
3060 SELECT PathId,Path FROM Path WHERE Path = ?";
3061 #print STDERR $query,"\n" if $debug;
3062 $sth = $self->{dbh}->prepare_cached($query);
3063 $sth->execute($path);
3065 $query = " DELETE FROM Path WHERE Path = ?";
3066 #print STDERR $query,"\n" if $debug;
3067 $sth = $self->{dbh}->prepare_cached($query);
3068 $sth->execute($path);
3070 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3071 #print STDERR $query,"\n" if $debug;
3072 $sth = $self->{dbh}->prepare_cached($query);
3073 $sth->execute($path);
3074 $result = $sth->fetchrow_arrayref();
3076 return $result->[0];
3088 # Root Windows case :
3089 if ($path =~ /^[a-z]+:\/$/i)
3094 my @tmp = split('/',$path);
3095 # We remove the last ...
3097 my $tmp = join ('/',@tmp) . '/';
3103 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3104 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3105 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3106 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3107 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3108 'data_stream' => 15);;
3111 my ($attrib,$ref_attrib)=@_;
3112 return $ref_attrib->[$attrib_name_id{$attrib}];
3116 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
3118 my ($file, $attrib)=@_;
3120 if (defined $attrib_name_id{$attrib}) {
3122 my @d = split(' ', $file->[2]) ; # TODO : cache this
3124 return from_base64($d[$attrib_name_id{$attrib}]);
3126 } elsif ($attrib eq 'jobid') {
3130 } elsif ($attrib eq 'name') {
3135 die "Attribute not known : $attrib.\n";
3139 # Return the jobid or attribute asked for a dir
3142 my ($self,$dir,$attrib)=@_;
3144 my @dir = split('/',$dir,-1);
3145 my $refdir=$self->{dirtree}->{$self->current_client};
3147 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3149 die "Attribute not known : $attrib.\n";
3152 foreach my $subdir (@dir)
3154 $refdir = $refdir->[0]->{$subdir};
3157 # $refdir is now the reference to the dir's array
3158 # Is the a jobid in @CurrentJobIds where the lstat is
3159 # defined (we'll search in reverse order)
3160 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3162 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3164 if ($attrib eq 'jobid')
3170 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3171 return $attribs[$attrib_name_id{$attrib}+1];
3176 return 0; # We cannot get a good attribute.
3177 # This directory is here for the sake of visibility
3182 my ($lstat,$attrib)=@_;
3183 if ($lstat and defined $attrib_name_id{$attrib})
3185 my @d = split(' ', $lstat) ; # TODO : cache this
3186 return from_base64($d[$attrib_name_id{$attrib}]);
3193 # Base 64 functions, directly from recover.pl.
3195 # Karl Hakimian <hakimian@aha.com>
3196 # This section is also under GPL v2 or later.
3203 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3204 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3205 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3206 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3207 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3209 @base64_map = (0) x 128;
3211 for (my $i=0; $i<64; $i++) {
3212 $base64_map[ord($base64_digits[$i])] = $i;
3227 if (substr($where, 0, 1) eq '-') {
3229 $where = substr($where, 1);
3232 while ($where ne '') {
3234 my $d = substr($where, 0, 1);
3235 $val += $base64_map[ord(substr($where, 0, 1))];
3236 $where = substr($where, 1);
3244 my @attribs = split(' ',$lstat);
3245 foreach my $element (@attribs)
3247 $element = from_base64($element);
3256 ################################################################
3259 use base qw/DlgResto/;
3263 my ($class, $conf) = @_;
3264 my $self = bless {info => $conf}, $class;
3266 $self->{dbh} = $conf->{dbh};
3275 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3276 my $jobs = $self->dbh_selectall_arrayref($query);
3278 $self->update_brestore_table(map { $_->[0] } @$jobs);
3289 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3293 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3296 GetOptions("conf=s" => \$file_conf,
3297 "batch" => \$batch_mod,
3299 "help" => \&HELP_MESSAGE) ;
3301 my $p = new Pref($file_conf);
3303 if (! -f $file_conf) {
3308 my $b = new Batch($p);
3309 if ($p->connect_db()) {
3310 $b->set_dbh($p->{dbh});
3316 $glade_file = $p->{glade_file};
3318 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3319 if (-f "$path/$glade_file") {
3320 $glade_file = "$path/$glade_file" ;
3325 # gtk have lots of warning on stderr
3326 if ($^O eq 'MSWin32')
3329 open(STDERR, ">stderr.log");
3334 if ( -f $glade_file) {
3335 my $w = new DlgResto($p);
3338 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3339 "Can't find your brestore.glade (glade_file => '$glade_file')
3340 Please, edit your $file_conf to setup it." );
3342 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3347 Gtk2->main; # Start Gtk2 main loop
3359 # Code pour trier les colonnes
3360 my $mod = $fileview->get_model();
3361 $mod->set_default_sort_func(sub {
3362 my ($model, $item1, $item2) = @_;
3363 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3364 my $b = $model->get($item2, 1); # colonne (indice 1)
3369 $fileview->set_headers_clickable(1);
3370 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3371 $col->signal_connect('clicked', sub {
3372 my ($colonne, $model) = @_;
3373 $model->set_sort_column_id (1, 'ascending');