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