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 $self->{conf}->{dbh}->commit();
2401 $self->{conf}->{dbh}->begin_work();
2403 print STDERR "Cleaning path visibility\n";
2405 my $nb = $self->dbh_do("
2406 DELETE FROM brestore_pathvisibility
2408 (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
2410 print STDERR "$nb rows affected\n";
2411 print STDERR "Cleaning known jobid\n";
2413 $nb = $self->dbh_do("
2414 DELETE FROM brestore_knownjobid
2416 (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
2418 print STDERR "$nb rows affected\n";
2420 $self->{conf}->{dbh}->commit();
2425 my ($self, $dir) = @_;
2426 return $self->get_pathid('');
2431 my ($self, $pathid) = @_;
2432 $self->{cwdid} = $pathid;
2440 FROM brestore_pathhierarchy
2441 WHERE PathId IN ($self->{cwdid}) ";
2443 my $all = $self->dbh_selectall_arrayref($query);
2444 return unless ($all); # already at root
2446 my $dir = join(',', map { $_->[0] } @$all);
2448 $self->ch_dir($dir);
2455 return $self->get_path($self->{cwdid});
2460 my ($self, $pathid) = @_;
2461 $self->debug("Call with pathid = $pathid");
2463 "SELECT Path FROM Path WHERE PathId IN (?)";
2465 my $sth = $self->dbh_prepare($query);
2466 $sth->execute($pathid);
2467 my $result = $sth->fetchrow_arrayref();
2469 return $result->[0];
2474 my ($self, @jobids) = @_;
2475 $self->{curjobids} = join(',', @jobids);
2476 $self->update_brestore_table(@jobids);
2483 return undef unless ($self->{curjobids});
2485 my $inclause = $self->{curjobids};
2486 my $inlistpath = $self->{cwdid};
2489 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2491 (SELECT Filename.Name, max(File.FileId) as id
2493 WHERE File.FilenameId = Filename.FilenameId
2494 AND Filename.Name != ''
2495 AND File.PathId IN ($inlistpath)
2496 AND File.JobId IN ($inclause)
2497 GROUP BY Filename.Name
2498 ORDER BY Filename.Name) AS listfiles,
2500 WHERE File.FileId = listfiles.id";
2502 $self->debug($query);
2503 my $result = $self->dbh_selectall_arrayref($query);
2504 $self->debug($result);
2509 # return ($dirid,$dir_basename,$lstat,$jobid)
2514 return undef unless ($self->{curjobids});
2516 my $pathid = $self->{cwdid};
2517 my $jobclause = $self->{curjobids};
2519 # Let's retrieve the list of the visible dirs in this dir ...
2520 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2521 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2522 my $sth = $self->dbh_prepare($query);
2524 my $result = $sth->fetchrow_arrayref();
2526 my $dir_filenameid = $result->[0];
2528 # Then we get all the dir entries from File ...
2530 SELECT PathId, Path, JobId, Lstat FROM (
2532 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2533 listfile1.JobId, listfile1.Lstat
2535 SELECT DISTINCT brestore_pathhierarchy1.PathId
2536 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2538 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2539 JOIN brestore_pathvisibility AS brestore_pathvisibility1
2540 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2541 WHERE brestore_pathhierarchy1.PPathId = $pathid
2542 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2543 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2545 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2546 WHERE File1.FilenameId = $dir_filenameid
2547 AND File1.JobId IN ($jobclause)) AS listfile1
2548 ON (listpath1.PathId = listfile1.PathId)
2549 ) AS A ORDER BY 2,3 DESC
2551 $self->debug($query);
2552 $sth=$self->dbh_prepare($query);
2554 $result = $sth->fetchall_arrayref();
2557 foreach my $refrow (@{$result})
2559 my $dirid = $refrow->[0];
2560 my $dir = $refrow->[1];
2561 my $lstat = $refrow->[3];
2562 my $jobid = $refrow->[2] || 0;
2563 next if ($dirid eq $prev_dir);
2564 # We have to clean up this dirname ... we only want it's 'basename'
2568 my @temp = split ('/',$dir);
2569 $return_value = pop @temp;
2573 $return_value = '/';
2575 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2576 push @return_list,(\@return_array);
2579 $self->debug(\@return_list);
2580 return \@return_list;
2583 # Returns the list of media required for a list of jobids.
2584 # Input : self, jobid1, jobid2...
2585 # Output : reference to array of (joibd, inchanger)
2586 sub get_required_media_from_jobid
2588 my ($self, @jobids)=@_;
2589 my $inclause = join(',',@jobids);
2591 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2592 FROM JobMedia, Media
2593 WHERE JobMedia.MediaId=Media.MediaId
2594 AND JobId In ($inclause)
2596 my $result = $self->dbh_selectall_arrayref($query);
2600 # Returns the fileindex from dirname and jobid.
2601 # Input : self, dirid, jobid
2602 # Output : fileindex
2603 sub get_fileindex_from_dir_jobid
2605 my ($self, $dirid, $jobid)=@_;
2607 $query = "SELECT File.FileIndex
2609 WHERE File.FilenameId = Filename.FilenameId
2610 AND File.PathId = $dirid
2611 AND Filename.Name = ''
2612 AND File.JobId = '$jobid'
2615 $self->debug($query);
2616 my $result = $self->dbh_selectall_arrayref($query);
2617 return $result->[0]->[0];
2620 # Returns the fileindex from filename and jobid.
2621 # Input : self, dirid, filenameid, jobid
2622 # Output : fileindex
2623 sub get_fileindex_from_file_jobid
2625 my ($self, $dirid, $filenameid, $jobid)=@_;
2629 "SELECT File.FileIndex
2631 WHERE File.PathId = $dirid
2632 AND File.FilenameId = $filenameid
2633 AND File.JobId = $jobid";
2635 $self->debug($query);
2636 my $result = $self->dbh_selectall_arrayref($query);
2637 return $result->[0]->[0];
2640 # This function estimates the size to be restored for an entry of the restore
2642 # In : self,reference to the entry
2643 # Out : size in bytes, number of files
2644 sub estimate_restore_size
2646 # reminder : restore_list looks like this :
2647 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2648 # undef, undef, undef, $dirfileindex);
2649 my ($self, $entry, $refresh) = @_;
2651 if ($entry->[4] eq 'dir')
2653 my $dir = $entry->[0];
2655 my $inclause = $entry->[5]; #curjobids
2657 "SELECT Path.Path, File.FilenameId, File.LStat
2658 FROM File, Path, Job
2659 WHERE Path.PathId = File.PathId
2660 AND File.JobId = Job.JobId
2662 (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2664 AND File.JobId IN ($inclause)
2665 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2669 # It's a file. Great, we allready have most
2670 # of what is needed. Simple and efficient query
2671 my $dir = $entry->[0];
2672 my $fileid = $entry->[1];
2674 my $jobid = $entry->[3];
2675 my $fileindex = $entry->[9];
2676 my $inclause = $entry->[5]; # curjobids
2678 "SELECT Path.Path, File.FilenameId, File.Lstat
2680 WHERE Path.PathId = File.PathId
2681 AND Path.PathId = $dir
2682 AND File.FilenameId = $fileid
2683 AND File.JobId = $jobid";
2686 my ($path,$nameid,$lstat);
2687 my $sth = $self->dbh_prepare($query);
2689 $sth->bind_columns(\$path,\$nameid,\$lstat);
2699 while ($sth->fetchrow_arrayref())
2701 # Only the latest version of a file
2702 next if ($nameid eq $old_nameid and $path eq $old_path);
2704 if ($rcount > 15000) {
2711 # We get the size of this file
2712 my $size=lstat_attrib($lstat,'st_size');
2713 $total_size += $size;
2716 $old_nameid=$nameid;
2719 return ($total_size,$total_files);
2722 # Returns list of versions of a file that could be restored
2723 # returns an array of
2724 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2725 # there will be only one jobid in the array of jobids...
2726 sub get_all_file_versions
2728 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2730 defined $see_all or $see_all=0;
2735 "SELECT File.JobId, File.FileIndex, File.Lstat,
2736 File.Md5, Media.VolumeName, Media.InChanger
2737 FROM File, Job, Client, JobMedia, Media
2738 WHERE File.FilenameId = $fileid
2739 AND File.PathId=$pathid
2740 AND File.JobId = Job.JobId
2741 AND Job.ClientId = Client.ClientId
2742 AND Job.JobId = JobMedia.JobId
2743 AND File.FileIndex >= JobMedia.FirstIndex
2744 AND File.FileIndex <= JobMedia.LastIndex
2745 AND JobMedia.MediaId = Media.MediaId
2746 AND Client.Name = '$client'";
2748 $self->debug($query);
2750 my $result = $self->dbh_selectall_arrayref($query);
2752 foreach my $refrow (@$result)
2754 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2755 my @attribs = parse_lstat($lstat);
2756 my $mtime = array_attrib('st_mtime',\@attribs);
2757 my $size = array_attrib('st_size',\@attribs);
2759 my @list = ('FILE:',$pathid,$fileid,$jobid,
2760 $fileindex, $mtime, $size, $inchanger,
2762 push @versions, (\@list);
2765 # We have the list of all versions of this file.
2766 # We'll sort it by mtime desc, size, md5, inchanger desc
2767 # the rest of the algorithm will be simpler
2768 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2769 @versions = sort { $b->[5] <=> $a->[5]
2770 || $a->[6] <=> $b->[6]
2771 || $a->[8] cmp $a->[8]
2772 || $b->[7] <=> $a->[7]} @versions;
2776 my %allready_seen_by_mtime;
2777 my %allready_seen_by_md5;
2778 # Now we should create a new array with only the interesting records
2779 foreach my $ref (@versions)
2783 # The file has a md5. We compare his md5 to other known md5...
2784 # We take size into account. It may happen that 2 files
2785 # have the same md5sum and are different. size is a supplementary
2788 # If we allready have a (better) version
2789 next if ( (not $see_all)
2790 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2792 # we never met this one before...
2793 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2795 # Even if it has a md5, we should also work with mtimes
2796 # We allready have a (better) version
2797 next if ( (not $see_all)
2798 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2799 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2801 # We reached there. The file hasn't been seen.
2802 push @good_versions,($ref);
2805 # To be nice with the user, we re-sort good_versions by
2806 # inchanger desc, mtime desc
2807 @good_versions = sort { $b->[5] <=> $a->[5]
2808 || $b->[3] <=> $a->[3]} @good_versions;
2810 return @good_versions;
2814 sub update_brestore_table
2816 my ($self, @jobs) = @_;
2818 $self->debug(\@jobs);
2820 foreach my $job (sort {$a <=> $b} @jobs)
2822 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2823 my $retour = $self->dbh_selectrow_arrayref($query);
2824 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2826 print STDERR "Inserting path records for JobId $job\n";
2827 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2828 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2830 $self->dbh_do($query);
2832 # Now we have to do the directory recursion stuff to determine missing visibility
2833 # We try to avoid recursion, to be as fast as possible
2834 # We also only work on not allready hierarchised directories...
2836 print STDERR "Creating missing recursion paths for $job\n";
2838 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2839 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2840 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2841 WHERE brestore_pathvisibility.JobId = $job
2842 AND brestore_pathhierarchy.PathId IS NULL
2845 my $sth = $self->dbh_prepare($query);
2847 my $pathid; my $path;
2848 $sth->bind_columns(\$pathid,\$path);
2852 $self->build_path_hierarchy($path,$pathid);
2856 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2857 # This query gives all parent pathids for a given jobid that aren't stored.
2858 # It has to be called until no record is updated ...
2860 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2861 SELECT a.PathId,$job
2863 (SELECT DISTINCT h.PPathId AS PathId
2864 FROM brestore_pathhierarchy AS h
2865 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2866 WHERE p.JobId=$job) AS a
2869 FROM brestore_pathvisibility
2870 WHERE JobId=$job) AS b
2871 ON (a.PathId = b.PathId)
2872 WHERE b.PathId IS NULL)";
2875 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2877 print STDERR "Recursively adding $rows_affected records from $job\n";
2880 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2881 $self->dbh_do($query);
2893 # Root Windows case :
2894 if ($path =~ /^[a-z]+:\/$/i)
2899 my @tmp = split('/',$path);
2900 # We remove the last ...
2902 my $tmp = join ('/',@tmp) . '/';
2906 sub build_path_hierarchy
2908 my ($self, $path,$pathid)=@_;
2909 # Does the ppathid exist for this ? we use a memory cache...
2910 # In order to avoid the full loop, we consider that if a dir is allready in the
2911 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2914 if (! $self->{cache_ppathid}->{$pathid})
2916 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2917 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2918 $sth2->execute($pathid);
2919 # Do we have a result ?
2920 if (my $refrow = $sth2->fetchrow_arrayref)
2922 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2924 # This dir was in the db ...
2925 # It means we can leave, the tree has allready been built for
2930 # We have to create the record ...
2931 # What's the current p_path ?
2932 my $ppath = parent_dir($path);
2933 my $ppathid = $self->return_pathid_from_path($ppath);
2934 $self->{cache_ppathid}->{$pathid}= $ppathid;
2936 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2937 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2938 $sth2->execute($pathid,$ppathid);
2944 # It's allready in the cache.
2945 # We can leave, no time to waste here, all the parent dirs have allready
2954 sub return_pathid_from_path
2956 my ($self, $path) = @_;
2957 my $query = "SELECT PathId FROM Path WHERE Path = ?";
2959 #print STDERR $query,"\n" if $debug;
2960 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2961 $sth->execute($path);
2962 my $result =$sth->fetchrow_arrayref();
2964 if (defined $result)
2966 return $result->[0];
2969 # A bit dirty : we insert into path, and we have to be sure
2970 # we aren't deleted by a purge. We still need to insert into path to get
2971 # the pathid, because of mysql
2972 $query = "INSERT INTO Path (Path) VALUES (?)";
2973 #print STDERR $query,"\n" if $debug;
2974 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2975 $sth->execute($path);
2978 $query = "SELECT PathId FROM Path WHERE Path = ?";
2979 #print STDERR $query,"\n" if $debug;
2980 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2981 $sth->execute($path);
2982 $result = $sth->fetchrow_arrayref();
2984 return $result->[0];
2989 sub create_brestore_tables
2993 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2995 unless ($self->dbh_do($verif)) {
2999 CREATE TABLE brestore_knownjobid
3001 JobId int4 NOT NULL,
3002 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
3004 $self->dbh_do($req);
3007 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
3008 unless ($self->dbh_do($verif)) {
3011 CREATE TABLE brestore_pathhierarchy
3013 PathId int4 NOT NULL,
3014 PPathId int4 NOT NULL,
3015 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
3017 $self->dbh_do($req);
3020 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
3021 ON brestore_pathhierarchy (PPathId)";
3022 $self->dbh_do($req);
3025 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
3026 unless ($self->dbh_do($verif)) {
3029 CREATE TABLE brestore_pathvisibility
3031 PathId int4 NOT NULL,
3032 JobId int4 NOT NULL,
3033 Size int8 DEFAULT 0,
3034 Files int4 DEFAULT 0,
3035 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
3037 $self->dbh_do($req);
3039 $req = "CREATE INDEX brestore_pathvisibility_jobid
3040 ON brestore_pathvisibility (JobId)";
3041 $self->dbh_do($req);
3048 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3049 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3050 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3051 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3052 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3053 'data_stream' => 15);;
3056 my ($attrib,$ref_attrib)=@_;
3057 return $ref_attrib->[$attrib_name_id{$attrib}];
3061 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3063 my ($file, $attrib)=@_;
3065 if (defined $attrib_name_id{$attrib}) {
3067 my @d = split(' ', $file->[3]) ; # TODO : cache this
3069 return from_base64($d[$attrib_name_id{$attrib}]);
3071 } elsif ($attrib eq 'jobid') {
3075 } elsif ($attrib eq 'name') {
3080 die "Attribute not known : $attrib.\n";
3086 my ($lstat,$attrib)=@_;
3087 if ($lstat and defined $attrib_name_id{$attrib})
3089 my @d = split(' ', $lstat) ; # TODO : cache this
3090 return from_base64($d[$attrib_name_id{$attrib}]);
3097 # Base 64 functions, directly from recover.pl.
3099 # Karl Hakimian <hakimian@aha.com>
3100 # This section is also under GPL v2 or later.
3107 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3108 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3109 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3110 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3111 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3113 @base64_map = (0) x 128;
3115 for (my $i=0; $i<64; $i++) {
3116 $base64_map[ord($base64_digits[$i])] = $i;
3131 if (substr($where, 0, 1) eq '-') {
3133 $where = substr($where, 1);
3136 while ($where ne '') {
3138 my $d = substr($where, 0, 1);
3139 $val += $base64_map[ord(substr($where, 0, 1))];
3140 $where = substr($where, 1);
3148 my @attribs = split(' ',$lstat);
3149 foreach my $element (@attribs)
3151 $element = from_base64($element);
3159 ################################################################
3160 package BwebConsole;
3162 use HTTP::Request::Common;
3166 my ($class, %arg) = @_;
3169 pref => $arg{pref}, # Pref object
3170 timeout => $arg{timeout} || 20,
3171 debug => $arg{debug} || 0,
3173 'list_client' => '',
3174 'list_fileset' => '',
3175 'list_storage' => '',
3184 my ($self, @what) = @_;
3185 my $ua = LWP::UserAgent->new();
3186 $ua->agent("Brestore/$VERSION");
3187 my $req = POST($self->{pref}->{bconsole},
3188 Content_Type => 'form-data',
3189 Content => [ map { (action => $_) } @what ]);
3190 #$req->authorization_basic('eric', 'test');
3192 my $res = $ua->request($req);
3194 if ($res->is_success) {
3195 foreach my $l (split(/\n/, $res->content)) {
3196 my ($k, $c) = split(/=/,$l,2);
3200 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3201 new DlgWarn($self->{error});
3207 my ($self, %arg) = @_;
3209 my $ua = LWP::UserAgent->new();
3210 $ua->agent("Brestore/$VERSION");
3211 my $req = POST($self->{pref}->{bconsole},
3212 Content_Type => 'form-data',
3213 Content => [ job => $arg{job},
3214 client => $arg{client},
3215 storage => $arg{storage} || '',
3216 fileset => $arg{fileset} || '',
3217 where => $arg{where} || '',
3218 regexwhere => $arg{regexwhere} || '',
3219 priority=> $arg{prio} || '',
3220 replace => $arg{replace},
3223 bootstrap => [$arg{bootstrap}],
3225 #$req->authorization_basic('eric', 'test');
3227 my $res = $ua->request($req);
3229 if ($res->is_success) {
3230 foreach my $l (split(/\n/, $res->content)) {
3231 my ($k, $c) = split(/=/,$l,2);
3236 if (!$self->{run}) {
3237 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3240 unlink($arg{bootstrap});
3242 return $self->{run};
3248 return sort split(/;/, $self->{'list_job'});
3254 return sort split(/;/, $self->{'list_fileset'});
3260 return sort split(/;/, $self->{'list_storage'});
3265 return sort split(/;/, $self->{'list_client'});
3269 ################################################################
3277 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3281 my $file_conf = (exists $ENV{HOME})? "$ENV{HOME}/.brestore.conf" : undef ;
3284 GetOptions("conf=s" => \$file_conf,
3285 "batch" => \$batch_mod,
3287 "help" => \&HELP_MESSAGE) ;
3289 if (! defined $file_conf) {
3290 print STDERR "Could not detect default config and no config file specified\n";
3294 my $p = new Pref($file_conf);
3296 if (! -f $file_conf) {
3301 my $vfs = new Bvfs(conf => $p);
3302 if ($p->connect_db()) {
3303 if ($vfs->create_brestore_tables()) {
3304 print "Creating brestore tables\n";
3306 $vfs->update_cache();
3311 $glade_file = $p->{glade_file} || $glade_file;
3313 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3314 if (-f "$path/$glade_file") {
3315 $glade_file = "$path/$glade_file" ;
3320 # gtk have lots of warning on stderr
3321 if ($^O eq 'MSWin32')
3324 open(STDERR, ">stderr.log");
3329 if ( -f $glade_file) {
3330 my $w = new DlgResto($p);
3333 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3334 "Can't find your brestore.glade (glade_file => '$glade_file')
3335 Please, edit your $file_conf to setup it." );
3337 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3342 Gtk2->main; # Start Gtk2 main loop
3351 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3353 $p->connect_db() || print $p->{error};
3355 my $bvfs = new Bvfs(conf => $p);
3357 $bvfs->debug($bvfs->get_root());
3358 $bvfs->ch_dir($bvfs->get_root());
3360 $bvfs->set_curjobids(268,178,282,281,279);
3362 my $dirs = $bvfs->ls_dirs();
3363 $bvfs->ch_dir(123496);
3364 $dirs = $bvfs->ls_dirs();
3366 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwdid},312433, "exw3srv3", 1);