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$';
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");
428 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
429 "Your job have been submited to bacula.
430 To follow it, you must use bconsole (or install/configure bweb)");
435 $self->on_cancel_resto_clicked();
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"
548 if ($dst =~ m!file:/(/.+)!) {
549 $ret = copy($src, $1);
551 $dstfile = "$1/" . basename($src) ;
553 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
554 $err = `scp $src $1 2>&1` ;
556 $dstfile = "$2/" . basename($src) ;
560 $err = "$dst not implemented yet";
561 File::Copy::copy($src, \*STDOUT);
564 $self->{error} = $err;
567 $self->{error} = $err;
576 ################################################################
584 unless ($about_widget) {
585 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
586 $about_widget = $glade_box->get_widget("dlg_about") ;
587 $glade_box->signal_autoconnect_from_package('DlgAbout');
589 $about_widget->show() ;
592 sub on_about_okbutton_clicked
594 $about_widget->hide() ;
599 ################################################################
605 my ($class, $config_file) = @_;
608 config_file => $config_file,
609 password => '', # db passwd
610 username => '', # db username
611 connection_string => '',# db connection string
612 bconsole => 'bconsole', # path and arg to bconsole
613 bsr_dest => '', # destination url for bsr files
614 debug => 0, # debug level 0|1
615 use_ok_bkp_only => 1, # dont use bad backup
616 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
617 glade_file => $glade_file,
618 see_all_versions => 0, # display all file versions in FileInfo
619 mozilla => 'mozilla', # mozilla bin
620 default_restore_job => 'restore', # regular expression to select default
623 # keywords that are used to fill DlgPref
624 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
625 entry_keyword => [ qw/username password bweb mozilla
626 connection_string default_restore_job
627 bconsole bsr_dest glade_file/],
630 $self->read_config();
639 # We read the parameters. They come from the configuration files
640 my $cfgfile ; my $tmpbuffer;
641 if (open FICCFG, $self->{config_file})
643 while(read FICCFG,$tmpbuffer,4096)
645 $cfgfile .= $tmpbuffer;
649 no strict; # I have no idea of the contents of the file
650 eval '$refparams' . " = $cfgfile";
653 for my $p (keys %{$refparams}) {
654 $self->{$p} = $refparams->{$p};
657 if (defined $self->{debug}) {
658 $debug = $self->{debug} ;
661 # TODO : Force dumb default values and display a message
671 for my $k (@{ $self->{entry_keyword} }) {
672 $parameters{$k} = $self->{$k};
675 for my $k (@{ $self->{chk_keyword} }) {
676 $parameters{$k} = $self->{$k};
679 if (open FICCFG,">$self->{config_file}")
681 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
686 # TODO : Display a message
695 $self->{dbh}->disconnect() ;
699 delete $self->{error};
701 if (not $self->{connection_string})
703 # The parameters have not been set. Maybe the conf
704 # file is empty for now
705 $self->{error} = "No configuration found for database connection. " .
706 "Please set this up.";
711 $self->{dbh} = DBI->connect($self->{connection_string},
716 $self->{error} = "Can't open bacula database. " .
717 "Database connect string '" .
718 $self->{connection_string} ."' $!";
721 $self->{dbh}->{RowCacheSize}=100;
727 my ($self, $url, $msg) = @_;
729 unless ($self->{mozilla} and $self->{bweb}) {
730 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
734 if ($^O eq 'MSWin32') {
735 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
738 system("$self->{mozilla} -remote 'Ping()'");
739 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
741 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
751 ################################################################
755 # my $pref = new Pref(config_file => 'brestore.conf');
756 # my $dlg = new DlgPref($pref);
757 # my $dlg_resto = new DlgResto($pref);
758 # $dlg->display($dlg_resto);
761 my ($class, $pref) = @_;
764 pref => $pref, # Pref ref
765 dlgresto => undef, # DlgResto ref
773 my ($self, $dlgresto) = @_ ;
775 unless ($self->{glade}) {
776 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
777 $self->{glade}->signal_autoconnect_from_package($self);
780 $self->{dlgresto} = $dlgresto;
782 my $g = $self->{glade};
783 my $p = $self->{pref};
785 for my $k (@{ $p->{entry_keyword} }) {
786 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
789 for my $k (@{ $p->{chk_keyword} }) {
790 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
793 $g->get_widget("dlg_pref")->show_all() ;
796 sub on_applybutton_clicked
799 my $glade = $self->{glade};
800 my $pref = $self->{pref};
802 for my $k (@{ $pref->{entry_keyword} }) {
803 my $w = $glade->get_widget("entry_$k") ;
804 $pref->{$k} = $w->get_text();
807 for my $k (@{ $pref->{chk_keyword} }) {
808 my $w = $glade->get_widget("chkbp_$k") ;
809 $pref->{$k} = $w->get_active();
812 $pref->write_config();
813 if ($pref->connect_db()) {
814 $self->{dlgresto}->set_dbh($pref->{dbh});
815 $self->{dlgresto}->set_status('Preferences updated');
816 $self->{dlgresto}->init_server_backup_combobox();
817 $self->{dlgresto}->create_brestore_tables();
818 $self->{dlgresto}->set_status($pref->{error});
820 $self->{dlgresto}->set_status($pref->{error});
824 # Handle prefs ok click (apply/dismiss dialog)
825 sub on_okbutton_clicked
828 $self->on_applybutton_clicked();
830 unless ($self->{pref}->{error}) {
831 $self->on_cancelbutton_clicked();
834 sub on_dialog_delete_event
837 $self->on_cancelbutton_clicked();
841 sub on_cancelbutton_clicked
844 $self->{glade}->get_widget('dlg_pref')->hide();
845 delete $self->{dlgresto};
849 ################################################################
859 # Kept as is from the perl-gtk example. Draws the pretty icons
865 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
866 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
867 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
868 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
872 # init combo (and create ListStore object)
875 my ($widget, @type) = @_ ;
876 my %type_info = ('text' => 'Glib::String',
877 'markup' => 'Glib::String',
880 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
882 $widget->set_model($lst);
886 if ($t eq 'text' or $t eq 'markup') {
887 $cell = new Gtk2::CellRendererText();
889 $widget->pack_start($cell, 1);
890 $widget->add_attribute($cell, $t, $i++);
895 # fill simple combo (one element per row)
898 my ($list, @what) = @_;
902 foreach my $w (@what)
905 my $i = $list->append();
906 $list->set($i, 0, $w);
913 my @unit = qw(b Kb Mb Gb Tb);
916 my $format = '%i %s';
917 while ($val / 1024 > 1) {
921 $format = ($i>0)?'%0.1f %s':'%i %s';
922 return sprintf($format, $val, $unit[$i]);
927 my ($self, $dbh) = @_;
933 my ($fileview) = shift;
934 my $fileview_target_entry = {target => 'STRING',
935 flags => ['GTK_TARGET_SAME_APP'],
938 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
939 ['copy'],$fileview_target_entry);
940 $fileview->get_selection->set_mode('multiple');
942 # set some useful SimpleList properties
943 $fileview->set_headers_clickable(0);
944 foreach ($fileview->get_columns())
946 $_->set_resizable(1);
947 $_->set_sizing('grow-only');
953 my ($self, $what) = @_;
957 print Data::Dumper::Dumper($what);
958 } elsif (defined $what) {
966 my ($self, $query) = @_;
967 $self->debug($query);
968 return $self->{dbh}->prepare($query);
973 my ($self, $query) = @_;
974 $self->debug($query);
975 return $self->{dbh}->do($query);
978 sub dbh_selectall_arrayref
980 my ($self, $query) = @_;
981 $self->debug($query);
982 return $self->{dbh}->selectall_arrayref($query);
985 sub dbh_selectrow_arrayref
987 my ($self, $query) = @_;
988 $self->debug($query);
989 return $self->{dbh}->selectrow_arrayref($query);
994 my ($class, $pref) = @_;
999 location => undef, # location entry widget
1000 mainwin => undef, # mainwin widget
1001 filelist_file_menu => undef, # file menu widget
1002 filelist_dir_menu => undef, # dir menu widget
1003 glade => undef, # glade object
1004 status => undef, # status bar widget
1005 dlg_pref => undef, # DlgPref object
1006 fileattrib => {}, # cache file
1007 fileview => undef, # fileview widget SimpleList
1008 fileinfo => undef, # fileinfo widget SimpleList
1010 client_combobox => undef, # client_combobox widget
1011 restore_backup_combobox => undef, # date combobox widget
1012 list_client => undef, # Gtk2::ListStore
1013 list_backup => undef, # Gtk2::ListStore
1014 cache_ppathid => {}, #
1017 # load menu (to use handler with self reference)
1018 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1019 $glade->signal_autoconnect_from_package($self);
1020 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1022 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1023 $glade->signal_autoconnect_from_package($self);
1024 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1026 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1027 $glade->signal_autoconnect_from_package($self);
1029 $self->{status} = $glade->get_widget('statusbar');
1030 $self->{mainwin} = $glade->get_widget('dlg_resto');
1031 $self->{location} = $glade->get_widget('entry_location');
1032 $self->render_icons();
1034 $self->{dlg_pref} = new DlgPref($pref);
1036 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1037 $self->{list_client} = init_combo($c, 'text');
1039 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1040 $self->{list_backup} = init_combo($c, 'text', 'markup');
1042 # Connect glade-fileview to Gtk2::SimpleList
1043 # and set up drag n drop between $fileview and $restore_list
1045 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1046 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1048 my $widget = $glade->get_widget('fileview');
1049 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1051 'h_name' => 'hidden',
1052 'h_jobid' => 'hidden',
1053 'h_type' => 'hidden',
1056 'File Name' => 'text',
1059 init_drag_drop($fileview);
1060 $fileview->set_search_column(4); # search on File Name
1062 # Connect glade-restore_list to Gtk2::SimpleList
1063 $widget = $glade->get_widget('restorelist');
1064 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1066 'h_name' => 'hidden',
1067 'h_jobid' => 'hidden',
1068 'h_type' => 'hidden',
1069 'h_curjobid' => 'hidden',
1072 'File Name' => 'text',
1074 'FileIndex' => 'text',
1076 'Nb Files' => 'text', #8
1077 'Size' => 'text', #9
1078 'size_b' => 'hidden', #10
1081 my @restore_list_target_table = ({'target' => 'STRING',
1085 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1086 $restore_list->get_selection->set_mode('multiple');
1088 $widget = $glade->get_widget('infoview');
1089 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1091 'h_name' => 'hidden',
1092 'h_jobid' => 'hidden',
1093 'h_type' => 'hidden',
1095 'InChanger' => 'pixbuf',
1102 init_drag_drop($infoview);
1104 $pref->connect_db() || $self->{dlg_pref}->display($self);
1107 $self->{dbh} = $pref->{dbh};
1108 $self->init_server_backup_combobox();
1109 $self->create_brestore_tables();
1112 $self->set_status($pref->{error});
1115 # set status bar informations
1118 my ($self, $string) = @_;
1119 return unless ($string);
1121 my $context = $self->{status}->get_context_id('Main');
1122 $self->{status}->push($context, $string);
1125 sub on_time_select_changed
1133 my $c = $self->{glade}->get_widget('combo_time');
1134 return $c->get_active_text;
1137 # This sub returns all clients declared in DB
1141 my $query = "SELECT Name FROM Client ORDER BY Name";
1142 print STDERR $query,"\n" if $debug;
1144 my $result = $dbh->selectall_arrayref($query);
1146 return map { $_->[0] } @$result;
1149 sub get_wanted_job_status
1156 return "'T', 'A', 'E'";
1160 # This sub gives a full list of the EndTimes for a ClientId
1161 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
1162 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
1163 sub get_all_endtimes_for_job
1165 my ($dbh, $client, $ok_only)=@_;
1166 my $status = get_wanted_job_status($ok_only);
1168 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
1169 FROM Job,Client,FileSet
1170 WHERE Job.ClientId=Client.ClientId
1171 AND Client.Name = '$client'
1173 AND JobStatus IN ($status)
1174 AND Job.FileSetId = FileSet.FileSetId
1175 ORDER BY EndTime desc";
1176 print STDERR $query,"\n" if $debug;
1177 my $result = $dbh->selectall_arrayref($query);
1183 # init infoview widget
1187 @{$self->{fileinfo}->{data}} = ();
1191 sub on_clear_clicked
1194 @{$self->{restore_list}->{data}} = ();
1197 sub on_estimate_clicked
1204 # TODO : If we get here, things could get lenghty ... draw a popup window .
1205 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1206 'destroy-with-parent',
1208 'Computing size...');
1212 my $title = "Computing size...\n";
1214 foreach my $entry (@{$self->{restore_list}->{data}})
1216 unless ($entry->[9]) {
1217 my ($size, $nb) = $self->estimate_restore_size($entry);
1218 $entry->[10] = $size;
1219 $entry->[9] = human($size);
1223 my $name = unpack('u', $entry->[0]);
1225 $txt .= "\n<i>$name</i> : " . $entry->[8] . " file(s)/" . $entry->[9] ;
1226 $widget->set_markup($title . $txt);
1228 $size_total+=$entry->[10];
1229 $nb_total+=$entry->[8];
1233 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1234 $widget->set_markup("Size estimation :\n" . $txt);
1235 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1240 sub on_gen_bsr_clicked
1244 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1245 'gtk-save','ok', 'gtk-cancel', 'cancel');
1248 my $w = new Gtk2::FileChooserDialog ( @options );
1253 if ($a eq 'cancel') {
1258 my $f = $w->get_filename();
1260 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1261 'destroy-with-parent',
1262 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1263 if ($dlg->run() eq 'ok') {
1277 if (open(FP, ">$save")) {
1278 my $bsr = $self->create_filelist();
1281 $self->set_status("Dumping BSR to $save ok");
1283 $self->set_status("Can't dump BSR to $save: $!");
1288 use File::Temp qw/tempfile/;
1290 sub on_go_button_clicked
1293 unless (scalar(@{$self->{restore_list}->{data}})) {
1294 new DlgWarn("No file to restore");
1297 my $bsr = $self->create_filelist();
1298 my ($fh, $filename) = tempfile();
1301 chmod(0644, $filename);
1303 print "Dumping BSR info to $filename\n"
1306 # we get Volume list
1307 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1308 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1310 new DlgLaunch(pref => $self->{pref},
1312 bsr_file => $filename,
1317 our $client_list_empty = 'Clients list';
1318 our %type_markup = ('F' => '<b>$label F</b>',
1321 'B' => '<b>$label B</b>',
1323 'A' => '<span foreground=\"red\">$label</span>',
1325 'E' => '<span foreground=\"red\">$label</span>',
1328 sub on_list_client_changed
1330 my ($self, $widget) = @_;
1331 return 0 unless defined $self->{fileview};
1332 my $dbh = $self->{dbh};
1334 $self->{list_backup}->clear();
1336 if ($self->current_client eq $client_list_empty) {
1340 my @endtimes=get_all_endtimes_for_job($dbh,
1341 $self->current_client,
1342 $self->{pref}->{use_ok_bkp_only});
1343 foreach my $endtime (@endtimes)
1345 my $i = $self->{list_backup}->append();
1347 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1348 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1349 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1351 $self->{list_backup}->set($i,
1356 $self->{restore_backup_combobox}->set_active(0);
1358 $self->{CurrentJobIds} = [
1359 set_job_ids_for_date($dbh,
1360 $self->current_client,
1361 $self->current_date,
1362 $self->{pref}->{use_ok_bkp_only})
1365 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1368 $self->refresh_fileview();
1372 sub fill_server_list
1374 my ($dbh, $combo, $list) = @_;
1376 my @clients=get_all_clients($dbh);
1380 my $i = $list->append();
1381 $list->set($i, 0, $client_list_empty);
1383 foreach my $client (@clients)
1385 $i = $list->append();
1386 $list->set($i, 0, $client);
1388 $combo->set_active(0);
1391 sub init_server_backup_combobox
1394 fill_server_list($self->{dbh},
1395 $self->{client_combobox},
1396 $self->{list_client}) ;
1399 #----------------------------------------------------------------------
1400 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1401 #data isn't. There is additionnal complexity for dirs (visibility problems),
1402 #so the @CurrentJobIds is not sufficient.
1403 sub refresh_fileview
1406 my $fileview = $self->{fileview};
1407 my $client_combobox = $self->{client_combobox};
1408 my $cwd = $self->{cwd};
1410 @{$fileview->{data}} = ();
1412 $self->clear_infoview();
1414 my $client_name = $self->current_client;
1416 if (!$client_name or ($client_name eq $client_list_empty)) {
1417 $self->set_status("Client list empty");
1421 my @list_dirs = $self->list_dirs($cwd,$client_name);
1422 # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1423 my $files = $self->list_files($cwd);
1424 print "CWD : $cwd\n" if ($debug);
1426 my $file_count = 0 ;
1427 my $total_bytes = 0;
1429 # Add directories to view
1430 foreach my $dir_entry (@list_dirs) {
1431 #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
1432 my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime'));
1433 my $dir = $dir_entry->[0];
1434 $total_bytes += 4096;
1437 listview_push($fileview,
1439 $self->dir_attrib("$cwd/$dir",'jobid'),
1449 foreach my $file (@$files)
1451 my $size = file_attrib($file,'st_size');
1452 my $time = localtime(file_attrib($file,'st_mtime'));
1453 $total_bytes += $size;
1455 # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
1457 listview_push($fileview,
1464 human($size), $time);
1467 $self->set_status("$file_count files/" . human($total_bytes));
1469 # set a decent default selection (makes keyboard nav easy)
1470 $fileview->select(0);
1474 sub on_about_activate
1476 DlgAbout::display();
1481 my ($tree, $path, $data) = @_;
1483 my @items = listview_get_all($tree) ;
1485 foreach my $i (@items)
1487 my @file_info = @{$i};
1490 # Ok, we have a corner case :
1495 $file = pack("u", $file_info[0]);
1499 $file = pack("u", $path . '/' . $file_info[0]);
1501 push @ret, join(" ; ", $file,
1502 $file_info[1], # $jobid
1503 $file_info[2], # $type
1507 my $data_get = join(" :: ", @ret);
1509 $data->set_text($data_get,-1);
1512 sub fileview_data_get
1514 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1515 drag_set_info($widget, $self->{cwd}, $data);
1518 sub fileinfo_data_get
1520 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1521 drag_set_info($widget, $self->{cwd}, $data);
1524 sub restore_list_data_received
1526 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1529 if ($info eq 40 || $info eq 0) # patch for display!=:0
1531 foreach my $elt (split(/ :: /, $data->data()))
1534 my ($file, $jobid, $type) =
1536 $file = unpack("u", $file);
1538 $self->add_selected_file_to_list($file, $jobid, $type);
1543 sub on_back_button_clicked {
1547 sub on_location_go_button_clicked
1550 $self->ch_dir($self->{location}->get_text());
1552 sub on_quit_activate {Gtk2->main_quit;}
1553 sub on_preferences_activate
1556 $self->{dlg_pref}->display($self) ;
1558 sub on_main_delete_event {Gtk2->main_quit;}
1559 sub on_bweb_activate
1562 $self->set_status("Open bweb on your browser");
1563 $self->{pref}->go_bweb('', "go on bweb");
1566 # Change to parent directory
1570 if ($self->{cwd} eq '/')
1574 my @dirs = split(/\//, $self->{cwd});
1576 $self->ch_dir(join('/', @dirs));
1579 # Change the current working directory
1580 # * Updates fileview, location, and selection
1585 $self->{cwd} = shift;
1587 $self->refresh_fileview();
1588 $self->{location}->set_text($self->{cwd});
1593 # Handle dialog 'close' (window-decoration induced close)
1594 # * Just hide the dialog, and tell Gtk not to do anything else
1598 my ($self, $w) = @_;
1601 1; # consume this event!
1604 # Handle key presses in location text edit control
1605 # * Translate a Return/Enter key into a 'Go' command
1606 # * All other key presses left for GTK
1608 sub on_location_entry_key_release_event
1614 my $keypress = $event->keyval;
1615 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1616 $keypress == $Gtk2::Gdk::Keysyms{Return})
1618 $self->ch_dir($widget->get_text());
1620 return 1; # consume keypress
1623 return 0; # let gtk have the keypress
1626 sub on_fileview_key_press_event
1628 my ($self, $widget, $event) = @_;
1632 sub listview_get_first
1635 my @selected = $list->get_selected_indices();
1636 if (@selected > 0) {
1637 my ($name, @other) = @{$list->{data}->[$selected[0]]};
1638 return (unpack('u', $name), @other);
1644 sub listview_get_all
1648 my @selected = $list->get_selected_indices();
1650 for my $i (@selected) {
1651 my ($name, @other) = @{$list->{data}->[$i]};
1652 push @ret, [unpack('u', $name), @other];
1660 my ($list, $name, @other) = @_;
1661 push @{$list->{data}}, [pack('u', $name), @other];
1664 #----------------------------------------------------------------------
1665 # Handle keypress in file-view
1666 # * Translates backspace into a 'cd ..' command
1667 # * All other key presses left for GTK
1669 sub on_fileview_key_release_event
1671 my ($self, $widget, $event) = @_;
1672 if (not $event->keyval)
1676 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1678 return 1; # eat keypress
1681 return 0; # let gtk have keypress
1684 sub on_forward_keypress
1689 #----------------------------------------------------------------------
1690 # Handle double-click (or enter) on file-view
1691 # * Translates into a 'cd <dir>' command
1693 sub on_fileview_row_activated
1695 my ($self, $widget) = @_;
1697 my ($name, undef, $type, undef) = listview_get_first($widget);
1701 if ($self->{cwd} eq '')
1703 $self->ch_dir($name);
1705 elsif ($self->{cwd} eq '/')
1707 $self->ch_dir('/' . $name);
1711 $self->ch_dir($self->{cwd} . '/' . $name);
1715 $self->fill_infoview($self->{cwd}, $name);
1718 return 1; # consume event
1723 my ($self, $path, $file) = @_;
1724 $self->clear_infoview();
1725 my @v = get_all_file_versions($self->{dbh},
1728 $self->current_client,
1729 $self->{pref}->{see_all_versions});
1731 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
1733 my $icon = ($inchanger)?$yesicon:$noicon;
1735 $mtime = localtime($mtime) ;
1737 listview_push($self->{fileinfo},
1738 $file, $jobid, 'file',
1739 $icon, $volname, $jobid, human($size), $mtime, $md5);
1746 return $self->{restore_backup_combobox}->get_active_text;
1752 return $self->{client_combobox}->get_active_text;
1755 sub on_list_backups_changed
1757 my ($self, $widget) = @_;
1758 return 0 unless defined $self->{fileview};
1760 $self->{CurrentJobIds} = [
1761 set_job_ids_for_date($self->{dbh},
1762 $self->current_client,
1763 $self->current_date,
1764 $self->{pref}->{use_ok_bkp_only})
1766 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1768 $self->refresh_fileview();
1772 sub on_restore_list_keypress
1774 my ($self, $widget, $event) = @_;
1775 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1777 my @sel = $widget->get_selected_indices;
1778 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1780 splice @{$self->{restore_list}->{data}},$elt,1;
1785 sub on_fileview_button_press_event
1787 my ($self,$widget,$event) = @_;
1788 if ($event->button == 3)
1790 $self->on_right_click_filelist($widget,$event);
1794 if ($event->button == 2)
1796 $self->on_see_all_version();
1803 sub on_see_all_version
1807 my @lst = listview_get_all($self->{fileview});
1810 my ($name, undef) = @{$i};
1812 new DlgFileVersion($self->{dbh},
1813 $self->current_client,
1814 $self->{cwd}, $name);
1818 sub on_right_click_filelist
1820 my ($self,$widget,$event) = @_;
1821 # I need to know what's selected
1822 my @sel = listview_get_all($self->{fileview});
1827 $type = $sel[0]->[2]; # $type
1832 if (@sel >=2 or $type eq 'dir')
1834 # We have selected more than one or it is a directories
1835 $w = $self->{filelist_dir_menu};
1839 $w = $self->{filelist_file_menu};
1845 $event->button, $event->time);
1848 sub context_add_to_filelist
1852 my @sel = listview_get_all($self->{fileview});
1854 foreach my $i (@sel)
1856 my ($file, $jobid, $type, undef) = @{$i};
1857 $file = $self->{cwd} . '/' . $file;
1858 $self->add_selected_file_to_list($file, $jobid, $type);
1862 # Adds a file to the filelist
1863 sub add_selected_file_to_list
1865 my ($self, $name, $jobid, $type)=@_;
1867 my $dbh = $self->{dbh};
1868 my $restore_list = $self->{restore_list};
1870 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1877 if ($name and substr $name,-1 ne '/')
1879 $name .= '/'; # For bacula
1881 my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
1882 listview_push($restore_list,
1883 $name, $jobid, 'dir', $curjobids,
1884 $diricon, $name,$curjobids,$dirfileindex);
1886 elsif ($type eq 'file')
1888 my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
1890 listview_push($restore_list,
1891 $name, $jobid, 'file', $curjobids,
1892 $fileicon, $name, $jobid, $fileindex );
1896 # TODO : we want be able to restore files from a bad ended backup
1897 # we have JobStatus IN ('T', 'A', 'E') and we must
1899 # Data acces subs from here. Interaction with SGBD and caching
1901 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1902 # GUI and stores them in @CurrentJobIds
1903 sub set_job_ids_for_date
1905 my ($dbh, $client, $date, $only_ok)=@_;
1907 if (!$client or !$date) {
1911 my $status = get_wanted_job_status($only_ok);
1913 # The algorithm : for a client, we get all the backups for each
1914 # fileset, in reverse order Then, for each fileset, we store the 'good'
1915 # incrementals and differentials until we have found a full so it goes
1916 # like this : store all incrementals until we have found a differential
1917 # or a full, then find the full #
1919 my $query = "SELECT JobId, FileSet, Level, JobStatus
1920 FROM Job, Client, FileSet
1921 WHERE Job.ClientId = Client.ClientId
1922 AND FileSet.FileSetId = Job.FileSetId
1923 AND EndTime <= '$date'
1924 AND Client.Name = '$client'
1926 AND JobStatus IN ($status)
1927 ORDER BY FileSet, JobTDate DESC";
1929 print STDERR $query,"\n" if $debug;
1931 my $result = $dbh->selectall_arrayref($query);
1933 foreach my $refrow (@$result)
1935 my $jobid = $refrow->[0];
1936 my $fileset = $refrow->[1];
1937 my $level = $refrow->[2];
1939 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1941 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1945 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1946 push @CurrentJobIds,($jobid);
1948 elsif ($level eq 'D')
1950 next if $progress{$fileset} eq 'D'; # We allready have a differential
1951 push @CurrentJobIds,($jobid);
1953 elsif ($level eq 'F')
1955 push @CurrentJobIds,($jobid);
1958 my $status = $refrow->[3] ;
1959 if ($status eq 'T') { # good end of job
1960 $progress{$fileset} = $level;
1963 print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
1965 return @CurrentJobIds;
1968 # Lists all directories contained inside a directory.
1969 # Uses the current dir, the client name, and CurrentJobIds for visibility.
1970 # Returns an array of dirs
1973 my ($self,$dir,$client)=@_;
1975 print "list_dirs(<$dir>, <$client>)\n" if $debug;
1977 if ($dir ne '' and substr $dir,-1 ne '/')
1979 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1982 my $dbh = $self->{dbh};
1983 my $query = "SELECT PathId FROM Path WHERE Path = ?
1984 UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?";
1985 my $sth = $dbh->prepare($query);
1986 $sth->execute($dir,$dir);
1987 my $result = $sth->fetchrow_arrayref();
1989 my $pathid = $result->[0];
1990 my @jobids = @{$self->{CurrentJobIds}};
1991 my $jobclause = join (',', @jobids);
1992 # Let's retrieve the list of the visible dirs in this dir ...
1993 # First, I need the empty filenameid to locate efficiently the dirs in the file table
1994 $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
1995 $sth = $dbh->prepare($query);
1997 $result = $sth->fetchrow_arrayref();
1999 my $dir_filenameid = $result->[0];
2001 # Then we get all the dir entries from File ...
2002 # It's ugly because there are records in brestore_missing_path ...
2004 SELECT Path, JobId, Lstat FROM(
2006 SELECT Path.Path, lower(Path.Path),
2007 listfile.JobId, listfile.Lstat
2009 SELECT DISTINCT brestore_pathhierarchy.PathId
2010 FROM brestore_pathhierarchy
2012 ON (brestore_pathhierarchy.PathId = Path.PathId)
2013 JOIN brestore_pathvisibility
2014 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2015 WHERE brestore_pathhierarchy.PPathId = $pathid
2016 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2017 JOIN Path ON (listpath.PathId = Path.PathId)
2019 SELECT File.PathId, File.JobId, File.Lstat FROM File
2020 WHERE File.FilenameId = $dir_filenameid
2021 AND File.JobId IN ($jobclause)) AS listfile
2022 ON (listpath.PathId = listfile.PathId)
2024 SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path),
2025 listfile.JobId, listfile.Lstat
2027 SELECT DISTINCT brestore_pathhierarchy.PathId
2028 FROM brestore_pathhierarchy
2029 JOIN brestore_missing_path
2030 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2031 JOIN brestore_pathvisibility
2032 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2033 WHERE brestore_pathhierarchy.PPathId = $pathid
2034 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2035 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2037 SELECT File.PathId, File.JobId, File.Lstat FROM File
2038 WHERE File.FilenameId = $dir_filenameid
2039 AND File.JobId IN ($jobclause)) AS listfile
2040 ON (listpath.PathId = listfile.PathId))
2041 ORDER BY 2,3 DESC ) As a";
2042 print STDERR "$query\n" if $debug;
2043 $sth=$dbh->prepare($query);
2045 $result = $sth->fetchall_arrayref();
2048 foreach my $refrow (@{$result})
2050 my $dir = $refrow->[0];
2051 my $jobid = $refrow->[1];
2052 my $lstat = $refrow->[2];
2053 next if ($dir eq $prev_dir);
2054 # We have to clean up this dirname ... we only want it's 'basename'
2058 my @temp = split ('/',$dir);
2059 $return_value = pop @temp;
2063 $return_value = '/';
2065 my @return_array = ($return_value,$lstat);
2066 push @return_list,(\@return_array);
2069 return @return_list;
2073 # List all files in a directory. dir as parameter, CurrentJobIds for visibility
2074 # Returns an array of dirs
2077 my ($self, $dir)=@_;
2078 my $dbh = $self->{dbh};
2082 print "list_files($dir)\n" if $debug;
2084 if ($dir ne '' and substr $dir,-1 ne '/')
2086 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
2089 my $query = "SELECT Path.PathId
2091 WHERE Path.Path = '$dir'
2093 SELECT brestore_missing_path.PathId
2094 FROM brestore_missing_path
2095 WHERE brestore_missing_path.Path = '$dir'";
2096 print $query,"\n" if $debug;
2098 my $result = $dbh->selectall_arrayref($query);
2099 foreach my $refrow (@$result)
2101 push @list_pathid,($refrow->[0]);
2104 if (@list_pathid == 0)
2106 print "No pathid found for $dir\n" if $debug;
2110 my $inlistpath = join (',', @list_pathid);
2111 my $inclause = join (',', @{$self->{CurrentJobIds}});
2112 if ($inclause eq '')
2118 "SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
2120 (SELECT Filename.Name, max(File.FileId) as id
2122 WHERE File.FilenameId = Filename.FilenameId
2123 AND Filename.Name != ''
2124 AND File.PathId IN ($inlistpath)
2125 AND File.JobId IN ($inclause)
2126 GROUP BY Filename.Name
2127 ORDER BY Filename.Name) AS listfiles,
2129 WHERE File.FileId = listfiles.id";
2131 print STDERR $query,"\n" if $debug;
2132 $result = $dbh->selectall_arrayref($query);
2139 Gtk2->main_iteration while (Gtk2->events_pending);
2142 sub create_brestore_tables
2146 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2148 unless ($self->dbh_do($verif)) {
2149 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2151 $self->{error} = "Creating internal brestore tables";
2153 CREATE TABLE brestore_knownjobid
2155 JobId int4 NOT NULL,
2156 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2158 $self->dbh_do($req);
2161 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2162 unless ($self->dbh_do($verif)) {
2164 CREATE TABLE brestore_pathhierarchy
2166 PathId int4 NOT NULL,
2167 PPathId int4 NOT NULL,
2168 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2170 $self->dbh_do($req);
2173 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2174 ON brestore_pathhierarchy (PPathId)";
2175 $self->dbh_do($req);
2178 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2179 unless ($self->dbh_do($verif)) {
2181 CREATE TABLE brestore_pathvisibility
2183 PathId int4 NOT NULL,
2184 JobId int4 NOT NULL,
2185 Size int8 DEFAULT 0,
2186 Files int4 DEFAULT 0,
2187 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2189 $self->dbh_do($req);
2191 $req = "CREATE INDEX brestore_pathvisibility_jobid
2192 ON brestore_pathvisibility (JobId)";
2193 $self->dbh_do($req);
2196 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2197 unless ($self->dbh_do($verif)) {
2199 CREATE TABLE brestore_missing_path
2201 PathId int4 NOT NULL,
2203 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2205 $self->dbh_do($req);
2207 $req = "CREATE INDEX brestore_missing_path_path
2208 ON brestore_missing_path (Path)";
2209 $self->dbh_do($req);
2213 # Recursive function to calculate the visibility of each directory in the cache
2214 # tree Working with references to save time and memory
2215 # For each directory, we want to propagate it's visible jobids onto it's
2216 # parents directory.
2217 # A tree is visible if
2218 # - it's been in a backup pointed by the CurrentJobIds
2219 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2220 # In the second case, the directory is visible but has no metadata.
2221 # We symbolize this with lstat = 1 for this jobid in the cache.
2223 # Input : reference directory
2224 # Output : visibility of this dir. Has to know visibility of all subdirs
2225 # to know it's visibility, hence the recursing.
2231 # Get the subdirs array references list
2232 my @list_ref_subdirs;
2233 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2235 push @list_ref_subdirs,($ref_subdir);
2238 # Now lets recurse over these subdirs and retrieve the reference of a hash
2239 # containing the jobs where they are visible
2240 foreach my $ref_subdir (@list_ref_subdirs)
2242 my $ref_list_jobs = list_visible($ref_subdir);
2243 foreach my $jobid (keys %$ref_list_jobs)
2245 $visibility{$jobid}=1;
2249 # Ok. Now, we've got the list of those jobs. We are going to update our
2250 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2251 # the lstat for the known jobids. Put 1 in the new elements... But first,
2252 # let's store the current jobids
2254 foreach my $jobid (keys %{$refdir->[1]})
2256 push @known_jobids,($jobid);
2260 foreach my $jobid (keys %visibility)
2262 next if ($refdir->[1]->{$jobid});
2263 $refdir->[1]->{$jobid} = 1;
2265 # Add the known_jobids to %visibility
2266 foreach my $jobid (@known_jobids)
2268 $visibility{$jobid}=1;
2270 return \%visibility;
2273 # Returns the list of media required for a list of jobids.
2274 # Input : dbh, jobid1, jobid2...
2275 # Output : reference to array of (joibd, inchanger)
2276 sub get_required_media_from_jobid
2278 my ($dbh, @jobids)=@_;
2279 my $inclause = join(',',@jobids);
2281 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2282 FROM JobMedia, Media
2283 WHERE JobMedia.MediaId=Media.MediaId
2284 AND JobId In ($inclause)
2286 my $result = $dbh->selectall_arrayref($query);
2290 # Returns the fileindex from dirname and jobid.
2291 # Input : dbh, dirname, jobid
2292 # Output : fileindex
2293 sub get_fileindex_from_dir_jobid
2295 my ($dbh, $dirname, $jobid)=@_;
2297 $query = "SELECT File.FileIndex
2298 FROM File, Filename, Path
2299 WHERE File.FilenameId = Filename.FilenameId
2300 AND File.PathId = Path.PathId
2301 AND Filename.Name = ''
2302 AND Path.Path = '$dirname'
2303 AND File.JobId = '$jobid'
2306 print STDERR $query,"\n" if $debug;
2307 my $result = $dbh->selectall_arrayref($query);
2308 return $result->[0]->[0];
2311 # Returns the fileindex from filename and jobid.
2312 # Input : dbh, filename, jobid
2313 # Output : fileindex
2314 sub get_fileindex_from_file_jobid
2316 my ($dbh, $filename, $jobid)=@_;
2318 my @dirs = split(/\//, $filename);
2319 $filename=pop(@dirs);
2320 my $dirname = join('/', @dirs) . '/';
2325 "SELECT File.FileIndex
2326 FROM File, Filename, Path
2327 WHERE File.FilenameId = Filename.FilenameId
2328 AND File.PathId = Path.PathId
2329 AND Filename.Name = '$filename'
2330 AND Path.Path = '$dirname'
2331 AND File.JobId = '$jobid'";
2333 print STDERR $query,"\n" if $debug;
2334 my $result = $dbh->selectall_arrayref($query);
2335 return $result->[0]->[0];
2339 # Returns list of versions of a file that could be restored
2340 # returns an array of
2341 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2342 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2343 # and volname and md5
2344 # and of course, there will be only one jobid in the array of jobids...
2345 sub get_all_file_versions
2347 my ($dbh,$path,$file,$client,$see_all)=@_;
2349 defined $see_all or $see_all=0;
2354 "SELECT File.JobId, File.FileIndex, File.Lstat,
2355 File.Md5, Media.VolumeName, Media.InChanger
2356 FROM File, Filename, Path, Job, Client, JobMedia, Media
2357 WHERE File.FilenameId = Filename.FilenameId
2358 AND File.PathId=Path.PathId
2359 AND File.JobId = Job.JobId
2360 AND Job.ClientId = Client.ClientId
2361 AND Job.JobId = JobMedia.JobId
2362 AND File.FileIndex >= JobMedia.FirstIndex
2363 AND File.FileIndex <= JobMedia.LastIndex
2364 AND JobMedia.MediaId = Media.MediaId
2365 AND Path.Path = '$path'
2366 AND Filename.Name = '$file'
2367 AND Client.Name = '$client'";
2369 print STDERR $query if $debug;
2371 my $result = $dbh->selectall_arrayref($query);
2373 foreach my $refrow (@$result)
2375 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2376 my @attribs = parse_lstat($lstat);
2377 my $mtime = array_attrib('st_mtime',\@attribs);
2378 my $size = array_attrib('st_size',\@attribs);
2380 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2381 $inchanger, $md5, $volname);
2382 push @versions, (\@list);
2385 # We have the list of all versions of this file.
2386 # We'll sort it by mtime desc, size, md5, inchanger desc
2387 # the rest of the algorithm will be simpler
2388 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2389 @versions = sort { $b->[4] <=> $a->[4]
2390 || $a->[5] <=> $b->[5]
2391 || $a->[7] cmp $a->[7]
2392 || $b->[6] <=> $a->[6]} @versions;
2395 my %allready_seen_by_mtime;
2396 my %allready_seen_by_md5;
2397 # Now we should create a new array with only the interesting records
2398 foreach my $ref (@versions)
2402 # The file has a md5. We compare his md5 to other known md5...
2403 # We take size into account. It may happen that 2 files
2404 # have the same md5sum and are different. size is a supplementary
2407 # If we allready have a (better) version
2408 next if ( (not $see_all)
2409 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2411 # we never met this one before...
2412 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2414 # Even if it has a md5, we should also work with mtimes
2415 # We allready have a (better) version
2416 next if ( (not $see_all)
2417 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2418 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2420 # We reached there. The file hasn't been seen.
2421 push @good_versions,($ref);
2424 # To be nice with the user, we re-sort good_versions by
2425 # inchanger desc, mtime desc
2426 @good_versions = sort { $b->[4] <=> $a->[4]
2427 || $b->[2] <=> $a->[2]} @good_versions;
2429 return @good_versions;
2432 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2433 # This sub creates a BSR from the information in the restore_list
2434 # Returns the BSR as a string
2439 # This query gets all jobid/jobmedia/media combination.
2441 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2442 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2443 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2444 Media.Volumename, Media.MediaType
2445 FROM Job, JobMedia, Media
2446 WHERE Job.JobId = JobMedia.JobId
2447 AND JobMedia.MediaId = Media.MediaId
2448 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2451 my $result = $self->dbh_selectall_arrayref($query);
2453 # We will store everything hashed by jobid.
2455 foreach my $refrow (@$result)
2457 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2458 $firstindex, $lastindex, $startblock, $endblock,
2459 $volindex, $volumename, $mediatype) = @{$refrow};
2461 # We just have to deal with the case where starfile != endfile
2462 # In this case, we concatenate both, for the bsr
2463 if ($startfile != $endfile) {
2464 $startfile = $startfile . '-' . $endfile;
2468 ($jobid, $volsessionid, $volsessiontime, $startfile,
2469 $firstindex, $lastindex, $startblock .'-'. $endblock,
2470 $volindex, $volumename, $mediatype);
2472 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2476 # reminder : restore_list looks like this :
2477 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2479 # Here, we retrieve every file/dir that could be in the restore
2480 # We do as simple as possible for the SQL engine (no crazy joins,
2481 # no pseudo join (>= FirstIndex ...), etc ...
2482 # We do a SQL union of all the files/dirs specified in the restore_list
2484 foreach my $entry (@{$self->{restore_list}->{data}})
2486 if ($entry->[2] eq 'dir')
2488 my $dir = unpack('u', $entry->[0]);
2489 my $inclause = $entry->[3]; #curjobids
2492 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2493 FROM File, Path, Filename
2494 WHERE Path.PathId = File.PathId
2495 AND File.FilenameId = Filename.FilenameId
2496 AND Path.Path LIKE '$dir%'
2497 AND File.JobId IN ($inclause) )";
2498 push @select_queries,($query);
2502 # It's a file. Great, we allready have most
2503 # of what is needed. Simple and efficient query
2504 my $file = unpack('u', $entry->[0]);
2505 my @file = split '/',$file;
2507 my $dir = join('/',@file);
2509 my $jobid = $entry->[1];
2510 my $fileindex = $entry->[7];
2511 my $inclause = $entry->[3]; # curjobids
2513 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2514 FROM File, Path, Filename
2515 WHERE Path.PathId = File.PathId
2516 AND File.FilenameId = Filename.FilenameId
2517 AND Path.Path = '$dir/'
2518 AND Filename.Name = '$file'
2519 AND File.JobId = $jobid)";
2520 push @select_queries,($query);
2523 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2525 print STDERR $query,"\n" if $debug;
2527 #Now we run the query and parse the result...
2528 # there may be a lot of records, so we better be efficient
2529 # We use the bind column method, working with references...
2531 my $sth = $self->dbh_prepare($query);
2534 my ($path,$name,$fileindex,$jobid);
2535 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2537 # The temp place we're going to save all file
2538 # list to before the real list
2542 while ($sth->fetchrow_arrayref())
2544 # This may look dumb, but we're going to do a join by ourselves,
2545 # to save memory and avoid sending a complex query to mysql
2546 my $complete_path = $path . $name;
2554 # Remove trailing slash (normalize file and dir name)
2555 $complete_path =~ s/\/$//;
2557 # Let's find the ref(s) for the %mediainfo element(s)
2558 # containing the data for this file
2559 # There can be several matches. It is the pseudo join.
2561 my $max_elt=@{$mediainfos{$jobid}}-1;
2563 while($med_idx <= $max_elt)
2565 my $ref = $mediainfos{$jobid}->[$med_idx];
2566 # First, can we get rid of the first elements of the
2567 # array ? (if they don't contain valuable records
2569 if ($fileindex > $ref->[5])
2571 # It seems we don't need anymore
2572 # this entry in %mediainfo (the input data
2575 shift @{$mediainfos{$jobid}};
2579 # We will do work on this elt. We can ++
2580 # $med_idx for next loop
2583 # %mediainfo row looks like :
2584 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2585 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2588 # We are in range. We store and continue looping
2590 if ($fileindex >= $ref->[4])
2592 my @data = ($complete_path,$is_dir,
2594 push @temp_list,(\@data);
2598 # We are not in range. No point in continuing looping
2599 # We go to next record.
2603 # Now we have the array.
2604 # We're going to sort it, by
2605 # path, volsessiontime DESC (get the most recent file...)
2606 # The array rows look like this :
2607 # complete_path,is_dir,fileindex,
2608 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2609 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2610 @temp_list = sort {$a->[0] cmp $b->[0]
2611 || $b->[3]->[2] <=> $a->[3]->[2]
2615 my $prev_complete_path='////'; # Sure not to match
2619 while (my $refrow = shift @temp_list)
2621 # For the sake of readability, we load $refrow
2622 # contents in real scalars
2623 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2624 my $jobid= $refother->[0]; # We don't need the rest...
2626 # We skip this entry.
2627 # We allready have a newer one and this
2628 # isn't a continuation of the same file
2629 next if ($complete_path eq $prev_complete_path
2630 and $jobid != $prev_jobid);
2634 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2636 # We would be recursing inside a file.
2637 # Just what we don't want (dir replaced by file
2638 # between two backups
2644 push @restore_list,($refrow);
2646 $prev_complete_path = $complete_path;
2647 $prev_jobid = $jobid;
2653 push @restore_list,($refrow);
2655 $prev_complete_path = $complete_path;
2656 $prev_jobid = $jobid;
2660 # We get rid of @temp_list... save memory
2663 # Ok everything is in the list. Let's sort it again in another way.
2664 # This time it will be in the bsr file order
2666 # we sort the results by
2667 # volsessiontime, volsessionid, volindex, fileindex
2668 # to get all files in right order...
2669 # Reminder : The array rows look like this :
2670 # complete_path,is_dir,fileindex,
2671 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2672 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2674 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2675 || $a->[3]->[1] <=> $b->[3]->[1]
2676 || $a->[3]->[7] <=> $b->[3]->[7]
2677 || $a->[2] <=> $b->[2] }
2680 # Now that everything is ready, we create the bsr
2681 my $prev_fileindex=-1;
2682 my $prev_volsessionid=-1;
2683 my $prev_volsessiontime=-1;
2684 my $prev_volumename=-1;
2685 my $prev_volfile=-1;
2689 my $first_of_current_range=0;
2690 my @fileindex_ranges;
2693 foreach my $refrow (@restore_list)
2695 my (undef,undef,$fileindex,$refother)=@{$refrow};
2696 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2697 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2699 # We can specifiy the number of files in each section of the
2700 # bsr to speedup restore (bacula can then jump over the
2701 # end of tape files.
2705 if ($prev_volumename eq '-1')
2707 # We only have to start the new range...
2708 $first_of_current_range=$fileindex;
2710 elsif ($prev_volsessionid != $volsessionid
2711 or $prev_volsessiontime != $volsessiontime
2712 or $prev_volumename ne $volumename
2713 or $prev_volfile ne $volfile)
2715 # We have to create a new section in the bsr...
2716 # We print the previous one ...
2717 # (before that, save the current range ...)
2718 if ($first_of_current_range != $prev_fileindex)
2721 push @fileindex_ranges,
2722 ("$first_of_current_range-$prev_fileindex");
2726 # We are out of a range,
2727 # but there is only one element in the range
2728 push @fileindex_ranges,
2729 ("$first_of_current_range");
2732 $bsr.=print_bsr_section(\@fileindex_ranges,
2734 $prev_volsessiontime,
2741 # Reset for next loop
2742 @fileindex_ranges=();
2743 $first_of_current_range=$fileindex;
2745 elsif ($fileindex-1 != $prev_fileindex)
2747 # End of a range of fileindexes
2748 if ($first_of_current_range != $prev_fileindex)
2751 push @fileindex_ranges,
2752 ("$first_of_current_range-$prev_fileindex");
2756 # We are out of a range,
2757 # but there is only one element in the range
2758 push @fileindex_ranges,
2759 ("$first_of_current_range");
2761 $first_of_current_range=$fileindex;
2763 $prev_fileindex=$fileindex;
2764 $prev_volsessionid = $volsessionid;
2765 $prev_volsessiontime = $volsessiontime;
2766 $prev_volumename = $volumename;
2767 $prev_volfile=$volfile;
2768 $prev_mediatype=$mediatype;
2769 $prev_volblocks=$volblocks;
2773 # Ok, we're out of the loop. Alas, there's still the last record ...
2774 if ($first_of_current_range != $prev_fileindex)
2777 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2782 # We are out of a range,
2783 # but there is only one element in the range
2784 push @fileindex_ranges,("$first_of_current_range");
2787 $bsr.=print_bsr_section(\@fileindex_ranges,
2789 $prev_volsessiontime,
2799 sub print_bsr_section
2801 my ($ref_fileindex_ranges,$volsessionid,
2802 $volsessiontime,$volumename,$volfile,
2803 $mediatype,$volblocks,$count)=@_;
2806 $bsr .= "Volume=\"$volumename\"\n";
2807 $bsr .= "MediaType=\"$mediatype\"\n";
2808 $bsr .= "VolSessionId=$volsessionid\n";
2809 $bsr .= "VolSessionTime=$volsessiontime\n";
2810 $bsr .= "VolFile=$volfile\n";
2811 $bsr .= "VolBlock=$volblocks\n";
2813 foreach my $range (@{$ref_fileindex_ranges})
2815 $bsr .= "FileIndex=$range\n";
2818 $bsr .= "Count=$count\n";
2822 # This function estimates the size to be restored for an entry of the restore
2824 # In : self,reference to the entry
2825 # Out : size in bytes, number of files
2826 sub estimate_restore_size
2828 # reminder : restore_list looks like this :
2829 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2833 if ($entry->[2] eq 'dir')
2835 my $dir = unpack('u', $entry->[0]);
2836 my $inclause = $entry->[3]; #curjobids
2838 "SELECT Path.Path, File.FilenameId, File.LStat
2839 FROM File, Path, Job
2840 WHERE Path.PathId = File.PathId
2841 AND File.JobId = Job.JobId
2842 AND Path.Path LIKE '$dir%'
2843 AND File.JobId IN ($inclause)
2844 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2848 # It's a file. Great, we allready have most
2849 # of what is needed. Simple and efficient query
2850 my $file = unpack('u', $entry->[0]);
2851 my @file = split '/',$file;
2853 my $dir = join('/',@file);
2855 my $jobid = $entry->[1];
2856 my $fileindex = $entry->[7];
2857 my $inclause = $entry->[3]; # curjobids
2859 "SELECT Path.Path, File.FilenameId, File.Lstat
2860 FROM File, Path, Filename
2861 WHERE Path.PathId = File.PathId
2862 AND Path.Path = '$dir/'
2863 AND Filename.Name = '$file'
2864 AND File.JobId = $jobid
2865 AND Filename.FilenameId = File.FilenameId";
2868 print STDERR $query,"\n" if $debug;
2869 my ($path,$nameid,$lstat);
2870 my $sth = $self->dbh_prepare($query);
2872 $sth->bind_columns(\$path,\$nameid,\$lstat);
2882 while ($sth->fetchrow_arrayref())
2884 # Only the latest version of a file
2885 next if ($nameid eq $old_nameid and $path eq $old_path);
2887 if ($rcount > 15000) {
2894 # We get the size of this file
2895 my $size=lstat_attrib($lstat,'st_size');
2896 $total_size += $size;
2899 $old_nameid=$nameid;
2901 return ($total_size,$total_files);
2904 sub update_brestore_table
2906 my ($self, @jobs) = @_;
2907 my $dbh = $self->{dbh};
2909 foreach my $job (sort {$a <=> $b} @jobs)
2911 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2912 my $retour = $self->dbh_selectrow_arrayref($query);
2913 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2915 print STDERR "Inserting path records for JobId $job\n";
2916 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2917 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2919 $self->dbh_do($query);
2921 # Now we have to do the directory recursion stuff to determine missing visibility
2922 # We try to avoid recursion, to be as fast as possible
2923 # We also only work on not allready hierarchised directories...
2925 print STDERR "Creating missing recursion paths for $job\n";
2927 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2928 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2929 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2930 WHERE brestore_pathvisibility.JobId = $job
2931 AND brestore_pathhierarchy.PathId IS NULL
2934 my $sth = $self->dbh_prepare($query);
2936 my $pathid; my $path;
2937 $sth->bind_columns(\$pathid,\$path);
2941 $self->build_path_hierarchy($path,$pathid);
2945 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2946 # This query gives all parent pathids for a given jobid that aren't stored.
2947 # It has to be called until no record is updated ...
2949 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2950 SELECT a.PathId,$job
2952 (SELECT DISTINCT h.PPathId AS PathId
2953 FROM brestore_pathhierarchy AS h
2954 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2955 WHERE p.JobId=$job) AS a
2958 FROM brestore_pathvisibility
2959 WHERE JobId=$job) AS b
2960 ON (a.PathId = b.PathId)
2961 WHERE b.PathId IS NULL)";
2962 print STDERR $query,"\n" if ($debug);
2964 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2966 print STDERR "Recursively adding $rows_affected records from $job\n";
2969 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2974 sub cleanup_brestore_table
2977 my $dbh = $self->{dbh};
2979 my $query = "SELECT JobId from brestore_knownjobid";
2980 my @jobs = @{$dbh->selectall_arrayref($query)};
2982 foreach my $jobentry (@jobs)
2984 my $job = $jobentry->[0];
2985 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2986 my $result = $dbh->selectall_arrayref($query);
2987 if (scalar(@{$result}))
2989 # There are still files for this jobid
2990 print STDERR "$job still exists. Not cleaning...\n";
2993 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2995 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
3001 sub build_path_hierarchy
3003 my ($self, $path,$pathid)=@_;
3004 # Does the ppathid exist for this ? we use a memory cache...
3005 # In order to avoid the full loop, we consider that if a dir is allready in the
3006 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
3009 #print STDERR "$path\n" if $debug;
3010 if (! $self->{cache_ppathid}->{$pathid})
3012 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
3013 my $sth2 = $self->{dbh}->prepare_cached($query);
3014 $sth2->execute($pathid);
3015 # Do we have a result ?
3016 if (my $refrow = $sth2->fetchrow_arrayref)
3018 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
3020 # This dir was in the db ...
3021 # It means we can leave, the tree has allready been built for
3026 # We have to create the record ...
3027 # What's the current p_path ?
3028 my $ppath = parent_dir($path);
3029 my $ppathid = $self->return_pathid_from_path($ppath);
3030 $self->{cache_ppathid}->{$pathid}= $ppathid;
3032 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
3033 $sth2 = $self->{dbh}->prepare_cached($query);
3034 $sth2->execute($pathid,$ppathid);
3040 # It's allready in the cache.
3041 # We can leave, no time to waste here, all the parent dirs have allready
3049 sub return_pathid_from_path
3051 my ($self, $path) = @_;
3052 my $query = "SELECT PathId FROM Path WHERE Path = ?
3054 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3055 #print STDERR $query,"\n" if $debug;
3056 my $sth = $self->{dbh}->prepare_cached($query);
3057 $sth->execute($path,$path);
3058 my $result =$sth->fetchrow_arrayref();
3060 if (defined $result)
3062 return $result->[0];
3065 # A bit dirty : we insert into path AND missing_path, to be sure
3066 # we aren't deleted by a purge. We still need to insert into path to get
3067 # the pathid, because of mysql
3068 $query = "INSERT INTO Path (Path) VALUES (?)";
3069 #print STDERR $query,"\n" if $debug;
3070 $sth = $self->{dbh}->prepare_cached($query);
3071 $sth->execute($path);
3074 $query = " INSERT INTO brestore_missing_path (PathId,Path)
3075 SELECT PathId,Path FROM Path WHERE Path = ?";
3076 #print STDERR $query,"\n" if $debug;
3077 $sth = $self->{dbh}->prepare_cached($query);
3078 $sth->execute($path);
3080 $query = " DELETE FROM Path WHERE Path = ?";
3081 #print STDERR $query,"\n" if $debug;
3082 $sth = $self->{dbh}->prepare_cached($query);
3083 $sth->execute($path);
3085 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
3086 #print STDERR $query,"\n" if $debug;
3087 $sth = $self->{dbh}->prepare_cached($query);
3088 $sth->execute($path);
3089 $result = $sth->fetchrow_arrayref();
3091 return $result->[0];
3103 # Root Windows case :
3104 if ($path =~ /^[a-z]+:\/$/i)
3109 my @tmp = split('/',$path);
3110 # We remove the last ...
3112 my $tmp = join ('/',@tmp) . '/';
3118 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3119 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3120 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3121 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3122 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3123 'data_stream' => 15);;
3126 my ($attrib,$ref_attrib)=@_;
3127 return $ref_attrib->[$attrib_name_id{$attrib}];
3131 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
3133 my ($file, $attrib)=@_;
3135 if (defined $attrib_name_id{$attrib}) {
3137 my @d = split(' ', $file->[2]) ; # TODO : cache this
3139 return from_base64($d[$attrib_name_id{$attrib}]);
3141 } elsif ($attrib eq 'jobid') {
3145 } elsif ($attrib eq 'name') {
3150 die "Attribute not known : $attrib.\n";
3154 # Return the jobid or attribute asked for a dir
3157 my ($self,$dir,$attrib)=@_;
3159 my @dir = split('/',$dir,-1);
3160 my $refdir=$self->{dirtree}->{$self->current_client};
3162 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3164 die "Attribute not known : $attrib.\n";
3167 foreach my $subdir (@dir)
3169 $refdir = $refdir->[0]->{$subdir};
3172 # $refdir is now the reference to the dir's array
3173 # Is the a jobid in @CurrentJobIds where the lstat is
3174 # defined (we'll search in reverse order)
3175 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3177 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3179 if ($attrib eq 'jobid')
3185 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3186 return $attribs[$attrib_name_id{$attrib}+1];
3191 return 0; # We cannot get a good attribute.
3192 # This directory is here for the sake of visibility
3197 my ($lstat,$attrib)=@_;
3198 if ($lstat and defined $attrib_name_id{$attrib})
3200 my @d = split(' ', $lstat) ; # TODO : cache this
3201 return from_base64($d[$attrib_name_id{$attrib}]);
3208 # Base 64 functions, directly from recover.pl.
3210 # Karl Hakimian <hakimian@aha.com>
3211 # This section is also under GPL v2 or later.
3218 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3219 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3220 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3221 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3222 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3224 @base64_map = (0) x 128;
3226 for (my $i=0; $i<64; $i++) {
3227 $base64_map[ord($base64_digits[$i])] = $i;
3242 if (substr($where, 0, 1) eq '-') {
3244 $where = substr($where, 1);
3247 while ($where ne '') {
3249 my $d = substr($where, 0, 1);
3250 $val += $base64_map[ord(substr($where, 0, 1))];
3251 $where = substr($where, 1);
3259 my @attribs = split(' ',$lstat);
3260 foreach my $element (@attribs)
3262 $element = from_base64($element);
3271 ################################################################
3274 use base qw/DlgResto/;
3278 my ($class, $conf) = @_;
3279 my $self = bless {info => $conf}, $class;
3281 $self->{dbh} = $conf->{dbh};
3290 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3291 my $jobs = $self->dbh_selectall_arrayref($query);
3293 $self->update_brestore_table(map { $_->[0] } @$jobs);
3304 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3308 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3311 GetOptions("conf=s" => \$file_conf,
3312 "batch" => \$batch_mod,
3314 "help" => \&HELP_MESSAGE) ;
3316 my $p = new Pref($file_conf);
3318 if (! -f $file_conf) {
3323 my $b = new Batch($p);
3324 if ($p->connect_db()) {
3325 $b->set_dbh($p->{dbh});
3331 $glade_file = $p->{glade_file};
3333 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3334 if (-f "$path/$glade_file") {
3335 $glade_file = "$path/$glade_file" ;
3340 # gtk have lots of warning on stderr
3341 if ($^O eq 'MSWin32')
3344 open(STDERR, ">stderr.log");
3349 if ( -f $glade_file) {
3350 my $w = new DlgResto($p);
3353 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3354 "Can't find your brestore.glade (glade_file => '$glade_file')
3355 Please, edit your $file_conf to setup it." );
3357 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3362 Gtk2->main; # Start Gtk2 main loop
3374 # Code pour trier les colonnes
3375 my $mod = $fileview->get_model();
3376 $mod->set_default_sort_func(sub {
3377 my ($model, $item1, $item2) = @_;
3378 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3379 my $b = $model->get($item2, 1); # colonne (indice 1)
3384 $fileview->set_headers_clickable(1);
3385 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3386 $col->signal_connect('clicked', sub {
3387 my ($colonne, $model) = @_;
3388 $model->set_sort_column_id (1, 'ascending');