5 # path to your brestore.glade
6 my $glade_file = 'brestore.glade' ;
10 brestore.pl - A Perl/Gtk console for Bacula
18 Setup ~/.brestore.conf to find your brestore.glade
20 On debian like system, you need :
21 - libgtk2-gladexml-perl
22 - libdbd-mysql-perl or libdbd-pg-perl
25 You have to add brestore_xxx tables to your catalog.
27 To speed up database query you have to create theses indexes
28 - CREATE INDEX file_pathid on File(PathId);
31 To follow restore job, you must have a running Bweb installation.
35 Bacula® - The Network Backup Solution
37 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
39 Brestore authors are Marc Cousin and Eric Bollengier.
40 The main author of Bacula is Kern Sibbald, with contributions from
41 many others, a complete list can be found in the file AUTHORS.
43 This program is Free Software; you can redistribute it and/or
44 modify it under the terms of version two of the GNU General Public
45 License as published by the Free Software Foundation plus additions
46 that are listed in the file LICENSE.
48 This program is distributed in the hope that it will be useful, but
49 WITHOUT ANY WARRANTY; without even the implied warranty of
50 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
51 General Public License for more details.
53 You should have received a copy of the GNU General Public License
54 along with this program; if not, write to the Free Software
55 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
58 Bacula® is a registered trademark of John Walker.
59 The licensor of Bacula is the Free Software Foundation Europe
60 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
61 Switzerland, email:ftf@fsfeurope.org.
63 Base 64 functions from Karl Hakimian <hakimian@aha.com>
64 Integrally copied from recover.pl from bacula source distribution.
68 use Gtk2; # auto-initialize Gtk2
70 use Gtk2::SimpleList; # easy wrapper for list views
71 use Gtk2::Gdk::Keysyms; # keyboard code constants
72 use Data::Dumper qw/Dumper/;
73 my $debug=0; # can be on brestore.conf
74 our ($VERSION) = ('$Revision$' =~ /(\d+\.\d+)/);
80 my ($class, $config_file) = @_;
83 config_file => $config_file,
84 password => '', # db passwd
85 username => '', # db username
86 connection_string => '',# db connection string
87 bconsole => 'bconsole', # path and arg to bconsole
88 bsr_dest => '', # destination url for bsr files
89 debug => 0, # debug level 0|1
90 use_ok_bkp_only => 1, # dont use bad backup
91 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
92 see_all_versions => 0, # display all file versions in FileInfo
93 mozilla => 'mozilla', # mozilla bin
94 default_restore_job => 'restore', # regular expression to select default
97 # keywords that are used to fill DlgPref
98 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
99 entry_keyword => [ qw/username password bweb mozilla
100 connection_string default_restore_job
101 bconsole bsr_dest glade_file/],
104 $self->read_config();
113 # We read the parameters. They come from the configuration files
114 my $cfgfile ; my $tmpbuffer;
115 if (open FICCFG, $self->{config_file})
117 while(read FICCFG,$tmpbuffer,4096)
119 $cfgfile .= $tmpbuffer;
123 no strict; # I have no idea of the contents of the file
124 eval '$refparams' . " = $cfgfile";
127 for my $p (keys %{$refparams}) {
128 $self->{$p} = $refparams->{$p};
132 # TODO : Force dumb default values and display a message
142 for my $k (@{ $self->{entry_keyword} }) {
143 $parameters{$k} = $self->{$k};
146 for my $k (@{ $self->{chk_keyword} }) {
147 $parameters{$k} = $self->{$k};
150 if (open FICCFG,">$self->{config_file}")
152 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
157 # TODO : Display a message
166 $self->{dbh}->disconnect() ;
170 delete $self->{error};
172 if (not $self->{connection_string})
174 # The parameters have not been set. Maybe the conf
175 # file is empty for now
176 $self->{error} = "No configuration found for database connection. " .
177 "Please set this up.";
182 $self->{dbh} = DBI->connect($self->{connection_string},
187 $self->{error} = "Can't open bacula database. " .
188 "Database connect string '" .
189 $self->{connection_string} ."' $!";
192 $self->{dbh}->{RowCacheSize}=100;
198 my ($self, $url, $msg) = @_;
200 unless ($self->{mozilla} and $self->{bweb}) {
201 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
205 if ($^O eq 'MSWin32') {
206 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
209 system("$self->{mozilla} -remote 'Ping()'");
210 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
212 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
222 ################################################################
228 my ($class, %arg) = @_;
241 my ($self, $what, %arg) = @_;
243 if ($self->{conf}->{debug} and defined $what) {
248 my $line = (caller($level))[2];
249 my $func = (caller($level+1))[3] || 'main';
250 print "$func:$line\t";
252 print Data::Dumper::Dumper($what);
253 } elsif ($arg{md5}) {
254 print "MD5=", md5_base64($what), " str=", $what,"\n";
263 my ($self, @what) = @_;
264 if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
265 return join(' || ', @what);
267 return 'CONCAT(' . join(',', @what) . ')' ;
273 my ($self, $query) = @_;
274 $self->debug($query, up => 1);
275 return $self->{conf}->{dbh}->prepare($query);
280 my ($self, $query) = @_;
281 $self->debug($query, up => 1);
282 return $self->{conf}->{dbh}->do($query);
285 sub dbh_selectall_arrayref
287 my ($self, $query) = @_;
288 $self->debug($query, up => 1);
289 return $self->{conf}->{dbh}->selectall_arrayref($query);
292 sub dbh_selectrow_arrayref
294 my ($self, $query) = @_;
295 $self->debug($query, up => 1);
296 return $self->{conf}->{dbh}->selectrow_arrayref($query);
302 return $self->{conf}->{dbh};
307 ################################################################
311 # my $pref = new Pref(config_file => 'brestore.conf');
312 # my $dlg = new DlgPref($pref);
313 # my $dlg_resto = new DlgResto($pref);
314 # $dlg->display($dlg_resto);
317 my ($class, $pref) = @_;
320 pref => $pref, # Pref ref
321 dlgresto => undef, # DlgResto ref
329 my ($self, $dlgresto) = @_ ;
331 unless ($self->{glade}) {
332 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
333 $self->{glade}->signal_autoconnect_from_package($self);
336 $self->{dlgresto} = $dlgresto;
338 my $g = $self->{glade};
339 my $p = $self->{pref};
341 for my $k (@{ $p->{entry_keyword} }) {
342 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
345 for my $k (@{ $p->{chk_keyword} }) {
346 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
349 $g->get_widget("dlg_pref")->show_all() ;
352 sub on_applybutton_clicked
355 my $glade = $self->{glade};
356 my $pref = $self->{pref};
358 for my $k (@{ $pref->{entry_keyword} }) {
359 my $w = $glade->get_widget("entry_$k") ;
360 $pref->{$k} = $w->get_text();
363 for my $k (@{ $pref->{chk_keyword} }) {
364 my $w = $glade->get_widget("chkbp_$k") ;
365 $pref->{$k} = $w->get_active();
368 $pref->write_config();
369 if ($pref->connect_db()) {
370 $self->{dlgresto}->set_status('Preferences updated');
371 $self->{dlgresto}->init_server_backup_combobox();
372 $self->{dlgresto}->set_status($pref->{error});
374 $self->{dlgresto}->set_status($pref->{error});
378 # Handle prefs ok click (apply/dismiss dialog)
379 sub on_okbutton_clicked
382 $self->on_applybutton_clicked();
384 unless ($self->{pref}->{error}) {
385 $self->on_cancelbutton_clicked();
388 sub on_dialog_delete_event
391 $self->on_cancelbutton_clicked();
395 sub on_cancelbutton_clicked
398 $self->{glade}->get_widget('dlg_pref')->hide();
399 delete $self->{dlgresto};
403 ################################################################
405 package DlgFileVersion;
407 sub on_versions_close_clicked
409 my ($self, $widget)=@_;
410 $self->{version}->destroy();
413 sub on_selection_button_press_event
415 print STDERR "on_selection_button_press_event()\n";
418 sub fileview_data_get
420 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
422 DlgResto::drag_set_info($widget,
429 my ($class, $bvfs, $client, $path, $file, $cwd, $fn) = @_;
432 version => undef, # main window
435 # we load version widget of $glade_file
436 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
438 # Connect signals magically
439 $glade_box->signal_autoconnect_from_package($self);
441 $glade_box->get_widget("version_label")
442 ->set_markup("<b>File revisions : $client:$cwd$fn</b>");
444 my $widget = $glade_box->get_widget('version_fileview');
445 my $fileview = Gtk2::SimpleList->new_from_treeview(
447 'h_pathid' => 'hidden',
448 'h_filenameid' => 'hidden',
449 'h_name' => 'hidden',
450 'h_jobid' => 'hidden',
451 'h_type' => 'hidden',
453 'InChanger' => 'pixbuf',
460 DlgResto::init_drag_drop($fileview);
462 my @v = $bvfs->get_all_file_versions($path,
467 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
468 $inchanger,$md5,$volname) = @{$ver};
469 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
471 DlgResto::listview_push($fileview,$pid,$fid,
473 $icon, $volname, $jobid,DlgResto::human($size),
474 scalar(localtime($mtime)), $md5);
477 $self->{version} = $glade_box->get_widget('dlg_version');
478 $self->{version}->show();
483 sub on_forward_keypress
489 ################################################################
494 my ($package, $text) = @_;
498 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
500 # Connect signals magically
501 $glade->signal_autoconnect_from_package($self);
502 $glade->get_widget('label_warn')->set_text($text);
504 print STDERR "$text\n";
506 $self->{window} = $glade->get_widget('dlg_warn');
507 $self->{window}->show_all();
514 $self->{window}->destroy();
518 ################################################################
524 # %arg = (bsr_file => '/path/to/bsr', # on director
525 # volumes => [ '00001', '00004']
533 if ($pref->{bconsole} =~ /^http/) {
534 return new BwebConsole(pref => $pref);
536 if (eval { require Bconsole; }) {
537 return new Bconsole(pref => $pref);
539 new DlgWarn("Can't use bconsole, verify your setup");
547 my ($class, %arg) = @_;
550 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
551 pref => $arg{pref}, # Pref ref
552 glade => undef, # GladeXML ref
553 bconsole => undef, # Bconsole ref
556 my $console = $self->{bconsole} = get_bconsole($arg{pref});
561 # we load launch widget of $glade_file
562 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
565 # Connect signals magically
566 $glade->signal_autoconnect_from_package($self);
568 my $widget = $glade->get_widget('volumeview');
569 my $volview = Gtk2::SimpleList->new_from_treeview(
571 'InChanger' => 'pixbuf',
575 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
577 # we replace 0 and 1 by $noicon and $yesicon
578 for my $i (@{$infos}) {
580 $i->[0] = $DlgResto::noicon;
582 $i->[0] = $DlgResto::yesicon;
587 push @{ $volview->{data} }, @{$infos} ;
589 $console->prepare(qw/list_client list_job list_fileset list_storage/);
591 # fill client combobox (with director defined clients
592 my @clients = $console->list_client() ; # get from bconsole
593 if ($console->{error}) {
594 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
596 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
597 $self->{list_client} = DlgResto::init_combo($w, 'text');
598 DlgResto::fill_combo($self->{list_client},
599 $DlgResto::client_list_empty,
603 # fill fileset combobox
604 my @fileset = $console->list_fileset() ;
605 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
606 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
607 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
610 my @job = $console->list_job() ;
611 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
612 $self->{list_job} = DlgResto::init_combo($w, 'text');
613 DlgResto::fill_combo($self->{list_job}, '', @job);
615 # find default_restore_job in jobs list
616 my $default_restore_job = $arg{pref}->{default_restore_job} ;
620 if ($j =~ /$default_restore_job/io) {
626 $w->set_active($index);
628 # fill storage combobox
629 my @storage = $console->list_storage() ;
630 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
631 $self->{list_storage} = DlgResto::init_combo($w, 'text');
632 DlgResto::fill_combo($self->{list_storage}, '', @storage);
634 $glade->get_widget('dlg_launch')->show_all();
641 my ($self, $client, $jobid) = @_;
643 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
645 $self->on_cancel_resto_clicked();
648 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
649 "Your job have been submited to bacula.
650 To follow it, you must use bconsole (or install/configure bweb)");
656 sub on_cancel_resto_clicked
659 $self->{glade}->get_widget('dlg_launch')->destroy();
662 sub on_submit_resto_clicked
665 my $glade = $self->{glade};
667 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
670 new DlgWarn("Can't copy bsr file to director ($self->{error})");
674 my $fileset = $glade->get_widget('combo_launch_fileset')
677 my $storage = $glade->get_widget('combo_launch_storage')
680 my $where = $glade->get_widget('entry_launch_where')->get_text();
682 my $job = $glade->get_widget('combo_launch_job')
686 new DlgWarn("Can't use this job");
690 my $client = $glade->get_widget('combo_launch_client')
693 if (! $client or $client eq $DlgResto::client_list_empty) {
694 new DlgWarn("Can't use this client ($client)");
698 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
700 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
701 $replace=($replace)?'always':'never';
703 my $jobid = $self->{bconsole}->run(job => $job,
712 $self->show_job($client, $jobid);
715 sub on_combo_storage_button_press_event
718 print "on_combo_storage_button_press_event()\n";
721 sub on_combo_fileset_button_press_event
724 print "on_combo_fileset_button_press_event()\n";
728 sub on_combo_job_button_press_event
731 print "on_combo_job_button_press_event()\n";
734 sub get_volume_inchanger
736 my ($dbh, $vols) = @_;
738 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
740 my $rq = "SELECT InChanger, VolumeName
742 WHERE VolumeName IN ($lst)
745 my $res = $dbh->selectall_arrayref($rq);
746 return $res; # [ [ 1, VolName].. ]
750 use File::Copy qw/copy/;
751 use File::Basename qw/basename/;
753 # We must kown the path+filename destination
754 # $self->{error} contains error message
755 # it return 0/1 if fail/success
758 my ($self, $src, $dst) = @_ ;
759 print "$src => $dst\n"
770 if ($dst =~ m!file:/(/.+)!) {
771 $ret = copy($src, $1);
773 $dstfile = "$1/" . basename($src) ;
775 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
776 $err = `scp $src $1 2>&1` ;
778 $dstfile = "$2/" . basename($src) ;
782 $err = "$dst not implemented yet";
783 File::Copy::copy($src, \*STDOUT);
786 $self->{error} = $err;
789 $self->{error} = $err;
798 ################################################################
806 unless ($about_widget) {
807 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
808 $about_widget = $glade_box->get_widget("dlg_about") ;
809 $glade_box->signal_autoconnect_from_package('DlgAbout');
811 $about_widget->show() ;
814 sub on_about_okbutton_clicked
816 $about_widget->hide() ;
821 ################################################################
831 # Kept as is from the perl-gtk example. Draws the pretty icons
837 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
838 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
839 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
840 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
843 # init combo (and create ListStore object)
846 my ($widget, @type) = @_ ;
847 my %type_info = ('text' => 'Glib::String',
848 'markup' => 'Glib::String',
851 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
853 $widget->set_model($lst);
857 if ($t eq 'text' or $t eq 'markup') {
858 $cell = new Gtk2::CellRendererText();
860 $widget->pack_start($cell, 1);
861 $widget->add_attribute($cell, $t, $i++);
866 # fill simple combo (one element per row)
869 my ($list, @what) = @_;
873 foreach my $w (@what)
876 my $i = $list->append();
877 $list->set($i, 0, $w);
884 my @unit = qw(b Kb Mb Gb Tb);
887 my $format = '%i %s';
888 while ($val / 1024 > 1) {
892 $format = ($i>0)?'%0.1f %s':'%i %s';
893 return sprintf($format, $val, $unit[$i]);
896 sub get_wanted_job_status
903 return "'T', 'A', 'E'";
907 # This sub gives a full list of the EndTimes for a ClientId
908 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
909 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
910 sub get_all_endtimes_for_job
912 my ($self, $client, $ok_only)=@_;
913 my $status = get_wanted_job_status($ok_only);
915 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
916 FROM Job,Client,FileSet
917 WHERE Job.ClientId=Client.ClientId
918 AND Client.Name = '$client'
920 AND JobStatus IN ($status)
921 AND Job.FileSetId = FileSet.FileSetId
922 ORDER BY EndTime desc";
923 my $result = $self->dbh_selectall_arrayref($query);
930 my ($fileview) = shift;
931 my $fileview_target_entry = {target => 'STRING',
932 flags => ['GTK_TARGET_SAME_APP'],
935 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
936 ['copy'],$fileview_target_entry);
937 $fileview->get_selection->set_mode('multiple');
939 # set some useful SimpleList properties
940 $fileview->set_headers_clickable(0);
941 foreach ($fileview->get_columns())
943 $_->set_resizable(1);
944 $_->set_sizing('grow-only');
950 my ($class, $pref) = @_;
955 location => undef, # location entry widget
956 mainwin => undef, # mainwin widget
957 filelist_file_menu => undef, # file menu widget
958 filelist_dir_menu => undef, # dir menu widget
959 glade => undef, # glade object
960 status => undef, # status bar widget
961 dlg_pref => undef, # DlgPref object
962 fileattrib => {}, # cache file
963 fileview => undef, # fileview widget SimpleList
964 fileinfo => undef, # fileinfo widget SimpleList
966 client_combobox => undef, # client_combobox widget
967 restore_backup_combobox => undef, # date combobox widget
968 list_client => undef, # Gtk2::ListStore
969 list_backup => undef, # Gtk2::ListStore
970 cache_ppathid => {}, #
974 $self->{bvfs} = new Bvfs(conf => $pref);
976 # load menu (to use handler with self reference)
977 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
978 $glade->signal_autoconnect_from_package($self);
979 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
981 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
982 $glade->signal_autoconnect_from_package($self);
983 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
985 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
986 $glade->signal_autoconnect_from_package($self);
988 $self->{status} = $glade->get_widget('statusbar');
989 $self->{mainwin} = $glade->get_widget('dlg_resto');
990 $self->{location} = $glade->get_widget('entry_location');
991 $self->render_icons();
993 $self->{dlg_pref} = new DlgPref($pref);
995 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
996 $self->{list_client} = init_combo($c, 'text');
998 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
999 $self->{list_backup} = init_combo($c, 'text', 'markup');
1001 # Connect glade-fileview to Gtk2::SimpleList
1002 # and set up drag n drop between $fileview and $restore_list
1004 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1005 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1007 my $widget = $glade->get_widget('fileview');
1008 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1010 'h_pathid' => 'hidden',
1011 'h_filenameid' => 'hidden',
1012 'h_name' => 'hidden',
1013 'h_jobid' => 'hidden',
1014 'h_type' => 'hidden',
1017 'File Name' => 'text',
1020 init_drag_drop($fileview);
1021 $fileview->set_search_column(4); # search on File Name
1023 # Connect glade-restore_list to Gtk2::SimpleList
1024 $widget = $glade->get_widget('restorelist');
1025 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1027 'h_pathid' => 'hidden', #0
1028 'h_filenameid' => 'hidden',
1029 'h_name' => 'hidden',
1030 'h_jobid' => 'hidden',
1031 'h_type' => 'hidden',
1032 'h_curjobid' => 'hidden', #5
1035 'File Name' => 'text',
1037 'FileIndex' => 'text',
1039 'Nb Files' => 'text', #10
1040 'Size' => 'text', #11
1041 'size_b' => 'hidden', #12
1044 my @restore_list_target_table = ({'target' => 'STRING',
1048 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1049 $restore_list->get_selection->set_mode('multiple');
1051 $widget = $glade->get_widget('infoview');
1052 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1054 'h_pathid' => 'hidden',
1055 'h_filenameid' => 'hidden',
1056 'h_name' => 'hidden',
1057 'h_jobid' => 'hidden',
1058 'h_type' => 'hidden',
1060 'InChanger' => 'pixbuf',
1067 init_drag_drop($infoview);
1069 $pref->connect_db() || $self->{dlg_pref}->display($self);
1072 $self->init_server_backup_combobox();
1073 $self->{bvfs}->create_brestore_tables();
1076 $self->set_status($pref->{error});
1079 # set status bar informations
1082 my ($self, $string) = @_;
1083 return unless ($string);
1085 my $context = $self->{status}->get_context_id('Main');
1086 $self->{status}->push($context, $string);
1089 sub on_time_select_changed
1097 my $c = $self->{glade}->get_widget('combo_time');
1098 return $c->get_active_text;
1101 # This sub returns all clients declared in DB
1105 my $query = "SELECT Name FROM Client ORDER BY Name";
1106 print STDERR $query,"\n" if $debug;
1108 my $result = $dbh->selectall_arrayref($query);
1110 return map { $_->[0] } @$result;
1113 # init infoview widget
1117 @{$self->{fileinfo}->{data}} = ();
1121 sub on_clear_clicked
1124 @{$self->{restore_list}->{data}} = ();
1127 sub on_estimate_clicked
1134 # TODO : If we get here, things could get lenghty ... draw a popup window .
1135 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1136 'destroy-with-parent',
1138 'Computing size...');
1142 my $title = "Computing size...\n";
1145 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1146 # undef, undef, undef, $dirfileindex);
1147 foreach my $entry (@{$self->{restore_list}->{data}})
1149 unless ($entry->[11]) {
1150 my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1151 $entry->[12] = $size;
1152 $entry->[11] = human($size);
1156 my $name = unpack('u', $entry->[2]);
1158 $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1159 $self->debug($title . $txt);
1160 $widget->set_markup($title . $txt);
1162 $size_total+=$entry->[12];
1163 $nb_total+=$entry->[10];
1167 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1168 $widget->set_markup("Size estimation :\n" . $txt);
1169 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1176 sub on_gen_bsr_clicked
1180 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1181 'gtk-save','ok', 'gtk-cancel', 'cancel');
1184 my $w = new Gtk2::FileChooserDialog ( @options );
1189 if ($a eq 'cancel') {
1194 my $f = $w->get_filename();
1196 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1197 'destroy-with-parent',
1198 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1199 if ($dlg->run() eq 'ok') {
1213 if (open(FP, ">$save")) {
1214 my $bsr = $self->create_filelist();
1217 $self->set_status("Dumping BSR to $save ok");
1219 $self->set_status("Can't dump BSR to $save: $!");
1225 use File::Temp qw/tempfile/;
1227 sub on_go_button_clicked
1230 unless (scalar(@{$self->{restore_list}->{data}})) {
1231 new DlgWarn("No file to restore");
1234 my $bsr = $self->create_filelist();
1235 my ($fh, $filename) = tempfile();
1238 chmod(0644, $filename);
1240 print "Dumping BSR info to $filename\n"
1243 # we get Volume list
1244 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1245 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1247 new DlgLaunch(pref => $self->{conf},
1249 bsr_file => $filename,
1255 our $client_list_empty = 'Clients list';
1256 our %type_markup = ('F' => '<b>$label F</b>',
1259 'B' => '<b>$label B</b>',
1261 'A' => '<span foreground=\"red\">$label</span>',
1263 'E' => '<span foreground=\"red\">$label</span>',
1266 sub on_list_client_changed
1268 my ($self, $widget) = @_;
1269 return 0 unless defined $self->{fileview};
1271 $self->{list_backup}->clear();
1273 if ($self->current_client eq $client_list_empty) {
1277 $self->{CurrentJobIds} = [
1278 set_job_ids_for_date($self->dbh(),
1279 $self->current_client,
1280 $self->current_date,
1281 $self->{pref}->{use_ok_bkp_only})
1284 my $fs = $self->{bvfs};
1285 $fs->set_curjobids(@{$self->{CurrentJobIds}});
1286 $fs->update_brestore_table(@{$self->{CurrentJobIds}});
1287 $fs->ch_dir($fs->get_root());
1288 # refresh_fileview will be done by list_backup_changed
1291 my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1292 $self->{pref}->{use_ok_bkp_only});
1294 foreach my $endtime (@endtimes)
1296 my $i = $self->{list_backup}->append();
1298 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1299 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1300 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1302 $self->{list_backup}->set($i,
1307 $self->{restore_backup_combobox}->set_active(0);
1312 sub fill_server_list
1314 my ($dbh, $combo, $list) = @_;
1316 my @clients=get_all_clients($dbh);
1320 my $i = $list->append();
1321 $list->set($i, 0, $client_list_empty);
1323 foreach my $client (@clients)
1325 $i = $list->append();
1326 $list->set($i, 0, $client);
1328 $combo->set_active(0);
1331 sub init_server_backup_combobox
1334 fill_server_list($self->{conf}->{dbh},
1335 $self->{client_combobox},
1336 $self->{list_client}) ;
1339 #----------------------------------------------------------------------
1340 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1341 #data isn't. There is additionnal complexity for dirs (visibility problems),
1342 #so the @CurrentJobIds is not sufficient.
1343 sub refresh_fileview
1346 my $fileview = $self->{fileview};
1347 my $client_combobox = $self->{client_combobox};
1348 my $bvfs = $self->{bvfs};
1350 @{$fileview->{data}} = ();
1352 $self->clear_infoview();
1354 my $client_name = $self->current_client;
1356 if (!$client_name or ($client_name eq $client_list_empty)) {
1357 $self->set_status("Client list empty");
1361 # [ [dirid, dir_basename, File.LStat, jobid]..]
1362 my $list_dirs = $bvfs->ls_dirs();
1363 # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1364 my $files = $bvfs->ls_files();
1366 my $file_count = 0 ;
1367 my $total_bytes = 0;
1369 # Add directories to view
1370 foreach my $dir_entry (@$list_dirs) {
1371 my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1372 $total_bytes += 4096;
1375 listview_push($fileview,
1379 # TODO: voir ce que l'on met la
1390 foreach my $file (@$files)
1392 my $size = Bvfs::file_attrib($file,'st_size');
1393 my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1394 $total_bytes += $size;
1396 # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1397 listview_push($fileview,
1406 human($size), $time);
1409 $self->set_status("$file_count files/" . human($total_bytes));
1410 $self->{cwd} = $self->{bvfs}->pwd();
1411 $self->{location}->set_text($self->{cwd});
1412 # set a decent default selection (makes keyboard nav easy)
1413 $fileview->select(0);
1417 sub on_about_activate
1419 DlgAbout::display();
1424 my ($tree, $path, $data) = @_;
1426 my @items = listview_get_all($tree) ;
1428 foreach my $i (@items)
1430 my @file_info = @{$i};
1433 # Ok, we have a corner case :
1435 my $file = pack("u", $path . $file_info[2]);
1437 push @ret, join(" ; ", $file,
1438 $file_info[0], # $pathid
1439 $file_info[1], # $filenameid
1440 $file_info[3], # $jobid
1441 $file_info[4], # $type
1445 my $data_get = join(" :: ", @ret);
1447 $data->set_text($data_get,-1);
1452 sub fileview_data_get
1454 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1455 drag_set_info($widget, $self->{cwd}, $data);
1458 sub fileinfo_data_get
1460 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1461 drag_set_info($widget, $self->{cwd}, $data);
1464 sub restore_list_data_received
1466 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1468 $self->debug("start\n");
1469 if ($info eq 40 || $info eq 0) # patch for display!=:0
1471 foreach my $elt (split(/ :: /, $data->data()))
1473 my ($file, $pathid, $filenameid, $jobid, $type) =
1475 $file = unpack("u", $file);
1477 $self->add_selected_file_to_list($pathid,$filenameid,
1478 $file, $jobid, $type);
1481 $self->debug("end\n");
1484 sub on_back_button_clicked {
1486 $self->{bvfs}->up_dir();
1487 $self->refresh_fileview();
1489 sub on_location_go_button_clicked
1492 $self->ch_dir($self->{location}->get_text());
1494 sub on_quit_activate {Gtk2->main_quit;}
1495 sub on_preferences_activate
1498 $self->{dlg_pref}->display($self) ;
1500 sub on_main_delete_event {Gtk2->main_quit;}
1501 sub on_bweb_activate
1504 $self->set_status("Open bweb on your browser");
1505 $self->{pref}->go_bweb('', "go on bweb");
1508 # Change the current working directory
1509 # * Updates fileview, location, and selection
1515 my $p = $self->{bvfs}->get_pathid($l);
1517 $self->{bvfs}->ch_dir($p);
1518 $self->refresh_fileview();
1520 $self->set_status("Can't find $l");
1525 # Handle dialog 'close' (window-decoration induced close)
1526 # * Just hide the dialog, and tell Gtk not to do anything else
1530 my ($self, $w) = @_;
1533 1; # consume this event!
1536 # Handle key presses in location text edit control
1537 # * Translate a Return/Enter key into a 'Go' command
1538 # * All other key presses left for GTK
1540 sub on_location_entry_key_release_event
1546 my $keypress = $event->keyval;
1547 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1548 $keypress == $Gtk2::Gdk::Keysyms{Return})
1550 $self->ch_dir($widget->get_text());
1552 return 1; # consume keypress
1555 return 0; # let gtk have the keypress
1558 sub on_fileview_key_press_event
1560 my ($self, $widget, $event) = @_;
1564 sub listview_get_first
1567 my @selected = $list->get_selected_indices();
1568 if (@selected > 0) {
1569 my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1570 return ($pid,$fid,unpack('u', $name), @other);
1576 sub listview_get_all
1580 my @selected = $list->get_selected_indices();
1582 for my $i (@selected) {
1583 my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1584 push @ret, [$pid,$fid,unpack('u', $name), @other];
1591 my ($list, $pid, $fid, $name, @other) = @_;
1592 push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1595 #-----------------------------------------------------------------
1596 # Handle keypress in file-view
1597 # * Translates backspace into a 'cd ..' command
1598 # * All other key presses left for GTK
1600 sub on_fileview_key_release_event
1602 my ($self, $widget, $event) = @_;
1603 if (not $event->keyval)
1607 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1608 $self->on_back_button_clicked();
1609 return 1; # eat keypress
1612 return 0; # let gtk have keypress
1615 sub on_forward_keypress
1620 #-------------------------------------------------------------------
1621 # Handle double-click (or enter) on file-view
1622 # * Translates into a 'cd <dir>' command
1624 sub on_fileview_row_activated
1626 my ($self, $widget) = @_;
1628 my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1632 $self->{bvfs}->ch_dir($pid);
1633 $self->refresh_fileview();
1635 $self->fill_infoview($pid,$fid,$name);
1638 return 1; # consume event
1643 my ($self, $path, $file, $fn) = @_;
1644 $self->clear_infoview();
1645 my @v = $self->{bvfs}->get_all_file_versions($path,
1647 $self->current_client,
1648 $self->{pref}->{see_all_versions});
1650 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1651 $size,$inchanger,$md5,$volname) = @{$ver};
1652 my $icon = ($inchanger)?$yesicon:$noicon;
1654 $mtime = localtime($mtime) ;
1656 listview_push($self->{fileinfo},$pid,$fid,
1657 $fn, $jobid, 'file',
1658 $icon, $volname, $jobid, human($size), $mtime, $md5);
1665 return $self->{restore_backup_combobox}->get_active_text;
1671 return $self->{client_combobox}->get_active_text;
1674 sub on_list_backups_changed
1676 my ($self, $widget) = @_;
1677 return 0 unless defined $self->{fileview};
1679 $self->{CurrentJobIds} = [
1680 set_job_ids_for_date($self->dbh(),
1681 $self->current_client,
1682 $self->current_date,
1683 $self->{pref}->{use_ok_bkp_only})
1685 $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1686 $self->refresh_fileview();
1690 sub on_restore_list_keypress
1692 my ($self, $widget, $event) = @_;
1693 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1695 my @sel = $widget->get_selected_indices;
1696 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1698 splice @{$self->{restore_list}->{data}},$elt,1;
1703 sub on_fileview_button_press_event
1705 my ($self,$widget,$event) = @_;
1706 if ($event->button == 3)
1708 $self->on_right_click_filelist($widget,$event);
1712 if ($event->button == 2)
1714 $self->on_see_all_version();
1721 sub on_see_all_version
1725 my @lst = listview_get_all($self->{fileview});
1728 my ($pid,$fid,$name, undef) = @{$i};
1730 new DlgFileVersion($self->{bvfs},
1731 $self->current_client,
1732 $pid,$fid,$self->{cwd},$name);
1736 sub on_right_click_filelist
1738 my ($self,$widget,$event) = @_;
1739 # I need to know what's selected
1740 my @sel = listview_get_all($self->{fileview});
1745 $type = $sel[0]->[4]; # $type
1750 if (@sel >=2 or $type eq 'dir')
1752 # We have selected more than one or it is a directories
1753 $w = $self->{filelist_dir_menu};
1757 $w = $self->{filelist_file_menu};
1763 $event->button, $event->time);
1766 sub context_add_to_filelist
1770 my @sel = listview_get_all($self->{fileview});
1772 foreach my $i (@sel)
1774 my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1775 $file = $self->{cwd} . '/' . $file;
1776 $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1780 # Adds a file to the filelist
1781 sub add_selected_file_to_list
1783 my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1785 my $restore_list = $self->{restore_list};
1787 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1794 if ($name and substr $name,-1 ne '/')
1796 $name .= '/'; # For bacula
1798 my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1799 listview_push($restore_list,$pid,0,
1800 $name, $jobid, 'dir', $curjobids,
1801 $diricon, $name,$curjobids,$dirfileindex);
1803 elsif ($type eq 'file')
1805 my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1807 listview_push($restore_list,$pid,$fid,
1808 $name, $jobid, 'file', $curjobids,
1809 $fileicon, $name, $jobid, $fileindex );
1813 # TODO : we want be able to restore files from a bad ended backup
1814 # we have JobStatus IN ('T', 'A', 'E') and we must
1816 # Data acces subs from here. Interaction with SGBD and caching
1818 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1819 # GUI and stores them in @CurrentJobIds
1820 sub set_job_ids_for_date
1822 my ($dbh, $client, $date, $only_ok)=@_;
1824 if (!$client or !$date) {
1828 my $status = get_wanted_job_status($only_ok);
1830 # The algorithm : for a client, we get all the backups for each
1831 # fileset, in reverse order Then, for each fileset, we store the 'good'
1832 # incrementals and differentials until we have found a full so it goes
1833 # like this : store all incrementals until we have found a differential
1834 # or a full, then find the full #
1836 my $query = "SELECT JobId, FileSet, Level, JobStatus
1837 FROM Job, Client, FileSet
1838 WHERE Job.ClientId = Client.ClientId
1839 AND FileSet.FileSetId = Job.FileSetId
1840 AND EndTime <= '$date'
1841 AND Client.Name = '$client'
1843 AND JobStatus IN ($status)
1844 ORDER BY FileSet, JobTDate DESC";
1847 my $result = $dbh->selectall_arrayref($query);
1849 foreach my $refrow (@$result)
1851 my $jobid = $refrow->[0];
1852 my $fileset = $refrow->[1];
1853 my $level = $refrow->[2];
1855 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1857 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1861 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1862 push @CurrentJobIds,($jobid);
1864 elsif ($level eq 'D')
1866 next if $progress{$fileset} eq 'D'; # We allready have a differential
1867 push @CurrentJobIds,($jobid);
1869 elsif ($level eq 'F')
1871 push @CurrentJobIds,($jobid);
1874 my $status = $refrow->[3] ;
1875 if ($status eq 'T') { # good end of job
1876 $progress{$fileset} = $level;
1880 return @CurrentJobIds;
1885 Gtk2->main_iteration while (Gtk2->events_pending);
1888 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1889 # This sub creates a BSR from the information in the restore_list
1890 # Returns the BSR as a string
1895 # This query gets all jobid/jobmedia/media combination.
1897 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
1898 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1899 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
1900 Media.Volumename, Media.MediaType
1901 FROM Job, JobMedia, Media
1902 WHERE Job.JobId = JobMedia.JobId
1903 AND JobMedia.MediaId = Media.MediaId
1904 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1907 my $result = $self->dbh_selectall_arrayref($query);
1909 # We will store everything hashed by jobid.
1911 foreach my $refrow (@$result)
1913 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1914 $firstindex, $lastindex, $startblock, $endblock,
1915 $volindex, $volumename, $mediatype) = @{$refrow};
1917 # We just have to deal with the case where starfile != endfile
1918 # In this case, we concatenate both, for the bsr
1919 if ($startfile != $endfile) {
1920 $startfile = $startfile . '-' . $endfile;
1924 ($jobid, $volsessionid, $volsessiontime, $startfile,
1925 $firstindex, $lastindex, $startblock .'-'. $endblock,
1926 $volindex, $volumename, $mediatype);
1928 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
1932 # reminder : restore_list looks like this :
1933 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1934 # undef, undef, undef, $dirfileindex);
1936 # Here, we retrieve every file/dir that could be in the restore
1937 # We do as simple as possible for the SQL engine (no crazy joins,
1938 # no pseudo join (>= FirstIndex ...), etc ...
1939 # We do a SQL union of all the files/dirs specified in the restore_list
1941 foreach my $entry (@{$self->{restore_list}->{data}})
1943 if ($entry->[4] eq 'dir')
1945 my $dirid = $entry->[0];
1946 my $inclause = $entry->[5]; #curjobids
1949 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1950 FROM File, Path, Filename
1951 WHERE Path.PathId = File.PathId
1952 AND File.FilenameId = Filename.FilenameId
1954 (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
1955 WHERE PathId IN ($dirid)
1957 SELECT " . $self->dbh_strcat('Path',"'\%'") ." FROM brestore_missing_path WHERE PathId IN ($dirid)
1959 AND File.JobId IN ($inclause) )";
1960 push @select_queries,($query);
1964 # It's a file. Great, we allready have most
1965 # of what is needed. Simple and efficient query
1966 my $dir = $entry->[0];
1967 my $file = $entry->[1];
1969 my $jobid = $entry->[3];
1970 my $fileindex = $entry->[9];
1971 my $inclause = $entry->[5]; # curjobids
1973 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1974 FROM File,Path,Filename
1975 WHERE File.PathId = $dir
1976 AND File.PathId = Path.PathId
1977 AND File.FilenameId = $file
1978 AND File.FilenameId = Filename.FilenameId
1979 AND File.JobId = $jobid
1982 push @select_queries,($query);
1985 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
1987 #Now we run the query and parse the result...
1988 # there may be a lot of records, so we better be efficient
1989 # We use the bind column method, working with references...
1991 my $sth = $self->dbh_prepare($query);
1994 my ($path,$name,$fileindex,$jobid);
1995 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
1997 # The temp place we're going to save all file
1998 # list to before the real list
2002 while ($sth->fetchrow_arrayref())
2004 # This may look dumb, but we're going to do a join by ourselves,
2005 # to save memory and avoid sending a complex query to mysql
2006 my $complete_path = $path . $name;
2014 # Remove trailing slash (normalize file and dir name)
2015 $complete_path =~ s/\/$//;
2017 # Let's find the ref(s) for the %mediainfo element(s)
2018 # containing the data for this file
2019 # There can be several matches. It is the pseudo join.
2021 my $max_elt=@{$mediainfos{$jobid}}-1;
2023 while($med_idx <= $max_elt)
2025 my $ref = $mediainfos{$jobid}->[$med_idx];
2026 # First, can we get rid of the first elements of the
2027 # array ? (if they don't contain valuable records
2029 if ($fileindex > $ref->[5])
2031 # It seems we don't need anymore
2032 # this entry in %mediainfo (the input data
2035 shift @{$mediainfos{$jobid}};
2039 # We will do work on this elt. We can ++
2040 # $med_idx for next loop
2043 # %mediainfo row looks like :
2044 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2045 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2048 # We are in range. We store and continue looping
2050 if ($fileindex >= $ref->[4])
2052 my @data = ($complete_path,$is_dir,
2054 push @temp_list,(\@data);
2058 # We are not in range. No point in continuing looping
2059 # We go to next record.
2063 # Now we have the array.
2064 # We're going to sort it, by
2065 # path, volsessiontime DESC (get the most recent file...)
2066 # The array rows look like this :
2067 # complete_path,is_dir,fileindex,
2068 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2069 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2070 @temp_list = sort {$a->[0] cmp $b->[0]
2071 || $b->[3]->[2] <=> $a->[3]->[2]
2075 my $prev_complete_path='////'; # Sure not to match
2079 while (my $refrow = shift @temp_list)
2081 # For the sake of readability, we load $refrow
2082 # contents in real scalars
2083 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2084 my $jobid= $refother->[0]; # We don't need the rest...
2086 # We skip this entry.
2087 # We allready have a newer one and this
2088 # isn't a continuation of the same file
2089 next if ($complete_path eq $prev_complete_path
2090 and $jobid != $prev_jobid);
2094 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2096 # We would be recursing inside a file.
2097 # Just what we don't want (dir replaced by file
2098 # between two backups
2104 push @restore_list,($refrow);
2106 $prev_complete_path = $complete_path;
2107 $prev_jobid = $jobid;
2113 push @restore_list,($refrow);
2115 $prev_complete_path = $complete_path;
2116 $prev_jobid = $jobid;
2120 # We get rid of @temp_list... save memory
2123 # Ok everything is in the list. Let's sort it again in another way.
2124 # This time it will be in the bsr file order
2126 # we sort the results by
2127 # volsessiontime, volsessionid, volindex, fileindex
2128 # to get all files in right order...
2129 # Reminder : The array rows look like this :
2130 # complete_path,is_dir,fileindex,
2131 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2132 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2134 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2135 || $a->[3]->[1] <=> $b->[3]->[1]
2136 || $a->[3]->[7] <=> $b->[3]->[7]
2137 || $a->[2] <=> $b->[2] }
2140 # Now that everything is ready, we create the bsr
2141 my $prev_fileindex=-1;
2142 my $prev_volsessionid=-1;
2143 my $prev_volsessiontime=-1;
2144 my $prev_volumename=-1;
2145 my $prev_volfile=-1;
2149 my $first_of_current_range=0;
2150 my @fileindex_ranges;
2153 foreach my $refrow (@restore_list)
2155 my (undef,undef,$fileindex,$refother)=@{$refrow};
2156 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2157 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2159 # We can specifiy the number of files in each section of the
2160 # bsr to speedup restore (bacula can then jump over the
2161 # end of tape files.
2165 if ($prev_volumename eq '-1')
2167 # We only have to start the new range...
2168 $first_of_current_range=$fileindex;
2170 elsif ($prev_volsessionid != $volsessionid
2171 or $prev_volsessiontime != $volsessiontime
2172 or $prev_volumename ne $volumename
2173 or $prev_volfile ne $volfile)
2175 # We have to create a new section in the bsr...
2176 # We print the previous one ...
2177 # (before that, save the current range ...)
2178 if ($first_of_current_range != $prev_fileindex)
2181 push @fileindex_ranges,
2182 ("$first_of_current_range-$prev_fileindex");
2186 # We are out of a range,
2187 # but there is only one element in the range
2188 push @fileindex_ranges,
2189 ("$first_of_current_range");
2192 $bsr.=print_bsr_section(\@fileindex_ranges,
2194 $prev_volsessiontime,
2201 # Reset for next loop
2202 @fileindex_ranges=();
2203 $first_of_current_range=$fileindex;
2205 elsif ($fileindex-1 != $prev_fileindex)
2207 # End of a range of fileindexes
2208 if ($first_of_current_range != $prev_fileindex)
2211 push @fileindex_ranges,
2212 ("$first_of_current_range-$prev_fileindex");
2216 # We are out of a range,
2217 # but there is only one element in the range
2218 push @fileindex_ranges,
2219 ("$first_of_current_range");
2221 $first_of_current_range=$fileindex;
2223 $prev_fileindex=$fileindex;
2224 $prev_volsessionid = $volsessionid;
2225 $prev_volsessiontime = $volsessiontime;
2226 $prev_volumename = $volumename;
2227 $prev_volfile=$volfile;
2228 $prev_mediatype=$mediatype;
2229 $prev_volblocks=$volblocks;
2233 # Ok, we're out of the loop. Alas, there's still the last record ...
2234 if ($first_of_current_range != $prev_fileindex)
2237 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2242 # We are out of a range,
2243 # but there is only one element in the range
2244 push @fileindex_ranges,("$first_of_current_range");
2247 $bsr.=print_bsr_section(\@fileindex_ranges,
2249 $prev_volsessiontime,
2259 sub print_bsr_section
2261 my ($ref_fileindex_ranges,$volsessionid,
2262 $volsessiontime,$volumename,$volfile,
2263 $mediatype,$volblocks,$count)=@_;
2266 $bsr .= "Volume=\"$volumename\"\n";
2267 $bsr .= "MediaType=\"$mediatype\"\n";
2268 $bsr .= "VolSessionId=$volsessionid\n";
2269 $bsr .= "VolSessionTime=$volsessiontime\n";
2270 $bsr .= "VolFile=$volfile\n";
2271 $bsr .= "VolBlock=$volblocks\n";
2273 foreach my $range (@{$ref_fileindex_ranges})
2275 $bsr .= "FileIndex=$range\n";
2278 $bsr .= "Count=$count\n";
2284 ################################################################
2291 my ($self, $dir) = @_;
2293 "SELECT PathId FROM Path WHERE Path = ?
2295 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2296 my $sth = $self->dbh_prepare($query);
2297 $sth->execute($dir,$dir);
2298 my $result = $sth->fetchall_arrayref();
2301 return join(',', map { $_->[0] } @$result);
2306 my ($self, $dir) = @_;
2307 return $self->get_pathid('');
2312 my ($self, $pathid) = @_;
2313 $self->{cwd} = $pathid;
2320 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) ";
2322 my $all = $self->dbh_selectall_arrayref($query);
2323 return unless ($all); # already at root
2325 my $dir = join(',', map { $_->[0] } @$all);
2327 $self->{cwd} = $dir;
2334 return $self->get_path($self->{cwd});
2339 my ($self, $pathid) = @_;
2340 $self->debug("Call with pathid = $pathid");
2342 "SELECT Path FROM Path WHERE PathId IN (?)
2344 SELECT Path FROM brestore_missing_path WHERE PathId IN (?)";
2345 my $sth = $self->dbh_prepare($query);
2346 $sth->execute($pathid,$pathid);
2347 my $result = $sth->fetchrow_arrayref();
2349 return $result->[0];
2354 my ($self, @jobids) = @_;
2355 $self->{curjobids} = join(',', @jobids);
2362 return undef unless ($self->{curjobids});
2364 my $inclause = $self->{curjobids};
2365 my $inlistpath = $self->{cwd};
2368 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2370 (SELECT Filename.Name, max(File.FileId) as id
2372 WHERE File.FilenameId = Filename.FilenameId
2373 AND Filename.Name != ''
2374 AND File.PathId IN ($inlistpath)
2375 AND File.JobId IN ($inclause)
2376 GROUP BY Filename.Name
2377 ORDER BY Filename.Name) AS listfiles,
2379 WHERE File.FileId = listfiles.id";
2381 $self->debug($query);
2382 my $result = $self->dbh_selectall_arrayref($query);
2383 $self->debug($result);
2388 # return ($dirid,$dir_basename,$lstat,$jobid)
2393 return undef unless ($self->{curjobids});
2395 my $pathid = $self->{cwd};
2396 my $jobclause = $self->{curjobids};
2398 # Let's retrieve the list of the visible dirs in this dir ...
2399 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2400 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2401 my $sth = $self->dbh_prepare($query);
2403 my $result = $sth->fetchrow_arrayref();
2405 my $dir_filenameid = $result->[0];
2407 # Then we get all the dir entries from File ...
2408 # It's ugly because there are records in brestore_missing_path ...
2410 SELECT PathId, Path, JobId, Lstat FROM(
2412 SELECT Path.PathId, Path.Path, lower(Path.Path),
2413 listfile.JobId, listfile.Lstat
2415 SELECT DISTINCT brestore_pathhierarchy.PathId
2416 FROM brestore_pathhierarchy
2418 ON (brestore_pathhierarchy.PathId = Path.PathId)
2419 JOIN brestore_pathvisibility
2420 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2421 WHERE brestore_pathhierarchy.PPathId = $pathid
2422 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2423 JOIN Path ON (listpath.PathId = Path.PathId)
2425 SELECT File.PathId, File.JobId, File.Lstat FROM File
2426 WHERE File.FilenameId = $dir_filenameid
2427 AND File.JobId IN ($jobclause)) AS listfile
2428 ON (listpath.PathId = listfile.PathId)
2430 SELECT brestore_missing_path.PathId, brestore_missing_path.Path,
2431 lower(brestore_missing_path.Path), listfile.JobId, listfile.Lstat
2433 SELECT DISTINCT brestore_pathhierarchy.PathId
2434 FROM brestore_pathhierarchy
2435 JOIN brestore_missing_path
2436 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2437 JOIN brestore_pathvisibility
2438 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2439 WHERE brestore_pathhierarchy.PPathId = $pathid
2440 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2441 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2443 SELECT File.PathId, File.JobId, File.Lstat FROM File
2444 WHERE File.FilenameId = $dir_filenameid
2445 AND File.JobId IN ($jobclause)) AS listfile
2446 ON (listpath.PathId = listfile.PathId))
2447 ORDER BY 2,3 DESC ) As a";
2448 $self->debug($query);
2449 $sth=$self->dbh_prepare($query);
2451 $result = $sth->fetchall_arrayref();
2454 foreach my $refrow (@{$result})
2456 my $dirid = $refrow->[0];
2457 my $dir = $refrow->[1];
2458 my $lstat = $refrow->[3];
2459 my $jobid = $refrow->[2] || 0;
2460 next if ($dirid eq $prev_dir);
2461 # We have to clean up this dirname ... we only want it's 'basename'
2465 my @temp = split ('/',$dir);
2466 $return_value = pop @temp;
2470 $return_value = '/';
2472 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2473 push @return_list,(\@return_array);
2476 $self->debug(\@return_list);
2477 return \@return_list;
2480 # Returns the list of media required for a list of jobids.
2481 # Input : self, jobid1, jobid2...
2482 # Output : reference to array of (joibd, inchanger)
2483 sub get_required_media_from_jobid
2485 my ($self, @jobids)=@_;
2486 my $inclause = join(',',@jobids);
2488 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2489 FROM JobMedia, Media
2490 WHERE JobMedia.MediaId=Media.MediaId
2491 AND JobId In ($inclause)
2493 my $result = $self->dbh_selectall_arrayref($query);
2497 # Returns the fileindex from dirname and jobid.
2498 # Input : self, dirid, jobid
2499 # Output : fileindex
2500 sub get_fileindex_from_dir_jobid
2502 my ($self, $dirid, $jobid)=@_;
2504 $query = "SELECT File.FileIndex
2506 WHERE File.FilenameId = Filename.FilenameId
2507 AND File.PathId = $dirid
2508 AND Filename.Name = ''
2509 AND File.JobId = '$jobid'
2512 $self->debug($query);
2513 my $result = $self->dbh_selectall_arrayref($query);
2514 return $result->[0]->[0];
2517 # Returns the fileindex from filename and jobid.
2518 # Input : self, dirid, filenameid, jobid
2519 # Output : fileindex
2520 sub get_fileindex_from_file_jobid
2522 my ($self, $dirid, $filenameid, $jobid)=@_;
2526 "SELECT File.FileIndex
2528 WHERE File.PathId = $dirid
2529 AND File.FilenameId = $filenameid
2530 AND File.JobId = $jobid";
2532 $self->debug($query);
2533 my $result = $self->dbh_selectall_arrayref($query);
2534 return $result->[0]->[0];
2537 # This function estimates the size to be restored for an entry of the restore
2539 # In : self,reference to the entry
2540 # Out : size in bytes, number of files
2541 sub estimate_restore_size
2543 # reminder : restore_list looks like this :
2544 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2545 # undef, undef, undef, $dirfileindex);
2546 my ($self, $entry, $refresh) = @_;
2548 if ($entry->[4] eq 'dir')
2550 my $dir = $entry->[0];
2552 my $inclause = $entry->[5]; #curjobids
2554 "SELECT Path.Path, File.FilenameId, File.LStat
2555 FROM File, Path, Job
2556 WHERE Path.PathId = File.PathId
2557 AND File.JobId = Job.JobId
2559 (SELECT Path || '%' FROM Path WHERE PathId IN ($dir)
2561 SELECT Path || '%' FROM brestore_missing_path WHERE PathId IN ($dir)
2563 AND File.JobId IN ($inclause)
2564 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2568 # It's a file. Great, we allready have most
2569 # of what is needed. Simple and efficient query
2570 my $dir = $entry->[0];
2571 my $fileid = $entry->[1];
2573 my $jobid = $entry->[3];
2574 my $fileindex = $entry->[9];
2575 my $inclause = $entry->[5]; # curjobids
2577 "SELECT Path.Path, File.FilenameId, File.Lstat
2579 WHERE Path.PathId = File.PathId
2580 AND Path.PathId = $dir
2581 AND File.FilenameId = $fileid
2582 AND File.JobId = $jobid";
2585 my ($path,$nameid,$lstat);
2586 my $sth = $self->dbh_prepare($query);
2588 $sth->bind_columns(\$path,\$nameid,\$lstat);
2598 while ($sth->fetchrow_arrayref())
2600 # Only the latest version of a file
2601 next if ($nameid eq $old_nameid and $path eq $old_path);
2603 if ($rcount > 15000) {
2610 # We get the size of this file
2611 my $size=lstat_attrib($lstat,'st_size');
2612 $total_size += $size;
2615 $old_nameid=$nameid;
2618 return ($total_size,$total_files);
2621 # Returns list of versions of a file that could be restored
2622 # returns an array of
2623 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2624 # there will be only one jobid in the array of jobids...
2625 sub get_all_file_versions
2627 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2629 defined $see_all or $see_all=0;
2634 "SELECT File.JobId, File.FileIndex, File.Lstat,
2635 File.Md5, Media.VolumeName, Media.InChanger
2636 FROM File, Job, Client, JobMedia, Media
2637 WHERE File.FilenameId = $fileid
2638 AND File.PathId=$pathid
2639 AND File.JobId = Job.JobId
2640 AND Job.ClientId = Client.ClientId
2641 AND Job.JobId = JobMedia.JobId
2642 AND File.FileIndex >= JobMedia.FirstIndex
2643 AND File.FileIndex <= JobMedia.LastIndex
2644 AND JobMedia.MediaId = Media.MediaId
2645 AND Client.Name = '$client'";
2647 $self->debug($query);
2649 my $result = $self->dbh_selectall_arrayref($query);
2651 foreach my $refrow (@$result)
2653 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2654 my @attribs = parse_lstat($lstat);
2655 my $mtime = array_attrib('st_mtime',\@attribs);
2656 my $size = array_attrib('st_size',\@attribs);
2658 my @list = ('FILE:',$pathid,$fileid,$jobid,
2659 $fileindex, $mtime, $size, $inchanger,
2661 push @versions, (\@list);
2664 # We have the list of all versions of this file.
2665 # We'll sort it by mtime desc, size, md5, inchanger desc
2666 # the rest of the algorithm will be simpler
2667 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2668 @versions = sort { $b->[5] <=> $a->[5]
2669 || $a->[6] <=> $b->[6]
2670 || $a->[8] cmp $a->[8]
2671 || $b->[7] <=> $a->[7]} @versions;
2675 my %allready_seen_by_mtime;
2676 my %allready_seen_by_md5;
2677 # Now we should create a new array with only the interesting records
2678 foreach my $ref (@versions)
2682 # The file has a md5. We compare his md5 to other known md5...
2683 # We take size into account. It may happen that 2 files
2684 # have the same md5sum and are different. size is a supplementary
2687 # If we allready have a (better) version
2688 next if ( (not $see_all)
2689 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2691 # we never met this one before...
2692 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2694 # Even if it has a md5, we should also work with mtimes
2695 # We allready have a (better) version
2696 next if ( (not $see_all)
2697 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2698 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2700 # We reached there. The file hasn't been seen.
2701 push @good_versions,($ref);
2704 # To be nice with the user, we re-sort good_versions by
2705 # inchanger desc, mtime desc
2706 @good_versions = sort { $b->[5] <=> $a->[5]
2707 || $b->[3] <=> $a->[3]} @good_versions;
2709 return @good_versions;
2713 sub update_brestore_table
2715 my ($self, @jobs) = @_;
2717 foreach my $job (sort {$a <=> $b} @jobs)
2719 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2720 my $retour = $self->dbh_selectrow_arrayref($query);
2721 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2723 print STDERR "Inserting path records for JobId $job\n";
2724 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2725 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2727 $self->dbh_do($query);
2729 # Now we have to do the directory recursion stuff to determine missing visibility
2730 # We try to avoid recursion, to be as fast as possible
2731 # We also only work on not allready hierarchised directories...
2733 print STDERR "Creating missing recursion paths for $job\n";
2735 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2736 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2737 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2738 WHERE brestore_pathvisibility.JobId = $job
2739 AND brestore_pathhierarchy.PathId IS NULL
2742 my $sth = $self->dbh_prepare($query);
2744 my $pathid; my $path;
2745 $sth->bind_columns(\$pathid,\$path);
2749 $self->build_path_hierarchy($path,$pathid);
2753 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2754 # This query gives all parent pathids for a given jobid that aren't stored.
2755 # It has to be called until no record is updated ...
2757 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2758 SELECT a.PathId,$job
2760 (SELECT DISTINCT h.PPathId AS PathId
2761 FROM brestore_pathhierarchy AS h
2762 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2763 WHERE p.JobId=$job) AS a
2766 FROM brestore_pathvisibility
2767 WHERE JobId=$job) AS b
2768 ON (a.PathId = b.PathId)
2769 WHERE b.PathId IS NULL)";
2772 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2774 print STDERR "Recursively adding $rows_affected records from $job\n";
2777 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2778 $self->dbh_do($query);
2782 sub cleanup_brestore_table
2786 my $query = "SELECT JobId from brestore_knownjobid";
2787 my @jobs = @{$self->dbh_selectall_arrayref($query)};
2789 foreach my $jobentry (@jobs)
2791 my $job = $jobentry->[0];
2792 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2793 my $result = $self->dbh_selectall_arrayref($query);
2794 if (scalar(@{$result}))
2796 # There are still files for this jobid
2797 print STDERR "$job still exists. Not cleaning...\n";
2800 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2801 $self->dbh_do($query);
2802 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2803 $self->dbh_do($query);
2816 # Root Windows case :
2817 if ($path =~ /^[a-z]+:\/$/i)
2822 my @tmp = split('/',$path);
2823 # We remove the last ...
2825 my $tmp = join ('/',@tmp) . '/';
2829 sub build_path_hierarchy
2831 my ($self, $path,$pathid)=@_;
2832 # Does the ppathid exist for this ? we use a memory cache...
2833 # In order to avoid the full loop, we consider that if a dir is allready in the
2834 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2837 if (! $self->{cache_ppathid}->{$pathid})
2839 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2840 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2841 $sth2->execute($pathid);
2842 # Do we have a result ?
2843 if (my $refrow = $sth2->fetchrow_arrayref)
2845 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2847 # This dir was in the db ...
2848 # It means we can leave, the tree has allready been built for
2853 # We have to create the record ...
2854 # What's the current p_path ?
2855 my $ppath = parent_dir($path);
2856 my $ppathid = $self->return_pathid_from_path($ppath);
2857 $self->{cache_ppathid}->{$pathid}= $ppathid;
2859 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2860 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2861 $sth2->execute($pathid,$ppathid);
2867 # It's allready in the cache.
2868 # We can leave, no time to waste here, all the parent dirs have allready
2877 sub return_pathid_from_path
2879 my ($self, $path) = @_;
2880 my $query = "SELECT PathId FROM Path WHERE Path = ?
2882 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2883 #print STDERR $query,"\n" if $debug;
2884 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2885 $sth->execute($path,$path);
2886 my $result =$sth->fetchrow_arrayref();
2888 if (defined $result)
2890 return $result->[0];
2893 # A bit dirty : we insert into path AND missing_path, to be sure
2894 # we aren't deleted by a purge. We still need to insert into path to get
2895 # the pathid, because of mysql
2896 $query = "INSERT INTO Path (Path) VALUES (?)";
2897 #print STDERR $query,"\n" if $debug;
2898 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2899 $sth->execute($path);
2902 $query = " INSERT INTO brestore_missing_path (PathId,Path)
2903 SELECT PathId,Path FROM Path WHERE Path = ?";
2904 #print STDERR $query,"\n" if $debug;
2905 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2906 $sth->execute($path);
2908 $query = " DELETE FROM Path WHERE Path = ?";
2909 #print STDERR $query,"\n" if $debug;
2910 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2911 $sth->execute($path);
2913 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2914 #print STDERR $query,"\n" if $debug;
2915 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2916 $sth->execute($path);
2917 $result = $sth->fetchrow_arrayref();
2919 return $result->[0];
2924 sub create_brestore_tables
2928 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2930 unless ($self->dbh_do($verif)) {
2931 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2933 $self->{error} = "Creating internal brestore tables";
2935 CREATE TABLE brestore_knownjobid
2937 JobId int4 NOT NULL,
2938 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2940 $self->dbh_do($req);
2943 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2944 unless ($self->dbh_do($verif)) {
2946 CREATE TABLE brestore_pathhierarchy
2948 PathId int4 NOT NULL,
2949 PPathId int4 NOT NULL,
2950 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2952 $self->dbh_do($req);
2955 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2956 ON brestore_pathhierarchy (PPathId)";
2957 $self->dbh_do($req);
2960 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2961 unless ($self->dbh_do($verif)) {
2963 CREATE TABLE brestore_pathvisibility
2965 PathId int4 NOT NULL,
2966 JobId int4 NOT NULL,
2967 Size int8 DEFAULT 0,
2968 Files int4 DEFAULT 0,
2969 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2971 $self->dbh_do($req);
2973 $req = "CREATE INDEX brestore_pathvisibility_jobid
2974 ON brestore_pathvisibility (JobId)";
2975 $self->dbh_do($req);
2978 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2979 unless ($self->dbh_do($verif)) {
2981 CREATE TABLE brestore_missing_path
2983 PathId int4 NOT NULL,
2985 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2987 $self->dbh_do($req);
2989 $req = "CREATE INDEX brestore_missing_path_path
2990 ON brestore_missing_path (Path)";
2991 $self->dbh_do($req);
2997 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
2998 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
2999 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3000 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3001 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3002 'data_stream' => 15);;
3005 my ($attrib,$ref_attrib)=@_;
3006 return $ref_attrib->[$attrib_name_id{$attrib}];
3010 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3012 my ($file, $attrib)=@_;
3014 if (defined $attrib_name_id{$attrib}) {
3016 my @d = split(' ', $file->[3]) ; # TODO : cache this
3018 return from_base64($d[$attrib_name_id{$attrib}]);
3020 } elsif ($attrib eq 'jobid') {
3024 } elsif ($attrib eq 'name') {
3029 die "Attribute not known : $attrib.\n";
3035 my ($lstat,$attrib)=@_;
3036 if ($lstat and defined $attrib_name_id{$attrib})
3038 my @d = split(' ', $lstat) ; # TODO : cache this
3039 return from_base64($d[$attrib_name_id{$attrib}]);
3046 # Base 64 functions, directly from recover.pl.
3048 # Karl Hakimian <hakimian@aha.com>
3049 # This section is also under GPL v2 or later.
3056 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3057 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3058 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3059 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3060 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3062 @base64_map = (0) x 128;
3064 for (my $i=0; $i<64; $i++) {
3065 $base64_map[ord($base64_digits[$i])] = $i;
3080 if (substr($where, 0, 1) eq '-') {
3082 $where = substr($where, 1);
3085 while ($where ne '') {
3087 my $d = substr($where, 0, 1);
3088 $val += $base64_map[ord(substr($where, 0, 1))];
3089 $where = substr($where, 1);
3097 my @attribs = split(' ',$lstat);
3098 foreach my $element (@attribs)
3100 $element = from_base64($element);
3108 ################################################################
3109 package BwebConsole;
3111 use HTTP::Request::Common;
3115 my ($class, %arg) = @_;
3118 pref => $arg{pref}, # Pref object
3119 timeout => $arg{timeout} || 20,
3120 debug => $arg{debug} || 0,
3122 'list_client' => '',
3123 'list_fileset' => '',
3124 'list_storage' => '',
3133 my ($self, @what) = @_;
3134 my $ua = LWP::UserAgent->new();
3135 $ua->agent("Brestore/$VERSION");
3136 my $req = POST($self->{pref}->{bconsole},
3137 Content_Type => 'form-data',
3138 Content => [ map { (action => $_) } @what ]);
3139 #$req->authorization_basic('eric', 'test');
3141 my $res = $ua->request($req);
3143 if ($res->is_success) {
3144 foreach my $l (split(/\n/, $res->content)) {
3145 my ($k, $c) = split(/=/,$l,2);
3149 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3150 new DlgWarn($self->{error});
3156 my ($self, %arg) = @_;
3158 my $ua = LWP::UserAgent->new();
3159 $ua->agent("Brestore/$VERSION");
3160 my $req = POST($self->{pref}->{bconsole},
3161 Content_Type => 'form-data',
3162 Content => [ job => $arg{job},
3163 client => $arg{client},
3164 storage => $arg{storage} || '',
3165 fileset => $arg{fileset} || '',
3166 where => $arg{where},
3167 replace => $arg{replace},
3168 priority=> $arg{prio} || '',
3171 bootstrap => [$arg{bootstrap}],
3173 #$req->authorization_basic('eric', 'test');
3175 my $res = $ua->request($req);
3177 if ($res->is_success) {
3178 foreach my $l (split(/\n/, $res->content)) {
3179 my ($k, $c) = split(/=/,$l,2);
3184 if (!$self->{run}) {
3185 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3188 unlink($arg{bootstrap});
3190 return $self->{run};
3196 return sort split(/;/, $self->{'list_job'});
3202 return sort split(/;/, $self->{'list_fileset'});
3208 return sort split(/;/, $self->{'list_storage'});
3213 return sort split(/;/, $self->{'list_client'});
3218 ################################################################
3221 use base qw/DlgResto/;
3225 my ($class, $conf) = @_;
3226 my $self = bless {info => $conf}, $class;
3228 $self->{dbh} = $conf->{dbh};
3237 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3238 my $jobs = $self->dbh_selectall_arrayref($query);
3240 $self->{bvfs}->update_brestore_table(map { $_->[0] } @$jobs);
3251 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3255 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3258 GetOptions("conf=s" => \$file_conf,
3259 "batch" => \$batch_mod,
3261 "help" => \&HELP_MESSAGE) ;
3263 my $p = new Pref($file_conf);
3265 if (! -f $file_conf) {
3270 my $b = new Batch($p);
3271 if ($p->connect_db()) {
3272 $b->set_dbh($p->{dbh});
3278 $glade_file = $p->{glade_file};
3280 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3281 if (-f "$path/$glade_file") {
3282 $glade_file = "$path/$glade_file" ;
3287 # gtk have lots of warning on stderr
3288 if ($^O eq 'MSWin32')
3291 open(STDERR, ">stderr.log");
3296 if ( -f $glade_file) {
3297 my $w = new DlgResto($p);
3300 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3301 "Can't find your brestore.glade (glade_file => '$glade_file')
3302 Please, edit your $file_conf to setup it." );
3304 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3309 Gtk2->main; # Start Gtk2 main loop
3318 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3320 $p->connect_db() || print $p->{error};
3322 my $bvfs = new Bvfs(conf => $p);
3324 $bvfs->debug($bvfs->get_root());
3325 $bvfs->ch_dir($bvfs->get_root());
3327 $bvfs->set_curjobids(268,178,282,281,279);
3329 my $dirs = $bvfs->ls_dirs();
3330 $bvfs->ch_dir(123496);
3331 $dirs = $bvfs->ls_dirs();
3333 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);