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+)/);
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
143 for my $k (@{ $self->{entry_keyword} }) {
144 $parameters{$k} = $self->{$k};
147 for my $k (@{ $self->{chk_keyword} }) {
148 $parameters{$k} = $self->{$k};
151 if (open FICCFG,">$self->{config_file}")
153 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
158 $self->{error} = "Can't write configuration $!";
160 return $self->{error};
168 $self->{dbh}->disconnect() ;
172 delete $self->{error};
174 if (not $self->{connection_string})
176 # The parameters have not been set. Maybe the conf
177 # file is empty for now
178 $self->{error} = "No configuration found for database connection. " .
179 "Please set this up.";
184 $self->{dbh} = DBI->connect($self->{connection_string},
189 $self->{error} = "Can't open bacula database. " .
190 "Database connect string '" .
191 $self->{connection_string} ."' $!";
194 $self->{is_mysql} = ($self->{connection_string} =~ m/dbi:mysql/i);
195 $self->{dbh}->{RowCacheSize}=100;
201 my ($self, $url, $msg) = @_;
203 unless ($self->{mozilla} and $self->{bweb}) {
204 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
208 if ($^O eq 'MSWin32') {
209 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
212 system("$self->{mozilla} -remote 'Ping()'");
213 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
215 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
225 ################################################################
231 my ($class, %arg) = @_;
244 my ($self, $what, %arg) = @_;
246 if ($self->{conf}->{debug} and defined $what) {
251 my $line = (caller($level))[2];
252 my $func = (caller($level+1))[3] || 'main';
253 print "$func:$line\t";
255 print Data::Dumper::Dumper($what);
256 } elsif ($arg{md5}) {
257 print "MD5=", md5_base64($what), " str=", $what,"\n";
266 my ($self, @what) = @_;
267 if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
268 return join(' || ', @what);
270 return 'CONCAT(' . join(',', @what) . ')' ;
276 my ($self, $query) = @_;
277 $self->debug($query, up => 1);
278 return $self->{conf}->{dbh}->prepare($query);
283 my ($self, $query) = @_;
284 $self->debug($query, up => 1);
285 return $self->{conf}->{dbh}->do($query);
288 sub dbh_selectall_arrayref
290 my ($self, $query) = @_;
291 $self->debug($query, up => 1);
292 return $self->{conf}->{dbh}->selectall_arrayref($query);
295 sub dbh_selectrow_arrayref
297 my ($self, $query) = @_;
298 $self->debug($query, up => 1);
299 return $self->{conf}->{dbh}->selectrow_arrayref($query);
305 return $self->{conf}->{dbh};
310 ################################################################
314 # my $pref = new Pref(config_file => 'brestore.conf');
315 # my $dlg = new DlgPref($pref);
316 # my $dlg_resto = new DlgResto($pref);
317 # $dlg->display($dlg_resto);
320 my ($class, $pref) = @_;
323 pref => $pref, # Pref ref
324 dlgresto => undef, # DlgResto ref
332 my ($self, $dlgresto) = @_ ;
334 unless ($self->{glade}) {
335 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
336 $self->{glade}->signal_autoconnect_from_package($self);
339 $self->{dlgresto} = $dlgresto;
341 my $g = $self->{glade};
342 my $p = $self->{pref};
344 for my $k (@{ $p->{entry_keyword} }) {
345 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
348 for my $k (@{ $p->{chk_keyword} }) {
349 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
352 $g->get_widget("dlg_pref")->show_all() ;
355 sub on_applybutton_clicked
358 my $glade = $self->{glade};
359 my $pref = $self->{pref};
361 for my $k (@{ $pref->{entry_keyword} }) {
362 my $w = $glade->get_widget("entry_$k") ;
363 $pref->{$k} = $w->get_text();
366 for my $k (@{ $pref->{chk_keyword} }) {
367 my $w = $glade->get_widget("chkbp_$k") ;
368 $pref->{$k} = $w->get_active();
371 if (!$pref->write_config() && $pref->connect_db()) {
372 $self->{dlgresto}->set_status('Preferences updated');
373 $self->{dlgresto}->init_server_backup_combobox();
374 $self->{dlgresto}->set_status($pref->{error});
377 $self->{dlgresto}->set_status($pref->{error});
381 # Handle prefs ok click (apply/dismiss dialog)
382 sub on_okbutton_clicked
385 $self->on_applybutton_clicked();
387 unless ($self->{pref}->{error}) {
388 $self->on_cancelbutton_clicked();
391 sub on_dialog_delete_event
394 $self->on_cancelbutton_clicked();
398 sub on_cancelbutton_clicked
401 $self->{glade}->get_widget('dlg_pref')->hide();
402 delete $self->{dlgresto};
406 ################################################################
408 package DlgFileVersion;
410 sub on_versions_close_clicked
412 my ($self, $widget)=@_;
413 $self->{version}->destroy();
416 sub on_selection_button_press_event
418 print STDERR "on_selection_button_press_event()\n";
421 sub fileview_data_get
423 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
425 DlgResto::drag_set_info($widget,
432 my ($class, $bvfs, $client, $path, $file, $cwd, $fn) = @_;
435 version => undef, # main window
438 # we load version widget of $glade_file
439 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
441 # Connect signals magically
442 $glade_box->signal_autoconnect_from_package($self);
444 $glade_box->get_widget("version_label")
445 ->set_markup("<b>File revisions : $client:$cwd$fn</b>");
447 my $widget = $glade_box->get_widget('version_fileview');
448 my $fileview = Gtk2::SimpleList->new_from_treeview(
450 'h_pathid' => 'hidden',
451 'h_filenameid' => 'hidden',
452 'h_name' => 'hidden',
453 'h_jobid' => 'hidden',
454 'h_type' => 'hidden',
456 'InChanger' => 'pixbuf',
463 DlgResto::init_drag_drop($fileview);
465 my @v = $bvfs->get_all_file_versions($path,
470 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
471 $inchanger,$md5,$volname) = @{$ver};
472 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
474 DlgResto::listview_push($fileview,$pid,$fid,
476 $icon, $volname, $jobid,DlgResto::human($size),
477 scalar(localtime($mtime)), $md5);
480 $self->{version} = $glade_box->get_widget('dlg_version');
481 $self->{version}->show();
486 sub on_forward_keypress
492 ################################################################
497 my ($package, $text) = @_;
501 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
503 # Connect signals magically
504 $glade->signal_autoconnect_from_package($self);
505 $glade->get_widget('label_warn')->set_text($text);
507 print STDERR "$text\n";
509 $self->{window} = $glade->get_widget('dlg_warn');
510 $self->{window}->show_all();
517 $self->{window}->destroy();
521 ################################################################
527 # %arg = (bsr_file => '/path/to/bsr', # on director
528 # volumes => [ '00001', '00004']
536 if ($pref->{bconsole} =~ /^http/) {
537 return new BwebConsole(pref => $pref);
539 if (eval { require Bconsole; }) {
540 return new Bconsole(pref => $pref);
542 new DlgWarn("Can't use bconsole, verify your setup");
550 my ($class, %arg) = @_;
553 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
554 pref => $arg{pref}, # Pref ref
555 glade => undef, # GladeXML ref
556 bconsole => undef, # Bconsole ref
559 my $console = $self->{bconsole} = get_bconsole($arg{pref});
564 # we load launch widget of $glade_file
565 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
568 # Connect signals magically
569 $glade->signal_autoconnect_from_package($self);
571 my $widget = $glade->get_widget('volumeview');
572 my $volview = Gtk2::SimpleList->new_from_treeview(
574 'InChanger' => 'pixbuf',
578 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
580 # we replace 0 and 1 by $noicon and $yesicon
581 for my $i (@{$infos}) {
583 $i->[0] = $DlgResto::noicon;
585 $i->[0] = $DlgResto::yesicon;
590 push @{ $volview->{data} }, @{$infos} ;
592 $console->prepare(qw/list_client list_job list_fileset list_storage/);
594 # fill client combobox (with director defined clients
595 my @clients = $console->list_client() ; # get from bconsole
596 if ($console->{error}) {
597 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
599 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
600 $self->{list_client} = DlgResto::init_combo($w, 'text');
601 DlgResto::fill_combo($self->{list_client},
602 $DlgResto::client_list_empty,
606 # fill fileset combobox
607 my @fileset = $console->list_fileset() ;
608 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
609 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
610 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
613 my @job = $console->list_job() ;
614 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
615 $self->{list_job} = DlgResto::init_combo($w, 'text');
616 DlgResto::fill_combo($self->{list_job}, '', @job);
618 # find default_restore_job in jobs list
619 my $default_restore_job = $arg{pref}->{default_restore_job} ;
623 if ($j =~ /$default_restore_job/io) {
629 $w->set_active($index);
631 # fill storage combobox
632 my @storage = $console->list_storage() ;
633 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
634 $self->{list_storage} = DlgResto::init_combo($w, 'text');
635 DlgResto::fill_combo($self->{list_storage}, '', @storage);
637 $glade->get_widget('dlg_launch')->show_all();
644 my ($self, $client, $jobid) = @_;
646 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
648 $self->on_cancel_resto_clicked();
651 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
652 "Your job have been submited to bacula.
653 To follow it, you must use bconsole (or install/configure bweb)");
659 sub on_use_regexp_toggled
661 my ($self,$widget) = @_;
662 my $act = $widget->get_active();
664 foreach my $w ('entry_launch_where') {
665 $self->{glade}->get_widget($w)->set_sensitive(!$act);
668 foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
669 'entry_add_suffix','entry_rwhere','chk_use_regexp')
671 $self->{glade}->get_widget($w)->set_sensitive($act);
674 if ($act) { # if we activate file relocation, we reset use_regexp
675 $self->{glade}->get_widget('entry_rwhere')->set_sensitive(0);
676 $self->{glade}->get_widget('chk_use_regexp')->set_active(0);
681 sub on_use_rwhere_toggled
683 my ($self,$widget) = @_;
684 my $act = $widget->get_active();
686 foreach my $w ('entry_rwhere') {
687 $self->{glade}->get_widget($w)->set_sensitive($act);
690 foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
693 $self->{glade}->get_widget($w)->set_sensitive(!$act);
697 sub on_cancel_resto_clicked
700 $self->{glade}->get_widget('dlg_launch')->destroy();
707 if ($self->{glade}->get_widget('chk_file_relocation')->get_active()) {
709 if ($self->{glade}->get_widget('chk_use_regexp')->get_active()) {
711 return ('regexwhere',
712 $self->{glade}->get_widget('entry_rwhere')->get_active());
717 my ($strip_prefix, $add_prefix, $add_suffix) =
718 ($self->{glade}->get_widget('entry_strip_prefix')->get_text(),
719 $self->{glade}->get_widget('entry_add_prefix')->get_text(),
720 $self->{glade}->get_widget('entry_add_suffix')->get_text());
723 push @ret,"!$strip_prefix!!i";
727 push @ret,"!^!$add_prefix!";
731 push @ret,"!([^/])\$!\$1$add_suffix!";
734 return ('regexwhere', join(',', @ret));
736 } else { # using where
738 $self->{glade}->get_widget('entry_launch_where')->get_text());
742 sub on_submit_resto_clicked
745 my $glade = $self->{glade};
747 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
750 new DlgWarn("Can't copy bsr file to director ($self->{error})");
754 my $fileset = $glade->get_widget('combo_launch_fileset')
757 my $storage = $glade->get_widget('combo_launch_storage')
760 my ($where_cmd, $where) = $self->get_where();
761 print "$where_cmd => $where\n";
763 my $job = $glade->get_widget('combo_launch_job')
767 new DlgWarn("Can't use this job");
771 my $client = $glade->get_widget('combo_launch_client')
774 if (! $client or $client eq $DlgResto::client_list_empty) {
775 new DlgWarn("Can't use this client ($client)");
779 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
781 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
782 $replace=($replace)?'always':'never';
784 my $jobid = $self->{bconsole}->run(job => $job,
788 $where_cmd => $where,
793 $self->show_job($client, $jobid);
796 sub on_combo_storage_button_press_event
799 print "on_combo_storage_button_press_event()\n";
802 sub on_combo_fileset_button_press_event
805 print "on_combo_fileset_button_press_event()\n";
809 sub on_combo_job_button_press_event
812 print "on_combo_job_button_press_event()\n";
815 sub get_volume_inchanger
817 my ($dbh, $vols) = @_;
819 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
821 my $rq = "SELECT InChanger, VolumeName
823 WHERE VolumeName IN ($lst)
826 my $res = $dbh->selectall_arrayref($rq);
827 return $res; # [ [ 1, VolName].. ]
831 use File::Copy qw/copy/;
832 use File::Basename qw/basename/;
834 # We must kown the path+filename destination
835 # $self->{error} contains error message
836 # it return 0/1 if fail/success
839 my ($self, $src, $dst) = @_ ;
840 print "$src => $dst\n"
851 if ($dst =~ m!file:/(/.+)!) {
852 $ret = copy($src, $1);
854 $dstfile = "$1/" . basename($src) ;
856 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
857 $err = `scp $src $1 2>&1` ;
859 $dstfile = "$2/" . basename($src) ;
863 $err = "$dst not implemented yet";
864 File::Copy::copy($src, \*STDOUT);
867 $self->{error} = $err;
870 $self->{error} = $err;
879 ################################################################
887 unless ($about_widget) {
888 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
889 $about_widget = $glade_box->get_widget("dlg_about") ;
890 $glade_box->signal_autoconnect_from_package('DlgAbout');
892 $about_widget->show() ;
895 sub on_about_okbutton_clicked
897 $about_widget->hide() ;
902 ################################################################
912 # Kept as is from the perl-gtk example. Draws the pretty icons
918 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
919 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
920 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
921 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
924 # init combo (and create ListStore object)
927 my ($widget, @type) = @_ ;
928 my %type_info = ('text' => 'Glib::String',
929 'markup' => 'Glib::String',
932 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
934 $widget->set_model($lst);
938 if ($t eq 'text' or $t eq 'markup') {
939 $cell = new Gtk2::CellRendererText();
941 $widget->pack_start($cell, 1);
942 $widget->add_attribute($cell, $t, $i++);
947 # fill simple combo (one element per row)
950 my ($list, @what) = @_;
954 foreach my $w (@what)
957 my $i = $list->append();
958 $list->set($i, 0, $w);
965 my @unit = qw(b Kb Mb Gb Tb);
968 my $format = '%i %s';
969 while ($val / 1024 > 1) {
973 $format = ($i>0)?'%0.1f %s':'%i %s';
974 return sprintf($format, $val, $unit[$i]);
977 sub get_wanted_job_status
984 return "'T', 'A', 'E'";
988 # This sub gives a full list of the EndTimes for a ClientId
989 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
990 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
991 sub get_all_endtimes_for_job
993 my ($self, $client, $ok_only)=@_;
994 my $status = get_wanted_job_status($ok_only);
996 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
997 FROM Job,Client,FileSet
998 WHERE Job.ClientId=Client.ClientId
999 AND Client.Name = '$client'
1001 AND JobStatus IN ($status)
1002 AND Job.FileSetId = FileSet.FileSetId
1003 ORDER BY EndTime desc";
1004 my $result = $self->dbh_selectall_arrayref($query);
1011 my ($fileview) = shift;
1012 my $fileview_target_entry = {target => 'STRING',
1013 flags => ['GTK_TARGET_SAME_APP'],
1016 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
1017 ['copy'],$fileview_target_entry);
1018 $fileview->get_selection->set_mode('multiple');
1020 # set some useful SimpleList properties
1021 $fileview->set_headers_clickable(0);
1022 foreach ($fileview->get_columns())
1024 $_->set_resizable(1);
1025 $_->set_sizing('grow-only');
1031 my ($class, $pref) = @_;
1035 CurrentJobIds => [],
1036 location => undef, # location entry widget
1037 mainwin => undef, # mainwin widget
1038 filelist_file_menu => undef, # file menu widget
1039 filelist_dir_menu => undef, # dir menu widget
1040 glade => undef, # glade object
1041 status => undef, # status bar widget
1042 dlg_pref => undef, # DlgPref object
1043 fileattrib => {}, # cache file
1044 fileview => undef, # fileview widget SimpleList
1045 fileinfo => undef, # fileinfo widget SimpleList
1047 client_combobox => undef, # client_combobox widget
1048 restore_backup_combobox => undef, # date combobox widget
1049 list_client => undef, # Gtk2::ListStore
1050 list_backup => undef, # Gtk2::ListStore
1051 cache_ppathid => {}, #
1055 $self->{bvfs} = new Bvfs(conf => $pref);
1057 # load menu (to use handler with self reference)
1058 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1059 $glade->signal_autoconnect_from_package($self);
1060 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1062 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1063 $glade->signal_autoconnect_from_package($self);
1064 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1066 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1067 $glade->signal_autoconnect_from_package($self);
1069 $self->{status} = $glade->get_widget('statusbar');
1070 $self->{mainwin} = $glade->get_widget('dlg_resto');
1071 $self->{location} = $glade->get_widget('entry_location');
1072 $self->render_icons();
1074 $self->{dlg_pref} = new DlgPref($pref);
1076 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1077 $self->{list_client} = init_combo($c, 'text');
1079 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1080 $self->{list_backup} = init_combo($c, 'text', 'markup');
1082 # Connect glade-fileview to Gtk2::SimpleList
1083 # and set up drag n drop between $fileview and $restore_list
1085 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1086 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1088 my $widget = $glade->get_widget('fileview');
1089 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1091 'h_pathid' => 'hidden',
1092 'h_filenameid' => 'hidden',
1093 'h_name' => 'hidden',
1094 'h_jobid' => 'hidden',
1095 'h_type' => 'hidden',
1098 'File Name' => 'text',
1101 init_drag_drop($fileview);
1102 $fileview->set_search_column(6); # search on File Name
1104 # Connect glade-restore_list to Gtk2::SimpleList
1105 $widget = $glade->get_widget('restorelist');
1106 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1108 'h_pathid' => 'hidden', #0
1109 'h_filenameid' => 'hidden',
1110 'h_name' => 'hidden',
1111 'h_jobid' => 'hidden',
1112 'h_type' => 'hidden',
1113 'h_curjobid' => 'hidden', #5
1116 'File Name' => 'text',
1118 'FileIndex' => 'text',
1120 'Nb Files' => 'text', #10
1121 'Size' => 'text', #11
1122 'size_b' => 'hidden', #12
1125 my @restore_list_target_table = ({'target' => 'STRING',
1129 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1130 $restore_list->get_selection->set_mode('multiple');
1132 $widget = $glade->get_widget('infoview');
1133 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1135 'h_pathid' => 'hidden',
1136 'h_filenameid' => 'hidden',
1137 'h_name' => 'hidden',
1138 'h_jobid' => 'hidden',
1139 'h_type' => 'hidden',
1141 'InChanger' => 'pixbuf',
1148 init_drag_drop($infoview);
1150 $pref->connect_db() || $self->{dlg_pref}->display($self);
1153 $self->init_server_backup_combobox();
1154 $self->{bvfs}->create_brestore_tables();
1157 $self->set_status($pref->{error});
1160 # set status bar informations
1163 my ($self, $string) = @_;
1164 return unless ($string);
1166 my $context = $self->{status}->get_context_id('Main');
1167 $self->{status}->push($context, $string);
1170 sub on_time_select_changed
1178 my $c = $self->{glade}->get_widget('combo_time');
1179 return $c->get_active_text;
1182 # This sub returns all clients declared in DB
1186 my $query = "SELECT Name FROM Client ORDER BY Name";
1187 print STDERR $query,"\n" if $debug;
1189 my $result = $dbh->selectall_arrayref($query);
1191 return map { $_->[0] } @$result;
1194 # init infoview widget
1198 @{$self->{fileinfo}->{data}} = ();
1202 sub on_clear_clicked
1205 @{$self->{restore_list}->{data}} = ();
1208 sub on_estimate_clicked
1215 # TODO : If we get here, things could get lenghty ... draw a popup window .
1216 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1217 'destroy-with-parent',
1219 'Computing size...');
1223 my $title = "Computing size...\n";
1226 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1227 # undef, undef, undef, $dirfileindex);
1228 foreach my $entry (@{$self->{restore_list}->{data}})
1230 unless ($entry->[11]) {
1231 my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1232 $entry->[12] = $size;
1233 $entry->[11] = human($size);
1237 my $name = unpack('u', $entry->[2]);
1239 $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1240 $self->debug($title . $txt);
1241 $widget->set_markup($title . $txt);
1243 $size_total+=$entry->[12];
1244 $nb_total+=$entry->[10];
1248 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1249 $widget->set_markup("Size estimation :\n" . $txt);
1250 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1257 sub on_gen_bsr_clicked
1261 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1262 'gtk-save','ok', 'gtk-cancel', 'cancel');
1265 my $w = new Gtk2::FileChooserDialog ( @options );
1270 if ($a eq 'cancel') {
1275 my $f = $w->get_filename();
1277 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1278 'destroy-with-parent',
1279 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1280 if ($dlg->run() eq 'ok') {
1294 if (open(FP, ">$save")) {
1295 my $bsr = $self->create_filelist();
1298 $self->set_status("Dumping BSR to $save ok");
1300 $self->set_status("Can't dump BSR to $save: $!");
1306 use File::Temp qw/tempfile/;
1308 sub on_go_button_clicked
1311 unless (scalar(@{$self->{restore_list}->{data}})) {
1312 new DlgWarn("No file to restore");
1315 my $bsr = $self->create_filelist();
1316 my ($fh, $filename) = tempfile();
1319 chmod(0644, $filename);
1321 print "Dumping BSR info to $filename\n"
1324 # we get Volume list
1325 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1326 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1328 new DlgLaunch(pref => $self->{conf},
1330 bsr_file => $filename,
1336 our $client_list_empty = 'Clients list';
1337 our %type_markup = ('F' => '<b>$label F</b>',
1340 'B' => '<b>$label B</b>',
1342 'A' => '<span foreground=\"red\">$label</span>',
1344 'E' => '<span foreground=\"red\">$label</span>',
1347 sub on_list_client_changed
1349 my ($self, $widget) = @_;
1350 return 0 unless defined $self->{fileview};
1352 $self->{list_backup}->clear();
1354 if ($self->current_client eq $client_list_empty) {
1358 $self->{CurrentJobIds} = [
1359 set_job_ids_for_date($self->dbh(),
1360 $self->current_client,
1361 $self->current_date,
1362 $self->{conf}->{use_ok_bkp_only})
1365 my $fs = $self->{bvfs};
1366 $fs->set_curjobids(@{$self->{CurrentJobIds}});
1367 $fs->ch_dir($fs->get_root());
1368 # refresh_fileview will be done by list_backup_changed
1371 my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1372 $self->{conf}->{use_ok_bkp_only});
1374 foreach my $endtime (@endtimes)
1376 my $i = $self->{list_backup}->append();
1378 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1379 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1380 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1382 $self->{list_backup}->set($i,
1387 $self->{restore_backup_combobox}->set_active(0);
1392 sub fill_server_list
1394 my ($dbh, $combo, $list) = @_;
1396 my @clients=get_all_clients($dbh);
1400 my $i = $list->append();
1401 $list->set($i, 0, $client_list_empty);
1403 foreach my $client (@clients)
1405 $i = $list->append();
1406 $list->set($i, 0, $client);
1408 $combo->set_active(0);
1411 sub init_server_backup_combobox
1414 fill_server_list($self->{conf}->{dbh},
1415 $self->{client_combobox},
1416 $self->{list_client}) ;
1419 #----------------------------------------------------------------------
1420 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1421 #data isn't. There is additionnal complexity for dirs (visibility problems),
1422 #so the @CurrentJobIds is not sufficient.
1423 sub refresh_fileview
1426 my $fileview = $self->{fileview};
1427 my $client_combobox = $self->{client_combobox};
1428 my $bvfs = $self->{bvfs};
1430 @{$fileview->{data}} = ();
1432 $self->clear_infoview();
1434 my $client_name = $self->current_client;
1436 if (!$client_name or ($client_name eq $client_list_empty)) {
1437 $self->set_status("Client list empty");
1441 # [ [dirid, dir_basename, File.LStat, jobid]..]
1442 my $list_dirs = $bvfs->ls_dirs();
1443 # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1444 my $files = $bvfs->ls_files();
1446 my $file_count = 0 ;
1447 my $total_bytes = 0;
1449 # Add directories to view
1450 foreach my $dir_entry (@$list_dirs) {
1451 my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1452 $total_bytes += 4096;
1455 listview_push($fileview,
1459 # TODO: voir ce que l'on met la
1470 foreach my $file (@$files)
1472 my $size = Bvfs::file_attrib($file,'st_size');
1473 my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1474 $total_bytes += $size;
1476 # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1477 listview_push($fileview,
1486 human($size), $time);
1489 $self->set_status("$file_count files/" . human($total_bytes));
1490 $self->{cwd} = $self->{bvfs}->pwd();
1491 $self->{location}->set_text($self->{cwd});
1492 # set a decent default selection (makes keyboard nav easy)
1493 $fileview->select(0);
1497 sub on_about_activate
1499 DlgAbout::display();
1504 my ($tree, $path, $data) = @_;
1506 my @items = listview_get_all($tree) ;
1508 foreach my $i (@items)
1510 my @file_info = @{$i};
1513 # Ok, we have a corner case :
1515 my $file = pack("u", $path . $file_info[2]);
1517 push @ret, join(" ; ", $file,
1518 $file_info[0], # $pathid
1519 $file_info[1], # $filenameid
1520 $file_info[3], # $jobid
1521 $file_info[4], # $type
1525 my $data_get = join(" :: ", @ret);
1527 $data->set_text($data_get,-1);
1532 sub fileview_data_get
1534 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1535 drag_set_info($widget, $self->{cwd}, $data);
1538 sub fileinfo_data_get
1540 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1541 drag_set_info($widget, $self->{cwd}, $data);
1544 sub restore_list_data_received
1546 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1548 $self->debug("start\n");
1549 if ($info eq 40 || $info eq 0) # patch for display!=:0
1551 foreach my $elt (split(/ :: /, $data->data()))
1553 my ($file, $pathid, $filenameid, $jobid, $type) =
1555 $file = unpack("u", $file);
1557 $self->add_selected_file_to_list($pathid,$filenameid,
1558 $file, $jobid, $type);
1561 $self->debug("end\n");
1564 sub on_back_button_clicked {
1566 $self->{bvfs}->up_dir();
1567 $self->refresh_fileview();
1569 sub on_location_go_button_clicked
1572 $self->ch_dir($self->{location}->get_text());
1574 sub on_quit_activate {Gtk2->main_quit;}
1575 sub on_preferences_activate
1578 $self->{dlg_pref}->display($self) ;
1580 sub on_main_delete_event {Gtk2->main_quit;}
1581 sub on_bweb_activate
1584 $self->set_status("Open bweb on your browser");
1585 $self->{conf}->go_bweb('', "go on bweb");
1588 # Change the current working directory
1589 # * Updates fileview, location, and selection
1595 my $p = $self->{bvfs}->get_pathid($l);
1597 $self->{bvfs}->ch_dir($p);
1598 $self->refresh_fileview();
1600 $self->set_status("Can't find $l");
1605 # Handle dialog 'close' (window-decoration induced close)
1606 # * Just hide the dialog, and tell Gtk not to do anything else
1610 my ($self, $w) = @_;
1613 1; # consume this event!
1616 # Handle key presses in location text edit control
1617 # * Translate a Return/Enter key into a 'Go' command
1618 # * All other key presses left for GTK
1620 sub on_location_entry_key_release_event
1626 my $keypress = $event->keyval;
1627 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1628 $keypress == $Gtk2::Gdk::Keysyms{Return})
1630 $self->ch_dir($widget->get_text());
1632 return 1; # consume keypress
1635 return 0; # let gtk have the keypress
1638 sub on_fileview_key_press_event
1640 my ($self, $widget, $event) = @_;
1644 sub listview_get_first
1647 my @selected = $list->get_selected_indices();
1648 if (@selected > 0) {
1649 my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1650 return ($pid,$fid,unpack('u', $name), @other);
1656 sub listview_get_all
1660 my @selected = $list->get_selected_indices();
1662 for my $i (@selected) {
1663 my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1664 push @ret, [$pid,$fid,unpack('u', $name), @other];
1671 my ($list, $pid, $fid, $name, @other) = @_;
1672 push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1675 #-----------------------------------------------------------------
1676 # Handle keypress in file-view
1677 # * Translates backspace into a 'cd ..' command
1678 # * All other key presses left for GTK
1680 sub on_fileview_key_release_event
1682 my ($self, $widget, $event) = @_;
1683 if (not $event->keyval)
1687 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1688 $self->on_back_button_clicked();
1689 return 1; # eat keypress
1692 return 0; # let gtk have keypress
1695 sub on_forward_keypress
1700 #-------------------------------------------------------------------
1701 # Handle double-click (or enter) on file-view
1702 # * Translates into a 'cd <dir>' command
1704 sub on_fileview_row_activated
1706 my ($self, $widget) = @_;
1708 my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1712 $self->{bvfs}->ch_dir($pid);
1713 $self->refresh_fileview();
1715 $self->fill_infoview($pid,$fid,$name);
1718 return 1; # consume event
1723 my ($self, $path, $file, $fn) = @_;
1724 $self->clear_infoview();
1725 my @v = $self->{bvfs}->get_all_file_versions($path,
1727 $self->current_client,
1728 $self->{conf}->{see_all_versions});
1730 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1731 $size,$inchanger,$md5,$volname) = @{$ver};
1732 my $icon = ($inchanger)?$yesicon:$noicon;
1734 $mtime = localtime($mtime) ;
1736 listview_push($self->{fileinfo},$pid,$fid,
1737 $fn, $jobid, 'file',
1738 $icon, $volname, $jobid, human($size), $mtime, $md5);
1745 return $self->{restore_backup_combobox}->get_active_text;
1751 return $self->{client_combobox}->get_active_text;
1754 sub on_list_backups_changed
1756 my ($self, $widget) = @_;
1757 return 0 unless defined $self->{fileview};
1759 $self->{CurrentJobIds} = [
1760 set_job_ids_for_date($self->dbh(),
1761 $self->current_client,
1762 $self->current_date,
1763 $self->{conf}->{use_ok_bkp_only})
1765 $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1766 $self->refresh_fileview();
1770 sub on_restore_list_keypress
1772 my ($self, $widget, $event) = @_;
1773 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1775 my @sel = $widget->get_selected_indices;
1776 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1778 splice @{$self->{restore_list}->{data}},$elt,1;
1783 sub on_fileview_button_press_event
1785 my ($self,$widget,$event) = @_;
1786 if ($event->button == 3)
1788 $self->on_right_click_filelist($widget,$event);
1792 if ($event->button == 2)
1794 $self->on_see_all_version();
1801 sub on_see_all_version
1805 my @lst = listview_get_all($self->{fileview});
1808 my ($pid,$fid,$name, undef) = @{$i};
1810 new DlgFileVersion($self->{bvfs},
1811 $self->current_client,
1812 $pid,$fid,$self->{cwd},$name);
1816 sub on_right_click_filelist
1818 my ($self,$widget,$event) = @_;
1819 # I need to know what's selected
1820 my @sel = listview_get_all($self->{fileview});
1825 $type = $sel[0]->[4]; # $type
1830 if (@sel >=2 or $type eq 'dir')
1832 # We have selected more than one or it is a directories
1833 $w = $self->{filelist_dir_menu};
1837 $w = $self->{filelist_file_menu};
1843 $event->button, $event->time);
1846 sub context_add_to_filelist
1850 my @sel = listview_get_all($self->{fileview});
1852 foreach my $i (@sel)
1854 my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1855 $file = $self->{cwd} . '/' . $file;
1856 $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1860 # Adds a file to the filelist
1861 sub add_selected_file_to_list
1863 my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1865 my $restore_list = $self->{restore_list};
1867 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1874 if ($name and substr $name,-1 ne '/')
1876 $name .= '/'; # For bacula
1878 my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1879 listview_push($restore_list,$pid,0,
1880 $name, $jobid, 'dir', $curjobids,
1881 $diricon, $name,$curjobids,$dirfileindex);
1883 elsif ($type eq 'file')
1885 my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1887 listview_push($restore_list,$pid,$fid,
1888 $name, $jobid, 'file', $curjobids,
1889 $fileicon, $name, $jobid, $fileindex );
1893 # TODO : we want be able to restore files from a bad ended backup
1894 # we have JobStatus IN ('T', 'A', 'E') and we must
1896 # Data acces subs from here. Interaction with SGBD and caching
1898 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1899 # GUI and stores them in @CurrentJobIds
1900 sub set_job_ids_for_date
1902 my ($dbh, $client, $date, $only_ok)=@_;
1904 if (!$client or !$date) {
1908 my $status = get_wanted_job_status($only_ok);
1910 # The algorithm : for a client, we get all the backups for each
1911 # fileset, in reverse order Then, for each fileset, we store the 'good'
1912 # incrementals and differentials until we have found a full so it goes
1913 # like this : store all incrementals until we have found a differential
1914 # or a full, then find the full #
1916 my $query = "SELECT JobId, FileSet, Level, JobStatus
1917 FROM Job, Client, FileSet
1918 WHERE Job.ClientId = Client.ClientId
1919 AND FileSet.FileSetId = Job.FileSetId
1920 AND EndTime <= '$date'
1921 AND Client.Name = '$client'
1923 AND JobStatus IN ($status)
1924 ORDER BY FileSet, JobTDate DESC";
1927 my $result = $dbh->selectall_arrayref($query);
1929 foreach my $refrow (@$result)
1931 my $jobid = $refrow->[0];
1932 my $fileset = $refrow->[1];
1933 my $level = $refrow->[2];
1935 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1937 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1941 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1942 push @CurrentJobIds,($jobid);
1944 elsif ($level eq 'D')
1946 next if $progress{$fileset} eq 'D'; # We allready have a differential
1947 push @CurrentJobIds,($jobid);
1949 elsif ($level eq 'F')
1951 push @CurrentJobIds,($jobid);
1954 my $status = $refrow->[3] ;
1955 if ($status eq 'T') { # good end of job
1956 $progress{$fileset} = $level;
1960 return @CurrentJobIds;
1965 Gtk2->main_iteration while (Gtk2->events_pending);
1968 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1969 # This sub creates a BSR from the information in the restore_list
1970 # Returns the BSR as a string
1975 # This query gets all jobid/jobmedia/media combination.
1977 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
1978 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1979 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
1980 Media.Volumename, Media.MediaType
1981 FROM Job, JobMedia, Media
1982 WHERE Job.JobId = JobMedia.JobId
1983 AND JobMedia.MediaId = Media.MediaId
1984 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1987 my $result = $self->dbh_selectall_arrayref($query);
1989 # We will store everything hashed by jobid.
1991 foreach my $refrow (@$result)
1993 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1994 $firstindex, $lastindex, $startblock, $endblock,
1995 $volindex, $volumename, $mediatype) = @{$refrow};
1997 # We just have to deal with the case where starfile != endfile
1998 # In this case, we concatenate both, for the bsr
1999 if ($startfile != $endfile) {
2000 $startfile = $startfile . '-' . $endfile;
2004 ($jobid, $volsessionid, $volsessiontime, $startfile,
2005 $firstindex, $lastindex, $startblock .'-'. $endblock,
2006 $volindex, $volumename, $mediatype);
2008 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2012 # reminder : restore_list looks like this :
2013 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2014 # undef, undef, undef, $dirfileindex);
2016 # Here, we retrieve every file/dir that could be in the restore
2017 # We do as simple as possible for the SQL engine (no crazy joins,
2018 # no pseudo join (>= FirstIndex ...), etc ...
2019 # We do a SQL union of all the files/dirs specified in the restore_list
2021 foreach my $entry (@{$self->{restore_list}->{data}})
2023 if ($entry->[4] eq 'dir')
2025 my $dirid = $entry->[0];
2026 my $inclause = $entry->[5]; #curjobids
2029 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2030 FROM File, Path, Filename
2031 WHERE Path.PathId = File.PathId
2032 AND File.FilenameId = Filename.FilenameId
2034 (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
2035 WHERE PathId IN ($dirid)
2037 AND File.JobId IN ($inclause) )";
2038 push @select_queries,($query);
2042 # It's a file. Great, we allready have most
2043 # of what is needed. Simple and efficient query
2044 my $dir = $entry->[0];
2045 my $file = $entry->[1];
2047 my $jobid = $entry->[3];
2048 my $fileindex = $entry->[9];
2049 my $inclause = $entry->[5]; # curjobids
2051 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2052 FROM File,Path,Filename
2053 WHERE File.PathId = $dir
2054 AND File.PathId = Path.PathId
2055 AND File.FilenameId = $file
2056 AND File.FilenameId = Filename.FilenameId
2057 AND File.JobId = $jobid
2060 push @select_queries,($query);
2063 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2065 #Now we run the query and parse the result...
2066 # there may be a lot of records, so we better be efficient
2067 # We use the bind column method, working with references...
2069 my $sth = $self->dbh_prepare($query);
2072 my ($path,$name,$fileindex,$jobid);
2073 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2075 # The temp place we're going to save all file
2076 # list to before the real list
2080 while ($sth->fetchrow_arrayref())
2082 # This may look dumb, but we're going to do a join by ourselves,
2083 # to save memory and avoid sending a complex query to mysql
2084 my $complete_path = $path . $name;
2092 # Remove trailing slash (normalize file and dir name)
2093 $complete_path =~ s/\/$//;
2095 # Let's find the ref(s) for the %mediainfo element(s)
2096 # containing the data for this file
2097 # There can be several matches. It is the pseudo join.
2099 my $max_elt=@{$mediainfos{$jobid}}-1;
2101 while($med_idx <= $max_elt)
2103 my $ref = $mediainfos{$jobid}->[$med_idx];
2104 # First, can we get rid of the first elements of the
2105 # array ? (if they don't contain valuable records
2107 if ($fileindex > $ref->[5])
2109 # It seems we don't need anymore
2110 # this entry in %mediainfo (the input data
2113 shift @{$mediainfos{$jobid}};
2117 # We will do work on this elt. We can ++
2118 # $med_idx for next loop
2121 # %mediainfo row looks like :
2122 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2123 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2126 # We are in range. We store and continue looping
2128 if ($fileindex >= $ref->[4])
2130 my @data = ($complete_path,$is_dir,
2132 push @temp_list,(\@data);
2136 # We are not in range. No point in continuing looping
2137 # We go to next record.
2141 # Now we have the array.
2142 # We're going to sort it, by
2143 # path, volsessiontime DESC (get the most recent file...)
2144 # The array rows look like this :
2145 # complete_path,is_dir,fileindex,
2146 #
\81 ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2147 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2148 @temp_list = sort {$a->[0] cmp $b->[0]
2149 || $b->[3]->[2] <=> $a->[3]->[2]
2153 my $prev_complete_path='////'; # Sure not to match
2157 while (my $refrow = shift @temp_list)
2159 # For the sake of readability, we load $refrow
2160 # contents in real scalars
2161 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2162 my $jobid= $refother->[0]; # We don't need the rest...
2164 # We skip this entry.
2165 # We allready have a newer one and this
2166 # isn't a continuation of the same file
2167 next if ($complete_path eq $prev_complete_path
2168 and $jobid != $prev_jobid);
2172 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2174 # We would be recursing inside a file.
2175 # Just what we don't want (dir replaced by file
2176 # between two backups
2182 push @restore_list,($refrow);
2184 $prev_complete_path = $complete_path;
2185 $prev_jobid = $jobid;
2191 push @restore_list,($refrow);
2193 $prev_complete_path = $complete_path;
2194 $prev_jobid = $jobid;
2198 # We get rid of @temp_list... save memory
2201 # Ok everything is in the list. Let's sort it again in another way.
2202 # This time it will be in the bsr file order
2204 # we sort the results by
2205 # volsessiontime, volsessionid, volindex, fileindex
2206 # to get all files in right order...
2207 # Reminder : The array rows look like this :
2208 # complete_path,is_dir,fileindex,
2209 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2210 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2212 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2213 || $a->[3]->[1] <=> $b->[3]->[1]
2214 || $a->[3]->[7] <=> $b->[3]->[7]
2215 || $a->[2] <=> $b->[2] }
2218 # Now that everything is ready, we create the bsr
2219 my $prev_fileindex=-1;
2220 my $prev_volsessionid=-1;
2221 my $prev_volsessiontime=-1;
2222 my $prev_volumename=-1;
2223 my $prev_volfile=-1;
2227 my $first_of_current_range=0;
2228 my @fileindex_ranges;
2231 foreach my $refrow (@restore_list)
2233 my (undef,undef,$fileindex,$refother)=@{$refrow};
2234 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2235 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2237 # We can specifiy the number of files in each section of the
2238 # bsr to speedup restore (bacula can then jump over the
2239 # end of tape files.
2243 if ($prev_volumename eq '-1')
2245 # We only have to start the new range...
2246 $first_of_current_range=$fileindex;
2248 elsif ($prev_volsessionid != $volsessionid
2249 or $prev_volsessiontime != $volsessiontime
2250 or $prev_volumename ne $volumename
2251 or $prev_volfile ne $volfile)
2253 # We have to create a new section in the bsr...
2254 #
\81Â
\81 We print the previous one ...
2255 # (before that, save the current range ...)
2256 if ($first_of_current_range != $prev_fileindex)
2258 #
\81Â
\81 we are in a range
2259 push @fileindex_ranges,
2260 ("$first_of_current_range-$prev_fileindex");
2264 # We are out of a range,
2265 # but there is only one element in the range
2266 push @fileindex_ranges,
2267 ("$first_of_current_range");
2270 $bsr.=print_bsr_section(\@fileindex_ranges,
2272 $prev_volsessiontime,
2279 # Reset for next loop
2280 @fileindex_ranges=();
2281 $first_of_current_range=$fileindex;
2283 elsif ($fileindex-1 != $prev_fileindex)
2285 # End of a range of fileindexes
2286 if ($first_of_current_range != $prev_fileindex)
2289 push @fileindex_ranges,
2290 ("$first_of_current_range-$prev_fileindex");
2294 # We are out of a range,
2295 # but there is only one element in the range
2296 push @fileindex_ranges,
2297 ("$first_of_current_range");
2299 $first_of_current_range=$fileindex;
2301 $prev_fileindex=$fileindex;
2302 $prev_volsessionid = $volsessionid;
2303 $prev_volsessiontime = $volsessiontime;
2304 $prev_volumename = $volumename;
2305 $prev_volfile=$volfile;
2306 $prev_mediatype=$mediatype;
2307 $prev_volblocks=$volblocks;
2311 # Ok, we're out of the loop. Alas, there's still the last record ...
2312 if ($first_of_current_range != $prev_fileindex)
2315 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2320 # We are out of a range,
2321 # but there is only one element in the range
2322 push @fileindex_ranges,("$first_of_current_range");
2325 $bsr.=print_bsr_section(\@fileindex_ranges,
2327 $prev_volsessiontime,
2337 sub print_bsr_section
2339 my ($ref_fileindex_ranges,$volsessionid,
2340 $volsessiontime,$volumename,$volfile,
2341 $mediatype,$volblocks,$count)=@_;
2344 $bsr .= "Volume=\"$volumename\"\n";
2345 $bsr .= "MediaType=\"$mediatype\"\n";
2346 $bsr .= "VolSessionId=$volsessionid\n";
2347 $bsr .= "VolSessionTime=$volsessiontime\n";
2348 $bsr .= "VolFile=$volfile\n";
2349 $bsr .= "VolBlock=$volblocks\n";
2351 foreach my $range (@{$ref_fileindex_ranges})
2353 $bsr .= "FileIndex=$range\n";
2356 $bsr .= "Count=$count\n";
2362 ################################################################
2369 my ($self, $dir) = @_;
2371 "SELECT PathId FROM Path WHERE Path = ?";
2372 my $sth = $self->dbh_prepare($query);
2373 $sth->execute($dir);
2374 my $result = $sth->fetchall_arrayref();
2377 return join(',', map { $_->[0] } @$result);
2385 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
2386 my $jobs = $self->dbh_selectall_arrayref($query);
2388 $self->update_brestore_table(map { $_->[0] } @$jobs);
2393 my ($self, $dir) = @_;
2394 return $self->get_pathid('');
2399 my ($self, $pathid) = @_;
2400 $self->{cwd} = $pathid;
2407 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) ";
2409 my $all = $self->dbh_selectall_arrayref($query);
2410 return unless ($all); # already at root
2412 my $dir = join(',', map { $_->[0] } @$all);
2414 $self->{cwd} = $dir;
2421 return $self->get_path($self->{cwd});
2426 my ($self, $pathid) = @_;
2427 $self->debug("Call with pathid = $pathid");
2429 "SELECT Path FROM Path WHERE PathId IN (?)";
2431 my $sth = $self->dbh_prepare($query);
2432 $sth->execute($pathid);
2433 my $result = $sth->fetchrow_arrayref();
2435 return $result->[0];
2440 my ($self, @jobids) = @_;
2441 $self->{curjobids} = join(',', @jobids);
2442 $self->update_brestore_table(@jobids);
2449 return undef unless ($self->{curjobids});
2451 my $inclause = $self->{curjobids};
2452 my $inlistpath = $self->{cwd};
2455 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2457 (SELECT Filename.Name, max(File.FileId) as id
2459 WHERE File.FilenameId = Filename.FilenameId
2460 AND Filename.Name != ''
2461 AND File.PathId IN ($inlistpath)
2462 AND File.JobId IN ($inclause)
2463 GROUP BY Filename.Name
2464 ORDER BY Filename.Name) AS listfiles,
2466 WHERE File.FileId = listfiles.id";
2468 $self->debug($query);
2469 my $result = $self->dbh_selectall_arrayref($query);
2470 $self->debug($result);
2475 # return ($dirid,$dir_basename,$lstat,$jobid)
2480 return undef unless ($self->{curjobids});
2482 my $pathid = $self->{cwd};
2483 my $jobclause = $self->{curjobids};
2485 # Let's retrieve the list of the visible dirs in this dir ...
2486 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2487 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2488 my $sth = $self->dbh_prepare($query);
2490 my $result = $sth->fetchrow_arrayref();
2492 my $dir_filenameid = $result->[0];
2494 # Then we get all the dir entries from File ...
2496 SELECT PathId, Path, JobId, Lstat FROM (
2498 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2499 listfile1.JobId, listfile1.Lstat
2501 SELECT DISTINCT brestore_pathhierarchy1.PathId
2502 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2504 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2505 JOIN brestore_pathvisibility AS brestore_pathvisibility1
2506 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2507 WHERE brestore_pathhierarchy1.PPathId = $pathid
2508 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2509 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2511 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2512 WHERE File1.FilenameId = $dir_filenameid
2513 AND File1.JobId IN ($jobclause)) AS listfile1
2514 ON (listpath1.PathId = listfile1.PathId)
2515 ) AS A ORDER BY 2,3 DESC
2517 $self->debug($query);
2518 $sth=$self->dbh_prepare($query);
2520 $result = $sth->fetchall_arrayref();
2523 foreach my $refrow (@{$result})
2525 my $dirid = $refrow->[0];
2526 my $dir = $refrow->[1];
2527 my $lstat = $refrow->[3];
2528 my $jobid = $refrow->[2] || 0;
2529 next if ($dirid eq $prev_dir);
2530 # We have to clean up this dirname ... we only want it's 'basename'
2534 my @temp = split ('/',$dir);
2535 $return_value = pop @temp;
2539 $return_value = '/';
2541 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2542 push @return_list,(\@return_array);
2545 $self->debug(\@return_list);
2546 return \@return_list;
2549 # Returns the list of media required for a list of jobids.
2550 # Input : self, jobid1, jobid2...
2551 # Output : reference to array of (joibd, inchanger)
2552 sub get_required_media_from_jobid
2554 my ($self, @jobids)=@_;
2555 my $inclause = join(',',@jobids);
2557 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2558 FROM JobMedia, Media
2559 WHERE JobMedia.MediaId=Media.MediaId
2560 AND JobId In ($inclause)
2562 my $result = $self->dbh_selectall_arrayref($query);
2566 # Returns the fileindex from dirname and jobid.
2567 # Input : self, dirid, jobid
2568 # Output : fileindex
2569 sub get_fileindex_from_dir_jobid
2571 my ($self, $dirid, $jobid)=@_;
2573 $query = "SELECT File.FileIndex
2575 WHERE File.FilenameId = Filename.FilenameId
2576 AND File.PathId = $dirid
2577 AND Filename.Name = ''
2578 AND File.JobId = '$jobid'
2581 $self->debug($query);
2582 my $result = $self->dbh_selectall_arrayref($query);
2583 return $result->[0]->[0];
2586 # Returns the fileindex from filename and jobid.
2587 # Input : self, dirid, filenameid, jobid
2588 # Output : fileindex
2589 sub get_fileindex_from_file_jobid
2591 my ($self, $dirid, $filenameid, $jobid)=@_;
2595 "SELECT File.FileIndex
2597 WHERE File.PathId = $dirid
2598 AND File.FilenameId = $filenameid
2599 AND File.JobId = $jobid";
2601 $self->debug($query);
2602 my $result = $self->dbh_selectall_arrayref($query);
2603 return $result->[0]->[0];
2606 # This function estimates the size to be restored for an entry of the restore
2608 # In : self,reference to the entry
2609 # Out : size in bytes, number of files
2610 sub estimate_restore_size
2612 # reminder : restore_list looks like this :
2613 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2614 # undef, undef, undef, $dirfileindex);
2615 my ($self, $entry, $refresh) = @_;
2617 if ($entry->[4] eq 'dir')
2619 my $dir = $entry->[0];
2621 my $inclause = $entry->[5]; #curjobids
2623 "SELECT Path.Path, File.FilenameId, File.LStat
2624 FROM File, Path, Job
2625 WHERE Path.PathId = File.PathId
2626 AND File.JobId = Job.JobId
2628 (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2630 AND File.JobId IN ($inclause)
2631 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2635 # It's a file. Great, we allready have most
2636 # of what is needed. Simple and efficient query
2637 my $dir = $entry->[0];
2638 my $fileid = $entry->[1];
2640 my $jobid = $entry->[3];
2641 my $fileindex = $entry->[9];
2642 my $inclause = $entry->[5]; # curjobids
2644 "SELECT Path.Path, File.FilenameId, File.Lstat
2646 WHERE Path.PathId = File.PathId
2647 AND Path.PathId = $dir
2648 AND File.FilenameId = $fileid
2649 AND File.JobId = $jobid";
2652 my ($path,$nameid,$lstat);
2653 my $sth = $self->dbh_prepare($query);
2655 $sth->bind_columns(\$path,\$nameid,\$lstat);
2665 while ($sth->fetchrow_arrayref())
2667 # Only the latest version of a file
2668 next if ($nameid eq $old_nameid and $path eq $old_path);
2670 if ($rcount > 15000) {
2677 # We get the size of this file
2678 my $size=lstat_attrib($lstat,'st_size');
2679 $total_size += $size;
2682 $old_nameid=$nameid;
2685 return ($total_size,$total_files);
2688 # Returns list of versions of a file that could be restored
2689 # returns an array of
2690 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2691 # there will be only one jobid in the array of jobids...
2692 sub get_all_file_versions
2694 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2696 defined $see_all or $see_all=0;
2701 "SELECT File.JobId, File.FileIndex, File.Lstat,
2702 File.Md5, Media.VolumeName, Media.InChanger
2703 FROM File, Job, Client, JobMedia, Media
2704 WHERE File.FilenameId = $fileid
2705 AND File.PathId=$pathid
2706 AND File.JobId = Job.JobId
2707 AND Job.ClientId = Client.ClientId
2708 AND Job.JobId = JobMedia.JobId
2709 AND File.FileIndex >= JobMedia.FirstIndex
2710 AND File.FileIndex <= JobMedia.LastIndex
2711 AND JobMedia.MediaId = Media.MediaId
2712 AND Client.Name = '$client'";
2714 $self->debug($query);
2716 my $result = $self->dbh_selectall_arrayref($query);
2718 foreach my $refrow (@$result)
2720 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2721 my @attribs = parse_lstat($lstat);
2722 my $mtime = array_attrib('st_mtime',\@attribs);
2723 my $size = array_attrib('st_size',\@attribs);
2725 my @list = ('FILE:',$pathid,$fileid,$jobid,
2726 $fileindex, $mtime, $size, $inchanger,
2728 push @versions, (\@list);
2731 # We have the list of all versions of this file.
2732 # We'll sort it by mtime desc, size, md5, inchanger desc
2733 # the rest of the algorithm will be simpler
2734 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2735 @versions = sort { $b->[5] <=> $a->[5]
2736 || $a->[6] <=> $b->[6]
2737 || $a->[8] cmp $a->[8]
2738 || $b->[7] <=> $a->[7]} @versions;
2742 my %allready_seen_by_mtime;
2743 my %allready_seen_by_md5;
2744 # Now we should create a new array with only the interesting records
2745 foreach my $ref (@versions)
2749 # The file has a md5. We compare his md5 to other known md5...
2750 #
\81 We take size into account. It may happen that 2 files
2751 # have the same md5sum and are different. size is a supplementary
2754 # If we allready have a (better) version
2755 next if ( (not $see_all)
2756 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2758 # we never met this one before...
2759 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2761 #
\81 Even if it has a md5, we should also work with mtimes
2762 # We allready have a (better) version
2763 next if ( (not $see_all)
2764 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2765 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2767 # We reached there. The file hasn't been seen.
2768 push @good_versions,($ref);
2771 # To be nice with the user, we re-sort good_versions by
2772 # inchanger desc, mtime desc
2773 @good_versions = sort { $b->[5] <=> $a->[5]
2774 || $b->[3] <=> $a->[3]} @good_versions;
2776 return @good_versions;
2780 sub update_brestore_table
2782 my ($self, @jobs) = @_;
2784 $self->debug(\@jobs);
2786 foreach my $job (sort {$a <=> $b} @jobs)
2788 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2789 my $retour = $self->dbh_selectrow_arrayref($query);
2790 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2792 print STDERR "Inserting path records for JobId $job\n";
2793 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2794 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2796 $self->dbh_do($query);
2798 # Now we have to do the directory recursion stuff to determine missing visibility
2799 # We try to avoid recursion, to be as fast as possible
2800 # We also only work on not allready hierarchised directories...
2802 print STDERR "Creating missing recursion paths for $job\n";
2804 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2805 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2806 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2807 WHERE brestore_pathvisibility.JobId = $job
2808 AND brestore_pathhierarchy.PathId IS NULL
2811 my $sth = $self->dbh_prepare($query);
2813 my $pathid; my $path;
2814 $sth->bind_columns(\$pathid,\$path);
2818 $self->build_path_hierarchy($path,$pathid);
2822 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2823 # This query gives all parent pathids for a given jobid that aren't stored.
2824 # It has to be called until no record is updated ...
2826 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2827 SELECT a.PathId,$job
2829 (SELECT DISTINCT h.PPathId AS PathId
2830 FROM brestore_pathhierarchy AS h
2831 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2832 WHERE p.JobId=$job) AS a
2835 FROM brestore_pathvisibility
2836 WHERE JobId=$job) AS b
2837 ON (a.PathId = b.PathId)
2838 WHERE b.PathId IS NULL)";
2841 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2843 print STDERR "Recursively adding $rows_affected records from $job\n";
2846 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2847 $self->dbh_do($query);
2851 sub cleanup_brestore_table
2855 my $query = "SELECT JobId from brestore_knownjobid";
2856 my @jobs = @{$self->dbh_selectall_arrayref($query)};
2858 foreach my $jobentry (@jobs)
2860 my $job = $jobentry->[0];
2861 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2862 my $result = $self->dbh_selectall_arrayref($query);
2863 if (scalar(@{$result}))
2865 # There are still files for this jobid
2866 print STDERR "$job still exists. Not cleaning...\n";
2869 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2870 $self->dbh_do($query);
2871 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2872 $self->dbh_do($query);
2885 # Root Windows case :
2886 if ($path =~ /^[a-z]+:\/$/i)
2891 my @tmp = split('/',$path);
2892 # We remove the last ...
2894 my $tmp = join ('/',@tmp) . '/';
2898 sub build_path_hierarchy
2900 my ($self, $path,$pathid)=@_;
2901 # Does the ppathid exist for this ? we use a memory cache...
2902 # In order to avoid the full loop, we consider that if a dir is allready in the
2903 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2906 if (! $self->{cache_ppathid}->{$pathid})
2908 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2909 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2910 $sth2->execute($pathid);
2911 # Do we have a result ?
2912 if (my $refrow = $sth2->fetchrow_arrayref)
2914 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2916 # This dir was in the db ...
2917 # It means we can leave, the tree has allready been built for
2922 # We have to create the record ...
2923 # What's the current p_path ?
2924 my $ppath = parent_dir($path);
2925 my $ppathid = $self->return_pathid_from_path($ppath);
2926 $self->{cache_ppathid}->{$pathid}= $ppathid;
2928 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2929 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2930 $sth2->execute($pathid,$ppathid);
2936 # It's allready in the cache.
2937 # We can leave, no time to waste here, all the parent dirs have allready
2946 sub return_pathid_from_path
2948 my ($self, $path) = @_;
2949 my $query = "SELECT PathId FROM Path WHERE Path = ?";
2951 #print STDERR $query,"\n" if $debug;
2952 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2953 $sth->execute($path);
2954 my $result =$sth->fetchrow_arrayref();
2956 if (defined $result)
2958 return $result->[0];
2961 # A bit dirty : we insert into path, and we have to be sure
2962 # we aren't deleted by a purge. We still need to insert into path to get
2963 # the pathid, because of mysql
2964 $query = "INSERT INTO Path (Path) VALUES (?)";
2965 #print STDERR $query,"\n" if $debug;
2966 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2967 $sth->execute($path);
2970 $query = "SELECT PathId FROM Path WHERE Path = ?";
2971 #print STDERR $query,"\n" if $debug;
2972 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2973 $sth->execute($path);
2974 $result = $sth->fetchrow_arrayref();
2976 return $result->[0];
2981 sub create_brestore_tables
2985 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2987 unless ($self->dbh_do($verif)) {
2988 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2990 $self->{error} = "Creating internal brestore tables";
2992 CREATE TABLE brestore_knownjobid
2994 JobId int4 NOT NULL,
2995 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2997 $self->dbh_do($req);
3000 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
3001 unless ($self->dbh_do($verif)) {
3003 CREATE TABLE brestore_pathhierarchy
3005 PathId int4 NOT NULL,
3006 PPathId int4 NOT NULL,
3007 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
3009 $self->dbh_do($req);
3012 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
3013 ON brestore_pathhierarchy (PPathId)";
3014 $self->dbh_do($req);
3017 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
3018 unless ($self->dbh_do($verif)) {
3020 CREATE TABLE brestore_pathvisibility
3022 PathId int4 NOT NULL,
3023 JobId int4 NOT NULL,
3024 Size int8 DEFAULT 0,
3025 Files int4 DEFAULT 0,
3026 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
3028 $self->dbh_do($req);
3030 $req = "CREATE INDEX brestore_pathvisibility_jobid
3031 ON brestore_pathvisibility (JobId)";
3032 $self->dbh_do($req);
3038 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3039 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3040 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3041 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3042 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3043 'data_stream' => 15);;
3046 my ($attrib,$ref_attrib)=@_;
3047 return $ref_attrib->[$attrib_name_id{$attrib}];
3051 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3053 my ($file, $attrib)=@_;
3055 if (defined $attrib_name_id{$attrib}) {
3057 my @d = split(' ', $file->[3]) ; # TODO : cache this
3059 return from_base64($d[$attrib_name_id{$attrib}]);
3061 } elsif ($attrib eq 'jobid') {
3065 } elsif ($attrib eq 'name') {
3070 die "Attribute not known : $attrib.\n";
3076 my ($lstat,$attrib)=@_;
3077 if ($lstat and defined $attrib_name_id{$attrib})
3079 my @d = split(' ', $lstat) ; # TODO : cache this
3080 return from_base64($d[$attrib_name_id{$attrib}]);
3087 # Base 64 functions, directly from recover.pl.
3089 # Karl Hakimian <hakimian@aha.com>
3090 # This section is also under GPL v2 or later.
3097 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3098 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3099 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3100 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3101 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3103 @base64_map = (0) x 128;
3105 for (my $i=0; $i<64; $i++) {
3106 $base64_map[ord($base64_digits[$i])] = $i;
3121 if (substr($where, 0, 1) eq '-') {
3123 $where = substr($where, 1);
3126 while ($where ne '') {
3128 my $d = substr($where, 0, 1);
3129 $val += $base64_map[ord(substr($where, 0, 1))];
3130 $where = substr($where, 1);
3138 my @attribs = split(' ',$lstat);
3139 foreach my $element (@attribs)
3141 $element = from_base64($element);
3149 ################################################################
3150 package BwebConsole;
3152 use HTTP::Request::Common;
3156 my ($class, %arg) = @_;
3159 pref => $arg{pref}, # Pref object
3160 timeout => $arg{timeout} || 20,
3161 debug => $arg{debug} || 0,
3163 'list_client' => '',
3164 'list_fileset' => '',
3165 'list_storage' => '',
3174 my ($self, @what) = @_;
3175 my $ua = LWP::UserAgent->new();
3176 $ua->agent("Brestore/$VERSION");
3177 my $req = POST($self->{pref}->{bconsole},
3178 Content_Type => 'form-data',
3179 Content => [ map { (action => $_) } @what ]);
3180 #$req->authorization_basic('eric', 'test');
3182 my $res = $ua->request($req);
3184 if ($res->is_success) {
3185 foreach my $l (split(/\n/, $res->content)) {
3186 my ($k, $c) = split(/=/,$l,2);
3190 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3191 new DlgWarn($self->{error});
3197 my ($self, %arg) = @_;
3199 my $ua = LWP::UserAgent->new();
3200 $ua->agent("Brestore/$VERSION");
3201 my $req = POST($self->{pref}->{bconsole},
3202 Content_Type => 'form-data',
3203 Content => [ job => $arg{job},
3204 client => $arg{client},
3205 storage => $arg{storage} || '',
3206 fileset => $arg{fileset} || '',
3207 where => $arg{where} || '',
3208 regexwhere => $arg{regexwhere} || '',
3209 priority=> $arg{prio} || '',
3210 replace => $arg{replace},
3213 bootstrap => [$arg{bootstrap}],
3215 #$req->authorization_basic('eric', 'test');
3217 my $res = $ua->request($req);
3219 if ($res->is_success) {
3220 foreach my $l (split(/\n/, $res->content)) {
3221 my ($k, $c) = split(/=/,$l,2);
3226 if (!$self->{run}) {
3227 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3230 unlink($arg{bootstrap});
3232 return $self->{run};
3238 return sort split(/;/, $self->{'list_job'});
3244 return sort split(/;/, $self->{'list_fileset'});
3250 return sort split(/;/, $self->{'list_storage'});
3255 return sort split(/;/, $self->{'list_client'});
3266 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3270 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3273 GetOptions("conf=s" => \$file_conf,
3274 "batch" => \$batch_mod,
3276 "help" => \&HELP_MESSAGE) ;
3278 my $p = new Pref($file_conf);
3280 if (! -f $file_conf) {
3285 my $vfs = new Bvfs(conf => $p);
3286 if ($p->connect_db()) {
3287 $vfs->update_cache();
3292 $glade_file = $p->{glade_file};
3294 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3295 if (-f "$path/$glade_file") {
3296 $glade_file = "$path/$glade_file" ;
3301 # gtk have lots of warning on stderr
3302 if ($^O eq 'MSWin32')
3305 open(STDERR, ">stderr.log");
3310 if ( -f $glade_file) {
3311 my $w = new DlgResto($p);
3314 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3315 "Can't find your brestore.glade (glade_file => '$glade_file')
3316 Please, edit your $file_conf to setup it." );
3318 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3323 Gtk2->main; # Start Gtk2 main loop
3332 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3334 $p->connect_db() || print $p->{error};
3336 my $bvfs = new Bvfs(conf => $p);
3338 $bvfs->debug($bvfs->get_root());
3339 $bvfs->ch_dir($bvfs->get_root());
3341 $bvfs->set_curjobids(268,178,282,281,279);
3343 my $dirs = $bvfs->ls_dirs();
3344 $bvfs->ch_dir(123496);
3345 $dirs = $bvfs->ls_dirs();
3347 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);