4 # path to your brestore.glade
5 my $glade_file = 'brestore.glade' ;
9 brestore.pl - A Perl/Gtk console for Bacula
17 Setup ~/.brestore.conf to find your brestore.glade
19 On debian like system, you need :
20 - libgtk2-gladexml-perl
21 - libdbd-mysql-perl or libdbd-pg-perl
24 You have to add brestore_xxx tables to your catalog.
26 To speed up database query you have to create theses indexes
27 - CREATE INDEX file_pathid on File(PathId);
30 To follow restore job, you must have a running Bweb installation.
34 Bacula® - The Network Backup Solution
36 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
38 Brestore authors are Marc Cousin and Eric Bollengier.
39 The main author of Bacula is Kern Sibbald, with contributions from
40 many others, a complete list can be found in the file AUTHORS.
42 This program is Free Software; you can redistribute it and/or
43 modify it under the terms of version two of the GNU General Public
44 License as published by the Free Software Foundation plus additions
45 that are listed in the file LICENSE.
47 This program is distributed in the hope that it will be useful, but
48 WITHOUT ANY WARRANTY; without even the implied warranty of
49 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
50 General Public License for more details.
52 You should have received a copy of the GNU General Public License
53 along with this program; if not, write to the Free Software
54 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
57 Bacula® is a registered trademark of John Walker.
58 The licensor of Bacula is the Free Software Foundation Europe
59 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
60 Switzerland, email:ftf@fsfeurope.org.
62 Base 64 functions from Karl Hakimian <hakimian@aha.com>
63 Integrally copied from recover.pl from bacula source distribution.
67 use Gtk2; # auto-initialize Gtk2
69 use Gtk2::SimpleList; # easy wrapper for list views
70 use Gtk2::Gdk::Keysyms; # keyboard code constants
71 use Data::Dumper qw/Dumper/;
73 my $debug=0; # can be on brestore.conf
74 our ($VERSION) = ('$Revision$' =~ /(\d+\.\d+)/);
76 ################################################################
78 package DlgFileVersion;
80 sub on_versions_close_clicked
82 my ($self, $widget)=@_;
83 $self->{version}->destroy();
86 sub on_selection_button_press_event
88 print STDERR "on_selection_button_press_event()\n";
93 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
95 DlgResto::drag_set_info($widget,
102 my ($class, $dbh, $client, $path, $file) = @_;
105 version => undef, # main window
108 # we load version widget of $glade_file
109 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
111 # Connect signals magically
112 $glade_box->signal_autoconnect_from_package($self);
114 $glade_box->get_widget("version_label")
115 ->set_markup("<b>File revisions : $client:$path/$file</b>");
117 my $widget = $glade_box->get_widget('version_fileview');
118 my $fileview = Gtk2::SimpleList->new_from_treeview(
120 'h_name' => 'hidden',
121 'h_jobid' => 'hidden',
122 'h_type' => 'hidden',
124 'InChanger' => 'pixbuf',
131 DlgResto::init_drag_drop($fileview);
133 my @v = DlgResto::get_all_file_versions($dbh,
139 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
141 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
143 DlgResto::listview_push($fileview,
144 $file, $jobid, 'file',
145 $icon, $volname, $jobid,DlgResto::human($size),
146 scalar(localtime($mtime)), $md5);
149 $self->{version} = $glade_box->get_widget('dlg_version');
150 $self->{version}->show();
155 sub on_forward_keypress
161 ################################################################
166 my ($package, $text) = @_;
170 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
172 # Connect signals magically
173 $glade->signal_autoconnect_from_package($self);
174 $glade->get_widget('label_warn')->set_text($text);
176 print STDERR "$text\n";
178 $self->{window} = $glade->get_widget('dlg_warn');
179 $self->{window}->show_all();
186 $self->{window}->destroy();
190 ################################################################
193 use HTTP::Request::Common;
197 my ($class, %arg) = @_;
200 pref => $arg{pref}, # Pref object
201 timeout => $arg{timeout} || 20,
202 debug => $arg{debug} || 0,
205 'list_fileset' => '',
206 'list_storage' => '',
215 my ($self, @what) = @_;
216 my $ua = LWP::UserAgent->new();
217 $ua->agent("Brestore/$VERSION");
218 my $req = POST($self->{pref}->{bconsole},
219 Content_Type => 'form-data',
220 Content => [ map { (action => $_) } @what ]);
221 #$req->authorization_basic('eric', 'test');
223 my $res = $ua->request($req);
225 if ($res->is_success) {
226 foreach my $l (split(/\n/, $res->content)) {
227 my ($k, $c) = split(/=/,$l,2);
231 $self->{error} = "Can't connect to bweb : " . $res->status_line;
232 new DlgWarn($self->{error});
238 my ($self, %arg) = @_;
240 my $ua = LWP::UserAgent->new();
241 $ua->agent("Brestore/$VERSION");
242 my $req = POST($self->{pref}->{bconsole},
243 Content_Type => 'form-data',
244 Content => [ job => $arg{job},
245 client => $arg{client},
246 storage => $arg{storage} || '',
247 fileset => $arg{fileset} || '',
248 where => $arg{where},
249 replace => $arg{replace},
250 priority=> $arg{prio} || '',
253 bootstrap => [$arg{bootstrap}],
255 #$req->authorization_basic('eric', 'test');
257 my $res = $ua->request($req);
259 if ($res->is_success) {
260 foreach my $l (split(/\n/, $res->content)) {
261 my ($k, $c) = split(/=/,$l,2);
267 new DlgWarn("Can't connect to bweb : " . $res->status_line);
270 unlink($arg{bootstrap});
278 return sort split(/;/, $self->{'list_job'});
284 return sort split(/;/, $self->{'list_fileset'});
290 return sort split(/;/, $self->{'list_storage'});
295 return sort split(/;/, $self->{'list_client'});
300 ################################################################
306 # %arg = (bsr_file => '/path/to/bsr', # on director
307 # volumes => [ '00001', '00004']
315 if ($pref->{bconsole} =~ /^http/) {
316 return new BwebConsole(pref => $pref);
318 if (eval { require Bconsole; }) {
319 return new Bconsole(pref => $pref);
321 new DlgWarn("Can't use bconsole, verify your setup");
329 my ($class, %arg) = @_;
332 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
333 pref => $arg{pref}, # Pref ref
334 glade => undef, # GladeXML ref
335 bconsole => undef, # Bconsole ref
338 my $console = $self->{bconsole} = get_bconsole($arg{pref});
343 # we load launch widget of $glade_file
344 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
347 # Connect signals magically
348 $glade->signal_autoconnect_from_package($self);
350 my $widget = $glade->get_widget('volumeview');
351 my $volview = Gtk2::SimpleList->new_from_treeview(
353 'InChanger' => 'pixbuf',
357 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
359 # we replace 0 and 1 by $noicon and $yesicon
360 for my $i (@{$infos}) {
362 $i->[0] = $DlgResto::noicon;
364 $i->[0] = $DlgResto::yesicon;
369 push @{ $volview->{data} }, @{$infos} ;
371 $console->prepare(qw/list_client list_job list_fileset list_storage/);
373 # fill client combobox (with director defined clients
374 my @clients = $console->list_client() ; # get from bconsole
375 if ($console->{error}) {
376 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
378 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
379 $self->{list_client} = DlgResto::init_combo($w, 'text');
380 DlgResto::fill_combo($self->{list_client},
381 $DlgResto::client_list_empty,
385 # fill fileset combobox
386 my @fileset = $console->list_fileset() ;
387 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
388 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
389 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
392 my @job = $console->list_job() ;
393 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
394 $self->{list_job} = DlgResto::init_combo($w, 'text');
395 DlgResto::fill_combo($self->{list_job}, '', @job);
397 # find default_restore_job in jobs list
398 my $default_restore_job = $arg{pref}->{default_restore_job} ;
402 if ($j =~ /$default_restore_job/io) {
408 $w->set_active($index);
410 # fill storage combobox
411 my @storage = $console->list_storage() ;
412 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
413 $self->{list_storage} = DlgResto::init_combo($w, 'text');
414 DlgResto::fill_combo($self->{list_storage}, '', @storage);
416 $glade->get_widget('dlg_launch')->show_all();
423 my ($self, $client, $jobid) = @_;
425 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
427 $self->on_cancel_resto_clicked();
430 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
431 "Your job have been submited to bacula.
432 To follow it, you must use bconsole (or install/configure bweb)");
438 sub on_cancel_resto_clicked
441 $self->{glade}->get_widget('dlg_launch')->destroy();
444 sub on_submit_resto_clicked
447 my $glade = $self->{glade};
449 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
452 new DlgWarn("Can't copy bsr file to director ($self->{error})");
456 my $fileset = $glade->get_widget('combo_launch_fileset')
459 my $storage = $glade->get_widget('combo_launch_storage')
462 my $where = $glade->get_widget('entry_launch_where')->get_text();
464 my $job = $glade->get_widget('combo_launch_job')
468 new DlgWarn("Can't use this job");
472 my $client = $glade->get_widget('combo_launch_client')
475 if (! $client or $client eq $DlgResto::client_list_empty) {
476 new DlgWarn("Can't use this client ($client)");
480 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
482 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
483 $replace=($replace)?'always':'never';
485 my $jobid = $self->{bconsole}->run(job => $job,
494 $self->show_job($client, $jobid);
497 sub on_combo_storage_button_press_event
500 print "on_combo_storage_button_press_event()\n";
503 sub on_combo_fileset_button_press_event
506 print "on_combo_fileset_button_press_event()\n";
510 sub on_combo_job_button_press_event
513 print "on_combo_job_button_press_event()\n";
516 sub get_volume_inchanger
518 my ($dbh, $vols) = @_;
520 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
522 my $rq = "SELECT InChanger, VolumeName
524 WHERE VolumeName IN ($lst)
527 my $res = $dbh->selectall_arrayref($rq);
528 return $res; # [ [ 1, VolName].. ]
532 use File::Copy qw/copy/;
533 use File::Basename qw/basename/;
535 # We must kown the path+filename destination
536 # $self->{error} contains error message
537 # it return 0/1 if fail/success
540 my ($self, $src, $dst) = @_ ;
541 print "$src => $dst\n"
552 if ($dst =~ m!file:/(/.+)!) {
553 $ret = copy($src, $1);
555 $dstfile = "$1/" . basename($src) ;
557 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
558 $err = `scp $src $1 2>&1` ;
560 $dstfile = "$2/" . basename($src) ;
564 $err = "$dst not implemented yet";
565 File::Copy::copy($src, \*STDOUT);
568 $self->{error} = $err;
571 $self->{error} = $err;
580 ################################################################
588 unless ($about_widget) {
589 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
590 $about_widget = $glade_box->get_widget("dlg_about") ;
591 $glade_box->signal_autoconnect_from_package('DlgAbout');
593 $about_widget->show() ;
596 sub on_about_okbutton_clicked
598 $about_widget->hide() ;
603 ################################################################
609 my ($class, $config_file) = @_;
612 config_file => $config_file,
613 password => '', # db passwd
614 username => '', # db username
615 connection_string => '',# db connection string
616 bconsole => 'bconsole', # path and arg to bconsole
617 bsr_dest => '', # destination url for bsr files
618 debug => 0, # debug level 0|1
619 use_ok_bkp_only => 1, # dont use bad backup
620 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
621 glade_file => $glade_file,
622 see_all_versions => 0, # display all file versions in FileInfo
623 mozilla => 'mozilla', # mozilla bin
624 default_restore_job => 'restore', # regular expression to select default
627 # keywords that are used to fill DlgPref
628 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
629 entry_keyword => [ qw/username password bweb mozilla
630 connection_string default_restore_job
631 bconsole bsr_dest glade_file/],
634 $self->read_config();
643 # We read the parameters. They come from the configuration files
644 my $cfgfile ; my $tmpbuffer;
645 if (open FICCFG, $self->{config_file})
647 while(read FICCFG,$tmpbuffer,4096)
649 $cfgfile .= $tmpbuffer;
653 no strict; # I have no idea of the contents of the file
654 eval '$refparams' . " = $cfgfile";
657 for my $p (keys %{$refparams}) {
658 $self->{$p} = $refparams->{$p};
661 if (defined $self->{debug}) {
662 $debug = $self->{debug} ;
665 # TODO : Force dumb default values and display a message
675 for my $k (@{ $self->{entry_keyword} }) {
676 $parameters{$k} = $self->{$k};
679 for my $k (@{ $self->{chk_keyword} }) {
680 $parameters{$k} = $self->{$k};
683 if (open FICCFG,">$self->{config_file}")
685 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
690 # TODO : Display a message
699 $self->{dbh}->disconnect() ;
703 delete $self->{error};
705 if (not $self->{connection_string})
707 # The parameters have not been set. Maybe the conf
708 # file is empty for now
709 $self->{error} = "No configuration found for database connection. " .
710 "Please set this up.";
715 $self->{dbh} = DBI->connect($self->{connection_string},
720 $self->{error} = "Can't open bacula database. " .
721 "Database connect string '" .
722 $self->{connection_string} ."' $!";
725 $self->{dbh}->{RowCacheSize}=100;
731 my ($self, $url, $msg) = @_;
733 unless ($self->{mozilla} and $self->{bweb}) {
734 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
738 if ($^O eq 'MSWin32') {
739 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
742 system("$self->{mozilla} -remote 'Ping()'");
743 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
745 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
755 ################################################################
759 # my $pref = new Pref(config_file => 'brestore.conf');
760 # my $dlg = new DlgPref($pref);
761 # my $dlg_resto = new DlgResto($pref);
762 # $dlg->display($dlg_resto);
765 my ($class, $pref) = @_;
768 pref => $pref, # Pref ref
769 dlgresto => undef, # DlgResto ref
777 my ($self, $dlgresto) = @_ ;
779 unless ($self->{glade}) {
780 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
781 $self->{glade}->signal_autoconnect_from_package($self);
784 $self->{dlgresto} = $dlgresto;
786 my $g = $self->{glade};
787 my $p = $self->{pref};
789 for my $k (@{ $p->{entry_keyword} }) {
790 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
793 for my $k (@{ $p->{chk_keyword} }) {
794 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
797 $g->get_widget("dlg_pref")->show_all() ;
800 sub on_applybutton_clicked
803 my $glade = $self->{glade};
804 my $pref = $self->{pref};
806 for my $k (@{ $pref->{entry_keyword} }) {
807 my $w = $glade->get_widget("entry_$k") ;
808 $pref->{$k} = $w->get_text();
811 for my $k (@{ $pref->{chk_keyword} }) {
812 my $w = $glade->get_widget("chkbp_$k") ;
813 $pref->{$k} = $w->get_active();
816 $pref->write_config();
817 if ($pref->connect_db()) {
818 $self->{dlgresto}->set_dbh($pref->{dbh});
819 $self->{dlgresto}->set_status('Preferences updated');
820 $self->{dlgresto}->init_server_backup_combobox();
821 $self->{dlgresto}->create_brestore_tables();
822 $self->{dlgresto}->set_status($pref->{error});
824 $self->{dlgresto}->set_status($pref->{error});
828 # Handle prefs ok click (apply/dismiss dialog)
829 sub on_okbutton_clicked
832 $self->on_applybutton_clicked();
834 unless ($self->{pref}->{error}) {
835 $self->on_cancelbutton_clicked();
838 sub on_dialog_delete_event
841 $self->on_cancelbutton_clicked();
845 sub on_cancelbutton_clicked
848 $self->{glade}->get_widget('dlg_pref')->hide();
849 delete $self->{dlgresto};
853 ################################################################
863 # Kept as is from the perl-gtk example. Draws the pretty icons
869 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
870 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
871 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
872 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
876 # init combo (and create ListStore object)
879 my ($widget, @type) = @_ ;
880 my %type_info = ('text' => 'Glib::String',
881 'markup' => 'Glib::String',
884 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
886 $widget->set_model($lst);
890 if ($t eq 'text' or $t eq 'markup') {
891 $cell = new Gtk2::CellRendererText();
893 $widget->pack_start($cell, 1);
894 $widget->add_attribute($cell, $t, $i++);
899 # fill simple combo (one element per row)
902 my ($list, @what) = @_;
906 foreach my $w (@what)
909 my $i = $list->append();
910 $list->set($i, 0, $w);
917 my @unit = qw(b Kb Mb Gb Tb);
920 my $format = '%i %s';
921 while ($val / 1024 > 1) {
925 $format = ($i>0)?'%0.1f %s':'%i %s';
926 return sprintf($format, $val, $unit[$i]);
931 my ($self, $dbh) = @_;
937 my ($fileview) = shift;
938 my $fileview_target_entry = {target => 'STRING',
939 flags => ['GTK_TARGET_SAME_APP'],
942 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
943 ['copy'],$fileview_target_entry);
944 $fileview->get_selection->set_mode('multiple');
946 # set some useful SimpleList properties
947 $fileview->set_headers_clickable(0);
948 foreach ($fileview->get_columns())
950 $_->set_resizable(1);
951 $_->set_sizing('grow-only');
957 my ($self, $what) = @_;
961 print Data::Dumper::Dumper($what);
962 } elsif (defined $what) {
970 my ($self, $query) = @_;
971 $self->debug($query);
972 return $self->{dbh}->prepare($query);
977 my ($self, $query) = @_;
978 $self->debug($query);
979 return $self->{dbh}->do($query);
982 sub dbh_selectall_arrayref
984 my ($self, $query) = @_;
985 $self->debug($query);
986 return $self->{dbh}->selectall_arrayref($query);
989 sub dbh_selectrow_arrayref
991 my ($self, $query) = @_;
992 $self->debug($query);
993 return $self->{dbh}->selectrow_arrayref($query);
998 my ($class, $pref) = @_;
1002 CurrentJobIds => [],
1003 location => undef, # location entry widget
1004 mainwin => undef, # mainwin widget
1005 filelist_file_menu => undef, # file menu widget
1006 filelist_dir_menu => undef, # dir menu widget
1007 glade => undef, # glade object
1008 status => undef, # status bar widget
1009 dlg_pref => undef, # DlgPref object
1010 fileattrib => {}, # cache file
1011 fileview => undef, # fileview widget SimpleList
1012 fileinfo => undef, # fileinfo widget SimpleList
1014 client_combobox => undef, # client_combobox widget
1015 restore_backup_combobox => undef, # date combobox widget
1016 list_client => undef, # Gtk2::ListStore
1017 list_backup => undef, # Gtk2::ListStore
1018 cache_ppathid => {}, #
1021 # load menu (to use handler with self reference)
1022 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1023 $glade->signal_autoconnect_from_package($self);
1024 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1026 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1027 $glade->signal_autoconnect_from_package($self);
1028 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1030 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1031 $glade->signal_autoconnect_from_package($self);
1033 $self->{status} = $glade->get_widget('statusbar');
1034 $self->{mainwin} = $glade->get_widget('dlg_resto');
1035 $self->{location} = $glade->get_widget('entry_location');
1036 $self->render_icons();
1038 $self->{dlg_pref} = new DlgPref($pref);
1040 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1041 $self->{list_client} = init_combo($c, 'text');
1043 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1044 $self->{list_backup} = init_combo($c, 'text', 'markup');
1046 # Connect glade-fileview to Gtk2::SimpleList
1047 # and set up drag n drop between $fileview and $restore_list
1049 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1050 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1052 my $widget = $glade->get_widget('fileview');
1053 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1055 'h_name' => 'hidden',
1056 'h_jobid' => 'hidden',
1057 'h_type' => 'hidden',
1060 'File Name' => 'text',
1063 init_drag_drop($fileview);
1064 $fileview->set_search_column(4); # search on File Name
1066 # Connect glade-restore_list to Gtk2::SimpleList
1067 $widget = $glade->get_widget('restorelist');
1068 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1070 'h_name' => 'hidden',
1071 'h_jobid' => 'hidden',
1072 'h_type' => 'hidden',
1073 'h_curjobid' => 'hidden',
1076 'File Name' => 'text',
1078 'FileIndex' => 'text',
1080 'Nb Files' => 'text', #8
1081 'Size' => 'text', #9
1082 'size_b' => 'hidden', #10
1085 my @restore_list_target_table = ({'target' => 'STRING',
1089 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1090 $restore_list->get_selection->set_mode('multiple');
1092 $widget = $glade->get_widget('infoview');
1093 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1095 'h_name' => 'hidden',
1096 'h_jobid' => 'hidden',
1097 'h_type' => 'hidden',
1099 'InChanger' => 'pixbuf',
1106 init_drag_drop($infoview);
1108 $pref->connect_db() || $self->{dlg_pref}->display($self);
1111 $self->{dbh} = $pref->{dbh};
1112 $self->init_server_backup_combobox();
1113 $self->create_brestore_tables();
1116 $self->set_status($pref->{error});
1119 # set status bar informations
1122 my ($self, $string) = @_;
1123 return unless ($string);
1125 my $context = $self->{status}->get_context_id('Main');
1126 $self->{status}->push($context, $string);
1129 sub on_time_select_changed
1137 my $c = $self->{glade}->get_widget('combo_time');
1138 return $c->get_active_text;
1141 # This sub returns all clients declared in DB
1145 my $query = "SELECT Name FROM Client ORDER BY Name";
1146 print STDERR $query,"\n" if $debug;
1148 my $result = $dbh->selectall_arrayref($query);
1150 return map { $_->[0] } @$result;
1153 sub get_wanted_job_status
1160 return "'T', 'A', 'E'";
1164 # This sub gives a full list of the EndTimes for a ClientId
1165 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
1166 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
1167 sub get_all_endtimes_for_job
1169 my ($dbh, $client, $ok_only)=@_;
1170 my $status = get_wanted_job_status($ok_only);
1172 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
1173 FROM Job,Client,FileSet
1174 WHERE Job.ClientId=Client.ClientId
1175 AND Client.Name = '$client'
1177 AND JobStatus IN ($status)
1178 AND Job.FileSetId = FileSet.FileSetId
1179 ORDER BY EndTime desc";
1180 print STDERR $query,"\n" if $debug;
1181 my $result = $dbh->selectall_arrayref($query);
1187 # init infoview widget
1191 @{$self->{fileinfo}->{data}} = ();
1195 sub on_clear_clicked
1198 @{$self->{restore_list}->{data}} = ();
1201 sub on_estimate_clicked
1208 # TODO : If we get here, things could get lenghty ... draw a popup window .
1209 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1210 'destroy-with-parent',
1212 'Computing size...');
1216 my $title = "Computing size...\n";
1218 foreach my $entry (@{$self->{restore_list}->{data}})
1220 unless ($entry->[9]) {
1221 my ($size, $nb) = $self->estimate_restore_size($entry);
1222 $entry->[10] = $size;
1223 $entry->[9] = human($size);
1227 my $name = unpack('u', $entry->[0]);
1229 $txt .= "\n<i>$name</i> : " . $entry->[8] . " file(s)/" . $entry->[9] ;
1230 $widget->set_markup($title . $txt);
1232 $size_total+=$entry->[10];
1233 $nb_total+=$entry->[8];
1237 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1238 $widget->set_markup("Size estimation :\n" . $txt);
1239 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1244 sub on_gen_bsr_clicked
1248 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1249 'gtk-save','ok', 'gtk-cancel', 'cancel');
1252 my $w = new Gtk2::FileChooserDialog ( @options );
1257 if ($a eq 'cancel') {
1262 my $f = $w->get_filename();
1264 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1265 'destroy-with-parent',
1266 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1267 if ($dlg->run() eq 'ok') {
1281 if (open(FP, ">$save")) {
1282 my $bsr = $self->create_filelist();
1285 $self->set_status("Dumping BSR to $save ok");
1287 $self->set_status("Can't dump BSR to $save: $!");
1292 use File::Temp qw/tempfile/;
1294 sub on_go_button_clicked
1297 unless (scalar(@{$self->{restore_list}->{data}})) {
1298 new DlgWarn("No file to restore");
1301 my $bsr = $self->create_filelist();
1302 my ($fh, $filename) = tempfile();
1305 chmod(0644, $filename);
1307 print "Dumping BSR info to $filename\n"
1310 # we get Volume list
1311 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1312 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1314 new DlgLaunch(pref => $self->{pref},
1316 bsr_file => $filename,
1321 our $client_list_empty = 'Clients list';
1322 our %type_markup = ('F' => '<b>$label F</b>',
1325 'B' => '<b>$label B</b>',
1327 'A' => '<span foreground=\"red\">$label</span>',
1329 'E' => '<span foreground=\"red\">$label</span>',
1332 sub on_list_client_changed
1334 my ($self, $widget) = @_;
1335 return 0 unless defined $self->{fileview};
1336 my $dbh = $self->{dbh};
1338 $self->{list_backup}->clear();
1340 if ($self->current_client eq $client_list_empty) {
1344 my @endtimes=get_all_endtimes_for_job($dbh,
1345 $self->current_client,
1346 $self->{pref}->{use_ok_bkp_only});
1347 foreach my $endtime (@endtimes)
1349 my $i = $self->{list_backup}->append();
1351 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1352 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1353 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1355 $self->{list_backup}->set($i,
1360 $self->{restore_backup_combobox}->set_active(0);
1362 $self->{CurrentJobIds} = [
1363 set_job_ids_for_date($dbh,
1364 $self->current_client,
1365 $self->current_date,
1366 $self->{pref}->{use_ok_bkp_only})
1369 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1372 $self->refresh_fileview();
1376 sub fill_server_list
1378 my ($dbh, $combo, $list) = @_;
1380 my @clients=get_all_clients($dbh);
1384 my $i = $list->append();
1385 $list->set($i, 0, $client_list_empty);
1387 foreach my $client (@clients)
1389 $i = $list->append();
1390 $list->set($i, 0, $client);
1392 $combo->set_active(0);
1395 sub init_server_backup_combobox
1398 fill_server_list($self->{dbh},
1399 $self->{client_combobox},
1400 $self->{list_client}) ;
1403 #----------------------------------------------------------------------
1404 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1405 #data isn't. There is additionnal complexity for dirs (visibility problems),
1406 #so the @CurrentJobIds is not sufficient.
1407 sub refresh_fileview
1410 my $fileview = $self->{fileview};
1411 my $client_combobox = $self->{client_combobox};
1412 my $cwd = $self->{cwd};
1414 @{$fileview->{data}} = ();
1416 $self->clear_infoview();
1418 my $client_name = $self->current_client;
1420 if (!$client_name or ($client_name eq $client_list_empty)) {
1421 $self->set_status("Client list empty");
1425 my @list_dirs = $self->list_dirs($cwd,$client_name);
1426 # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1427 my $files = $self->list_files($cwd);
1428 print "CWD : $cwd\n" if ($debug);
1430 my $file_count = 0 ;
1431 my $total_bytes = 0;
1433 # Add directories to view
1434 foreach my $dir_entry (@list_dirs) {
1435 #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
1436 my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime'));
1437 my $dir = $dir_entry->[0];
1438 $total_bytes += 4096;
1441 listview_push($fileview,
1443 $self->dir_attrib("$cwd/$dir",'jobid'),
1453 foreach my $file (@$files)
1455 my $size = file_attrib($file,'st_size');
1456 my $time = localtime(file_attrib($file,'st_mtime'));
1457 $total_bytes += $size;
1459 # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
1461 listview_push($fileview,
1468 human($size), $time);
1471 $self->set_status("$file_count files/" . human($total_bytes));
1473 # set a decent default selection (makes keyboard nav easy)
1474 $fileview->select(0);
1478 sub on_about_activate
1480 DlgAbout::display();
1485 my ($tree, $path, $data) = @_;
1487 my @items = listview_get_all($tree) ;
1489 foreach my $i (@items)
1491 my @file_info = @{$i};
1494 # Ok, we have a corner case :
1499 $file = pack("u", $file_info[0]);
1503 $file = pack("u", $path . '/' . $file_info[0]);
1505 push @ret, join(" ; ", $file,
1506 $file_info[1], # $jobid
1507 $file_info[2], # $type
1511 my $data_get = join(" :: ", @ret);
1513 $data->set_text($data_get,-1);
1516 sub fileview_data_get
1518 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1519 drag_set_info($widget, $self->{cwd}, $data);
1522 sub fileinfo_data_get
1524 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1525 drag_set_info($widget, $self->{cwd}, $data);
1528 sub restore_list_data_received
1530 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1533 if ($info eq 40 || $info eq 0) # patch for display!=:0
1535 foreach my $elt (split(/ :: /, $data->data()))
1538 my ($file, $jobid, $type) =
1540 $file = unpack("u", $file);
1542 $self->add_selected_file_to_list($file, $jobid, $type);
1547 sub on_back_button_clicked {
1551 sub on_location_go_button_clicked
1554 $self->ch_dir($self->{location}->get_text());
1556 sub on_quit_activate {Gtk2->main_quit;}
1557 sub on_preferences_activate
1560 $self->{dlg_pref}->display($self) ;
1562 sub on_main_delete_event {Gtk2->main_quit;}
1563 sub on_bweb_activate
1566 $self->set_status("Open bweb on your browser");
1567 $self->{pref}->go_bweb('', "go on bweb");
1570 # Change to parent directory
1574 if ($self->{cwd} eq '/')
1578 my @dirs = split(/\//, $self->{cwd});
1580 $self->ch_dir(join('/', @dirs));
1583 # Change the current working directory
1584 # * Updates fileview, location, and selection
1589 $self->{cwd} = shift;
1591 $self->refresh_fileview();
1592 $self->{location}->set_text($self->{cwd});
1597 # Handle dialog 'close' (window-decoration induced close)
1598 # * Just hide the dialog, and tell Gtk not to do anything else
1602 my ($self, $w) = @_;
1605 1; # consume this event!
1608 # Handle key presses in location text edit control
1609 # * Translate a Return/Enter key into a 'Go' command
1610 # * All other key presses left for GTK
1612 sub on_location_entry_key_release_event
1618 my $keypress = $event->keyval;
1619 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1620 $keypress == $Gtk2::Gdk::Keysyms{Return})
1622 $self->ch_dir($widget->get_text());
1624 return 1; # consume keypress
1627 return 0; # let gtk have the keypress
1630 sub on_fileview_key_press_event
1632 my ($self, $widget, $event) = @_;
1636 sub listview_get_first
1639 my @selected = $list->get_selected_indices();
1640 if (@selected > 0) {
1641 my ($name, @other) = @{$list->{data}->[$selected[0]]};
1642 return (unpack('u', $name), @other);
1648 sub listview_get_all
1652 my @selected = $list->get_selected_indices();
1654 for my $i (@selected) {
1655 my ($name, @other) = @{$list->{data}->[$i]};
1656 push @ret, [unpack('u', $name), @other];
1664 my ($list, $name, @other) = @_;
1665 push @{$list->{data}}, [pack('u', $name), @other];
1668 #----------------------------------------------------------------------
1669 # Handle keypress in file-view
1670 # * Translates backspace into a 'cd ..' command
1671 # * All other key presses left for GTK
1673 sub on_fileview_key_release_event
1675 my ($self, $widget, $event) = @_;
1676 if (not $event->keyval)
1680 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1682 return 1; # eat keypress
1685 return 0; # let gtk have keypress
1688 sub on_forward_keypress
1693 #----------------------------------------------------------------------
1694 # Handle double-click (or enter) on file-view
1695 # * Translates into a 'cd <dir>' command
1697 sub on_fileview_row_activated
1699 my ($self, $widget) = @_;
1701 my ($name, undef, $type, undef) = listview_get_first($widget);
1705 if ($self->{cwd} eq '')
1707 $self->ch_dir($name);
1709 elsif ($self->{cwd} eq '/')
1711 $self->ch_dir('/' . $name);
1715 $self->ch_dir($self->{cwd} . '/' . $name);
1719 $self->fill_infoview($self->{cwd}, $name);
1722 return 1; # consume event
1727 my ($self, $path, $file) = @_;
1728 $self->clear_infoview();
1729 my @v = get_all_file_versions($self->{dbh},
1732 $self->current_client,
1733 $self->{pref}->{see_all_versions});
1735 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
1737 my $icon = ($inchanger)?$yesicon:$noicon;
1739 $mtime = localtime($mtime) ;
1741 listview_push($self->{fileinfo},
1742 $file, $jobid, 'file',
1743 $icon, $volname, $jobid, human($size), $mtime, $md5);
1750 return $self->{restore_backup_combobox}->get_active_text;
1756 return $self->{client_combobox}->get_active_text;
1759 sub on_list_backups_changed
1761 my ($self, $widget) = @_;
1762 return 0 unless defined $self->{fileview};
1764 $self->{CurrentJobIds} = [
1765 set_job_ids_for_date($self->{dbh},
1766 $self->current_client,
1767 $self->current_date,
1768 $self->{pref}->{use_ok_bkp_only})
1770 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1772 $self->refresh_fileview();
1776 sub on_restore_list_keypress
1778 my ($self, $widget, $event) = @_;
1779 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1781 my @sel = $widget->get_selected_indices;
1782 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1784 splice @{$self->{restore_list}->{data}},$elt,1;
1789 sub on_fileview_button_press_event
1791 my ($self,$widget,$event) = @_;
1792 if ($event->button == 3)
1794 $self->on_right_click_filelist($widget,$event);
1798 if ($event->button == 2)
1800 $self->on_see_all_version();
1807 sub on_see_all_version
1811 my @lst = listview_get_all($self->{fileview});
1814 my ($name, undef) = @{$i};
1816 new DlgFileVersion($self->{dbh},
1817 $self->current_client,
1818 $self->{cwd}, $name);
1822 sub on_right_click_filelist
1824 my ($self,$widget,$event) = @_;
1825 # I need to know what's selected
1826 my @sel = listview_get_all($self->{fileview});
1831 $type = $sel[0]->[2]; # $type
1836 if (@sel >=2 or $type eq 'dir')
1838 # We have selected more than one or it is a directories
1839 $w = $self->{filelist_dir_menu};
1843 $w = $self->{filelist_file_menu};
1849 $event->button, $event->time);
1852 sub context_add_to_filelist
1856 my @sel = listview_get_all($self->{fileview});
1858 foreach my $i (@sel)
1860 my ($file, $jobid, $type, undef) = @{$i};
1861 $file = $self->{cwd} . '/' . $file;
1862 $self->add_selected_file_to_list($file, $jobid, $type);
1866 # Adds a file to the filelist
1867 sub add_selected_file_to_list
1869 my ($self, $name, $jobid, $type)=@_;
1871 my $dbh = $self->{dbh};
1872 my $restore_list = $self->{restore_list};
1874 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1881 if ($name and substr $name,-1 ne '/')
1883 $name .= '/'; # For bacula
1885 my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
1886 listview_push($restore_list,
1887 $name, $jobid, 'dir', $curjobids,
1888 $diricon, $name,$curjobids,$dirfileindex);
1890 elsif ($type eq 'file')
1892 my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
1894 listview_push($restore_list,
1895 $name, $jobid, 'file', $curjobids,
1896 $fileicon, $name, $jobid, $fileindex );
1900 # TODO : we want be able to restore files from a bad ended backup
1901 # we have JobStatus IN ('T', 'A', 'E') and we must
1903 # Data acces subs from here. Interaction with SGBD and caching
1905 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1906 # GUI and stores them in @CurrentJobIds
1907 sub set_job_ids_for_date
1909 my ($dbh, $client, $date, $only_ok)=@_;
1911 if (!$client or !$date) {
1915 my $status = get_wanted_job_status($only_ok);
1917 # The algorithm : for a client, we get all the backups for each
1918 # fileset, in reverse order Then, for each fileset, we store the 'good'
1919 # incrementals and differentials until we have found a full so it goes
1920 # like this : store all incrementals until we have found a differential
1921 # or a full, then find the full #
1923 my $query = "SELECT JobId, FileSet, Level, JobStatus
1924 FROM Job, Client, FileSet
1925 WHERE Job.ClientId = Client.ClientId
1926 AND FileSet.FileSetId = Job.FileSetId
1927 AND EndTime <= '$date'
1928 AND Client.Name = '$client'
1930 AND JobStatus IN ($status)
1931 ORDER BY FileSet, JobTDate DESC";
1933 print STDERR $query,"\n" if $debug;
1935 my $result = $dbh->selectall_arrayref($query);
1937 foreach my $refrow (@$result)
1939 my $jobid = $refrow->[0];
1940 my $fileset = $refrow->[1];
1941 my $level = $refrow->[2];
1943 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1945 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1949 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1950 push @CurrentJobIds,($jobid);
1952 elsif ($level eq 'D')
1954 next if $progress{$fileset} eq 'D'; # We allready have a differential
1955 push @CurrentJobIds,($jobid);
1957 elsif ($level eq 'F')
1959 push @CurrentJobIds,($jobid);
1962 my $status = $refrow->[3] ;
1963 if ($status eq 'T') { # good end of job
1964 $progress{$fileset} = $level;
1967 print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
1969 return @CurrentJobIds;
1972 # Lists all directories contained inside a directory.
1973 # Uses the current dir, the client name, and CurrentJobIds for visibility.
1974 # Returns an array of dirs
1977 my ($self,$dir,$client)=@_;
1979 print "list_dirs(<$dir>, <$client>)\n" if $debug;
1981 if ($dir ne '' and substr $dir,-1 ne '/')
1983 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1986 my $dbh = $self->{dbh};
1987 my $query = "SELECT PathId FROM Path WHERE Path = ?
1988 UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?";
1989 my $sth = $dbh->prepare($query);
1990 $sth->execute($dir,$dir);
1991 my $result = $sth->fetchrow_arrayref();
1993 my $pathid = $result->[0];
1994 my @jobids = @{$self->{CurrentJobIds}};
1995 my $jobclause = join (',', @jobids);
1996 # Let's retrieve the list of the visible dirs in this dir ...
1997 # First, I need the empty filenameid to locate efficiently the dirs in the file table
1998 $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
1999 $sth = $dbh->prepare($query);
2001 $result = $sth->fetchrow_arrayref();
2003 my $dir_filenameid = $result->[0];
2005 # Then we get all the dir entries from File ...
2006 # It's ugly because there are records in brestore_missing_path ...
2008 SELECT Path, JobId, Lstat FROM(
2010 SELECT Path.Path, lower(Path.Path),
2011 listfile.JobId, listfile.Lstat
2013 SELECT DISTINCT brestore_pathhierarchy.PathId
2014 FROM brestore_pathhierarchy
2016 ON (brestore_pathhierarchy.PathId = Path.PathId)
2017 JOIN brestore_pathvisibility
2018 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2019 WHERE brestore_pathhierarchy.PPathId = $pathid
2020 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2021 JOIN Path ON (listpath.PathId = Path.PathId)
2023 SELECT File.PathId, File.JobId, File.Lstat FROM File
2024 WHERE File.FilenameId = $dir_filenameid
2025 AND File.JobId IN ($jobclause)) AS listfile
2026 ON (listpath.PathId = listfile.PathId)
2028 SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path),
2029 listfile.JobId, listfile.Lstat
2031 SELECT DISTINCT brestore_pathhierarchy.PathId
2032 FROM brestore_pathhierarchy
2033 JOIN brestore_missing_path
2034 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2035 JOIN brestore_pathvisibility
2036 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2037 WHERE brestore_pathhierarchy.PPathId = $pathid
2038 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2039 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2041 SELECT File.PathId, File.JobId, File.Lstat FROM File
2042 WHERE File.FilenameId = $dir_filenameid
2043 AND File.JobId IN ($jobclause)) AS listfile
2044 ON (listpath.PathId = listfile.PathId))
2045 ORDER BY 2,3 DESC ) As a";
2046 print STDERR "$query\n" if $debug;
2047 $sth=$dbh->prepare($query);
2049 $result = $sth->fetchall_arrayref();
2052 foreach my $refrow (@{$result})
2054 my $dir = $refrow->[0];
2055 my $jobid = $refrow->[1];
2056 my $lstat = $refrow->[2];
2057 next if ($dir eq $prev_dir);
2058 # We have to clean up this dirname ... we only want it's 'basename'
2062 my @temp = split ('/',$dir);
2063 $return_value = pop @temp;
2067 $return_value = '/';
2069 my @return_array = ($return_value,$lstat);
2070 push @return_list,(\@return_array);
2073 return @return_list;
2077 # List all files in a directory. dir as parameter, CurrentJobIds for visibility
2078 # Returns an array of dirs
2081 my ($self, $dir)=@_;
2082 my $dbh = $self->{dbh};
2086 print "list_files($dir)\n" if $debug;
2088 if ($dir ne '' and substr $dir,-1 ne '/')
2090 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
2093 my $query = "SELECT Path.PathId
2095 WHERE Path.Path = '$dir'
2097 SELECT brestore_missing_path.PathId
2098 FROM brestore_missing_path
2099 WHERE brestore_missing_path.Path = '$dir'";
2100 print $query,"\n" if $debug;
2102 my $result = $dbh->selectall_arrayref($query);
2103 foreach my $refrow (@$result)
2105 push @list_pathid,($refrow->[0]);
2108 if (@list_pathid == 0)
2110 print "No pathid found for $dir\n" if $debug;
2114 my $inlistpath = join (',', @list_pathid);
2115 my $inclause = join (',', @{$self->{CurrentJobIds}});
2116 if ($inclause eq '')
2122 "SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
2124 (SELECT Filename.Name, max(File.FileId) as id
2126 WHERE File.FilenameId = Filename.FilenameId
2127 AND Filename.Name != ''
2128 AND File.PathId IN ($inlistpath)
2129 AND File.JobId IN ($inclause)
2130 GROUP BY Filename.Name
2131 ORDER BY Filename.Name) AS listfiles,
2133 WHERE File.FileId = listfiles.id";
2135 print STDERR $query,"\n" if $debug;
2136 $result = $dbh->selectall_arrayref($query);
2143 Gtk2->main_iteration while (Gtk2->events_pending);
2146 sub create_brestore_tables
2150 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2152 unless ($self->dbh_do($verif)) {
2153 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2155 $self->{error} = "Creating internal brestore tables";
2157 CREATE TABLE brestore_knownjobid
2159 JobId int4 NOT NULL,
2160 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2162 $self->dbh_do($req);
2165 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2166 unless ($self->dbh_do($verif)) {
2168 CREATE TABLE brestore_pathhierarchy
2170 PathId int4 NOT NULL,
2171 PPathId int4 NOT NULL,
2172 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2174 $self->dbh_do($req);
2177 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2178 ON brestore_pathhierarchy (PPathId)";
2179 $self->dbh_do($req);
2182 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2183 unless ($self->dbh_do($verif)) {
2185 CREATE TABLE brestore_pathvisibility
2187 PathId int4 NOT NULL,
2188 JobId int4 NOT NULL,
2189 Size int8 DEFAULT 0,
2190 Files int4 DEFAULT 0,
2191 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2193 $self->dbh_do($req);
2195 $req = "CREATE INDEX brestore_pathvisibility_jobid
2196 ON brestore_pathvisibility (JobId)";
2197 $self->dbh_do($req);
2200 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2201 unless ($self->dbh_do($verif)) {
2203 CREATE TABLE brestore_missing_path
2205 PathId int4 NOT NULL,
2207 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2209 $self->dbh_do($req);
2211 $req = "CREATE INDEX brestore_missing_path_path
2212 ON brestore_missing_path (Path)";
2213 $self->dbh_do($req);
2217 # Recursive function to calculate the visibility of each directory in the cache
2218 # tree Working with references to save time and memory
2219 # For each directory, we want to propagate it's visible jobids onto it's
2220 # parents directory.
2221 # A tree is visible if
2222 # - it's been in a backup pointed by the CurrentJobIds
2223 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2224 # In the second case, the directory is visible but has no metadata.
2225 # We symbolize this with lstat = 1 for this jobid in the cache.
2227 # Input : reference directory
2228 # Output : visibility of this dir. Has to know visibility of all subdirs
2229 # to know it's visibility, hence the recursing.
2235 # Get the subdirs array references list
2236 my @list_ref_subdirs;
2237 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2239 push @list_ref_subdirs,($ref_subdir);
2242 # Now lets recurse over these subdirs and retrieve the reference of a hash
2243 # containing the jobs where they are visible
2244 foreach my $ref_subdir (@list_ref_subdirs)
2246 my $ref_list_jobs = list_visible($ref_subdir);
2247 foreach my $jobid (keys %$ref_list_jobs)
2249 $visibility{$jobid}=1;
2253 # Ok. Now, we've got the list of those jobs. We are going to update our
2254 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2255 # the lstat for the known jobids. Put 1 in the new elements... But first,
2256 # let's store the current jobids
2258 foreach my $jobid (keys %{$refdir->[1]})
2260 push @known_jobids,($jobid);
2264 foreach my $jobid (keys %visibility)
2266 next if ($refdir->[1]->{$jobid});
2267 $refdir->[1]->{$jobid} = 1;
2269 # Add the known_jobids to %visibility
2270 foreach my $jobid (@known_jobids)
2272 $visibility{$jobid}=1;
2274 return \%visibility;
2277 # Returns the list of media required for a list of jobids.
2278 # Input : dbh, jobid1, jobid2...
2279 # Output : reference to array of (joibd, inchanger)
2280 sub get_required_media_from_jobid
2282 my ($dbh, @jobids)=@_;
2283 my $inclause = join(',',@jobids);
2285 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2286 FROM JobMedia, Media
2287 WHERE JobMedia.MediaId=Media.MediaId
2288 AND JobId In ($inclause)
2290 my $result = $dbh->selectall_arrayref($query);
2294 # Returns the fileindex from dirname and jobid.
2295 # Input : dbh, dirname, jobid
2296 # Output : fileindex
2297 sub get_fileindex_from_dir_jobid
2299 my ($dbh, $dirname, $jobid)=@_;
2301 $query = "SELECT File.FileIndex
2302 FROM File, Filename, Path
2303 WHERE File.FilenameId = Filename.FilenameId
2304 AND File.PathId = Path.PathId
2305 AND Filename.Name = ''
2306 AND Path.Path = '$dirname'
2307 AND File.JobId = '$jobid'
2310 print STDERR $query,"\n" if $debug;
2311 my $result = $dbh->selectall_arrayref($query);
2312 return $result->[0]->[0];
2315 # Returns the fileindex from filename and jobid.
2316 # Input : dbh, filename, jobid
2317 # Output : fileindex
2318 sub get_fileindex_from_file_jobid
2320 my ($dbh, $filename, $jobid)=@_;
2322 my @dirs = split(/\//, $filename);
2323 $filename=pop(@dirs);
2324 my $dirname = join('/', @dirs) . '/';
2329 "SELECT File.FileIndex
2330 FROM File, Filename, Path
2331 WHERE File.FilenameId = Filename.FilenameId
2332 AND File.PathId = Path.PathId
2333 AND Filename.Name = '$filename'
2334 AND Path.Path = '$dirname'
2335 AND File.JobId = '$jobid'";
2337 print STDERR $query,"\n" if $debug;
2338 my $result = $dbh->selectall_arrayref($query);
2339 return $result->[0]->[0];
2343 # Returns list of versions of a file that could be restored
2344 # returns an array of
2345 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2346 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2347 # and volname and md5
2348 # and of course, there will be only one jobid in the array of jobids...
2349 sub get_all_file_versions
2351 my ($dbh,$path,$file,$client,$see_all)=@_;
2353 defined $see_all or $see_all=0;
2358 "SELECT File.JobId, File.FileIndex, File.Lstat,
2359 File.Md5, Media.VolumeName, Media.InChanger
2360 FROM File, Filename, Path, Job, Client, JobMedia, Media
2361 WHERE File.FilenameId = Filename.FilenameId
2362 AND File.PathId=Path.PathId
2363 AND File.JobId = Job.JobId
2364 AND Job.ClientId = Client.ClientId
2365 AND Job.JobId = JobMedia.JobId
2366 AND File.FileIndex >= JobMedia.FirstIndex
2367 AND File.FileIndex <= JobMedia.LastIndex
2368 AND JobMedia.MediaId = Media.MediaId
2369 AND Path.Path = '$path'
2370 AND Filename.Name = '$file'
2371 AND Client.Name = '$client'";
2373 print STDERR $query if $debug;
2375 my $result = $dbh->selectall_arrayref($query);
2377 foreach my $refrow (@$result)
2379 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2380 my @attribs = parse_lstat($lstat);
2381 my $mtime = array_attrib('st_mtime',\@attribs);
2382 my $size = array_attrib('st_size',\@attribs);
2384 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2385 $inchanger, $md5, $volname);
2386 push @versions, (\@list);
2389 # We have the list of all versions of this file.
2390 # We'll sort it by mtime desc, size, md5, inchanger desc
2391 # the rest of the algorithm will be simpler
2392 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2393 @versions = sort { $b->[4] <=> $a->[4]
2394 || $a->[5] <=> $b->[5]
2395 || $a->[7] cmp $a->[7]
2396 || $b->[6] <=> $a->[6]} @versions;
2399 my %allready_seen_by_mtime;
2400 my %allready_seen_by_md5;
2401 # Now we should create a new array with only the interesting records
2402 foreach my $ref (@versions)
2406 # The file has a md5. We compare his md5 to other known md5...
2407 # We take size into account. It may happen that 2 files
2408 # have the same md5sum and are different. size is a supplementary
2411 # If we allready have a (better) version
2412 next if ( (not $see_all)
2413 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2415 # we never met this one before...
2416 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2418 # Even if it has a md5, we should also work with mtimes
2419 # We allready have a (better) version
2420 next if ( (not $see_all)
2421 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2422 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2424 # We reached there. The file hasn't been seen.
2425 push @good_versions,($ref);
2428 # To be nice with the user, we re-sort good_versions by
2429 # inchanger desc, mtime desc
2430 @good_versions = sort { $b->[4] <=> $a->[4]
2431 || $b->[2] <=> $a->[2]} @good_versions;
2433 return @good_versions;
2436 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2437 # This sub creates a BSR from the information in the restore_list
2438 # Returns the BSR as a string
2443 # This query gets all jobid/jobmedia/media combination.
2445 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2446 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2447 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2448 Media.Volumename, Media.MediaType
2449 FROM Job, JobMedia, Media
2450 WHERE Job.JobId = JobMedia.JobId
2451 AND JobMedia.MediaId = Media.MediaId
2452 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2455 my $result = $self->dbh_selectall_arrayref($query);
2457 # We will store everything hashed by jobid.
2459 foreach my $refrow (@$result)
2461 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2462 $firstindex, $lastindex, $startblock, $endblock,
2463 $volindex, $volumename, $mediatype) = @{$refrow};
2465 # We just have to deal with the case where starfile != endfile
2466 # In this case, we concatenate both, for the bsr
2467 if ($startfile != $endfile) {
2468 $startfile = $startfile . '-' . $endfile;
2472 ($jobid, $volsessionid, $volsessiontime, $startfile,
2473 $firstindex, $lastindex, $startblock .'-'. $endblock,
2474 $volindex, $volumename, $mediatype);
2476 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2480 # reminder : restore_list looks like this :
2481 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2483 # Here, we retrieve every file/dir that could be in the restore
2484 # We do as simple as possible for the SQL engine (no crazy joins,
2485 # no pseudo join (>= FirstIndex ...), etc ...
2486 # We do a SQL union of all the files/dirs specified in the restore_list
2488 foreach my $entry (@{$self->{restore_list}->{data}})
2490 if ($entry->[2] eq 'dir')
2492 my $dir = unpack('u', $entry->[0]);
2493 my $inclause = $entry->[3]; #curjobids
2496 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2497 FROM File, Path, Filename
2498 WHERE Path.PathId = File.PathId
2499 AND File.FilenameId = Filename.FilenameId
2500 AND Path.Path LIKE '$dir%'
2501 AND File.JobId IN ($inclause) )";
2502 push @select_queries,($query);
2506 # It's a file. Great, we allready have most
2507 # of what is needed. Simple and efficient query
2508 my $file = unpack('u', $entry->[0]);
2509 my @file = split '/',$file;
2511 my $dir = join('/',@file);
2513 my $jobid = $entry->[1];
2514 my $fileindex = $entry->[7];
2515 my $inclause = $entry->[3]; # curjobids
2517 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2518 FROM File, Path, Filename
2519 WHERE Path.PathId = File.PathId
2520 AND File.FilenameId = Filename.FilenameId
2521 AND Path.Path = '$dir/'
2522 AND Filename.Name = '$file'
2523 AND File.JobId = $jobid)";
2524 push @select_queries,($query);
2527 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2529 print STDERR $query,"\n" if $debug;
2531 #Now we run the query and parse the result...
2532 # there may be a lot of records, so we better be efficient
2533 # We use the bind column method, working with references...
2535 my $sth = $self->dbh_prepare($query);
2538 my ($path,$name,$fileindex,$jobid);
2539 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2541 # The temp place we're going to save all file
2542 # list to before the real list
2546 while ($sth->fetchrow_arrayref())
2548 # This may look dumb, but we're going to do a join by ourselves,
2549 # to save memory and avoid sending a complex query to mysql
2550 my $complete_path = $path . $name;
2558 # Remove trailing slash (normalize file and dir name)
2559 $complete_path =~ s/\/$//;
2561 # Let's find the ref(s) for the %mediainfo element(s)
2562 # containing the data for this file
2563 # There can be several matches. It is the pseudo join.
2565 my $max_elt=@{$mediainfos{$jobid}}-1;
2567 while($med_idx <= $max_elt)
2569 my $ref = $mediainfos{$jobid}->[$med_idx];
2570 # First, can we get rid of the first elements of the
2571 # array ? (if they don't contain valuable records
2573 if ($fileindex > $ref->[5])
2575 # It seems we don't need anymore
2576 # this entry in %mediainfo (the input data
2579 shift @{$mediainfos{$jobid}};
2583 # We will do work on this elt. We can ++
2584 # $med_idx for next loop
2587 # %mediainfo row looks like :
2588 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2589 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2592 # We are in range. We store and continue looping
2594 if ($fileindex >= $ref->[4])
2596 my @data = ($complete_path,$is_dir,
2598 push @temp_list,(\@data);
2602 # We are not in range. No point in continuing looping
2603 # We go to next record.
2607 # Now we have the array.
2608 # We're going to sort it, by
2609 # path, volsessiontime DESC (get the most recent file...)
2610 # The array rows look like this :
2611 # complete_path,is_dir,fileindex,
2612 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2613 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2614 @temp_list = sort {$a->[0] cmp $b->[0]
2615 || $b->[3]->[2] <=> $a->[3]->[2]
2619 my $prev_complete_path='////'; # Sure not to match
2623 while (my $refrow = shift @temp_list)
2625 # For the sake of readability, we load $refrow
2626 # contents in real scalars
2627 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2628 my $jobid= $refother->[0]; # We don't need the rest...
2630 # We skip this entry.
2631 # We allready have a newer one and this
2632 # isn't a continuation of the same file
2633 next if ($complete_path eq $prev_complete_path
2634 and $jobid != $prev_jobid);
2638 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2640 # We would be recursing inside a file.
2641 # Just what we don't want (dir replaced by file
2642 # between two backups
2648 push @restore_list,($refrow);
2650 $prev_complete_path = $complete_path;
2651 $prev_jobid = $jobid;
2657 push @restore_list,($refrow);
2659 $prev_complete_path = $complete_path;
2660 $prev_jobid = $jobid;
2664 # We get rid of @temp_list... save memory
2667 # Ok everything is in the list. Let's sort it again in another way.
2668 # This time it will be in the bsr file order
2670 # we sort the results by
2671 # volsessiontime, volsessionid, volindex, fileindex
2672 # to get all files in right order...
2673 # Reminder : The array rows look like this :
2674 # complete_path,is_dir,fileindex,
2675 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2676 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2678 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2679 || $a->[3]->[1] <=> $b->[3]->[1]
2680 || $a->[3]->[7] <=> $b->[3]->[7]
2681 || $a->[2] <=> $b->[2] }
2684 # Now that everything is ready, we create the bsr
2685 my $prev_fileindex=-1;
2686 my $prev_volsessionid=-1;
2687 my $prev_volsessiontime=-1;
2688 my $prev_volumename=-1;
2689 my $prev_volfile=-1;
2693 my $first_of_current_range=0;
2694 my @fileindex_ranges;
2697 foreach my $refrow (@restore_list)
2699 my (undef,undef,$fileindex,$refother)=@{$refrow};
2700 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2701 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2703 # We can specifiy the number of files in each section of the
2704 # bsr to speedup restore (bacula can then jump over the
2705 # end of tape files.
2709 if ($prev_volumename eq '-1')
2711 # We only have to start the new range...
2712 $first_of_current_range=$fileindex;
2714 elsif ($prev_volsessionid != $volsessionid
2715 or $prev_volsessiontime != $volsessiontime
2716 or $prev_volumename ne $volumename
2717 or $prev_volfile ne $volfile)
2719 # We have to create a new section in the bsr...
2720 # We print the previous one ...
2721 # (before that, save the current range ...)
2722 if ($first_of_current_range != $prev_fileindex)
2725 push @fileindex_ranges,
2726 ("$first_of_current_range-$prev_fileindex");
2730 # We are out of a range,
2731 # but there is only one element in the range
2732 push @fileindex_ranges,
2733 ("$first_of_current_range");
2736 $bsr.=print_bsr_section(\@fileindex_ranges,
2738 $prev_volsessiontime,
2745 # Reset for next loop
2746 @fileindex_ranges=();
2747 $first_of_current_range=$fileindex;
2749 elsif ($fileindex-1 != $prev_fileindex)
2751 # End of a range of fileindexes
2752 if ($first_of_current_range != $prev_fileindex)
2755 push @fileindex_ranges,
2756 ("$first_of_current_range-$prev_fileindex");
2760 # We are out of a range,
2761 # but there is only one element in the range
2762 push @fileindex_ranges,
2763 ("$first_of_current_range");
2765 $first_of_current_range=$fileindex;
2767 $prev_fileindex=$fileindex;
2768 $prev_volsessionid = $volsessionid;
2769 $prev_volsessiontime = $volsessiontime;
2770 $prev_volumename = $volumename;
2771 $prev_volfile=$volfile;
2772 $prev_mediatype=$mediatype;
2773 $prev_volblocks=$volblocks;
2777 # Ok, we're out of the loop. Alas, there's still the last record ...
2778 if ($first_of_current_range != $prev_fileindex)
2781 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2786 # We are out of a range,
2787 # but there is only one element in the range
2788 push @fileindex_ranges,("$first_of_current_range");
2791 $bsr.=print_bsr_section(\@fileindex_ranges,
2793 $prev_volsessiontime,
2803 sub print_bsr_section
2805 my ($ref_fileindex_ranges,$volsessionid,
2806 $volsessiontime,$volumename,$volfile,
2807 $mediatype,$volblocks,$count)=@_;
2810 $bsr .= "Volume=\"$volumename\"\n";
2811 $bsr .= "MediaType=\"$mediatype\"\n";
2812 $bsr .= "VolSessionId=$volsessionid\n";
2813 $bsr .= "VolSessionTime=$volsessiontime\n";
2814 $bsr .= "VolFile=$volfile\n";
2815 $bsr .= "VolBlock=$volblocks\n";
2817 foreach my $range (@{$ref_fileindex_ranges})
2819 $bsr .= "FileIndex=$range\n";
2822 $bsr .= "Count=$count\n";
2826 # This function estimates the size to be restored for an entry of the restore
2828 # In : self,reference to the entry
2829 # Out : size in bytes, number of files
2830 sub estimate_restore_size
2832 # reminder : restore_list looks like this :
2833 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2837 if ($entry->[2] eq 'dir')
2839 my $dir = unpack('u', $entry->[0]);
2840 my $inclause = $entry->[3]; #curjobids
2842 "SELECT Path.Path, File.FilenameId, File.LStat
2843 FROM File, Path, Job
2844 WHERE Path.PathId = File.PathId
2845 AND File.JobId = Job.JobId
2846 AND Path.Path LIKE '$dir%'
2847 AND File.JobId IN ($inclause)
2848 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2852 # It's a file. Great, we allready have most
2853 # of what is needed. Simple and efficient query
2854 my $file = unpack('u', $entry->[0]);
2855 my @file = split '/',$file;
2857 my $dir = join('/',@file);
2859 my $jobid = $entry->[1];
2860 my $fileindex = $entry->[7];
2861 my $inclause = $entry->[3]; # curjobids
2863 "SELECT Path.Path, File.FilenameId, File.Lstat
2864 FROM File, Path, Filename
2865 WHERE Path.PathId = File.PathId
2866 AND Path.Path = '$dir/'
2867 AND Filename.Name = '$file'
2868 AND File.JobId = $jobid
2869 AND Filename.FilenameId = File.FilenameId";
2872 print STDERR $query,"\n" if $debug;
2873 my ($path,$nameid,$lstat);
2874 my $sth = $self->dbh_prepare($query);
2876 $sth->bind_columns(\$path,\$nameid,\$lstat);
2886 while ($sth->fetchrow_arrayref())
2888 # Only the latest version of a file
2889 next if ($nameid eq $old_nameid and $path eq $old_path);
2891 if ($rcount > 15000) {
2898 # We get the size of this file
2899 my $size=lstat_attrib($lstat,'st_size');
2900 $total_size += $size;
2903 $old_nameid=$nameid;
2905 return ($total_size,$total_files);
2908 sub update_brestore_table
2910 my ($self, @jobs) = @_;
2911 my $dbh = $self->{dbh};
2913 foreach my $job (sort {$a <=> $b} @jobs)
2915 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2916 my $retour = $self->dbh_selectrow_arrayref($query);
2917 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2919 print STDERR "Inserting path records for JobId $job\n";
2920 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2921 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2923 $self->dbh_do($query);
2925 # Now we have to do the directory recursion stuff to determine missing visibility
2926 # We try to avoid recursion, to be as fast as possible
2927 # We also only work on not allready hierarchised directories...
2929 print STDERR "Creating missing recursion paths for $job\n";
2931 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2932 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2933 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2934 WHERE brestore_pathvisibility.JobId = $job
2935 AND brestore_pathhierarchy.PathId IS NULL
2938 my $sth = $self->dbh_prepare($query);
2940 my $pathid; my $path;
2941 $sth->bind_columns(\$pathid,\$path);
2945 $self->build_path_hierarchy($path,$pathid);
2949 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2950 # This query gives all parent pathids for a given jobid that aren't stored.
2951 # It has to be called until no record is updated ...
2953 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2954 SELECT a.PathId,$job
2956 (SELECT DISTINCT h.PPathId AS PathId
2957 FROM brestore_pathhierarchy AS h
2958 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2959 WHERE p.JobId=$job) AS a
2962 FROM brestore_pathvisibility
2963 WHERE JobId=$job) AS b
2964 ON (a.PathId = b.PathId)
2965 WHERE b.PathId IS NULL)";
2966 print STDERR $query,"\n" if ($debug);
2968 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2970 print STDERR "Recursively adding $rows_affected records from $job\n";
2973 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2978 sub cleanup_brestore_table
2981 my $dbh = $self->{dbh};
2983 my $query = "SELECT JobId from brestore_knownjobid";
2984 my @jobs = @{$dbh->selectall_arrayref($query)};
2986 foreach my $jobentry (@jobs)
2988 my $job = $jobentry->[0];
2989 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2990 my $result = $dbh->selectall_arrayref($query);
2991 if (scalar(@{$result}))
2993 # There are still files for this jobid
2994 print STDERR "$job still exists. Not cleaning...\n";
2997 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2999 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
3005 sub build_path_hierarchy
3007 my ($self, $path,$pathid)=@_;
3008 # Does the ppathid exist for this ? we use a memory cache...
3009 # In order to avoid the full loop, we consider that if a dir is allready in the
3010 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
3013 #print STDERR "$path\n" if $debug;
3014 if (! $self->{cache_ppathid}->{$pathid})
3016 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
3017 my $sth2 = $self->{dbh}->prepare_cached($query);
3018 $sth2->execute($pathid);
3019 # Do we have a result ?
3020 if (my $refrow = $sth2->fetchrow_arrayref)
3022 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
3024 # This dir was in the db ...
3025 # It means we can leave, the tree has allready been built for
3030 # We have to create the record ...
3031 # What's the current p_path ?
3032 my $ppath = parent_dir($path);
3033 my $ppathid = $self->return_pathid_from_path($ppath);
3034 $self->{cache_ppathid}->{$pathid}= $ppathid;
3036 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
3037 $sth2 = $self->{dbh}->prepare_cached($query);
3038 $sth2->execute($pathid,$ppathid);
3044 # It's allready in the cache.
3045 # We can leave, no time to waste here, all the parent dirs have allready
3053 sub return_pathid_from_path
3055 my ($self, $path) = @_;
3056 my $query = "SELECT PathId FROM Path WHERE Path = ?
3058 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3059 #print STDERR $query,"\n" if $debug;
3060 my $sth = $self->{dbh}->prepare_cached($query);
3061 $sth->execute($path,$path);
3062 my $result =$sth->fetchrow_arrayref();
3064 if (defined $result)
3066 return $result->[0];
3069 # A bit dirty : we insert into path AND missing_path, to be sure
3070 # we aren't deleted by a purge. We still need to insert into path to get
3071 # the pathid, because of mysql
3072 $query = "INSERT INTO Path (Path) VALUES (?)";
3073 #print STDERR $query,"\n" if $debug;
3074 $sth = $self->{dbh}->prepare_cached($query);
3075 $sth->execute($path);
3078 $query = " INSERT INTO brestore_missing_path (PathId,Path)
3079 SELECT PathId,Path FROM Path WHERE Path = ?";
3080 #print STDERR $query,"\n" if $debug;
3081 $sth = $self->{dbh}->prepare_cached($query);
3082 $sth->execute($path);
3084 $query = " DELETE FROM Path WHERE Path = ?";
3085 #print STDERR $query,"\n" if $debug;
3086 $sth = $self->{dbh}->prepare_cached($query);
3087 $sth->execute($path);
3089 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3090 #print STDERR $query,"\n" if $debug;
3091 $sth = $self->{dbh}->prepare_cached($query);
3092 $sth->execute($path);
3093 $result = $sth->fetchrow_arrayref();
3095 return $result->[0];
3107 # Root Windows case :
3108 if ($path =~ /^[a-z]+:\/$/i)
3113 my @tmp = split('/',$path);
3114 # We remove the last ...
3116 my $tmp = join ('/',@tmp) . '/';
3122 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3123 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3124 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3125 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3126 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3127 'data_stream' => 15);;
3130 my ($attrib,$ref_attrib)=@_;
3131 return $ref_attrib->[$attrib_name_id{$attrib}];
3135 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
3137 my ($file, $attrib)=@_;
3139 if (defined $attrib_name_id{$attrib}) {
3141 my @d = split(' ', $file->[2]) ; # TODO : cache this
3143 return from_base64($d[$attrib_name_id{$attrib}]);
3145 } elsif ($attrib eq 'jobid') {
3149 } elsif ($attrib eq 'name') {
3154 die "Attribute not known : $attrib.\n";
3158 # Return the jobid or attribute asked for a dir
3161 my ($self,$dir,$attrib)=@_;
3163 my @dir = split('/',$dir,-1);
3164 my $refdir=$self->{dirtree}->{$self->current_client};
3166 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3168 die "Attribute not known : $attrib.\n";
3171 foreach my $subdir (@dir)
3173 $refdir = $refdir->[0]->{$subdir};
3176 # $refdir is now the reference to the dir's array
3177 # Is the a jobid in @CurrentJobIds where the lstat is
3178 # defined (we'll search in reverse order)
3179 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3181 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3183 if ($attrib eq 'jobid')
3189 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3190 return $attribs[$attrib_name_id{$attrib}+1];
3195 return 0; # We cannot get a good attribute.
3196 # This directory is here for the sake of visibility
3201 my ($lstat,$attrib)=@_;
3202 if ($lstat and defined $attrib_name_id{$attrib})
3204 my @d = split(' ', $lstat) ; # TODO : cache this
3205 return from_base64($d[$attrib_name_id{$attrib}]);
3212 # Base 64 functions, directly from recover.pl.
3214 # Karl Hakimian <hakimian@aha.com>
3215 # This section is also under GPL v2 or later.
3222 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3223 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3224 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3225 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3226 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3228 @base64_map = (0) x 128;
3230 for (my $i=0; $i<64; $i++) {
3231 $base64_map[ord($base64_digits[$i])] = $i;
3246 if (substr($where, 0, 1) eq '-') {
3248 $where = substr($where, 1);
3251 while ($where ne '') {
3253 my $d = substr($where, 0, 1);
3254 $val += $base64_map[ord(substr($where, 0, 1))];
3255 $where = substr($where, 1);
3263 my @attribs = split(' ',$lstat);
3264 foreach my $element (@attribs)
3266 $element = from_base64($element);
3275 ################################################################
3278 use base qw/DlgResto/;
3282 my ($class, $conf) = @_;
3283 my $self = bless {info => $conf}, $class;
3285 $self->{dbh} = $conf->{dbh};
3294 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3295 my $jobs = $self->dbh_selectall_arrayref($query);
3297 $self->update_brestore_table(map { $_->[0] } @$jobs);
3308 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3312 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3315 GetOptions("conf=s" => \$file_conf,
3316 "batch" => \$batch_mod,
3318 "help" => \&HELP_MESSAGE) ;
3320 my $p = new Pref($file_conf);
3322 if (! -f $file_conf) {
3327 my $b = new Batch($p);
3328 if ($p->connect_db()) {
3329 $b->set_dbh($p->{dbh});
3335 $glade_file = $p->{glade_file};
3337 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3338 if (-f "$path/$glade_file") {
3339 $glade_file = "$path/$glade_file" ;
3344 # gtk have lots of warning on stderr
3345 if ($^O eq 'MSWin32')
3348 open(STDERR, ">stderr.log");
3353 if ( -f $glade_file) {
3354 my $w = new DlgResto($p);
3357 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3358 "Can't find your brestore.glade (glade_file => '$glade_file')
3359 Please, edit your $file_conf to setup it." );
3361 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3366 Gtk2->main; # Start Gtk2 main loop
3378 # Code pour trier les colonnes
3379 my $mod = $fileview->get_model();
3380 $mod->set_default_sort_func(sub {
3381 my ($model, $item1, $item2) = @_;
3382 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3383 my $b = $model->get($item2, 1); # colonne (indice 1)
3388 $fileview->set_headers_clickable(1);
3389 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3390 $col->signal_connect('clicked', sub {
3391 my ($colonne, $model) = @_;
3392 $model->set_sort_column_id (1, 'ascending');