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
143 for my $k (@{ $self->{entry_keyword} }) {
144 $parameters{$k} = $self->{$k};
147 for my $k (@{ $self->{chk_keyword} }) {
148 $parameters{$k} = $self->{$k};
151 if (open FICCFG,">$self->{config_file}")
153 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
158 $self->{error} = "Can't write configuration $!";
160 return $self->{error};
168 $self->{dbh}->disconnect() ;
172 delete $self->{error};
174 if (not $self->{connection_string})
176 # The parameters have not been set. Maybe the conf
177 # file is empty for now
178 $self->{error} = "No configuration found for database connection. " .
179 "Please set this up.";
184 $self->{dbh} = DBI->connect($self->{connection_string},
189 $self->{error} = "Can't open bacula database. " .
190 "Database connect string '" .
191 $self->{connection_string} ."' $!";
194 $self->{is_mysql} = ($self->{connection_string} =~ m/dbi:mysql/i);
195 $self->{dbh}->{RowCacheSize}=100;
201 my ($self, $url, $msg) = @_;
203 unless ($self->{mozilla} and $self->{bweb}) {
204 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
208 if ($^O eq 'MSWin32') {
209 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
212 system("$self->{mozilla} -remote 'Ping()'");
213 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
215 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
225 ################################################################
231 my ($class, %arg) = @_;
244 my ($self, $what, %arg) = @_;
246 if ($self->{conf}->{debug} and defined $what) {
251 my $line = (caller($level))[2];
252 my $func = (caller($level+1))[3] || 'main';
253 print "$func:$line\t";
255 print Data::Dumper::Dumper($what);
256 } elsif ($arg{md5}) {
257 print "MD5=", md5_base64($what), " str=", $what,"\n";
266 my ($self, @what) = @_;
267 if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
268 return join(' || ', @what);
270 return 'CONCAT(' . join(',', @what) . ')' ;
276 my ($self, $query) = @_;
277 $self->debug($query, up => 1);
278 return $self->{conf}->{dbh}->prepare($query);
283 my ($self, $query) = @_;
284 $self->debug($query, up => 1);
285 return $self->{conf}->{dbh}->do($query);
288 sub dbh_selectall_arrayref
290 my ($self, $query) = @_;
291 $self->debug($query, up => 1);
292 return $self->{conf}->{dbh}->selectall_arrayref($query);
295 sub dbh_selectrow_arrayref
297 my ($self, $query) = @_;
298 $self->debug($query, up => 1);
299 return $self->{conf}->{dbh}->selectrow_arrayref($query);
305 return $self->{conf}->{dbh};
310 ################################################################
314 # my $pref = new Pref(config_file => 'brestore.conf');
315 # my $dlg = new DlgPref($pref);
316 # my $dlg_resto = new DlgResto($pref);
317 # $dlg->display($dlg_resto);
320 my ($class, $pref) = @_;
323 pref => $pref, # Pref ref
324 dlgresto => undef, # DlgResto ref
332 my ($self, $dlgresto) = @_ ;
334 unless ($self->{glade}) {
335 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
336 $self->{glade}->signal_autoconnect_from_package($self);
339 $self->{dlgresto} = $dlgresto;
341 my $g = $self->{glade};
342 my $p = $self->{pref};
344 for my $k (@{ $p->{entry_keyword} }) {
345 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
348 for my $k (@{ $p->{chk_keyword} }) {
349 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
352 $g->get_widget("dlg_pref")->show_all() ;
355 sub on_applybutton_clicked
358 my $glade = $self->{glade};
359 my $pref = $self->{pref};
361 for my $k (@{ $pref->{entry_keyword} }) {
362 my $w = $glade->get_widget("entry_$k") ;
363 $pref->{$k} = $w->get_text();
366 for my $k (@{ $pref->{chk_keyword} }) {
367 my $w = $glade->get_widget("chkbp_$k") ;
368 $pref->{$k} = $w->get_active();
371 if (!$pref->write_config() && $pref->connect_db()) {
372 $self->{dlgresto}->set_status('Preferences updated');
373 $self->{dlgresto}->init_server_backup_combobox();
374 $self->{dlgresto}->set_status($pref->{error});
377 $self->{dlgresto}->set_status($pref->{error});
381 # Handle prefs ok click (apply/dismiss dialog)
382 sub on_okbutton_clicked
385 $self->on_applybutton_clicked();
387 unless ($self->{pref}->{error}) {
388 $self->on_cancelbutton_clicked();
391 sub on_dialog_delete_event
394 $self->on_cancelbutton_clicked();
398 sub on_cancelbutton_clicked
401 $self->{glade}->get_widget('dlg_pref')->hide();
402 delete $self->{dlgresto};
406 ################################################################
408 package DlgFileVersion;
410 sub on_versions_close_clicked
412 my ($self, $widget)=@_;
413 $self->{version}->destroy();
416 sub on_selection_button_press_event
418 print STDERR "on_selection_button_press_event()\n";
421 sub fileview_data_get
423 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
425 DlgResto::drag_set_info($widget,
432 my ($class, $bvfs, $client, $path, $file, $cwd, $fn) = @_;
435 version => undef, # main window
438 # we load version widget of $glade_file
439 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
441 # Connect signals magically
442 $glade_box->signal_autoconnect_from_package($self);
444 $glade_box->get_widget("version_label")
445 ->set_markup("<b>File revisions : $client:$cwd$fn</b>");
447 my $widget = $glade_box->get_widget('version_fileview');
448 my $fileview = Gtk2::SimpleList->new_from_treeview(
450 'h_pathid' => 'hidden',
451 'h_filenameid' => 'hidden',
452 'h_name' => 'hidden',
453 'h_jobid' => 'hidden',
454 'h_type' => 'hidden',
456 'InChanger' => 'pixbuf',
463 DlgResto::init_drag_drop($fileview);
465 my @v = $bvfs->get_all_file_versions($path,
470 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
471 $inchanger,$md5,$volname) = @{$ver};
472 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
474 DlgResto::listview_push($fileview,$pid,$fid,
476 $icon, $volname, $jobid,DlgResto::human($size),
477 scalar(localtime($mtime)), $md5);
480 $self->{version} = $glade_box->get_widget('dlg_version');
481 $self->{version}->show();
486 sub on_forward_keypress
492 ################################################################
497 my ($package, $text) = @_;
501 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
503 # Connect signals magically
504 $glade->signal_autoconnect_from_package($self);
505 $glade->get_widget('label_warn')->set_text($text);
507 print STDERR "$text\n";
509 $self->{window} = $glade->get_widget('dlg_warn');
510 $self->{window}->show_all();
517 $self->{window}->destroy();
521 ################################################################
527 # %arg = (bsr_file => '/path/to/bsr', # on director
528 # volumes => [ '00001', '00004']
536 if ($pref->{bconsole} =~ /^http/) {
537 return new BwebConsole(pref => $pref);
539 if (eval { require Bconsole; }) {
540 return new Bconsole(pref => $pref);
542 new DlgWarn("Can't use bconsole, verify your setup");
550 my ($class, %arg) = @_;
553 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
554 pref => $arg{pref}, # Pref ref
555 glade => undef, # GladeXML ref
556 bconsole => undef, # Bconsole ref
559 my $console = $self->{bconsole} = get_bconsole($arg{pref});
564 # we load launch widget of $glade_file
565 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
568 # Connect signals magically
569 $glade->signal_autoconnect_from_package($self);
571 my $widget = $glade->get_widget('volumeview');
572 my $volview = Gtk2::SimpleList->new_from_treeview(
574 'InChanger' => 'pixbuf',
578 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
580 # we replace 0 and 1 by $noicon and $yesicon
581 for my $i (@{$infos}) {
583 $i->[0] = $DlgResto::noicon;
585 $i->[0] = $DlgResto::yesicon;
590 push @{ $volview->{data} }, @{$infos} ;
592 $console->prepare(qw/list_client list_job list_fileset list_storage/);
594 # fill client combobox (with director defined clients
595 my @clients = $console->list_client() ; # get from bconsole
596 if ($console->{error}) {
597 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
599 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
600 $self->{list_client} = DlgResto::init_combo($w, 'text');
601 DlgResto::fill_combo($self->{list_client},
602 $DlgResto::client_list_empty,
606 # fill fileset combobox
607 my @fileset = $console->list_fileset() ;
608 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
609 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
610 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
613 my @job = $console->list_job() ;
614 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
615 $self->{list_job} = DlgResto::init_combo($w, 'text');
616 DlgResto::fill_combo($self->{list_job}, '', @job);
618 # find default_restore_job in jobs list
619 my $default_restore_job = $arg{pref}->{default_restore_job} ;
623 if ($j =~ /$default_restore_job/io) {
629 $w->set_active($index);
631 # fill storage combobox
632 my @storage = $console->list_storage() ;
633 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
634 $self->{list_storage} = DlgResto::init_combo($w, 'text');
635 DlgResto::fill_combo($self->{list_storage}, '', @storage);
637 $glade->get_widget('dlg_launch')->show_all();
644 my ($self, $client, $jobid) = @_;
646 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
648 $self->on_cancel_resto_clicked();
651 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
652 "Your job have been submited to bacula.
653 To follow it, you must use bconsole (or install/configure bweb)");
659 sub on_cancel_resto_clicked
662 $self->{glade}->get_widget('dlg_launch')->destroy();
665 sub on_submit_resto_clicked
668 my $glade = $self->{glade};
670 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
673 new DlgWarn("Can't copy bsr file to director ($self->{error})");
677 my $fileset = $glade->get_widget('combo_launch_fileset')
680 my $storage = $glade->get_widget('combo_launch_storage')
683 my $where = $glade->get_widget('entry_launch_where')->get_text();
685 my $job = $glade->get_widget('combo_launch_job')
689 new DlgWarn("Can't use this job");
693 my $client = $glade->get_widget('combo_launch_client')
696 if (! $client or $client eq $DlgResto::client_list_empty) {
697 new DlgWarn("Can't use this client ($client)");
701 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
703 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
704 $replace=($replace)?'always':'never';
706 my $jobid = $self->{bconsole}->run(job => $job,
715 $self->show_job($client, $jobid);
718 sub on_combo_storage_button_press_event
721 print "on_combo_storage_button_press_event()\n";
724 sub on_combo_fileset_button_press_event
727 print "on_combo_fileset_button_press_event()\n";
731 sub on_combo_job_button_press_event
734 print "on_combo_job_button_press_event()\n";
737 sub get_volume_inchanger
739 my ($dbh, $vols) = @_;
741 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
743 my $rq = "SELECT InChanger, VolumeName
745 WHERE VolumeName IN ($lst)
748 my $res = $dbh->selectall_arrayref($rq);
749 return $res; # [ [ 1, VolName].. ]
753 use File::Copy qw/copy/;
754 use File::Basename qw/basename/;
756 # We must kown the path+filename destination
757 # $self->{error} contains error message
758 # it return 0/1 if fail/success
761 my ($self, $src, $dst) = @_ ;
762 print "$src => $dst\n"
773 if ($dst =~ m!file:/(/.+)!) {
774 $ret = copy($src, $1);
776 $dstfile = "$1/" . basename($src) ;
778 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
779 $err = `scp $src $1 2>&1` ;
781 $dstfile = "$2/" . basename($src) ;
785 $err = "$dst not implemented yet";
786 File::Copy::copy($src, \*STDOUT);
789 $self->{error} = $err;
792 $self->{error} = $err;
801 ################################################################
809 unless ($about_widget) {
810 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
811 $about_widget = $glade_box->get_widget("dlg_about") ;
812 $glade_box->signal_autoconnect_from_package('DlgAbout');
814 $about_widget->show() ;
817 sub on_about_okbutton_clicked
819 $about_widget->hide() ;
824 ################################################################
834 # Kept as is from the perl-gtk example. Draws the pretty icons
840 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
841 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
842 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
843 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
846 # init combo (and create ListStore object)
849 my ($widget, @type) = @_ ;
850 my %type_info = ('text' => 'Glib::String',
851 'markup' => 'Glib::String',
854 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
856 $widget->set_model($lst);
860 if ($t eq 'text' or $t eq 'markup') {
861 $cell = new Gtk2::CellRendererText();
863 $widget->pack_start($cell, 1);
864 $widget->add_attribute($cell, $t, $i++);
869 # fill simple combo (one element per row)
872 my ($list, @what) = @_;
876 foreach my $w (@what)
879 my $i = $list->append();
880 $list->set($i, 0, $w);
887 my @unit = qw(b Kb Mb Gb Tb);
890 my $format = '%i %s';
891 while ($val / 1024 > 1) {
895 $format = ($i>0)?'%0.1f %s':'%i %s';
896 return sprintf($format, $val, $unit[$i]);
899 sub get_wanted_job_status
906 return "'T', 'A', 'E'";
910 # This sub gives a full list of the EndTimes for a ClientId
911 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
912 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
913 sub get_all_endtimes_for_job
915 my ($self, $client, $ok_only)=@_;
916 my $status = get_wanted_job_status($ok_only);
918 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
919 FROM Job,Client,FileSet
920 WHERE Job.ClientId=Client.ClientId
921 AND Client.Name = '$client'
923 AND JobStatus IN ($status)
924 AND Job.FileSetId = FileSet.FileSetId
925 ORDER BY EndTime desc";
926 my $result = $self->dbh_selectall_arrayref($query);
933 my ($fileview) = shift;
934 my $fileview_target_entry = {target => 'STRING',
935 flags => ['GTK_TARGET_SAME_APP'],
938 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
939 ['copy'],$fileview_target_entry);
940 $fileview->get_selection->set_mode('multiple');
942 # set some useful SimpleList properties
943 $fileview->set_headers_clickable(0);
944 foreach ($fileview->get_columns())
946 $_->set_resizable(1);
947 $_->set_sizing('grow-only');
953 my ($class, $pref) = @_;
958 location => undef, # location entry widget
959 mainwin => undef, # mainwin widget
960 filelist_file_menu => undef, # file menu widget
961 filelist_dir_menu => undef, # dir menu widget
962 glade => undef, # glade object
963 status => undef, # status bar widget
964 dlg_pref => undef, # DlgPref object
965 fileattrib => {}, # cache file
966 fileview => undef, # fileview widget SimpleList
967 fileinfo => undef, # fileinfo widget SimpleList
969 client_combobox => undef, # client_combobox widget
970 restore_backup_combobox => undef, # date combobox widget
971 list_client => undef, # Gtk2::ListStore
972 list_backup => undef, # Gtk2::ListStore
973 cache_ppathid => {}, #
977 $self->{bvfs} = new Bvfs(conf => $pref);
979 # load menu (to use handler with self reference)
980 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
981 $glade->signal_autoconnect_from_package($self);
982 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
984 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
985 $glade->signal_autoconnect_from_package($self);
986 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
988 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
989 $glade->signal_autoconnect_from_package($self);
991 $self->{status} = $glade->get_widget('statusbar');
992 $self->{mainwin} = $glade->get_widget('dlg_resto');
993 $self->{location} = $glade->get_widget('entry_location');
994 $self->render_icons();
996 $self->{dlg_pref} = new DlgPref($pref);
998 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
999 $self->{list_client} = init_combo($c, 'text');
1001 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1002 $self->{list_backup} = init_combo($c, 'text', 'markup');
1004 # Connect glade-fileview to Gtk2::SimpleList
1005 # and set up drag n drop between $fileview and $restore_list
1007 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1008 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1010 my $widget = $glade->get_widget('fileview');
1011 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1013 'h_pathid' => 'hidden',
1014 'h_filenameid' => 'hidden',
1015 'h_name' => 'hidden',
1016 'h_jobid' => 'hidden',
1017 'h_type' => 'hidden',
1020 'File Name' => 'text',
1023 init_drag_drop($fileview);
1024 $fileview->set_search_column(6); # search on File Name
1026 # Connect glade-restore_list to Gtk2::SimpleList
1027 $widget = $glade->get_widget('restorelist');
1028 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1030 'h_pathid' => 'hidden', #0
1031 'h_filenameid' => 'hidden',
1032 'h_name' => 'hidden',
1033 'h_jobid' => 'hidden',
1034 'h_type' => 'hidden',
1035 'h_curjobid' => 'hidden', #5
1038 'File Name' => 'text',
1040 'FileIndex' => 'text',
1042 'Nb Files' => 'text', #10
1043 'Size' => 'text', #11
1044 'size_b' => 'hidden', #12
1047 my @restore_list_target_table = ({'target' => 'STRING',
1051 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1052 $restore_list->get_selection->set_mode('multiple');
1054 $widget = $glade->get_widget('infoview');
1055 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1057 'h_pathid' => 'hidden',
1058 'h_filenameid' => 'hidden',
1059 'h_name' => 'hidden',
1060 'h_jobid' => 'hidden',
1061 'h_type' => 'hidden',
1063 'InChanger' => 'pixbuf',
1070 init_drag_drop($infoview);
1072 $pref->connect_db() || $self->{dlg_pref}->display($self);
1075 $self->init_server_backup_combobox();
1076 $self->{bvfs}->create_brestore_tables();
1079 $self->set_status($pref->{error});
1082 # set status bar informations
1085 my ($self, $string) = @_;
1086 return unless ($string);
1088 my $context = $self->{status}->get_context_id('Main');
1089 $self->{status}->push($context, $string);
1092 sub on_time_select_changed
1100 my $c = $self->{glade}->get_widget('combo_time');
1101 return $c->get_active_text;
1104 # This sub returns all clients declared in DB
1108 my $query = "SELECT Name FROM Client ORDER BY Name";
1109 print STDERR $query,"\n" if $debug;
1111 my $result = $dbh->selectall_arrayref($query);
1113 return map { $_->[0] } @$result;
1116 # init infoview widget
1120 @{$self->{fileinfo}->{data}} = ();
1124 sub on_clear_clicked
1127 @{$self->{restore_list}->{data}} = ();
1130 sub on_estimate_clicked
1137 # TODO : If we get here, things could get lenghty ... draw a popup window .
1138 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1139 'destroy-with-parent',
1141 'Computing size...');
1145 my $title = "Computing size...\n";
1148 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1149 # undef, undef, undef, $dirfileindex);
1150 foreach my $entry (@{$self->{restore_list}->{data}})
1152 unless ($entry->[11]) {
1153 my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1154 $entry->[12] = $size;
1155 $entry->[11] = human($size);
1159 my $name = unpack('u', $entry->[2]);
1161 $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1162 $self->debug($title . $txt);
1163 $widget->set_markup($title . $txt);
1165 $size_total+=$entry->[12];
1166 $nb_total+=$entry->[10];
1170 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1171 $widget->set_markup("Size estimation :\n" . $txt);
1172 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1179 sub on_gen_bsr_clicked
1183 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1184 'gtk-save','ok', 'gtk-cancel', 'cancel');
1187 my $w = new Gtk2::FileChooserDialog ( @options );
1192 if ($a eq 'cancel') {
1197 my $f = $w->get_filename();
1199 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1200 'destroy-with-parent',
1201 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1202 if ($dlg->run() eq 'ok') {
1216 if (open(FP, ">$save")) {
1217 my $bsr = $self->create_filelist();
1220 $self->set_status("Dumping BSR to $save ok");
1222 $self->set_status("Can't dump BSR to $save: $!");
1228 use File::Temp qw/tempfile/;
1230 sub on_go_button_clicked
1233 unless (scalar(@{$self->{restore_list}->{data}})) {
1234 new DlgWarn("No file to restore");
1237 my $bsr = $self->create_filelist();
1238 my ($fh, $filename) = tempfile();
1241 chmod(0644, $filename);
1243 print "Dumping BSR info to $filename\n"
1246 # we get Volume list
1247 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1248 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1250 new DlgLaunch(pref => $self->{conf},
1252 bsr_file => $filename,
1258 our $client_list_empty = 'Clients list';
1259 our %type_markup = ('F' => '<b>$label F</b>',
1262 'B' => '<b>$label B</b>',
1264 'A' => '<span foreground=\"red\">$label</span>',
1266 'E' => '<span foreground=\"red\">$label</span>',
1269 sub on_list_client_changed
1271 my ($self, $widget) = @_;
1272 return 0 unless defined $self->{fileview};
1274 $self->{list_backup}->clear();
1276 if ($self->current_client eq $client_list_empty) {
1280 $self->{CurrentJobIds} = [
1281 set_job_ids_for_date($self->dbh(),
1282 $self->current_client,
1283 $self->current_date,
1284 $self->{conf}->{use_ok_bkp_only})
1287 my $fs = $self->{bvfs};
1288 $fs->set_curjobids(@{$self->{CurrentJobIds}});
1289 $fs->ch_dir($fs->get_root());
1290 # refresh_fileview will be done by list_backup_changed
1293 my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1294 $self->{conf}->{use_ok_bkp_only});
1296 foreach my $endtime (@endtimes)
1298 my $i = $self->{list_backup}->append();
1300 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1301 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1302 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1304 $self->{list_backup}->set($i,
1309 $self->{restore_backup_combobox}->set_active(0);
1314 sub fill_server_list
1316 my ($dbh, $combo, $list) = @_;
1318 my @clients=get_all_clients($dbh);
1322 my $i = $list->append();
1323 $list->set($i, 0, $client_list_empty);
1325 foreach my $client (@clients)
1327 $i = $list->append();
1328 $list->set($i, 0, $client);
1330 $combo->set_active(0);
1333 sub init_server_backup_combobox
1336 fill_server_list($self->{conf}->{dbh},
1337 $self->{client_combobox},
1338 $self->{list_client}) ;
1341 #----------------------------------------------------------------------
1342 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1343 #data isn't. There is additionnal complexity for dirs (visibility problems),
1344 #so the @CurrentJobIds is not sufficient.
1345 sub refresh_fileview
1348 my $fileview = $self->{fileview};
1349 my $client_combobox = $self->{client_combobox};
1350 my $bvfs = $self->{bvfs};
1352 @{$fileview->{data}} = ();
1354 $self->clear_infoview();
1356 my $client_name = $self->current_client;
1358 if (!$client_name or ($client_name eq $client_list_empty)) {
1359 $self->set_status("Client list empty");
1363 # [ [dirid, dir_basename, File.LStat, jobid]..]
1364 my $list_dirs = $bvfs->ls_dirs();
1365 # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1366 my $files = $bvfs->ls_files();
1368 my $file_count = 0 ;
1369 my $total_bytes = 0;
1371 # Add directories to view
1372 foreach my $dir_entry (@$list_dirs) {
1373 my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1374 $total_bytes += 4096;
1377 listview_push($fileview,
1381 # TODO: voir ce que l'on met la
1392 foreach my $file (@$files)
1394 my $size = Bvfs::file_attrib($file,'st_size');
1395 my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1396 $total_bytes += $size;
1398 # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1399 listview_push($fileview,
1408 human($size), $time);
1411 $self->set_status("$file_count files/" . human($total_bytes));
1412 $self->{cwd} = $self->{bvfs}->pwd();
1413 $self->{location}->set_text($self->{cwd});
1414 # set a decent default selection (makes keyboard nav easy)
1415 $fileview->select(0);
1419 sub on_about_activate
1421 DlgAbout::display();
1426 my ($tree, $path, $data) = @_;
1428 my @items = listview_get_all($tree) ;
1430 foreach my $i (@items)
1432 my @file_info = @{$i};
1435 # Ok, we have a corner case :
1437 my $file = pack("u", $path . $file_info[2]);
1439 push @ret, join(" ; ", $file,
1440 $file_info[0], # $pathid
1441 $file_info[1], # $filenameid
1442 $file_info[3], # $jobid
1443 $file_info[4], # $type
1447 my $data_get = join(" :: ", @ret);
1449 $data->set_text($data_get,-1);
1454 sub fileview_data_get
1456 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1457 drag_set_info($widget, $self->{cwd}, $data);
1460 sub fileinfo_data_get
1462 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1463 drag_set_info($widget, $self->{cwd}, $data);
1466 sub restore_list_data_received
1468 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1470 $self->debug("start\n");
1471 if ($info eq 40 || $info eq 0) # patch for display!=:0
1473 foreach my $elt (split(/ :: /, $data->data()))
1475 my ($file, $pathid, $filenameid, $jobid, $type) =
1477 $file = unpack("u", $file);
1479 $self->add_selected_file_to_list($pathid,$filenameid,
1480 $file, $jobid, $type);
1483 $self->debug("end\n");
1486 sub on_back_button_clicked {
1488 $self->{bvfs}->up_dir();
1489 $self->refresh_fileview();
1491 sub on_location_go_button_clicked
1494 $self->ch_dir($self->{location}->get_text());
1496 sub on_quit_activate {Gtk2->main_quit;}
1497 sub on_preferences_activate
1500 $self->{dlg_pref}->display($self) ;
1502 sub on_main_delete_event {Gtk2->main_quit;}
1503 sub on_bweb_activate
1506 $self->set_status("Open bweb on your browser");
1507 $self->{conf}->go_bweb('', "go on bweb");
1510 # Change the current working directory
1511 # * Updates fileview, location, and selection
1517 my $p = $self->{bvfs}->get_pathid($l);
1519 $self->{bvfs}->ch_dir($p);
1520 $self->refresh_fileview();
1522 $self->set_status("Can't find $l");
1527 # Handle dialog 'close' (window-decoration induced close)
1528 # * Just hide the dialog, and tell Gtk not to do anything else
1532 my ($self, $w) = @_;
1535 1; # consume this event!
1538 # Handle key presses in location text edit control
1539 # * Translate a Return/Enter key into a 'Go' command
1540 # * All other key presses left for GTK
1542 sub on_location_entry_key_release_event
1548 my $keypress = $event->keyval;
1549 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1550 $keypress == $Gtk2::Gdk::Keysyms{Return})
1552 $self->ch_dir($widget->get_text());
1554 return 1; # consume keypress
1557 return 0; # let gtk have the keypress
1560 sub on_fileview_key_press_event
1562 my ($self, $widget, $event) = @_;
1566 sub listview_get_first
1569 my @selected = $list->get_selected_indices();
1570 if (@selected > 0) {
1571 my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1572 return ($pid,$fid,unpack('u', $name), @other);
1578 sub listview_get_all
1582 my @selected = $list->get_selected_indices();
1584 for my $i (@selected) {
1585 my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1586 push @ret, [$pid,$fid,unpack('u', $name), @other];
1593 my ($list, $pid, $fid, $name, @other) = @_;
1594 push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1597 #-----------------------------------------------------------------
1598 # Handle keypress in file-view
1599 # * Translates backspace into a 'cd ..' command
1600 # * All other key presses left for GTK
1602 sub on_fileview_key_release_event
1604 my ($self, $widget, $event) = @_;
1605 if (not $event->keyval)
1609 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1610 $self->on_back_button_clicked();
1611 return 1; # eat keypress
1614 return 0; # let gtk have keypress
1617 sub on_forward_keypress
1622 #-------------------------------------------------------------------
1623 # Handle double-click (or enter) on file-view
1624 # * Translates into a 'cd <dir>' command
1626 sub on_fileview_row_activated
1628 my ($self, $widget) = @_;
1630 my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1634 $self->{bvfs}->ch_dir($pid);
1635 $self->refresh_fileview();
1637 $self->fill_infoview($pid,$fid,$name);
1640 return 1; # consume event
1645 my ($self, $path, $file, $fn) = @_;
1646 $self->clear_infoview();
1647 my @v = $self->{bvfs}->get_all_file_versions($path,
1649 $self->current_client,
1650 $self->{conf}->{see_all_versions});
1652 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1653 $size,$inchanger,$md5,$volname) = @{$ver};
1654 my $icon = ($inchanger)?$yesicon:$noicon;
1656 $mtime = localtime($mtime) ;
1658 listview_push($self->{fileinfo},$pid,$fid,
1659 $fn, $jobid, 'file',
1660 $icon, $volname, $jobid, human($size), $mtime, $md5);
1667 return $self->{restore_backup_combobox}->get_active_text;
1673 return $self->{client_combobox}->get_active_text;
1676 sub on_list_backups_changed
1678 my ($self, $widget) = @_;
1679 return 0 unless defined $self->{fileview};
1681 $self->{CurrentJobIds} = [
1682 set_job_ids_for_date($self->dbh(),
1683 $self->current_client,
1684 $self->current_date,
1685 $self->{conf}->{use_ok_bkp_only})
1687 $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1688 $self->refresh_fileview();
1692 sub on_restore_list_keypress
1694 my ($self, $widget, $event) = @_;
1695 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1697 my @sel = $widget->get_selected_indices;
1698 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1700 splice @{$self->{restore_list}->{data}},$elt,1;
1705 sub on_fileview_button_press_event
1707 my ($self,$widget,$event) = @_;
1708 if ($event->button == 3)
1710 $self->on_right_click_filelist($widget,$event);
1714 if ($event->button == 2)
1716 $self->on_see_all_version();
1723 sub on_see_all_version
1727 my @lst = listview_get_all($self->{fileview});
1730 my ($pid,$fid,$name, undef) = @{$i};
1732 new DlgFileVersion($self->{bvfs},
1733 $self->current_client,
1734 $pid,$fid,$self->{cwd},$name);
1738 sub on_right_click_filelist
1740 my ($self,$widget,$event) = @_;
1741 # I need to know what's selected
1742 my @sel = listview_get_all($self->{fileview});
1747 $type = $sel[0]->[4]; # $type
1752 if (@sel >=2 or $type eq 'dir')
1754 # We have selected more than one or it is a directories
1755 $w = $self->{filelist_dir_menu};
1759 $w = $self->{filelist_file_menu};
1765 $event->button, $event->time);
1768 sub context_add_to_filelist
1772 my @sel = listview_get_all($self->{fileview});
1774 foreach my $i (@sel)
1776 my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1777 $file = $self->{cwd} . '/' . $file;
1778 $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1782 # Adds a file to the filelist
1783 sub add_selected_file_to_list
1785 my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1787 my $restore_list = $self->{restore_list};
1789 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1796 if ($name and substr $name,-1 ne '/')
1798 $name .= '/'; # For bacula
1800 my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1801 listview_push($restore_list,$pid,0,
1802 $name, $jobid, 'dir', $curjobids,
1803 $diricon, $name,$curjobids,$dirfileindex);
1805 elsif ($type eq 'file')
1807 my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1809 listview_push($restore_list,$pid,$fid,
1810 $name, $jobid, 'file', $curjobids,
1811 $fileicon, $name, $jobid, $fileindex );
1815 # TODO : we want be able to restore files from a bad ended backup
1816 # we have JobStatus IN ('T', 'A', 'E') and we must
1818 # Data acces subs from here. Interaction with SGBD and caching
1820 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1821 # GUI and stores them in @CurrentJobIds
1822 sub set_job_ids_for_date
1824 my ($dbh, $client, $date, $only_ok)=@_;
1826 if (!$client or !$date) {
1830 my $status = get_wanted_job_status($only_ok);
1832 # The algorithm : for a client, we get all the backups for each
1833 # fileset, in reverse order Then, for each fileset, we store the 'good'
1834 # incrementals and differentials until we have found a full so it goes
1835 # like this : store all incrementals until we have found a differential
1836 # or a full, then find the full #
1838 my $query = "SELECT JobId, FileSet, Level, JobStatus
1839 FROM Job, Client, FileSet
1840 WHERE Job.ClientId = Client.ClientId
1841 AND FileSet.FileSetId = Job.FileSetId
1842 AND EndTime <= '$date'
1843 AND Client.Name = '$client'
1845 AND JobStatus IN ($status)
1846 ORDER BY FileSet, JobTDate DESC";
1849 my $result = $dbh->selectall_arrayref($query);
1851 foreach my $refrow (@$result)
1853 my $jobid = $refrow->[0];
1854 my $fileset = $refrow->[1];
1855 my $level = $refrow->[2];
1857 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1859 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1863 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1864 push @CurrentJobIds,($jobid);
1866 elsif ($level eq 'D')
1868 next if $progress{$fileset} eq 'D'; # We allready have a differential
1869 push @CurrentJobIds,($jobid);
1871 elsif ($level eq 'F')
1873 push @CurrentJobIds,($jobid);
1876 my $status = $refrow->[3] ;
1877 if ($status eq 'T') { # good end of job
1878 $progress{$fileset} = $level;
1882 return @CurrentJobIds;
1887 Gtk2->main_iteration while (Gtk2->events_pending);
1890 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1891 # This sub creates a BSR from the information in the restore_list
1892 # Returns the BSR as a string
1897 # This query gets all jobid/jobmedia/media combination.
1899 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
1900 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1901 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
1902 Media.Volumename, Media.MediaType
1903 FROM Job, JobMedia, Media
1904 WHERE Job.JobId = JobMedia.JobId
1905 AND JobMedia.MediaId = Media.MediaId
1906 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1909 my $result = $self->dbh_selectall_arrayref($query);
1911 # We will store everything hashed by jobid.
1913 foreach my $refrow (@$result)
1915 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1916 $firstindex, $lastindex, $startblock, $endblock,
1917 $volindex, $volumename, $mediatype) = @{$refrow};
1919 # We just have to deal with the case where starfile != endfile
1920 # In this case, we concatenate both, for the bsr
1921 if ($startfile != $endfile) {
1922 $startfile = $startfile . '-' . $endfile;
1926 ($jobid, $volsessionid, $volsessiontime, $startfile,
1927 $firstindex, $lastindex, $startblock .'-'. $endblock,
1928 $volindex, $volumename, $mediatype);
1930 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
1934 # reminder : restore_list looks like this :
1935 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1936 # undef, undef, undef, $dirfileindex);
1938 # Here, we retrieve every file/dir that could be in the restore
1939 # We do as simple as possible for the SQL engine (no crazy joins,
1940 # no pseudo join (>= FirstIndex ...), etc ...
1941 # We do a SQL union of all the files/dirs specified in the restore_list
1943 foreach my $entry (@{$self->{restore_list}->{data}})
1945 if ($entry->[4] eq 'dir')
1947 my $dirid = $entry->[0];
1948 my $inclause = $entry->[5]; #curjobids
1951 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1952 FROM File, Path, Filename
1953 WHERE Path.PathId = File.PathId
1954 AND File.FilenameId = Filename.FilenameId
1956 (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
1957 WHERE PathId IN ($dirid)
1959 SELECT " . $self->dbh_strcat('Path',"'\%'") ." FROM brestore_missing_path WHERE PathId IN ($dirid)
1961 AND File.JobId IN ($inclause) )";
1962 push @select_queries,($query);
1966 # It's a file. Great, we allready have most
1967 # of what is needed. Simple and efficient query
1968 my $dir = $entry->[0];
1969 my $file = $entry->[1];
1971 my $jobid = $entry->[3];
1972 my $fileindex = $entry->[9];
1973 my $inclause = $entry->[5]; # curjobids
1975 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1976 FROM File,Path,Filename
1977 WHERE File.PathId = $dir
1978 AND File.PathId = Path.PathId
1979 AND File.FilenameId = $file
1980 AND File.FilenameId = Filename.FilenameId
1981 AND File.JobId = $jobid
1984 push @select_queries,($query);
1987 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
1989 #Now we run the query and parse the result...
1990 # there may be a lot of records, so we better be efficient
1991 # We use the bind column method, working with references...
1993 my $sth = $self->dbh_prepare($query);
1996 my ($path,$name,$fileindex,$jobid);
1997 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
1999 # The temp place we're going to save all file
2000 # list to before the real list
2004 while ($sth->fetchrow_arrayref())
2006 # This may look dumb, but we're going to do a join by ourselves,
2007 # to save memory and avoid sending a complex query to mysql
2008 my $complete_path = $path . $name;
2016 # Remove trailing slash (normalize file and dir name)
2017 $complete_path =~ s/\/$//;
2019 # Let's find the ref(s) for the %mediainfo element(s)
2020 # containing the data for this file
2021 # There can be several matches. It is the pseudo join.
2023 my $max_elt=@{$mediainfos{$jobid}}-1;
2025 while($med_idx <= $max_elt)
2027 my $ref = $mediainfos{$jobid}->[$med_idx];
2028 # First, can we get rid of the first elements of the
2029 # array ? (if they don't contain valuable records
2031 if ($fileindex > $ref->[5])
2033 # It seems we don't need anymore
2034 # this entry in %mediainfo (the input data
2037 shift @{$mediainfos{$jobid}};
2041 # We will do work on this elt. We can ++
2042 # $med_idx for next loop
2045 # %mediainfo row looks like :
2046 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2047 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2050 # We are in range. We store and continue looping
2052 if ($fileindex >= $ref->[4])
2054 my @data = ($complete_path,$is_dir,
2056 push @temp_list,(\@data);
2060 # We are not in range. No point in continuing looping
2061 # We go to next record.
2065 # Now we have the array.
2066 # We're going to sort it, by
2067 # path, volsessiontime DESC (get the most recent file...)
2068 # The array rows look like this :
2069 # complete_path,is_dir,fileindex,
2070 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2071 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2072 @temp_list = sort {$a->[0] cmp $b->[0]
2073 || $b->[3]->[2] <=> $a->[3]->[2]
2077 my $prev_complete_path='////'; # Sure not to match
2081 while (my $refrow = shift @temp_list)
2083 # For the sake of readability, we load $refrow
2084 # contents in real scalars
2085 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2086 my $jobid= $refother->[0]; # We don't need the rest...
2088 # We skip this entry.
2089 # We allready have a newer one and this
2090 # isn't a continuation of the same file
2091 next if ($complete_path eq $prev_complete_path
2092 and $jobid != $prev_jobid);
2096 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2098 # We would be recursing inside a file.
2099 # Just what we don't want (dir replaced by file
2100 # between two backups
2106 push @restore_list,($refrow);
2108 $prev_complete_path = $complete_path;
2109 $prev_jobid = $jobid;
2115 push @restore_list,($refrow);
2117 $prev_complete_path = $complete_path;
2118 $prev_jobid = $jobid;
2122 # We get rid of @temp_list... save memory
2125 # Ok everything is in the list. Let's sort it again in another way.
2126 # This time it will be in the bsr file order
2128 # we sort the results by
2129 # volsessiontime, volsessionid, volindex, fileindex
2130 # to get all files in right order...
2131 # Reminder : The array rows look like this :
2132 # complete_path,is_dir,fileindex,
2133 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2134 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2136 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2137 || $a->[3]->[1] <=> $b->[3]->[1]
2138 || $a->[3]->[7] <=> $b->[3]->[7]
2139 || $a->[2] <=> $b->[2] }
2142 # Now that everything is ready, we create the bsr
2143 my $prev_fileindex=-1;
2144 my $prev_volsessionid=-1;
2145 my $prev_volsessiontime=-1;
2146 my $prev_volumename=-1;
2147 my $prev_volfile=-1;
2151 my $first_of_current_range=0;
2152 my @fileindex_ranges;
2155 foreach my $refrow (@restore_list)
2157 my (undef,undef,$fileindex,$refother)=@{$refrow};
2158 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2159 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2161 # We can specifiy the number of files in each section of the
2162 # bsr to speedup restore (bacula can then jump over the
2163 # end of tape files.
2167 if ($prev_volumename eq '-1')
2169 # We only have to start the new range...
2170 $first_of_current_range=$fileindex;
2172 elsif ($prev_volsessionid != $volsessionid
2173 or $prev_volsessiontime != $volsessiontime
2174 or $prev_volumename ne $volumename
2175 or $prev_volfile ne $volfile)
2177 # We have to create a new section in the bsr...
2178 # We print the previous one ...
2179 # (before that, save the current range ...)
2180 if ($first_of_current_range != $prev_fileindex)
2183 push @fileindex_ranges,
2184 ("$first_of_current_range-$prev_fileindex");
2188 # We are out of a range,
2189 # but there is only one element in the range
2190 push @fileindex_ranges,
2191 ("$first_of_current_range");
2194 $bsr.=print_bsr_section(\@fileindex_ranges,
2196 $prev_volsessiontime,
2203 # Reset for next loop
2204 @fileindex_ranges=();
2205 $first_of_current_range=$fileindex;
2207 elsif ($fileindex-1 != $prev_fileindex)
2209 # End of a range of fileindexes
2210 if ($first_of_current_range != $prev_fileindex)
2213 push @fileindex_ranges,
2214 ("$first_of_current_range-$prev_fileindex");
2218 # We are out of a range,
2219 # but there is only one element in the range
2220 push @fileindex_ranges,
2221 ("$first_of_current_range");
2223 $first_of_current_range=$fileindex;
2225 $prev_fileindex=$fileindex;
2226 $prev_volsessionid = $volsessionid;
2227 $prev_volsessiontime = $volsessiontime;
2228 $prev_volumename = $volumename;
2229 $prev_volfile=$volfile;
2230 $prev_mediatype=$mediatype;
2231 $prev_volblocks=$volblocks;
2235 # Ok, we're out of the loop. Alas, there's still the last record ...
2236 if ($first_of_current_range != $prev_fileindex)
2239 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2244 # We are out of a range,
2245 # but there is only one element in the range
2246 push @fileindex_ranges,("$first_of_current_range");
2249 $bsr.=print_bsr_section(\@fileindex_ranges,
2251 $prev_volsessiontime,
2261 sub print_bsr_section
2263 my ($ref_fileindex_ranges,$volsessionid,
2264 $volsessiontime,$volumename,$volfile,
2265 $mediatype,$volblocks,$count)=@_;
2268 $bsr .= "Volume=\"$volumename\"\n";
2269 $bsr .= "MediaType=\"$mediatype\"\n";
2270 $bsr .= "VolSessionId=$volsessionid\n";
2271 $bsr .= "VolSessionTime=$volsessiontime\n";
2272 $bsr .= "VolFile=$volfile\n";
2273 $bsr .= "VolBlock=$volblocks\n";
2275 foreach my $range (@{$ref_fileindex_ranges})
2277 $bsr .= "FileIndex=$range\n";
2280 $bsr .= "Count=$count\n";
2286 ################################################################
2293 my ($self, $dir) = @_;
2295 "SELECT PathId FROM Path WHERE Path = ?
2297 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2298 my $sth = $self->dbh_prepare($query);
2299 $sth->execute($dir,$dir);
2300 my $result = $sth->fetchall_arrayref();
2303 return join(',', map { $_->[0] } @$result);
2311 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
2312 my $jobs = $self->dbh_selectall_arrayref($query);
2314 $self->update_brestore_table(map { $_->[0] } @$jobs);
2319 my ($self, $dir) = @_;
2320 return $self->get_pathid('');
2325 my ($self, $pathid) = @_;
2326 $self->{cwd} = $pathid;
2333 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) ";
2335 my $all = $self->dbh_selectall_arrayref($query);
2336 return unless ($all); # already at root
2338 my $dir = join(',', map { $_->[0] } @$all);
2340 $self->{cwd} = $dir;
2347 return $self->get_path($self->{cwd});
2352 my ($self, $pathid) = @_;
2353 $self->debug("Call with pathid = $pathid");
2355 "SELECT Path FROM Path WHERE PathId IN (?)
2357 SELECT Path FROM brestore_missing_path WHERE PathId IN (?)";
2358 my $sth = $self->dbh_prepare($query);
2359 $sth->execute($pathid,$pathid);
2360 my $result = $sth->fetchrow_arrayref();
2362 return $result->[0];
2367 my ($self, @jobids) = @_;
2368 $self->{curjobids} = join(',', @jobids);
2369 $self->update_brestore_table(@jobids);
2376 return undef unless ($self->{curjobids});
2378 my $inclause = $self->{curjobids};
2379 my $inlistpath = $self->{cwd};
2382 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2384 (SELECT Filename.Name, max(File.FileId) as id
2386 WHERE File.FilenameId = Filename.FilenameId
2387 AND Filename.Name != ''
2388 AND File.PathId IN ($inlistpath)
2389 AND File.JobId IN ($inclause)
2390 GROUP BY Filename.Name
2391 ORDER BY Filename.Name) AS listfiles,
2393 WHERE File.FileId = listfiles.id";
2395 $self->debug($query);
2396 my $result = $self->dbh_selectall_arrayref($query);
2397 $self->debug($result);
2402 # return ($dirid,$dir_basename,$lstat,$jobid)
2407 return undef unless ($self->{curjobids});
2409 my $pathid = $self->{cwd};
2410 my $jobclause = $self->{curjobids};
2412 # Let's retrieve the list of the visible dirs in this dir ...
2413 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2414 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2415 my $sth = $self->dbh_prepare($query);
2417 my $result = $sth->fetchrow_arrayref();
2419 my $dir_filenameid = $result->[0];
2421 # Then we get all the dir entries from File ...
2422 # It's ugly because there are records in brestore_missing_path ...
2424 SELECT PathId, Path, JobId, Lstat FROM (
2426 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2427 listfile1.JobId, listfile1.Lstat
2429 SELECT DISTINCT brestore_pathhierarchy1.PathId
2430 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2432 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2433 JOIN brestore_pathvisibility AS brestore_pathvisibility1
2434 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2435 WHERE brestore_pathhierarchy1.PPathId = $pathid
2436 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2437 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2439 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2440 WHERE File1.FilenameId = $dir_filenameid
2441 AND File1.JobId IN ($jobclause)) AS listfile1
2442 ON (listpath1.PathId = listfile1.PathId)
2444 SELECT brestore_missing_path1.PathId, brestore_missing_path1.Path,
2445 lower(brestore_missing_path1.Path), listfile2.JobId, listfile2.Lstat
2447 SELECT DISTINCT brestore_pathhierarchy2.PathId
2448 FROM brestore_pathhierarchy AS brestore_pathhierarchy2
2449 JOIN brestore_missing_path AS brestore_missing_path2
2450 ON (brestore_pathhierarchy2.PathId = brestore_missing_path2.PathId)
2451 JOIN brestore_pathvisibility AS brestore_pathvisibility2
2452 ON (brestore_pathhierarchy2.PathId = brestore_pathvisibility2.PathId)
2453 WHERE brestore_pathhierarchy2.PPathId = $pathid
2454 AND brestore_pathvisibility2.jobid IN ($jobclause)) AS listpath2
2455 JOIN brestore_missing_path AS brestore_missing_path1 ON (listpath2.PathId = brestore_missing_path1.PathId)
2457 SELECT File2.PathId, File2.JobId, File2.Lstat FROM File AS File2
2458 WHERE File2.FilenameId = $dir_filenameid
2459 AND File2.JobId IN ($jobclause)) AS listfile2
2460 ON (listpath2.PathId = listfile2.PathId)
2461 ) AS A ORDER BY 2,3 DESC
2463 $self->debug($query);
2464 $sth=$self->dbh_prepare($query);
2466 $result = $sth->fetchall_arrayref();
2469 foreach my $refrow (@{$result})
2471 my $dirid = $refrow->[0];
2472 my $dir = $refrow->[1];
2473 my $lstat = $refrow->[3];
2474 my $jobid = $refrow->[2] || 0;
2475 next if ($dirid eq $prev_dir);
2476 # We have to clean up this dirname ... we only want it's 'basename'
2480 my @temp = split ('/',$dir);
2481 $return_value = pop @temp;
2485 $return_value = '/';
2487 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2488 push @return_list,(\@return_array);
2491 $self->debug(\@return_list);
2492 return \@return_list;
2495 # Returns the list of media required for a list of jobids.
2496 # Input : self, jobid1, jobid2...
2497 # Output : reference to array of (joibd, inchanger)
2498 sub get_required_media_from_jobid
2500 my ($self, @jobids)=@_;
2501 my $inclause = join(',',@jobids);
2503 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2504 FROM JobMedia, Media
2505 WHERE JobMedia.MediaId=Media.MediaId
2506 AND JobId In ($inclause)
2508 my $result = $self->dbh_selectall_arrayref($query);
2512 # Returns the fileindex from dirname and jobid.
2513 # Input : self, dirid, jobid
2514 # Output : fileindex
2515 sub get_fileindex_from_dir_jobid
2517 my ($self, $dirid, $jobid)=@_;
2519 $query = "SELECT File.FileIndex
2521 WHERE File.FilenameId = Filename.FilenameId
2522 AND File.PathId = $dirid
2523 AND Filename.Name = ''
2524 AND File.JobId = '$jobid'
2527 $self->debug($query);
2528 my $result = $self->dbh_selectall_arrayref($query);
2529 return $result->[0]->[0];
2532 # Returns the fileindex from filename and jobid.
2533 # Input : self, dirid, filenameid, jobid
2534 # Output : fileindex
2535 sub get_fileindex_from_file_jobid
2537 my ($self, $dirid, $filenameid, $jobid)=@_;
2541 "SELECT File.FileIndex
2543 WHERE File.PathId = $dirid
2544 AND File.FilenameId = $filenameid
2545 AND File.JobId = $jobid";
2547 $self->debug($query);
2548 my $result = $self->dbh_selectall_arrayref($query);
2549 return $result->[0]->[0];
2552 # This function estimates the size to be restored for an entry of the restore
2554 # In : self,reference to the entry
2555 # Out : size in bytes, number of files
2556 sub estimate_restore_size
2558 # reminder : restore_list looks like this :
2559 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2560 # undef, undef, undef, $dirfileindex);
2561 my ($self, $entry, $refresh) = @_;
2563 if ($entry->[4] eq 'dir')
2565 my $dir = $entry->[0];
2567 my $inclause = $entry->[5]; #curjobids
2569 "SELECT Path.Path, File.FilenameId, File.LStat
2570 FROM File, Path, Job
2571 WHERE Path.PathId = File.PathId
2572 AND File.JobId = Job.JobId
2574 (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2576 SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM brestore_missing_path WHERE PathId IN ($dir)
2578 AND File.JobId IN ($inclause)
2579 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2583 # It's a file. Great, we allready have most
2584 # of what is needed. Simple and efficient query
2585 my $dir = $entry->[0];
2586 my $fileid = $entry->[1];
2588 my $jobid = $entry->[3];
2589 my $fileindex = $entry->[9];
2590 my $inclause = $entry->[5]; # curjobids
2592 "SELECT Path.Path, File.FilenameId, File.Lstat
2594 WHERE Path.PathId = File.PathId
2595 AND Path.PathId = $dir
2596 AND File.FilenameId = $fileid
2597 AND File.JobId = $jobid";
2600 my ($path,$nameid,$lstat);
2601 my $sth = $self->dbh_prepare($query);
2603 $sth->bind_columns(\$path,\$nameid,\$lstat);
2613 while ($sth->fetchrow_arrayref())
2615 # Only the latest version of a file
2616 next if ($nameid eq $old_nameid and $path eq $old_path);
2618 if ($rcount > 15000) {
2625 # We get the size of this file
2626 my $size=lstat_attrib($lstat,'st_size');
2627 $total_size += $size;
2630 $old_nameid=$nameid;
2633 return ($total_size,$total_files);
2636 # Returns list of versions of a file that could be restored
2637 # returns an array of
2638 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2639 # there will be only one jobid in the array of jobids...
2640 sub get_all_file_versions
2642 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2644 defined $see_all or $see_all=0;
2649 "SELECT File.JobId, File.FileIndex, File.Lstat,
2650 File.Md5, Media.VolumeName, Media.InChanger
2651 FROM File, Job, Client, JobMedia, Media
2652 WHERE File.FilenameId = $fileid
2653 AND File.PathId=$pathid
2654 AND File.JobId = Job.JobId
2655 AND Job.ClientId = Client.ClientId
2656 AND Job.JobId = JobMedia.JobId
2657 AND File.FileIndex >= JobMedia.FirstIndex
2658 AND File.FileIndex <= JobMedia.LastIndex
2659 AND JobMedia.MediaId = Media.MediaId
2660 AND Client.Name = '$client'";
2662 $self->debug($query);
2664 my $result = $self->dbh_selectall_arrayref($query);
2666 foreach my $refrow (@$result)
2668 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2669 my @attribs = parse_lstat($lstat);
2670 my $mtime = array_attrib('st_mtime',\@attribs);
2671 my $size = array_attrib('st_size',\@attribs);
2673 my @list = ('FILE:',$pathid,$fileid,$jobid,
2674 $fileindex, $mtime, $size, $inchanger,
2676 push @versions, (\@list);
2679 # We have the list of all versions of this file.
2680 # We'll sort it by mtime desc, size, md5, inchanger desc
2681 # the rest of the algorithm will be simpler
2682 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2683 @versions = sort { $b->[5] <=> $a->[5]
2684 || $a->[6] <=> $b->[6]
2685 || $a->[8] cmp $a->[8]
2686 || $b->[7] <=> $a->[7]} @versions;
2690 my %allready_seen_by_mtime;
2691 my %allready_seen_by_md5;
2692 # Now we should create a new array with only the interesting records
2693 foreach my $ref (@versions)
2697 # The file has a md5. We compare his md5 to other known md5...
2698 # We take size into account. It may happen that 2 files
2699 # have the same md5sum and are different. size is a supplementary
2702 # If we allready have a (better) version
2703 next if ( (not $see_all)
2704 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2706 # we never met this one before...
2707 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2709 # Even if it has a md5, we should also work with mtimes
2710 # We allready have a (better) version
2711 next if ( (not $see_all)
2712 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2713 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2715 # We reached there. The file hasn't been seen.
2716 push @good_versions,($ref);
2719 # To be nice with the user, we re-sort good_versions by
2720 # inchanger desc, mtime desc
2721 @good_versions = sort { $b->[5] <=> $a->[5]
2722 || $b->[3] <=> $a->[3]} @good_versions;
2724 return @good_versions;
2728 sub update_brestore_table
2730 my ($self, @jobs) = @_;
2732 $self->debug(\@jobs);
2734 foreach my $job (sort {$a <=> $b} @jobs)
2736 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2737 my $retour = $self->dbh_selectrow_arrayref($query);
2738 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2740 print STDERR "Inserting path records for JobId $job\n";
2741 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2742 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2744 $self->dbh_do($query);
2746 # Now we have to do the directory recursion stuff to determine missing visibility
2747 # We try to avoid recursion, to be as fast as possible
2748 # We also only work on not allready hierarchised directories...
2750 print STDERR "Creating missing recursion paths for $job\n";
2752 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2753 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2754 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2755 WHERE brestore_pathvisibility.JobId = $job
2756 AND brestore_pathhierarchy.PathId IS NULL
2759 my $sth = $self->dbh_prepare($query);
2761 my $pathid; my $path;
2762 $sth->bind_columns(\$pathid,\$path);
2766 $self->build_path_hierarchy($path,$pathid);
2770 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2771 # This query gives all parent pathids for a given jobid that aren't stored.
2772 # It has to be called until no record is updated ...
2774 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2775 SELECT a.PathId,$job
2777 (SELECT DISTINCT h.PPathId AS PathId
2778 FROM brestore_pathhierarchy AS h
2779 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2780 WHERE p.JobId=$job) AS a
2783 FROM brestore_pathvisibility
2784 WHERE JobId=$job) AS b
2785 ON (a.PathId = b.PathId)
2786 WHERE b.PathId IS NULL)";
2789 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2791 print STDERR "Recursively adding $rows_affected records from $job\n";
2794 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2795 $self->dbh_do($query);
2799 sub cleanup_brestore_table
2803 my $query = "SELECT JobId from brestore_knownjobid";
2804 my @jobs = @{$self->dbh_selectall_arrayref($query)};
2806 foreach my $jobentry (@jobs)
2808 my $job = $jobentry->[0];
2809 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2810 my $result = $self->dbh_selectall_arrayref($query);
2811 if (scalar(@{$result}))
2813 # There are still files for this jobid
2814 print STDERR "$job still exists. Not cleaning...\n";
2817 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2818 $self->dbh_do($query);
2819 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2820 $self->dbh_do($query);
2833 # Root Windows case :
2834 if ($path =~ /^[a-z]+:\/$/i)
2839 my @tmp = split('/',$path);
2840 # We remove the last ...
2842 my $tmp = join ('/',@tmp) . '/';
2846 sub build_path_hierarchy
2848 my ($self, $path,$pathid)=@_;
2849 # Does the ppathid exist for this ? we use a memory cache...
2850 # In order to avoid the full loop, we consider that if a dir is allready in the
2851 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2854 if (! $self->{cache_ppathid}->{$pathid})
2856 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2857 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2858 $sth2->execute($pathid);
2859 # Do we have a result ?
2860 if (my $refrow = $sth2->fetchrow_arrayref)
2862 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2864 # This dir was in the db ...
2865 # It means we can leave, the tree has allready been built for
2870 # We have to create the record ...
2871 # What's the current p_path ?
2872 my $ppath = parent_dir($path);
2873 my $ppathid = $self->return_pathid_from_path($ppath);
2874 $self->{cache_ppathid}->{$pathid}= $ppathid;
2876 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2877 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2878 $sth2->execute($pathid,$ppathid);
2884 # It's allready in the cache.
2885 # We can leave, no time to waste here, all the parent dirs have allready
2894 sub return_pathid_from_path
2896 my ($self, $path) = @_;
2897 my $query = "SELECT PathId FROM Path WHERE Path = ?
2899 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2900 #print STDERR $query,"\n" if $debug;
2901 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2902 $sth->execute($path,$path);
2903 my $result =$sth->fetchrow_arrayref();
2905 if (defined $result)
2907 return $result->[0];
2910 # A bit dirty : we insert into path AND missing_path, to be sure
2911 # we aren't deleted by a purge. We still need to insert into path to get
2912 # the pathid, because of mysql
2913 $query = "INSERT INTO Path (Path) VALUES (?)";
2914 #print STDERR $query,"\n" if $debug;
2915 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2916 $sth->execute($path);
2919 $query = " INSERT INTO brestore_missing_path (PathId,Path)
2920 SELECT PathId,Path FROM Path WHERE Path = ?";
2921 #print STDERR $query,"\n" if $debug;
2922 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2923 $sth->execute($path);
2925 $query = " DELETE FROM Path WHERE Path = ?";
2926 #print STDERR $query,"\n" if $debug;
2927 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2928 $sth->execute($path);
2930 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2931 #print STDERR $query,"\n" if $debug;
2932 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2933 $sth->execute($path);
2934 $result = $sth->fetchrow_arrayref();
2936 return $result->[0];
2941 sub create_brestore_tables
2945 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2947 unless ($self->dbh_do($verif)) {
2948 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2950 $self->{error} = "Creating internal brestore tables";
2952 CREATE TABLE brestore_knownjobid
2954 JobId int4 NOT NULL,
2955 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2957 $self->dbh_do($req);
2960 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2961 unless ($self->dbh_do($verif)) {
2963 CREATE TABLE brestore_pathhierarchy
2965 PathId int4 NOT NULL,
2966 PPathId int4 NOT NULL,
2967 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2969 $self->dbh_do($req);
2972 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2973 ON brestore_pathhierarchy (PPathId)";
2974 $self->dbh_do($req);
2977 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2978 unless ($self->dbh_do($verif)) {
2980 CREATE TABLE brestore_pathvisibility
2982 PathId int4 NOT NULL,
2983 JobId int4 NOT NULL,
2984 Size int8 DEFAULT 0,
2985 Files int4 DEFAULT 0,
2986 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2988 $self->dbh_do($req);
2990 $req = "CREATE INDEX brestore_pathvisibility_jobid
2991 ON brestore_pathvisibility (JobId)";
2992 $self->dbh_do($req);
2995 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2996 unless ($self->dbh_do($verif)) {
2998 CREATE TABLE brestore_missing_path
3000 PathId int4 NOT NULL,
3002 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
3004 $self->dbh_do($req);
3006 my $len = ($self->{conf}->{is_mysql} == 1)?'(255)':'';
3008 $req = "CREATE INDEX brestore_missing_path_path
3009 ON brestore_missing_path (Path$len)";
3010 $self->dbh_do($req);
3016 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3017 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3018 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3019 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3020 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3021 'data_stream' => 15);;
3024 my ($attrib,$ref_attrib)=@_;
3025 return $ref_attrib->[$attrib_name_id{$attrib}];
3029 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3031 my ($file, $attrib)=@_;
3033 if (defined $attrib_name_id{$attrib}) {
3035 my @d = split(' ', $file->[3]) ; # TODO : cache this
3037 return from_base64($d[$attrib_name_id{$attrib}]);
3039 } elsif ($attrib eq 'jobid') {
3043 } elsif ($attrib eq 'name') {
3048 die "Attribute not known : $attrib.\n";
3054 my ($lstat,$attrib)=@_;
3055 if ($lstat and defined $attrib_name_id{$attrib})
3057 my @d = split(' ', $lstat) ; # TODO : cache this
3058 return from_base64($d[$attrib_name_id{$attrib}]);
3065 # Base 64 functions, directly from recover.pl.
3067 # Karl Hakimian <hakimian@aha.com>
3068 # This section is also under GPL v2 or later.
3075 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3076 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3077 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3078 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3079 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3081 @base64_map = (0) x 128;
3083 for (my $i=0; $i<64; $i++) {
3084 $base64_map[ord($base64_digits[$i])] = $i;
3099 if (substr($where, 0, 1) eq '-') {
3101 $where = substr($where, 1);
3104 while ($where ne '') {
3106 my $d = substr($where, 0, 1);
3107 $val += $base64_map[ord(substr($where, 0, 1))];
3108 $where = substr($where, 1);
3116 my @attribs = split(' ',$lstat);
3117 foreach my $element (@attribs)
3119 $element = from_base64($element);
3127 ################################################################
3128 package BwebConsole;
3130 use HTTP::Request::Common;
3134 my ($class, %arg) = @_;
3137 pref => $arg{pref}, # Pref object
3138 timeout => $arg{timeout} || 20,
3139 debug => $arg{debug} || 0,
3141 'list_client' => '',
3142 'list_fileset' => '',
3143 'list_storage' => '',
3152 my ($self, @what) = @_;
3153 my $ua = LWP::UserAgent->new();
3154 $ua->agent("Brestore/$VERSION");
3155 my $req = POST($self->{pref}->{bconsole},
3156 Content_Type => 'form-data',
3157 Content => [ map { (action => $_) } @what ]);
3158 #$req->authorization_basic('eric', 'test');
3160 my $res = $ua->request($req);
3162 if ($res->is_success) {
3163 foreach my $l (split(/\n/, $res->content)) {
3164 my ($k, $c) = split(/=/,$l,2);
3168 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3169 new DlgWarn($self->{error});
3175 my ($self, %arg) = @_;
3177 my $ua = LWP::UserAgent->new();
3178 $ua->agent("Brestore/$VERSION");
3179 my $req = POST($self->{pref}->{bconsole},
3180 Content_Type => 'form-data',
3181 Content => [ job => $arg{job},
3182 client => $arg{client},
3183 storage => $arg{storage} || '',
3184 fileset => $arg{fileset} || '',
3185 where => $arg{where},
3186 replace => $arg{replace},
3187 priority=> $arg{prio} || '',
3190 bootstrap => [$arg{bootstrap}],
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);
3203 if (!$self->{run}) {
3204 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3207 unlink($arg{bootstrap});
3209 return $self->{run};
3215 return sort split(/;/, $self->{'list_job'});
3221 return sort split(/;/, $self->{'list_fileset'});
3227 return sort split(/;/, $self->{'list_storage'});
3232 return sort split(/;/, $self->{'list_client'});
3243 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3247 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3250 GetOptions("conf=s" => \$file_conf,
3251 "batch" => \$batch_mod,
3253 "help" => \&HELP_MESSAGE) ;
3255 my $p = new Pref($file_conf);
3257 if (! -f $file_conf) {
3262 my $vfs = new Bvfs(conf => $p);
3263 if ($p->connect_db()) {
3264 $vfs->update_cache();
3269 $glade_file = $p->{glade_file};
3271 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3272 if (-f "$path/$glade_file") {
3273 $glade_file = "$path/$glade_file" ;
3278 # gtk have lots of warning on stderr
3279 if ($^O eq 'MSWin32')
3282 open(STDERR, ">stderr.log");
3287 if ( -f $glade_file) {
3288 my $w = new DlgResto($p);
3291 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3292 "Can't find your brestore.glade (glade_file => '$glade_file')
3293 Please, edit your $file_conf to setup it." );
3295 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3300 Gtk2->main; # Start Gtk2 main loop
3309 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3311 $p->connect_db() || print $p->{error};
3313 my $bvfs = new Bvfs(conf => $p);
3315 $bvfs->debug($bvfs->get_root());
3316 $bvfs->ch_dir($bvfs->get_root());
3318 $bvfs->set_curjobids(268,178,282,281,279);
3320 my $dirs = $bvfs->ls_dirs();
3321 $bvfs->ch_dir(123496);
3322 $dirs = $bvfs->ls_dirs();
3324 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);