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 ################################################################
407 # Display all file revision in a separated window
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,
431 # new DlgFileVersion(Bvfs, "client", pathid, fileid, "/path/to/", "filename");
435 my ($class, $bvfs, $client, $pathid, $fileid, $path, $fn) = @_;
438 version => undef, # main window
441 # we load version widget of $glade_file
442 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
444 # Connect signals magically
445 $glade_box->signal_autoconnect_from_package($self);
447 $glade_box->get_widget("version_label")
448 ->set_markup("<b>File revisions : $client:$path$fn</b>");
450 my $widget = $glade_box->get_widget('version_fileview');
451 my $fileview = Gtk2::SimpleList->new_from_treeview(
453 'h_pathid' => 'hidden',
454 'h_filenameid' => 'hidden',
455 'h_name' => 'hidden',
456 'h_jobid' => 'hidden',
457 'h_type' => 'hidden',
459 'InChanger' => 'pixbuf',
466 DlgResto::init_drag_drop($fileview);
468 my @v = $bvfs->get_all_file_versions($pathid,
473 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
474 $inchanger,$md5,$volname) = @{$ver};
475 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
477 DlgResto::listview_push($fileview,$pid,$fid,
479 $icon, $volname, $jobid,DlgResto::human($size),
480 scalar(localtime($mtime)), $md5);
483 $self->{version} = $glade_box->get_widget('dlg_version');
484 $self->{version}->show();
489 sub on_forward_keypress
495 ################################################################
496 # Display a warning message
501 my ($package, $text) = @_;
505 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
507 # Connect signals magically
508 $glade->signal_autoconnect_from_package($self);
509 $glade->get_widget('label_warn')->set_text($text);
511 print STDERR "$text\n";
513 $self->{window} = $glade->get_widget('dlg_warn');
514 $self->{window}->show_all();
521 $self->{window}->destroy();
525 ################################################################
531 # %arg = (bsr_file => '/path/to/bsr', # on director
532 # volumes => [ '00001', '00004']
540 if ($pref->{bconsole} =~ /^http/) {
541 return new BwebConsole(pref => $pref);
543 if (eval { require Bconsole; }) {
544 return new Bconsole(pref => $pref);
546 new DlgWarn("Can't use bconsole, verify your setup");
554 my ($class, %arg) = @_;
557 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
558 pref => $arg{pref}, # Pref ref
559 glade => undef, # GladeXML ref
560 bconsole => undef, # Bconsole ref
563 my $console = $self->{bconsole} = get_bconsole($arg{pref});
568 # we load launch widget of $glade_file
569 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
572 # Connect signals magically
573 $glade->signal_autoconnect_from_package($self);
575 my $widget = $glade->get_widget('volumeview');
576 my $volview = Gtk2::SimpleList->new_from_treeview(
578 'InChanger' => 'pixbuf',
582 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
584 # we replace 0 and 1 by $noicon and $yesicon
585 for my $i (@{$infos}) {
587 $i->[0] = $DlgResto::noicon;
589 $i->[0] = $DlgResto::yesicon;
594 push @{ $volview->{data} }, @{$infos} ;
596 $console->prepare(qw/list_client list_job list_fileset list_storage/);
598 # fill client combobox (with director defined clients
599 my @clients = $console->list_client() ; # get from bconsole
600 if ($console->{error}) {
601 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
603 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
604 $self->{list_client} = DlgResto::init_combo($w, 'text');
605 DlgResto::fill_combo($self->{list_client},
606 $DlgResto::client_list_empty,
610 # fill fileset combobox
611 my @fileset = $console->list_fileset() ;
612 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
613 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
614 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
617 my @job = $console->list_job() ;
618 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
619 $self->{list_job} = DlgResto::init_combo($w, 'text');
620 DlgResto::fill_combo($self->{list_job}, '', @job);
622 # find default_restore_job in jobs list
623 my $default_restore_job = $arg{pref}->{default_restore_job} ;
627 if ($j =~ /$default_restore_job/io) {
633 $w->set_active($index);
635 # fill storage combobox
636 my @storage = $console->list_storage() ;
637 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
638 $self->{list_storage} = DlgResto::init_combo($w, 'text');
639 DlgResto::fill_combo($self->{list_storage}, '', @storage);
641 $glade->get_widget('dlg_launch')->show_all();
648 my ($self, $client, $jobid) = @_;
650 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
652 $self->on_cancel_resto_clicked();
655 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
656 "Your job have been submited to bacula.
657 To follow it, you must use bconsole (or install/configure bweb)");
663 sub on_use_regexp_toggled
665 my ($self,$widget) = @_;
666 my $act = $widget->get_active();
668 foreach my $w ('entry_launch_where') {
669 $self->{glade}->get_widget($w)->set_sensitive(!$act);
672 foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
673 'entry_add_suffix','entry_rwhere','chk_use_regexp')
675 $self->{glade}->get_widget($w)->set_sensitive($act);
678 if ($act) { # if we activate file relocation, we reset use_regexp
679 $self->{glade}->get_widget('entry_rwhere')->set_sensitive(0);
680 $self->{glade}->get_widget('chk_use_regexp')->set_active(0);
685 sub on_use_rwhere_toggled
687 my ($self,$widget) = @_;
688 my $act = $widget->get_active();
690 foreach my $w ('entry_rwhere') {
691 $self->{glade}->get_widget($w)->set_sensitive($act);
694 foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
697 $self->{glade}->get_widget($w)->set_sensitive(!$act);
701 sub on_cancel_resto_clicked
704 $self->{glade}->get_widget('dlg_launch')->destroy();
711 if ($self->{glade}->get_widget('chk_file_relocation')->get_active()) {
713 if ($self->{glade}->get_widget('chk_use_regexp')->get_active()) {
715 return ('regexwhere',
716 $self->{glade}->get_widget('entry_rwhere')->get_active());
721 my ($strip_prefix, $add_prefix, $add_suffix) =
722 ($self->{glade}->get_widget('entry_strip_prefix')->get_text(),
723 $self->{glade}->get_widget('entry_add_prefix')->get_text(),
724 $self->{glade}->get_widget('entry_add_suffix')->get_text());
727 push @ret,"!$strip_prefix!!i";
731 push @ret,"!^!$add_prefix!";
735 push @ret,"!([^/])\$!\$1$add_suffix!";
738 return ('regexwhere', join(',', @ret));
740 } else { # using where
742 $self->{glade}->get_widget('entry_launch_where')->get_text());
746 sub on_submit_resto_clicked
749 my $glade = $self->{glade};
751 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
754 new DlgWarn("Can't copy bsr file to director ($self->{error})");
758 my $fileset = $glade->get_widget('combo_launch_fileset')
761 my $storage = $glade->get_widget('combo_launch_storage')
764 my ($where_cmd, $where) = $self->get_where();
765 print "$where_cmd => $where\n";
767 my $job = $glade->get_widget('combo_launch_job')
771 new DlgWarn("Can't use this job");
775 my $client = $glade->get_widget('combo_launch_client')
778 if (! $client or $client eq $DlgResto::client_list_empty) {
779 new DlgWarn("Can't use this client ($client)");
783 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
785 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
786 $replace=($replace)?'always':'never';
788 my $jobid = $self->{bconsole}->run(job => $job,
792 $where_cmd => $where,
797 $self->show_job($client, $jobid);
800 sub on_combo_storage_button_press_event
803 print "on_combo_storage_button_press_event()\n";
806 sub on_combo_fileset_button_press_event
809 print "on_combo_fileset_button_press_event()\n";
813 sub on_combo_job_button_press_event
816 print "on_combo_job_button_press_event()\n";
819 sub get_volume_inchanger
821 my ($dbh, $vols) = @_;
823 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
825 my $rq = "SELECT InChanger, VolumeName
827 WHERE VolumeName IN ($lst)
830 my $res = $dbh->selectall_arrayref($rq);
831 return $res; # [ [ 1, VolName].. ]
835 use File::Copy qw/copy/;
836 use File::Basename qw/basename/;
838 # We must kown the path+filename destination
839 # $self->{error} contains error message
840 # it return 0/1 if fail/success
843 my ($self, $src, $dst) = @_ ;
844 print "$src => $dst\n"
855 if ($dst =~ m!file:/(/.+)!) {
856 $ret = copy($src, $1);
858 $dstfile = "$1/" . basename($src) ;
860 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
861 $err = `scp $src $1 2>&1` ;
863 $dstfile = "$2/" . basename($src) ;
867 $err = "$dst not implemented yet";
868 File::Copy::copy($src, \*STDOUT);
871 $self->{error} = $err;
874 $self->{error} = $err;
883 ################################################################
891 unless ($about_widget) {
892 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
893 $about_widget = $glade_box->get_widget("dlg_about") ;
894 $glade_box->signal_autoconnect_from_package('DlgAbout');
896 $about_widget->show() ;
899 sub on_about_okbutton_clicked
901 $about_widget->hide() ;
906 ################################################################
916 # Kept as is from the perl-gtk example. Draws the pretty icons
922 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
923 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
924 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
925 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
928 # init combo (and create ListStore object)
931 my ($widget, @type) = @_ ;
932 my %type_info = ('text' => 'Glib::String',
933 'markup' => 'Glib::String',
936 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
938 $widget->set_model($lst);
942 if ($t eq 'text' or $t eq 'markup') {
943 $cell = new Gtk2::CellRendererText();
945 $widget->pack_start($cell, 1);
946 $widget->add_attribute($cell, $t, $i++);
951 # fill simple combo (one element per row)
954 my ($list, @what) = @_;
958 foreach my $w (@what)
961 my $i = $list->append();
962 $list->set($i, 0, $w);
969 my @unit = qw(B KB MB GB TB);
972 my $format = '%i %s';
973 while ($val / 1024 > 1) {
977 $format = ($i>0)?'%0.1f %s':'%i %s';
978 return sprintf($format, $val, $unit[$i]);
981 sub get_wanted_job_status
988 return "'T', 'A', 'E'";
992 # This sub gives a full list of the EndTimes for a ClientId
993 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
994 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
995 sub get_all_endtimes_for_job
997 my ($self, $client, $ok_only)=@_;
998 my $status = get_wanted_job_status($ok_only);
1000 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
1001 FROM Job,Client,FileSet
1002 WHERE Job.ClientId=Client.ClientId
1003 AND Client.Name = '$client'
1005 AND JobStatus IN ($status)
1006 AND Job.FileSetId = FileSet.FileSetId
1007 ORDER BY EndTime desc";
1008 my $result = $self->dbh_selectall_arrayref($query);
1015 my ($fileview) = shift;
1016 my $fileview_target_entry = {target => 'STRING',
1017 flags => ['GTK_TARGET_SAME_APP'],
1020 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
1021 ['copy'],$fileview_target_entry);
1022 $fileview->get_selection->set_mode('multiple');
1024 # set some useful SimpleList properties
1025 $fileview->set_headers_clickable(0);
1026 foreach ($fileview->get_columns())
1028 $_->set_resizable(1);
1029 $_->set_sizing('grow-only');
1035 my ($class, $pref) = @_;
1039 CurrentJobIds => [],
1040 location => undef, # location entry widget
1041 mainwin => undef, # mainwin widget
1042 filelist_file_menu => undef, # file menu widget
1043 filelist_dir_menu => undef, # dir menu widget
1044 glade => undef, # glade object
1045 status => undef, # status bar widget
1046 dlg_pref => undef, # DlgPref object
1047 fileattrib => {}, # cache file
1048 fileview => undef, # fileview widget SimpleList
1049 fileinfo => undef, # fileinfo widget SimpleList
1051 client_combobox => undef, # client_combobox widget
1052 restore_backup_combobox => undef, # date combobox widget
1053 list_client => undef, # Gtk2::ListStore
1054 list_backup => undef, # Gtk2::ListStore
1055 cache_ppathid => {}, #
1056 bvfs => undef, # Bfvs object
1059 $self->{bvfs} = new Bvfs(conf => $pref);
1061 # load menu (to use handler with self reference)
1062 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1063 $glade->signal_autoconnect_from_package($self);
1064 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1066 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1067 $glade->signal_autoconnect_from_package($self);
1068 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1070 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1071 $glade->signal_autoconnect_from_package($self);
1073 $self->{status} = $glade->get_widget('statusbar');
1074 $self->{mainwin} = $glade->get_widget('dlg_resto');
1075 $self->{location} = $glade->get_widget('entry_location');
1076 $self->render_icons();
1078 $self->{dlg_pref} = new DlgPref($pref);
1080 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1081 $self->{list_client} = init_combo($c, 'text');
1083 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1084 $self->{list_backup} = init_combo($c, 'text', 'markup');
1086 # Connect glade-fileview to Gtk2::SimpleList
1087 # and set up drag n drop between $fileview and $restore_list
1089 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1090 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1092 my $widget = $glade->get_widget('fileview');
1093 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1095 'h_pathid' => 'hidden',
1096 'h_filenameid' => 'hidden',
1097 'h_name' => 'hidden',
1098 'h_jobid' => 'hidden',
1099 'h_type' => 'hidden',
1102 'File Name' => 'text',
1105 init_drag_drop($fileview);
1106 $fileview->set_search_column(6); # search on File Name
1108 # Connect glade-restore_list to Gtk2::SimpleList
1109 $widget = $glade->get_widget('restorelist');
1110 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1112 'h_pathid' => 'hidden', #0
1113 'h_filenameid' => 'hidden',
1114 'h_name' => 'hidden',
1115 'h_jobid' => 'hidden',
1116 'h_type' => 'hidden',
1117 'h_curjobid' => 'hidden', #5
1120 'File Name' => 'text',
1122 'FileIndex' => 'text',
1124 'Nb Files' => 'text', #10
1125 'Size' => 'text', #11
1126 'size_b' => 'hidden', #12
1129 my @restore_list_target_table = ({'target' => 'STRING',
1133 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1134 $restore_list->get_selection->set_mode('multiple');
1136 $widget = $glade->get_widget('infoview');
1137 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1139 'h_pathid' => 'hidden',
1140 'h_filenameid' => 'hidden',
1141 'h_name' => 'hidden',
1142 'h_jobid' => 'hidden',
1143 'h_type' => 'hidden',
1145 'InChanger' => 'pixbuf',
1152 init_drag_drop($infoview);
1154 $pref->connect_db() || $self->{dlg_pref}->display($self);
1157 $self->init_server_backup_combobox();
1158 if ($self->{bvfs}->create_brestore_tables()) {
1159 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
1163 $self->set_status($pref->{error});
1166 # set status bar informations
1169 my ($self, $string) = @_;
1170 return unless ($string);
1172 my $context = $self->{status}->get_context_id('Main');
1173 $self->{status}->push($context, $string);
1176 sub on_time_select_changed
1184 my $c = $self->{glade}->get_widget('combo_time');
1185 return $c->get_active_text;
1188 # This sub returns all clients declared in DB
1192 my $query = "SELECT Name FROM Client ORDER BY Name";
1193 print STDERR $query,"\n" if $debug;
1195 my $result = $dbh->selectall_arrayref($query);
1197 return map { $_->[0] } @$result;
1200 # init infoview widget
1204 @{$self->{fileinfo}->{data}} = ();
1208 sub on_clear_clicked
1211 @{$self->{restore_list}->{data}} = ();
1214 sub on_estimate_clicked
1221 # TODO : If we get here, things could get lenghty ... draw a popup window .
1222 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1223 'destroy-with-parent',
1225 'Computing size...');
1229 my $title = "Computing size...\n";
1232 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1233 # undef, undef, undef, $dirfileindex);
1234 foreach my $entry (@{$self->{restore_list}->{data}})
1236 unless ($entry->[11]) {
1237 my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1238 $entry->[12] = $size;
1239 $entry->[11] = human($size);
1243 my $name = unpack('u', $entry->[2]);
1245 $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1246 $self->debug($title . $txt);
1247 $widget->set_markup($title . $txt);
1249 $size_total+=$entry->[12];
1250 $nb_total+=$entry->[10];
1254 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1255 $widget->set_markup("Size estimation :\n" . $txt);
1256 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1263 sub on_gen_bsr_clicked
1267 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1268 'gtk-save','ok', 'gtk-cancel', 'cancel');
1271 my $w = new Gtk2::FileChooserDialog ( @options );
1276 if ($a eq 'cancel') {
1281 my $f = $w->get_filename();
1283 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1284 'destroy-with-parent',
1285 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1286 if ($dlg->run() eq 'ok') {
1300 if (open(FP, ">$save")) {
1301 my $bsr = $self->create_filelist();
1304 $self->set_status("Dumping BSR to $save ok");
1306 $self->set_status("Can't dump BSR to $save: $!");
1312 use File::Temp qw/tempfile/;
1314 sub on_go_button_clicked
1317 unless (scalar(@{$self->{restore_list}->{data}})) {
1318 new DlgWarn("No file to restore");
1321 my $bsr = $self->create_filelist();
1322 my ($fh, $filename) = tempfile();
1325 chmod(0644, $filename);
1327 print "Dumping BSR info to $filename\n"
1330 # we get Volume list
1331 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1332 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1334 new DlgLaunch(pref => $self->{conf},
1336 bsr_file => $filename,
1342 our $client_list_empty = 'Clients list';
1343 our %type_markup = ('F' => '<b>$label F</b>',
1346 'B' => '<b>$label B</b>',
1348 'A' => '<span foreground=\"red\">$label</span>',
1350 'E' => '<span foreground=\"red\">$label</span>',
1353 sub on_list_client_changed
1355 my ($self, $widget) = @_;
1356 return 0 unless defined $self->{fileview};
1358 $self->{list_backup}->clear();
1360 if ($self->current_client eq $client_list_empty) {
1364 $self->{CurrentJobIds} = [
1365 set_job_ids_for_date($self->dbh(),
1366 $self->current_client,
1367 $self->current_date,
1368 $self->{conf}->{use_ok_bkp_only})
1371 my $fs = $self->{bvfs};
1372 $fs->set_curjobids(@{$self->{CurrentJobIds}});
1373 $fs->ch_dir($fs->get_root());
1374 # refresh_fileview will be done by list_backup_changed
1377 my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1378 $self->{conf}->{use_ok_bkp_only});
1380 foreach my $endtime (@endtimes)
1382 my $i = $self->{list_backup}->append();
1384 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1385 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1386 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1388 $self->{list_backup}->set($i,
1393 $self->{restore_backup_combobox}->set_active(0);
1398 sub fill_server_list
1400 my ($dbh, $combo, $list) = @_;
1402 my @clients=get_all_clients($dbh);
1406 my $i = $list->append();
1407 $list->set($i, 0, $client_list_empty);
1409 foreach my $client (@clients)
1411 $i = $list->append();
1412 $list->set($i, 0, $client);
1414 $combo->set_active(0);
1417 sub init_server_backup_combobox
1420 fill_server_list($self->{conf}->{dbh},
1421 $self->{client_combobox},
1422 $self->{list_client}) ;
1425 #----------------------------------------------------------------------
1426 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1427 #data isn't. There is additionnal complexity for dirs (visibility problems),
1428 #so the @CurrentJobIds is not sufficient.
1429 sub refresh_fileview
1432 my $fileview = $self->{fileview};
1433 my $client_combobox = $self->{client_combobox};
1434 my $bvfs = $self->{bvfs};
1436 @{$fileview->{data}} = ();
1438 $self->clear_infoview();
1440 my $client_name = $self->current_client;
1442 if (!$client_name or ($client_name eq $client_list_empty)) {
1443 $self->set_status("Client list empty");
1447 # [ [dirid, dir_basename, File.LStat, jobid]..]
1448 my $list_dirs = $bvfs->ls_dirs();
1449 # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1450 my $files = $bvfs->ls_files();
1452 my $file_count = 0 ;
1453 my $total_bytes = 0;
1455 # Add directories to view
1456 foreach my $dir_entry (@$list_dirs) {
1457 my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1458 $total_bytes += 4096;
1461 listview_push($fileview,
1465 # TODO: voir ce que l'on met la
1476 foreach my $file (@$files)
1478 my $size = Bvfs::file_attrib($file,'st_size');
1479 my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1480 $total_bytes += $size;
1482 # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1483 listview_push($fileview,
1492 human($size), $time);
1495 $self->set_status("$file_count files/" . human($total_bytes));
1496 $self->{cwd} = $self->{bvfs}->pwd();
1497 $self->{location}->set_text($self->{cwd});
1498 # set a decent default selection (makes keyboard nav easy)
1499 $fileview->select(0);
1503 sub on_about_activate
1505 DlgAbout::display();
1510 my ($tree, $path, $data) = @_;
1512 my @items = listview_get_all($tree) ;
1514 foreach my $i (@items)
1516 my @file_info = @{$i};
1519 # Ok, we have a corner case :
1521 my $file = pack("u", $path . $file_info[2]);
1523 push @ret, join(" ; ", $file,
1524 $file_info[0], # $pathid
1525 $file_info[1], # $filenameid
1526 $file_info[3], # $jobid
1527 $file_info[4], # $type
1531 my $data_get = join(" :: ", @ret);
1533 $data->set_text($data_get,-1);
1538 sub fileview_data_get
1540 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1541 drag_set_info($widget, $self->{cwd}, $data);
1544 sub fileinfo_data_get
1546 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1547 drag_set_info($widget, $self->{cwd}, $data);
1550 sub restore_list_data_received
1552 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1554 $self->debug("start\n");
1555 if ($info eq 40 || $info eq 0) # patch for display!=:0
1557 foreach my $elt (split(/ :: /, $data->data()))
1559 my ($file, $pathid, $filenameid, $jobid, $type) =
1561 $file = unpack("u", $file);
1563 $self->add_selected_file_to_list($pathid,$filenameid,
1564 $file, $jobid, $type);
1567 $self->debug("end\n");
1570 sub on_back_button_clicked {
1572 $self->{bvfs}->up_dir();
1573 $self->refresh_fileview();
1575 sub on_location_go_button_clicked
1578 $self->ch_dir($self->{location}->get_text());
1580 sub on_quit_activate {Gtk2->main_quit;}
1581 sub on_preferences_activate
1584 $self->{dlg_pref}->display($self) ;
1586 sub on_main_delete_event {Gtk2->main_quit;}
1587 sub on_bweb_activate
1590 $self->set_status("Open bweb on your browser");
1591 $self->{conf}->go_bweb('', "go on bweb");
1594 # Change the current working directory
1595 # * Updates fileview, location, and selection
1601 my $p = $self->{bvfs}->get_pathid($l);
1603 $self->{bvfs}->ch_dir($p);
1604 $self->refresh_fileview();
1606 $self->set_status("Can't find $l");
1611 # Handle dialog 'close' (window-decoration induced close)
1612 # * Just hide the dialog, and tell Gtk not to do anything else
1616 my ($self, $w) = @_;
1619 1; # consume this event!
1622 # Handle key presses in location text edit control
1623 # * Translate a Return/Enter key into a 'Go' command
1624 # * All other key presses left for GTK
1626 sub on_location_entry_key_release_event
1632 my $keypress = $event->keyval;
1633 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1634 $keypress == $Gtk2::Gdk::Keysyms{Return})
1636 $self->ch_dir($widget->get_text());
1638 return 1; # consume keypress
1641 return 0; # let gtk have the keypress
1644 sub on_fileview_key_press_event
1646 my ($self, $widget, $event) = @_;
1650 sub listview_get_first
1653 my @selected = $list->get_selected_indices();
1654 if (@selected > 0) {
1655 my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1656 return ($pid,$fid,unpack('u', $name), @other);
1662 sub listview_get_all
1666 my @selected = $list->get_selected_indices();
1668 for my $i (@selected) {
1669 my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1670 push @ret, [$pid,$fid,unpack('u', $name), @other];
1677 my ($list, $pid, $fid, $name, @other) = @_;
1678 push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1681 #-----------------------------------------------------------------
1682 # Handle keypress in file-view
1683 # * Translates backspace into a 'cd ..' command
1684 # * All other key presses left for GTK
1686 sub on_fileview_key_release_event
1688 my ($self, $widget, $event) = @_;
1689 if (not $event->keyval)
1693 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1694 $self->on_back_button_clicked();
1695 return 1; # eat keypress
1698 return 0; # let gtk have keypress
1701 sub on_forward_keypress
1706 #-------------------------------------------------------------------
1707 # Handle double-click (or enter) on file-view
1708 # * Translates into a 'cd <dir>' command
1710 sub on_fileview_row_activated
1712 my ($self, $widget) = @_;
1714 my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1718 $self->{bvfs}->ch_dir($pid);
1719 $self->refresh_fileview();
1721 $self->fill_infoview($pid,$fid,$name);
1724 return 1; # consume event
1729 my ($self, $path, $file, $fn) = @_;
1730 $self->clear_infoview();
1731 my @v = $self->{bvfs}->get_all_file_versions($path,
1733 $self->current_client,
1734 $self->{conf}->{see_all_versions});
1736 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1737 $size,$inchanger,$md5,$volname) = @{$ver};
1738 my $icon = ($inchanger)?$yesicon:$noicon;
1740 $mtime = localtime($mtime) ;
1742 listview_push($self->{fileinfo},$pid,$fid,
1743 $fn, $jobid, 'file',
1744 $icon, $volname, $jobid, human($size), $mtime, $md5);
1751 return $self->{restore_backup_combobox}->get_active_text;
1757 return $self->{client_combobox}->get_active_text;
1760 sub on_list_backups_changed
1762 my ($self, $widget) = @_;
1763 return 0 unless defined $self->{fileview};
1765 $self->{CurrentJobIds} = [
1766 set_job_ids_for_date($self->dbh(),
1767 $self->current_client,
1768 $self->current_date,
1769 $self->{conf}->{use_ok_bkp_only})
1771 $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1772 $self->refresh_fileview();
1776 sub on_restore_list_keypress
1778 my ($self, $widget, $event) = @_;
1779 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1781 my @sel = $widget->get_selected_indices;
1782 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1784 splice @{$self->{restore_list}->{data}},$elt,1;
1789 sub on_fileview_button_press_event
1791 my ($self,$widget,$event) = @_;
1792 if ($event->button == 3)
1794 $self->on_right_click_filelist($widget,$event);
1798 if ($event->button == 2)
1800 $self->on_see_all_version();
1807 sub on_see_all_version
1811 my @lst = listview_get_all($self->{fileview});
1814 my ($pid,$fid,$name, undef) = @{$i};
1816 new DlgFileVersion($self->{bvfs},
1817 $self->current_client,
1818 $pid,$fid,$self->{cwd},$name);
1822 sub on_right_click_filelist
1824 my ($self,$widget,$event) = @_;
1825 # I need to know what's selected
1826 my @sel = listview_get_all($self->{fileview});
1831 $type = $sel[0]->[4]; # $type
1836 if (@sel >=2 or $type eq 'dir')
1838 # We have selected more than one or it is a directories
1839 $w = $self->{filelist_dir_menu};
1843 $w = $self->{filelist_file_menu};
1849 $event->button, $event->time);
1852 sub context_add_to_filelist
1856 my @sel = listview_get_all($self->{fileview});
1858 foreach my $i (@sel)
1860 my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1861 $file = $self->{cwd} . $file;
1862 $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1866 # Adds a file to the filelist
1867 sub add_selected_file_to_list
1869 my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1871 my $restore_list = $self->{restore_list};
1873 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1880 if ($name and substr $name,-1 ne '/')
1882 $name .= '/'; # For bacula
1884 my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1885 listview_push($restore_list,$pid,0,
1886 $name, $jobid, 'dir', $curjobids,
1887 $diricon, $name,$curjobids,$dirfileindex);
1889 elsif ($type eq 'file')
1891 my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1893 listview_push($restore_list,$pid,$fid,
1894 $name, $jobid, 'file', $curjobids,
1895 $fileicon, $name, $jobid, $fileindex );
1899 # TODO : we want be able to restore files from a bad ended backup
1900 # we have JobStatus IN ('T', 'A', 'E') and we must
1902 # Data acces subs from here. Interaction with SGBD and caching
1904 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1905 # GUI and stores them in @CurrentJobIds
1906 sub set_job_ids_for_date
1908 my ($dbh, $client, $date, $only_ok)=@_;
1910 if (!$client or !$date) {
1914 my $status = get_wanted_job_status($only_ok);
1916 # The algorithm : for a client, we get all the backups for each
1917 # fileset, in reverse order Then, for each fileset, we store the 'good'
1918 # incrementals and differentials until we have found a full so it goes
1919 # like this : store all incrementals until we have found a differential
1920 # or a full, then find the full #
1922 my $query = "SELECT JobId, FileSet, Level, JobStatus
1923 FROM Job, Client, FileSet
1924 WHERE Job.ClientId = Client.ClientId
1925 AND FileSet.FileSetId = Job.FileSetId
1926 AND EndTime <= '$date'
1927 AND Client.Name = '$client'
1929 AND JobStatus IN ($status)
1930 ORDER BY FileSet, JobTDate DESC";
1933 my $result = $dbh->selectall_arrayref($query);
1935 foreach my $refrow (@$result)
1937 my $jobid = $refrow->[0];
1938 my $fileset = $refrow->[1];
1939 my $level = $refrow->[2];
1941 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1943 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1947 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1948 push @CurrentJobIds,($jobid);
1950 elsif ($level eq 'D')
1952 next if $progress{$fileset} eq 'D'; # We allready have a differential
1953 push @CurrentJobIds,($jobid);
1955 elsif ($level eq 'F')
1957 push @CurrentJobIds,($jobid);
1960 my $status = $refrow->[3] ;
1961 if ($status eq 'T') { # good end of job
1962 $progress{$fileset} = $level;
1966 return @CurrentJobIds;
1971 Gtk2->main_iteration while (Gtk2->events_pending);
1974 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1975 # This sub creates a BSR from the information in the restore_list
1976 # Returns the BSR as a string
1981 # This query gets all jobid/jobmedia/media combination.
1983 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
1984 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1985 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
1986 Media.Volumename, Media.MediaType
1987 FROM Job, JobMedia, Media
1988 WHERE Job.JobId = JobMedia.JobId
1989 AND JobMedia.MediaId = Media.MediaId
1990 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1993 my $result = $self->dbh_selectall_arrayref($query);
1995 # We will store everything hashed by jobid.
1997 foreach my $refrow (@$result)
1999 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2000 $firstindex, $lastindex, $startblock, $endblock,
2001 $volindex, $volumename, $mediatype) = @{$refrow};
2003 # We just have to deal with the case where starfile != endfile
2004 # In this case, we concatenate both, for the bsr
2005 if ($startfile != $endfile) {
2006 $startfile = $startfile . '-' . $endfile;
2010 ($jobid, $volsessionid, $volsessiontime, $startfile,
2011 $firstindex, $lastindex, $startblock .'-'. $endblock,
2012 $volindex, $volumename, $mediatype);
2014 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2018 # reminder : restore_list looks like this :
2019 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2020 # undef, undef, undef, $dirfileindex);
2022 # Here, we retrieve every file/dir that could be in the restore
2023 # We do as simple as possible for the SQL engine (no crazy joins,
2024 # no pseudo join (>= FirstIndex ...), etc ...
2025 # We do a SQL union of all the files/dirs specified in the restore_list
2027 foreach my $entry (@{$self->{restore_list}->{data}})
2029 if ($entry->[4] eq 'dir')
2031 my $dirid = $entry->[0];
2032 my $inclause = $entry->[5]; #curjobids
2035 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2036 FROM File, Path, Filename
2037 WHERE Path.PathId = File.PathId
2038 AND File.FilenameId = Filename.FilenameId
2040 (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
2041 WHERE PathId IN ($dirid)
2043 AND File.JobId IN ($inclause) )";
2044 push @select_queries,($query);
2048 # It's a file. Great, we allready have most
2049 # of what is needed. Simple and efficient query
2050 my $dir = $entry->[0];
2051 my $file = $entry->[1];
2053 my $jobid = $entry->[3];
2054 my $fileindex = $entry->[9];
2055 my $inclause = $entry->[5]; # curjobids
2057 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2058 FROM File,Path,Filename
2059 WHERE File.PathId = $dir
2060 AND File.PathId = Path.PathId
2061 AND File.FilenameId = $file
2062 AND File.FilenameId = Filename.FilenameId
2063 AND File.JobId = $jobid
2066 push @select_queries,($query);
2069 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2071 #Now we run the query and parse the result...
2072 # there may be a lot of records, so we better be efficient
2073 # We use the bind column method, working with references...
2075 my $sth = $self->dbh_prepare($query);
2078 my ($path,$name,$fileindex,$jobid);
2079 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2081 # The temp place we're going to save all file
2082 # list to before the real list
2086 while ($sth->fetchrow_arrayref())
2088 # This may look dumb, but we're going to do a join by ourselves,
2089 # to save memory and avoid sending a complex query to mysql
2090 my $complete_path = $path . $name;
2098 # Remove trailing slash (normalize file and dir name)
2099 $complete_path =~ s/\/$//;
2101 # Let's find the ref(s) for the %mediainfo element(s)
2102 # containing the data for this file
2103 # There can be several matches. It is the pseudo join.
2105 my $max_elt=@{$mediainfos{$jobid}}-1;
2107 while($med_idx <= $max_elt)
2109 my $ref = $mediainfos{$jobid}->[$med_idx];
2110 # First, can we get rid of the first elements of the
2111 # array ? (if they don't contain valuable records
2113 if ($fileindex > $ref->[5])
2115 # It seems we don't need anymore
2116 # this entry in %mediainfo (the input data
2119 shift @{$mediainfos{$jobid}};
2123 # We will do work on this elt. We can ++
2124 # $med_idx for next loop
2127 # %mediainfo row looks like :
2128 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2129 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2132 # We are in range. We store and continue looping
2134 if ($fileindex >= $ref->[4])
2136 my @data = ($complete_path,$is_dir,
2138 push @temp_list,(\@data);
2142 # We are not in range. No point in continuing looping
2143 # We go to next record.
2147 # Now we have the array.
2148 # We're going to sort it, by
2149 # path, volsessiontime DESC (get the most recent file...)
2150 # The array rows look like this :
2151 # complete_path,is_dir,fileindex,
2152 #
\81 ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2153 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2154 @temp_list = sort {$a->[0] cmp $b->[0]
2155 || $b->[3]->[2] <=> $a->[3]->[2]
2159 my $prev_complete_path='////'; # Sure not to match
2163 while (my $refrow = shift @temp_list)
2165 # For the sake of readability, we load $refrow
2166 # contents in real scalars
2167 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2168 my $jobid= $refother->[0]; # We don't need the rest...
2170 # We skip this entry.
2171 # We allready have a newer one and this
2172 # isn't a continuation of the same file
2173 next if ($complete_path eq $prev_complete_path
2174 and $jobid != $prev_jobid);
2178 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2180 # We would be recursing inside a file.
2181 # Just what we don't want (dir replaced by file
2182 # between two backups
2188 push @restore_list,($refrow);
2190 $prev_complete_path = $complete_path;
2191 $prev_jobid = $jobid;
2197 push @restore_list,($refrow);
2199 $prev_complete_path = $complete_path;
2200 $prev_jobid = $jobid;
2204 # We get rid of @temp_list... save memory
2207 # Ok everything is in the list. Let's sort it again in another way.
2208 # This time it will be in the bsr file order
2210 # we sort the results by
2211 # volsessiontime, volsessionid, volindex, fileindex
2212 # to get all files in right order...
2213 # Reminder : The array rows look like this :
2214 # complete_path,is_dir,fileindex,
2215 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2216 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2218 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2219 || $a->[3]->[1] <=> $b->[3]->[1]
2220 || $a->[3]->[7] <=> $b->[3]->[7]
2221 || $a->[2] <=> $b->[2] }
2224 # Now that everything is ready, we create the bsr
2225 my $prev_fileindex=-1;
2226 my $prev_volsessionid=-1;
2227 my $prev_volsessiontime=-1;
2228 my $prev_volumename=-1;
2229 my $prev_volfile=-1;
2233 my $first_of_current_range=0;
2234 my @fileindex_ranges;
2237 foreach my $refrow (@restore_list)
2239 my (undef,undef,$fileindex,$refother)=@{$refrow};
2240 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2241 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2243 # We can specifiy the number of files in each section of the
2244 # bsr to speedup restore (bacula can then jump over the
2245 # end of tape files.
2249 if ($prev_volumename eq '-1')
2251 # We only have to start the new range...
2252 $first_of_current_range=$fileindex;
2254 elsif ($prev_volsessionid != $volsessionid
2255 or $prev_volsessiontime != $volsessiontime
2256 or $prev_volumename ne $volumename
2257 or $prev_volfile ne $volfile)
2259 # We have to create a new section in the bsr...
2260 #
\81Â
\81 We print the previous one ...
2261 # (before that, save the current range ...)
2262 if ($first_of_current_range != $prev_fileindex)
2264 #
\81Â
\81 we are in a range
2265 push @fileindex_ranges,
2266 ("$first_of_current_range-$prev_fileindex");
2270 # We are out of a range,
2271 # but there is only one element in the range
2272 push @fileindex_ranges,
2273 ("$first_of_current_range");
2276 $bsr.=print_bsr_section(\@fileindex_ranges,
2278 $prev_volsessiontime,
2285 # Reset for next loop
2286 @fileindex_ranges=();
2287 $first_of_current_range=$fileindex;
2289 elsif ($fileindex-1 != $prev_fileindex)
2291 # End of a range of fileindexes
2292 if ($first_of_current_range != $prev_fileindex)
2295 push @fileindex_ranges,
2296 ("$first_of_current_range-$prev_fileindex");
2300 # We are out of a range,
2301 # but there is only one element in the range
2302 push @fileindex_ranges,
2303 ("$first_of_current_range");
2305 $first_of_current_range=$fileindex;
2307 $prev_fileindex=$fileindex;
2308 $prev_volsessionid = $volsessionid;
2309 $prev_volsessiontime = $volsessiontime;
2310 $prev_volumename = $volumename;
2311 $prev_volfile=$volfile;
2312 $prev_mediatype=$mediatype;
2313 $prev_volblocks=$volblocks;
2317 # Ok, we're out of the loop. Alas, there's still the last record ...
2318 if ($first_of_current_range != $prev_fileindex)
2321 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2326 # We are out of a range,
2327 # but there is only one element in the range
2328 push @fileindex_ranges,("$first_of_current_range");
2331 $bsr.=print_bsr_section(\@fileindex_ranges,
2333 $prev_volsessiontime,
2343 sub print_bsr_section
2345 my ($ref_fileindex_ranges,$volsessionid,
2346 $volsessiontime,$volumename,$volfile,
2347 $mediatype,$volblocks,$count)=@_;
2350 $bsr .= "Volume=\"$volumename\"\n";
2351 $bsr .= "MediaType=\"$mediatype\"\n";
2352 $bsr .= "VolSessionId=$volsessionid\n";
2353 $bsr .= "VolSessionTime=$volsessiontime\n";
2354 $bsr .= "VolFile=$volfile\n";
2355 $bsr .= "VolBlock=$volblocks\n";
2357 foreach my $range (@{$ref_fileindex_ranges})
2359 $bsr .= "FileIndex=$range\n";
2362 $bsr .= "Count=$count\n";
2368 ################################################################
2375 my ($self, $dir) = @_;
2377 "SELECT PathId FROM Path WHERE Path = ?";
2378 my $sth = $self->dbh_prepare($query);
2379 $sth->execute($dir);
2380 my $result = $sth->fetchall_arrayref();
2383 return join(',', map { $_->[0] } @$result);
2391 $self->{conf}->{dbh}->begin_work();
2394 SELECT JobId from Job
2395 WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) AND JobStatus IN ('T', 'f', 'A') ORDER BY JobId";
2396 my $jobs = $self->dbh_selectall_arrayref($query);
2398 $self->update_brestore_table(map { $_->[0] } @$jobs);
2400 print STDERR "Cleaning path visibility\n";
2402 my $nb = $self->dbh_do("
2403 DELETE FROM brestore_pathvisibility
2405 (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
2407 print STDERR "$nb rows affected\n";
2408 print STDERR "Cleaning known jobid\n";
2410 $nb = $self->dbh_do("
2411 DELETE FROM brestore_knownjobid
2413 (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
2415 print STDERR "$nb rows affected\n";
2417 $self->{conf}->{dbh}->commit();
2422 my ($self, $dir) = @_;
2423 return $self->get_pathid('');
2428 my ($self, $pathid) = @_;
2429 $self->{cwdid} = $pathid;
2437 FROM brestore_pathhierarchy
2438 WHERE PathId IN ($self->{cwdid}) ";
2440 my $all = $self->dbh_selectall_arrayref($query);
2441 return unless ($all); # already at root
2443 my $dir = join(',', map { $_->[0] } @$all);
2445 $self->ch_dir($dir);
2452 return $self->get_path($self->{cwdid});
2457 my ($self, $pathid) = @_;
2458 $self->debug("Call with pathid = $pathid");
2460 "SELECT Path FROM Path WHERE PathId IN (?)";
2462 my $sth = $self->dbh_prepare($query);
2463 $sth->execute($pathid);
2464 my $result = $sth->fetchrow_arrayref();
2466 return $result->[0];
2471 my ($self, @jobids) = @_;
2472 $self->{curjobids} = join(',', @jobids);
2473 $self->update_brestore_table(@jobids);
2480 return undef unless ($self->{curjobids});
2482 my $inclause = $self->{curjobids};
2483 my $inlistpath = $self->{cwdid};
2486 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2488 (SELECT Filename.Name, max(File.FileId) as id
2490 WHERE File.FilenameId = Filename.FilenameId
2491 AND Filename.Name != ''
2492 AND File.PathId IN ($inlistpath)
2493 AND File.JobId IN ($inclause)
2494 GROUP BY Filename.Name
2495 ORDER BY Filename.Name) AS listfiles,
2497 WHERE File.FileId = listfiles.id";
2499 $self->debug($query);
2500 my $result = $self->dbh_selectall_arrayref($query);
2501 $self->debug($result);
2506 # return ($dirid,$dir_basename,$lstat,$jobid)
2511 return undef unless ($self->{curjobids});
2513 my $pathid = $self->{cwdid};
2514 my $jobclause = $self->{curjobids};
2516 # Let's retrieve the list of the visible dirs in this dir ...
2517 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2518 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2519 my $sth = $self->dbh_prepare($query);
2521 my $result = $sth->fetchrow_arrayref();
2523 my $dir_filenameid = $result->[0];
2525 # Then we get all the dir entries from File ...
2527 SELECT PathId, Path, JobId, Lstat FROM (
2529 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2530 listfile1.JobId, listfile1.Lstat
2532 SELECT DISTINCT brestore_pathhierarchy1.PathId
2533 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2535 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2536 JOIN brestore_pathvisibility AS brestore_pathvisibility1
2537 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2538 WHERE brestore_pathhierarchy1.PPathId = $pathid
2539 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2540 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2542 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2543 WHERE File1.FilenameId = $dir_filenameid
2544 AND File1.JobId IN ($jobclause)) AS listfile1
2545 ON (listpath1.PathId = listfile1.PathId)
2546 ) AS A ORDER BY 2,3 DESC
2548 $self->debug($query);
2549 $sth=$self->dbh_prepare($query);
2551 $result = $sth->fetchall_arrayref();
2554 foreach my $refrow (@{$result})
2556 my $dirid = $refrow->[0];
2557 my $dir = $refrow->[1];
2558 my $lstat = $refrow->[3];
2559 my $jobid = $refrow->[2] || 0;
2560 next if ($dirid eq $prev_dir);
2561 # We have to clean up this dirname ... we only want it's 'basename'
2565 my @temp = split ('/',$dir);
2566 $return_value = pop @temp;
2570 $return_value = '/';
2572 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2573 push @return_list,(\@return_array);
2576 $self->debug(\@return_list);
2577 return \@return_list;
2580 # Returns the list of media required for a list of jobids.
2581 # Input : self, jobid1, jobid2...
2582 # Output : reference to array of (joibd, inchanger)
2583 sub get_required_media_from_jobid
2585 my ($self, @jobids)=@_;
2586 my $inclause = join(',',@jobids);
2588 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2589 FROM JobMedia, Media
2590 WHERE JobMedia.MediaId=Media.MediaId
2591 AND JobId In ($inclause)
2593 my $result = $self->dbh_selectall_arrayref($query);
2597 # Returns the fileindex from dirname and jobid.
2598 # Input : self, dirid, jobid
2599 # Output : fileindex
2600 sub get_fileindex_from_dir_jobid
2602 my ($self, $dirid, $jobid)=@_;
2604 $query = "SELECT File.FileIndex
2606 WHERE File.FilenameId = Filename.FilenameId
2607 AND File.PathId = $dirid
2608 AND Filename.Name = ''
2609 AND File.JobId = '$jobid'
2612 $self->debug($query);
2613 my $result = $self->dbh_selectall_arrayref($query);
2614 return $result->[0]->[0];
2617 # Returns the fileindex from filename and jobid.
2618 # Input : self, dirid, filenameid, jobid
2619 # Output : fileindex
2620 sub get_fileindex_from_file_jobid
2622 my ($self, $dirid, $filenameid, $jobid)=@_;
2626 "SELECT File.FileIndex
2628 WHERE File.PathId = $dirid
2629 AND File.FilenameId = $filenameid
2630 AND File.JobId = $jobid";
2632 $self->debug($query);
2633 my $result = $self->dbh_selectall_arrayref($query);
2634 return $result->[0]->[0];
2637 # This function estimates the size to be restored for an entry of the restore
2639 # In : self,reference to the entry
2640 # Out : size in bytes, number of files
2641 sub estimate_restore_size
2643 # reminder : restore_list looks like this :
2644 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2645 # undef, undef, undef, $dirfileindex);
2646 my ($self, $entry, $refresh) = @_;
2648 if ($entry->[4] eq 'dir')
2650 my $dir = $entry->[0];
2652 my $inclause = $entry->[5]; #curjobids
2654 "SELECT Path.Path, File.FilenameId, File.LStat
2655 FROM File, Path, Job
2656 WHERE Path.PathId = File.PathId
2657 AND File.JobId = Job.JobId
2659 (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2661 AND File.JobId IN ($inclause)
2662 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2666 # It's a file. Great, we allready have most
2667 # of what is needed. Simple and efficient query
2668 my $dir = $entry->[0];
2669 my $fileid = $entry->[1];
2671 my $jobid = $entry->[3];
2672 my $fileindex = $entry->[9];
2673 my $inclause = $entry->[5]; # curjobids
2675 "SELECT Path.Path, File.FilenameId, File.Lstat
2677 WHERE Path.PathId = File.PathId
2678 AND Path.PathId = $dir
2679 AND File.FilenameId = $fileid
2680 AND File.JobId = $jobid";
2683 my ($path,$nameid,$lstat);
2684 my $sth = $self->dbh_prepare($query);
2686 $sth->bind_columns(\$path,\$nameid,\$lstat);
2696 while ($sth->fetchrow_arrayref())
2698 # Only the latest version of a file
2699 next if ($nameid eq $old_nameid and $path eq $old_path);
2701 if ($rcount > 15000) {
2708 # We get the size of this file
2709 my $size=lstat_attrib($lstat,'st_size');
2710 $total_size += $size;
2713 $old_nameid=$nameid;
2716 return ($total_size,$total_files);
2719 # Returns list of versions of a file that could be restored
2720 # returns an array of
2721 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2722 # there will be only one jobid in the array of jobids...
2723 sub get_all_file_versions
2725 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2727 defined $see_all or $see_all=0;
2732 "SELECT File.JobId, File.FileIndex, File.Lstat,
2733 File.Md5, Media.VolumeName, Media.InChanger
2734 FROM File, Job, Client, JobMedia, Media
2735 WHERE File.FilenameId = $fileid
2736 AND File.PathId=$pathid
2737 AND File.JobId = Job.JobId
2738 AND Job.ClientId = Client.ClientId
2739 AND Job.JobId = JobMedia.JobId
2740 AND File.FileIndex >= JobMedia.FirstIndex
2741 AND File.FileIndex <= JobMedia.LastIndex
2742 AND JobMedia.MediaId = Media.MediaId
2743 AND Client.Name = '$client'";
2745 $self->debug($query);
2747 my $result = $self->dbh_selectall_arrayref($query);
2749 foreach my $refrow (@$result)
2751 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2752 my @attribs = parse_lstat($lstat);
2753 my $mtime = array_attrib('st_mtime',\@attribs);
2754 my $size = array_attrib('st_size',\@attribs);
2756 my @list = ('FILE:',$pathid,$fileid,$jobid,
2757 $fileindex, $mtime, $size, $inchanger,
2759 push @versions, (\@list);
2762 # We have the list of all versions of this file.
2763 # We'll sort it by mtime desc, size, md5, inchanger desc
2764 # the rest of the algorithm will be simpler
2765 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2766 @versions = sort { $b->[5] <=> $a->[5]
2767 || $a->[6] <=> $b->[6]
2768 || $a->[8] cmp $a->[8]
2769 || $b->[7] <=> $a->[7]} @versions;
2773 my %allready_seen_by_mtime;
2774 my %allready_seen_by_md5;
2775 # Now we should create a new array with only the interesting records
2776 foreach my $ref (@versions)
2780 # The file has a md5. We compare his md5 to other known md5...
2781 # We take size into account. It may happen that 2 files
2782 # have the same md5sum and are different. size is a supplementary
2785 # If we allready have a (better) version
2786 next if ( (not $see_all)
2787 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2789 # we never met this one before...
2790 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2792 # Even if it has a md5, we should also work with mtimes
2793 # We allready have a (better) version
2794 next if ( (not $see_all)
2795 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2796 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2798 # We reached there. The file hasn't been seen.
2799 push @good_versions,($ref);
2802 # To be nice with the user, we re-sort good_versions by
2803 # inchanger desc, mtime desc
2804 @good_versions = sort { $b->[5] <=> $a->[5]
2805 || $b->[3] <=> $a->[3]} @good_versions;
2807 return @good_versions;
2811 sub update_brestore_table
2813 my ($self, @jobs) = @_;
2815 $self->debug(\@jobs);
2817 foreach my $job (sort {$a <=> $b} @jobs)
2819 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2820 my $retour = $self->dbh_selectrow_arrayref($query);
2821 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2823 print STDERR "Inserting path records for JobId $job\n";
2824 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2825 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2827 $self->dbh_do($query);
2829 # Now we have to do the directory recursion stuff to determine missing visibility
2830 # We try to avoid recursion, to be as fast as possible
2831 # We also only work on not allready hierarchised directories...
2833 print STDERR "Creating missing recursion paths for $job\n";
2835 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2836 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2837 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2838 WHERE brestore_pathvisibility.JobId = $job
2839 AND brestore_pathhierarchy.PathId IS NULL
2842 my $sth = $self->dbh_prepare($query);
2844 my $pathid; my $path;
2845 $sth->bind_columns(\$pathid,\$path);
2849 $self->build_path_hierarchy($path,$pathid);
2853 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2854 # This query gives all parent pathids for a given jobid that aren't stored.
2855 # It has to be called until no record is updated ...
2857 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2858 SELECT a.PathId,$job
2860 (SELECT DISTINCT h.PPathId AS PathId
2861 FROM brestore_pathhierarchy AS h
2862 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2863 WHERE p.JobId=$job) AS a
2866 FROM brestore_pathvisibility
2867 WHERE JobId=$job) AS b
2868 ON (a.PathId = b.PathId)
2869 WHERE b.PathId IS NULL)";
2872 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2874 print STDERR "Recursively adding $rows_affected records from $job\n";
2877 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2878 $self->dbh_do($query);
2890 # Root Windows case :
2891 if ($path =~ /^[a-z]+:\/$/i)
2896 my @tmp = split('/',$path);
2897 # We remove the last ...
2899 my $tmp = join ('/',@tmp) . '/';
2903 sub build_path_hierarchy
2905 my ($self, $path,$pathid)=@_;
2906 # Does the ppathid exist for this ? we use a memory cache...
2907 # In order to avoid the full loop, we consider that if a dir is allready in the
2908 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2911 if (! $self->{cache_ppathid}->{$pathid})
2913 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2914 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2915 $sth2->execute($pathid);
2916 # Do we have a result ?
2917 if (my $refrow = $sth2->fetchrow_arrayref)
2919 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2921 # This dir was in the db ...
2922 # It means we can leave, the tree has allready been built for
2927 # We have to create the record ...
2928 # What's the current p_path ?
2929 my $ppath = parent_dir($path);
2930 my $ppathid = $self->return_pathid_from_path($ppath);
2931 $self->{cache_ppathid}->{$pathid}= $ppathid;
2933 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2934 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2935 $sth2->execute($pathid,$ppathid);
2941 # It's allready in the cache.
2942 # We can leave, no time to waste here, all the parent dirs have allready
2951 sub return_pathid_from_path
2953 my ($self, $path) = @_;
2954 my $query = "SELECT PathId FROM Path WHERE Path = ?";
2956 #print STDERR $query,"\n" if $debug;
2957 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2958 $sth->execute($path);
2959 my $result =$sth->fetchrow_arrayref();
2961 if (defined $result)
2963 return $result->[0];
2966 # A bit dirty : we insert into path, and we have to be sure
2967 # we aren't deleted by a purge. We still need to insert into path to get
2968 # the pathid, because of mysql
2969 $query = "INSERT INTO Path (Path) VALUES (?)";
2970 #print STDERR $query,"\n" if $debug;
2971 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2972 $sth->execute($path);
2975 $query = "SELECT PathId FROM Path WHERE Path = ?";
2976 #print STDERR $query,"\n" if $debug;
2977 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2978 $sth->execute($path);
2979 $result = $sth->fetchrow_arrayref();
2981 return $result->[0];
2986 sub create_brestore_tables
2990 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2992 unless ($self->dbh_do($verif)) {
2996 CREATE TABLE brestore_knownjobid
2998 JobId int4 NOT NULL,
2999 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
3001 $self->dbh_do($req);
3004 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
3005 unless ($self->dbh_do($verif)) {
3008 CREATE TABLE brestore_pathhierarchy
3010 PathId int4 NOT NULL,
3011 PPathId int4 NOT NULL,
3012 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
3014 $self->dbh_do($req);
3017 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
3018 ON brestore_pathhierarchy (PPathId)";
3019 $self->dbh_do($req);
3022 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
3023 unless ($self->dbh_do($verif)) {
3026 CREATE TABLE brestore_pathvisibility
3028 PathId int4 NOT NULL,
3029 JobId int4 NOT NULL,
3030 Size int8 DEFAULT 0,
3031 Files int4 DEFAULT 0,
3032 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
3034 $self->dbh_do($req);
3036 $req = "CREATE INDEX brestore_pathvisibility_jobid
3037 ON brestore_pathvisibility (JobId)";
3038 $self->dbh_do($req);
3045 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3046 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3047 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3048 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3049 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3050 'data_stream' => 15);;
3053 my ($attrib,$ref_attrib)=@_;
3054 return $ref_attrib->[$attrib_name_id{$attrib}];
3058 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3060 my ($file, $attrib)=@_;
3062 if (defined $attrib_name_id{$attrib}) {
3064 my @d = split(' ', $file->[3]) ; # TODO : cache this
3066 return from_base64($d[$attrib_name_id{$attrib}]);
3068 } elsif ($attrib eq 'jobid') {
3072 } elsif ($attrib eq 'name') {
3077 die "Attribute not known : $attrib.\n";
3083 my ($lstat,$attrib)=@_;
3084 if ($lstat and defined $attrib_name_id{$attrib})
3086 my @d = split(' ', $lstat) ; # TODO : cache this
3087 return from_base64($d[$attrib_name_id{$attrib}]);
3094 # Base 64 functions, directly from recover.pl.
3096 # Karl Hakimian <hakimian@aha.com>
3097 # This section is also under GPL v2 or later.
3104 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3105 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3106 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3107 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3108 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3110 @base64_map = (0) x 128;
3112 for (my $i=0; $i<64; $i++) {
3113 $base64_map[ord($base64_digits[$i])] = $i;
3128 if (substr($where, 0, 1) eq '-') {
3130 $where = substr($where, 1);
3133 while ($where ne '') {
3135 my $d = substr($where, 0, 1);
3136 $val += $base64_map[ord(substr($where, 0, 1))];
3137 $where = substr($where, 1);
3145 my @attribs = split(' ',$lstat);
3146 foreach my $element (@attribs)
3148 $element = from_base64($element);
3156 ################################################################
3157 package BwebConsole;
3159 use HTTP::Request::Common;
3163 my ($class, %arg) = @_;
3166 pref => $arg{pref}, # Pref object
3167 timeout => $arg{timeout} || 20,
3168 debug => $arg{debug} || 0,
3170 'list_client' => '',
3171 'list_fileset' => '',
3172 'list_storage' => '',
3181 my ($self, @what) = @_;
3182 my $ua = LWP::UserAgent->new();
3183 $ua->agent("Brestore/$VERSION");
3184 my $req = POST($self->{pref}->{bconsole},
3185 Content_Type => 'form-data',
3186 Content => [ map { (action => $_) } @what ]);
3187 #$req->authorization_basic('eric', 'test');
3189 my $res = $ua->request($req);
3191 if ($res->is_success) {
3192 foreach my $l (split(/\n/, $res->content)) {
3193 my ($k, $c) = split(/=/,$l,2);
3197 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3198 new DlgWarn($self->{error});
3204 my ($self, %arg) = @_;
3206 my $ua = LWP::UserAgent->new();
3207 $ua->agent("Brestore/$VERSION");
3208 my $req = POST($self->{pref}->{bconsole},
3209 Content_Type => 'form-data',
3210 Content => [ job => $arg{job},
3211 client => $arg{client},
3212 storage => $arg{storage} || '',
3213 fileset => $arg{fileset} || '',
3214 where => $arg{where} || '',
3215 regexwhere => $arg{regexwhere} || '',
3216 priority=> $arg{prio} || '',
3217 replace => $arg{replace},
3220 bootstrap => [$arg{bootstrap}],
3222 #$req->authorization_basic('eric', 'test');
3224 my $res = $ua->request($req);
3226 if ($res->is_success) {
3227 foreach my $l (split(/\n/, $res->content)) {
3228 my ($k, $c) = split(/=/,$l,2);
3233 if (!$self->{run}) {
3234 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3237 unlink($arg{bootstrap});
3239 return $self->{run};
3245 return sort split(/;/, $self->{'list_job'});
3251 return sort split(/;/, $self->{'list_fileset'});
3257 return sort split(/;/, $self->{'list_storage'});
3262 return sort split(/;/, $self->{'list_client'});
3266 ################################################################
3274 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3278 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3281 GetOptions("conf=s" => \$file_conf,
3282 "batch" => \$batch_mod,
3284 "help" => \&HELP_MESSAGE) ;
3286 my $p = new Pref($file_conf);
3288 if (! -f $file_conf) {
3293 my $vfs = new Bvfs(conf => $p);
3294 if ($p->connect_db()) {
3295 if ($vfs->create_brestore_tables()) {
3296 print "Creating brestore tables\n";
3298 $vfs->update_cache();
3303 $glade_file = $p->{glade_file} || $glade_file;
3305 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3306 if (-f "$path/$glade_file") {
3307 $glade_file = "$path/$glade_file" ;
3312 # gtk have lots of warning on stderr
3313 if ($^O eq 'MSWin32')
3316 open(STDERR, ">stderr.log");
3321 if ( -f $glade_file) {
3322 my $w = new DlgResto($p);
3325 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3326 "Can't find your brestore.glade (glade_file => '$glade_file')
3327 Please, edit your $file_conf to setup it." );
3329 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3334 Gtk2->main; # Start Gtk2 main loop
3343 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3345 $p->connect_db() || print $p->{error};
3347 my $bvfs = new Bvfs(conf => $p);
3349 $bvfs->debug($bvfs->get_root());
3350 $bvfs->ch_dir($bvfs->get_root());
3352 $bvfs->set_curjobids(268,178,282,281,279);
3354 my $dirs = $bvfs->ls_dirs();
3355 $bvfs->ch_dir(123496);
3356 $dirs = $bvfs->ls_dirs();
3358 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwdid},312433, "exw3srv3", 1);