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);
2392 SELECT JobId from Job
2393 WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) ORDER BY JobId";
2394 my $jobs = $self->dbh_selectall_arrayref($query);
2396 $self->update_brestore_table(map { $_->[0] } @$jobs);
2401 my ($self, $dir) = @_;
2402 return $self->get_pathid('');
2407 my ($self, $pathid) = @_;
2408 $self->{cwdid} = $pathid;
2416 FROM brestore_pathhierarchy
2417 WHERE PathId IN ($self->{cwdid}) ";
2419 my $all = $self->dbh_selectall_arrayref($query);
2420 return unless ($all); # already at root
2422 my $dir = join(',', map { $_->[0] } @$all);
2424 $self->ch_dir($dir);
2431 return $self->get_path($self->{cwdid});
2436 my ($self, $pathid) = @_;
2437 $self->debug("Call with pathid = $pathid");
2439 "SELECT Path FROM Path WHERE PathId IN (?)";
2441 my $sth = $self->dbh_prepare($query);
2442 $sth->execute($pathid);
2443 my $result = $sth->fetchrow_arrayref();
2445 return $result->[0];
2450 my ($self, @jobids) = @_;
2451 $self->{curjobids} = join(',', @jobids);
2452 $self->update_brestore_table(@jobids);
2459 return undef unless ($self->{curjobids});
2461 my $inclause = $self->{curjobids};
2462 my $inlistpath = $self->{cwdid};
2465 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2467 (SELECT Filename.Name, max(File.FileId) as id
2469 WHERE File.FilenameId = Filename.FilenameId
2470 AND Filename.Name != ''
2471 AND File.PathId IN ($inlistpath)
2472 AND File.JobId IN ($inclause)
2473 GROUP BY Filename.Name
2474 ORDER BY Filename.Name) AS listfiles,
2476 WHERE File.FileId = listfiles.id";
2478 $self->debug($query);
2479 my $result = $self->dbh_selectall_arrayref($query);
2480 $self->debug($result);
2485 # return ($dirid,$dir_basename,$lstat,$jobid)
2490 return undef unless ($self->{curjobids});
2492 my $pathid = $self->{cwdid};
2493 my $jobclause = $self->{curjobids};
2495 # Let's retrieve the list of the visible dirs in this dir ...
2496 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2497 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2498 my $sth = $self->dbh_prepare($query);
2500 my $result = $sth->fetchrow_arrayref();
2502 my $dir_filenameid = $result->[0];
2504 # Then we get all the dir entries from File ...
2506 SELECT PathId, Path, JobId, Lstat FROM (
2508 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2509 listfile1.JobId, listfile1.Lstat
2511 SELECT DISTINCT brestore_pathhierarchy1.PathId
2512 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2514 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2515 JOIN brestore_pathvisibility AS brestore_pathvisibility1
2516 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2517 WHERE brestore_pathhierarchy1.PPathId = $pathid
2518 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2519 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2521 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2522 WHERE File1.FilenameId = $dir_filenameid
2523 AND File1.JobId IN ($jobclause)) AS listfile1
2524 ON (listpath1.PathId = listfile1.PathId)
2525 ) AS A ORDER BY 2,3 DESC
2527 $self->debug($query);
2528 $sth=$self->dbh_prepare($query);
2530 $result = $sth->fetchall_arrayref();
2533 foreach my $refrow (@{$result})
2535 my $dirid = $refrow->[0];
2536 my $dir = $refrow->[1];
2537 my $lstat = $refrow->[3];
2538 my $jobid = $refrow->[2] || 0;
2539 next if ($dirid eq $prev_dir);
2540 # We have to clean up this dirname ... we only want it's 'basename'
2544 my @temp = split ('/',$dir);
2545 $return_value = pop @temp;
2549 $return_value = '/';
2551 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2552 push @return_list,(\@return_array);
2555 $self->debug(\@return_list);
2556 return \@return_list;
2559 # Returns the list of media required for a list of jobids.
2560 # Input : self, jobid1, jobid2...
2561 # Output : reference to array of (joibd, inchanger)
2562 sub get_required_media_from_jobid
2564 my ($self, @jobids)=@_;
2565 my $inclause = join(',',@jobids);
2567 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2568 FROM JobMedia, Media
2569 WHERE JobMedia.MediaId=Media.MediaId
2570 AND JobId In ($inclause)
2572 my $result = $self->dbh_selectall_arrayref($query);
2576 # Returns the fileindex from dirname and jobid.
2577 # Input : self, dirid, jobid
2578 # Output : fileindex
2579 sub get_fileindex_from_dir_jobid
2581 my ($self, $dirid, $jobid)=@_;
2583 $query = "SELECT File.FileIndex
2585 WHERE File.FilenameId = Filename.FilenameId
2586 AND File.PathId = $dirid
2587 AND Filename.Name = ''
2588 AND File.JobId = '$jobid'
2591 $self->debug($query);
2592 my $result = $self->dbh_selectall_arrayref($query);
2593 return $result->[0]->[0];
2596 # Returns the fileindex from filename and jobid.
2597 # Input : self, dirid, filenameid, jobid
2598 # Output : fileindex
2599 sub get_fileindex_from_file_jobid
2601 my ($self, $dirid, $filenameid, $jobid)=@_;
2605 "SELECT File.FileIndex
2607 WHERE File.PathId = $dirid
2608 AND File.FilenameId = $filenameid
2609 AND File.JobId = $jobid";
2611 $self->debug($query);
2612 my $result = $self->dbh_selectall_arrayref($query);
2613 return $result->[0]->[0];
2616 # This function estimates the size to be restored for an entry of the restore
2618 # In : self,reference to the entry
2619 # Out : size in bytes, number of files
2620 sub estimate_restore_size
2622 # reminder : restore_list looks like this :
2623 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2624 # undef, undef, undef, $dirfileindex);
2625 my ($self, $entry, $refresh) = @_;
2627 if ($entry->[4] eq 'dir')
2629 my $dir = $entry->[0];
2631 my $inclause = $entry->[5]; #curjobids
2633 "SELECT Path.Path, File.FilenameId, File.LStat
2634 FROM File, Path, Job
2635 WHERE Path.PathId = File.PathId
2636 AND File.JobId = Job.JobId
2638 (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2640 AND File.JobId IN ($inclause)
2641 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2645 # It's a file. Great, we allready have most
2646 # of what is needed. Simple and efficient query
2647 my $dir = $entry->[0];
2648 my $fileid = $entry->[1];
2650 my $jobid = $entry->[3];
2651 my $fileindex = $entry->[9];
2652 my $inclause = $entry->[5]; # curjobids
2654 "SELECT Path.Path, File.FilenameId, File.Lstat
2656 WHERE Path.PathId = File.PathId
2657 AND Path.PathId = $dir
2658 AND File.FilenameId = $fileid
2659 AND File.JobId = $jobid";
2662 my ($path,$nameid,$lstat);
2663 my $sth = $self->dbh_prepare($query);
2665 $sth->bind_columns(\$path,\$nameid,\$lstat);
2675 while ($sth->fetchrow_arrayref())
2677 # Only the latest version of a file
2678 next if ($nameid eq $old_nameid and $path eq $old_path);
2680 if ($rcount > 15000) {
2687 # We get the size of this file
2688 my $size=lstat_attrib($lstat,'st_size');
2689 $total_size += $size;
2692 $old_nameid=$nameid;
2695 return ($total_size,$total_files);
2698 # Returns list of versions of a file that could be restored
2699 # returns an array of
2700 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2701 # there will be only one jobid in the array of jobids...
2702 sub get_all_file_versions
2704 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2706 defined $see_all or $see_all=0;
2711 "SELECT File.JobId, File.FileIndex, File.Lstat,
2712 File.Md5, Media.VolumeName, Media.InChanger
2713 FROM File, Job, Client, JobMedia, Media
2714 WHERE File.FilenameId = $fileid
2715 AND File.PathId=$pathid
2716 AND File.JobId = Job.JobId
2717 AND Job.ClientId = Client.ClientId
2718 AND Job.JobId = JobMedia.JobId
2719 AND File.FileIndex >= JobMedia.FirstIndex
2720 AND File.FileIndex <= JobMedia.LastIndex
2721 AND JobMedia.MediaId = Media.MediaId
2722 AND Client.Name = '$client'";
2724 $self->debug($query);
2726 my $result = $self->dbh_selectall_arrayref($query);
2728 foreach my $refrow (@$result)
2730 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2731 my @attribs = parse_lstat($lstat);
2732 my $mtime = array_attrib('st_mtime',\@attribs);
2733 my $size = array_attrib('st_size',\@attribs);
2735 my @list = ('FILE:',$pathid,$fileid,$jobid,
2736 $fileindex, $mtime, $size, $inchanger,
2738 push @versions, (\@list);
2741 # We have the list of all versions of this file.
2742 # We'll sort it by mtime desc, size, md5, inchanger desc
2743 # the rest of the algorithm will be simpler
2744 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2745 @versions = sort { $b->[5] <=> $a->[5]
2746 || $a->[6] <=> $b->[6]
2747 || $a->[8] cmp $a->[8]
2748 || $b->[7] <=> $a->[7]} @versions;
2752 my %allready_seen_by_mtime;
2753 my %allready_seen_by_md5;
2754 # Now we should create a new array with only the interesting records
2755 foreach my $ref (@versions)
2759 # The file has a md5. We compare his md5 to other known md5...
2760 # We take size into account. It may happen that 2 files
2761 # have the same md5sum and are different. size is a supplementary
2764 # If we allready have a (better) version
2765 next if ( (not $see_all)
2766 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2768 # we never met this one before...
2769 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2771 # Even if it has a md5, we should also work with mtimes
2772 # We allready have a (better) version
2773 next if ( (not $see_all)
2774 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2775 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2777 # We reached there. The file hasn't been seen.
2778 push @good_versions,($ref);
2781 # To be nice with the user, we re-sort good_versions by
2782 # inchanger desc, mtime desc
2783 @good_versions = sort { $b->[5] <=> $a->[5]
2784 || $b->[3] <=> $a->[3]} @good_versions;
2786 return @good_versions;
2790 sub update_brestore_table
2792 my ($self, @jobs) = @_;
2794 $self->debug(\@jobs);
2796 foreach my $job (sort {$a <=> $b} @jobs)
2798 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2799 my $retour = $self->dbh_selectrow_arrayref($query);
2800 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2802 print STDERR "Inserting path records for JobId $job\n";
2803 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2804 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2806 $self->dbh_do($query);
2808 # Now we have to do the directory recursion stuff to determine missing visibility
2809 # We try to avoid recursion, to be as fast as possible
2810 # We also only work on not allready hierarchised directories...
2812 print STDERR "Creating missing recursion paths for $job\n";
2814 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2815 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2816 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2817 WHERE brestore_pathvisibility.JobId = $job
2818 AND brestore_pathhierarchy.PathId IS NULL
2821 my $sth = $self->dbh_prepare($query);
2823 my $pathid; my $path;
2824 $sth->bind_columns(\$pathid,\$path);
2828 $self->build_path_hierarchy($path,$pathid);
2832 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2833 # This query gives all parent pathids for a given jobid that aren't stored.
2834 # It has to be called until no record is updated ...
2836 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2837 SELECT a.PathId,$job
2839 (SELECT DISTINCT h.PPathId AS PathId
2840 FROM brestore_pathhierarchy AS h
2841 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2842 WHERE p.JobId=$job) AS a
2845 FROM brestore_pathvisibility
2846 WHERE JobId=$job) AS b
2847 ON (a.PathId = b.PathId)
2848 WHERE b.PathId IS NULL)";
2851 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2853 print STDERR "Recursively adding $rows_affected records from $job\n";
2856 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2857 $self->dbh_do($query);
2861 sub cleanup_brestore_table
2865 my $query = "SELECT JobId from brestore_knownjobid";
2866 my @jobs = @{$self->dbh_selectall_arrayref($query)};
2868 foreach my $jobentry (@jobs)
2870 my $job = $jobentry->[0];
2871 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2872 my $result = $self->dbh_selectall_arrayref($query);
2873 if (scalar(@{$result}))
2875 # There are still files for this jobid
2876 print STDERR "$job still exists. Not cleaning...\n";
2879 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2880 $self->dbh_do($query);
2881 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2882 $self->dbh_do($query);
2895 # Root Windows case :
2896 if ($path =~ /^[a-z]+:\/$/i)
2901 my @tmp = split('/',$path);
2902 # We remove the last ...
2904 my $tmp = join ('/',@tmp) . '/';
2908 sub build_path_hierarchy
2910 my ($self, $path,$pathid)=@_;
2911 # Does the ppathid exist for this ? we use a memory cache...
2912 # In order to avoid the full loop, we consider that if a dir is allready in the
2913 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2916 if (! $self->{cache_ppathid}->{$pathid})
2918 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2919 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2920 $sth2->execute($pathid);
2921 # Do we have a result ?
2922 if (my $refrow = $sth2->fetchrow_arrayref)
2924 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2926 # This dir was in the db ...
2927 # It means we can leave, the tree has allready been built for
2932 # We have to create the record ...
2933 # What's the current p_path ?
2934 my $ppath = parent_dir($path);
2935 my $ppathid = $self->return_pathid_from_path($ppath);
2936 $self->{cache_ppathid}->{$pathid}= $ppathid;
2938 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2939 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2940 $sth2->execute($pathid,$ppathid);
2946 # It's allready in the cache.
2947 # We can leave, no time to waste here, all the parent dirs have allready
2956 sub return_pathid_from_path
2958 my ($self, $path) = @_;
2959 my $query = "SELECT PathId FROM Path WHERE Path = ?";
2961 #print STDERR $query,"\n" if $debug;
2962 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2963 $sth->execute($path);
2964 my $result =$sth->fetchrow_arrayref();
2966 if (defined $result)
2968 return $result->[0];
2971 # A bit dirty : we insert into path, and we have to be sure
2972 # we aren't deleted by a purge. We still need to insert into path to get
2973 # the pathid, because of mysql
2974 $query = "INSERT INTO Path (Path) VALUES (?)";
2975 #print STDERR $query,"\n" if $debug;
2976 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2977 $sth->execute($path);
2980 $query = "SELECT PathId FROM Path WHERE Path = ?";
2981 #print STDERR $query,"\n" if $debug;
2982 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2983 $sth->execute($path);
2984 $result = $sth->fetchrow_arrayref();
2986 return $result->[0];
2991 sub create_brestore_tables
2995 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2997 unless ($self->dbh_do($verif)) {
3001 CREATE TABLE brestore_knownjobid
3003 JobId int4 NOT NULL,
3004 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
3006 $self->dbh_do($req);
3009 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
3010 unless ($self->dbh_do($verif)) {
3013 CREATE TABLE brestore_pathhierarchy
3015 PathId int4 NOT NULL,
3016 PPathId int4 NOT NULL,
3017 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
3019 $self->dbh_do($req);
3022 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
3023 ON brestore_pathhierarchy (PPathId)";
3024 $self->dbh_do($req);
3027 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
3028 unless ($self->dbh_do($verif)) {
3031 CREATE TABLE brestore_pathvisibility
3033 PathId int4 NOT NULL,
3034 JobId int4 NOT NULL,
3035 Size int8 DEFAULT 0,
3036 Files int4 DEFAULT 0,
3037 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
3039 $self->dbh_do($req);
3041 $req = "CREATE INDEX brestore_pathvisibility_jobid
3042 ON brestore_pathvisibility (JobId)";
3043 $self->dbh_do($req);
3050 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3051 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3052 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3053 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3054 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3055 'data_stream' => 15);;
3058 my ($attrib,$ref_attrib)=@_;
3059 return $ref_attrib->[$attrib_name_id{$attrib}];
3063 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3065 my ($file, $attrib)=@_;
3067 if (defined $attrib_name_id{$attrib}) {
3069 my @d = split(' ', $file->[3]) ; # TODO : cache this
3071 return from_base64($d[$attrib_name_id{$attrib}]);
3073 } elsif ($attrib eq 'jobid') {
3077 } elsif ($attrib eq 'name') {
3082 die "Attribute not known : $attrib.\n";
3088 my ($lstat,$attrib)=@_;
3089 if ($lstat and defined $attrib_name_id{$attrib})
3091 my @d = split(' ', $lstat) ; # TODO : cache this
3092 return from_base64($d[$attrib_name_id{$attrib}]);
3099 # Base 64 functions, directly from recover.pl.
3101 # Karl Hakimian <hakimian@aha.com>
3102 # This section is also under GPL v2 or later.
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 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3112 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3113 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3115 @base64_map = (0) x 128;
3117 for (my $i=0; $i<64; $i++) {
3118 $base64_map[ord($base64_digits[$i])] = $i;
3133 if (substr($where, 0, 1) eq '-') {
3135 $where = substr($where, 1);
3138 while ($where ne '') {
3140 my $d = substr($where, 0, 1);
3141 $val += $base64_map[ord(substr($where, 0, 1))];
3142 $where = substr($where, 1);
3150 my @attribs = split(' ',$lstat);
3151 foreach my $element (@attribs)
3153 $element = from_base64($element);
3161 ################################################################
3162 package BwebConsole;
3164 use HTTP::Request::Common;
3168 my ($class, %arg) = @_;
3171 pref => $arg{pref}, # Pref object
3172 timeout => $arg{timeout} || 20,
3173 debug => $arg{debug} || 0,
3175 'list_client' => '',
3176 'list_fileset' => '',
3177 'list_storage' => '',
3186 my ($self, @what) = @_;
3187 my $ua = LWP::UserAgent->new();
3188 $ua->agent("Brestore/$VERSION");
3189 my $req = POST($self->{pref}->{bconsole},
3190 Content_Type => 'form-data',
3191 Content => [ map { (action => $_) } @what ]);
3192 #$req->authorization_basic('eric', 'test');
3194 my $res = $ua->request($req);
3196 if ($res->is_success) {
3197 foreach my $l (split(/\n/, $res->content)) {
3198 my ($k, $c) = split(/=/,$l,2);
3202 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3203 new DlgWarn($self->{error});
3209 my ($self, %arg) = @_;
3211 my $ua = LWP::UserAgent->new();
3212 $ua->agent("Brestore/$VERSION");
3213 my $req = POST($self->{pref}->{bconsole},
3214 Content_Type => 'form-data',
3215 Content => [ job => $arg{job},
3216 client => $arg{client},
3217 storage => $arg{storage} || '',
3218 fileset => $arg{fileset} || '',
3219 where => $arg{where} || '',
3220 regexwhere => $arg{regexwhere} || '',
3221 priority=> $arg{prio} || '',
3222 replace => $arg{replace},
3225 bootstrap => [$arg{bootstrap}],
3227 #$req->authorization_basic('eric', 'test');
3229 my $res = $ua->request($req);
3231 if ($res->is_success) {
3232 foreach my $l (split(/\n/, $res->content)) {
3233 my ($k, $c) = split(/=/,$l,2);
3238 if (!$self->{run}) {
3239 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3242 unlink($arg{bootstrap});
3244 return $self->{run};
3250 return sort split(/;/, $self->{'list_job'});
3256 return sort split(/;/, $self->{'list_fileset'});
3262 return sort split(/;/, $self->{'list_storage'});
3267 return sort split(/;/, $self->{'list_client'});
3271 ################################################################
3279 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3283 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3286 GetOptions("conf=s" => \$file_conf,
3287 "batch" => \$batch_mod,
3289 "help" => \&HELP_MESSAGE) ;
3291 my $p = new Pref($file_conf);
3293 if (! -f $file_conf) {
3298 my $vfs = new Bvfs(conf => $p);
3299 if ($p->connect_db()) {
3300 if ($vfs->create_brestore_tables()) {
3301 print "Creating brestore tables\n";
3303 $vfs->update_cache();
3308 $glade_file = $p->{glade_file};
3310 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3311 if (-f "$path/$glade_file") {
3312 $glade_file = "$path/$glade_file" ;
3317 # gtk have lots of warning on stderr
3318 if ($^O eq 'MSWin32')
3321 open(STDERR, ">stderr.log");
3326 if ( -f $glade_file) {
3327 my $w = new DlgResto($p);
3330 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3331 "Can't find your brestore.glade (glade_file => '$glade_file')
3332 Please, edit your $file_conf to setup it." );
3334 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3339 Gtk2->main; # Start Gtk2 main loop
3348 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3350 $p->connect_db() || print $p->{error};
3352 my $bvfs = new Bvfs(conf => $p);
3354 $bvfs->debug($bvfs->get_root());
3355 $bvfs->ch_dir($bvfs->get_root());
3357 $bvfs->set_curjobids(268,178,282,281,279);
3359 my $dirs = $bvfs->ls_dirs();
3360 $bvfs->ch_dir(123496);
3361 $dirs = $bvfs->ls_dirs();
3363 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwdid},312433, "exw3srv3", 1);