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->{dbh}->{RowCacheSize}=100;
200 my ($self, $url, $msg) = @_;
202 unless ($self->{mozilla} and $self->{bweb}) {
203 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
207 if ($^O eq 'MSWin32') {
208 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
211 system("$self->{mozilla} -remote 'Ping()'");
212 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
214 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
224 ################################################################
230 my ($class, %arg) = @_;
243 my ($self, $what, %arg) = @_;
245 if ($self->{conf}->{debug} and defined $what) {
250 my $line = (caller($level))[2];
251 my $func = (caller($level+1))[3] || 'main';
252 print "$func:$line\t";
254 print Data::Dumper::Dumper($what);
255 } elsif ($arg{md5}) {
256 print "MD5=", md5_base64($what), " str=", $what,"\n";
265 my ($self, @what) = @_;
266 if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
267 return join(' || ', @what);
269 return 'CONCAT(' . join(',', @what) . ')' ;
275 my ($self, $query) = @_;
276 $self->debug($query, up => 1);
277 return $self->{conf}->{dbh}->prepare($query);
282 my ($self, $query) = @_;
283 $self->debug($query, up => 1);
284 return $self->{conf}->{dbh}->do($query);
287 sub dbh_selectall_arrayref
289 my ($self, $query) = @_;
290 $self->debug($query, up => 1);
291 return $self->{conf}->{dbh}->selectall_arrayref($query);
294 sub dbh_selectrow_arrayref
296 my ($self, $query) = @_;
297 $self->debug($query, up => 1);
298 return $self->{conf}->{dbh}->selectrow_arrayref($query);
304 return $self->{conf}->{dbh};
309 ################################################################
313 # my $pref = new Pref(config_file => 'brestore.conf');
314 # my $dlg = new DlgPref($pref);
315 # my $dlg_resto = new DlgResto($pref);
316 # $dlg->display($dlg_resto);
319 my ($class, $pref) = @_;
322 pref => $pref, # Pref ref
323 dlgresto => undef, # DlgResto ref
331 my ($self, $dlgresto) = @_ ;
333 unless ($self->{glade}) {
334 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
335 $self->{glade}->signal_autoconnect_from_package($self);
338 $self->{dlgresto} = $dlgresto;
340 my $g = $self->{glade};
341 my $p = $self->{pref};
343 for my $k (@{ $p->{entry_keyword} }) {
344 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
347 for my $k (@{ $p->{chk_keyword} }) {
348 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
351 $g->get_widget("dlg_pref")->show_all() ;
354 sub on_applybutton_clicked
357 my $glade = $self->{glade};
358 my $pref = $self->{pref};
360 for my $k (@{ $pref->{entry_keyword} }) {
361 my $w = $glade->get_widget("entry_$k") ;
362 $pref->{$k} = $w->get_text();
365 for my $k (@{ $pref->{chk_keyword} }) {
366 my $w = $glade->get_widget("chkbp_$k") ;
367 $pref->{$k} = $w->get_active();
370 if (!$pref->write_config() && $pref->connect_db()) {
371 $self->{dlgresto}->set_status('Preferences updated');
372 $self->{dlgresto}->init_server_backup_combobox();
373 $self->{dlgresto}->set_status($pref->{error});
376 $self->{dlgresto}->set_status($pref->{error});
380 # Handle prefs ok click (apply/dismiss dialog)
381 sub on_okbutton_clicked
384 $self->on_applybutton_clicked();
386 unless ($self->{pref}->{error}) {
387 $self->on_cancelbutton_clicked();
390 sub on_dialog_delete_event
393 $self->on_cancelbutton_clicked();
397 sub on_cancelbutton_clicked
400 $self->{glade}->get_widget('dlg_pref')->hide();
401 delete $self->{dlgresto};
405 ################################################################
407 package DlgFileVersion;
409 sub on_versions_close_clicked
411 my ($self, $widget)=@_;
412 $self->{version}->destroy();
415 sub on_selection_button_press_event
417 print STDERR "on_selection_button_press_event()\n";
420 sub fileview_data_get
422 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
424 DlgResto::drag_set_info($widget,
431 my ($class, $bvfs, $client, $path, $file, $cwd, $fn) = @_;
434 version => undef, # main window
437 # we load version widget of $glade_file
438 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
440 # Connect signals magically
441 $glade_box->signal_autoconnect_from_package($self);
443 $glade_box->get_widget("version_label")
444 ->set_markup("<b>File revisions : $client:$cwd$fn</b>");
446 my $widget = $glade_box->get_widget('version_fileview');
447 my $fileview = Gtk2::SimpleList->new_from_treeview(
449 'h_pathid' => 'hidden',
450 'h_filenameid' => 'hidden',
451 'h_name' => 'hidden',
452 'h_jobid' => 'hidden',
453 'h_type' => 'hidden',
455 'InChanger' => 'pixbuf',
462 DlgResto::init_drag_drop($fileview);
464 my @v = $bvfs->get_all_file_versions($path,
469 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
470 $inchanger,$md5,$volname) = @{$ver};
471 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
473 DlgResto::listview_push($fileview,$pid,$fid,
475 $icon, $volname, $jobid,DlgResto::human($size),
476 scalar(localtime($mtime)), $md5);
479 $self->{version} = $glade_box->get_widget('dlg_version');
480 $self->{version}->show();
485 sub on_forward_keypress
491 ################################################################
496 my ($package, $text) = @_;
500 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
502 # Connect signals magically
503 $glade->signal_autoconnect_from_package($self);
504 $glade->get_widget('label_warn')->set_text($text);
506 print STDERR "$text\n";
508 $self->{window} = $glade->get_widget('dlg_warn');
509 $self->{window}->show_all();
516 $self->{window}->destroy();
520 ################################################################
526 # %arg = (bsr_file => '/path/to/bsr', # on director
527 # volumes => [ '00001', '00004']
535 if ($pref->{bconsole} =~ /^http/) {
536 return new BwebConsole(pref => $pref);
538 if (eval { require Bconsole; }) {
539 return new Bconsole(pref => $pref);
541 new DlgWarn("Can't use bconsole, verify your setup");
549 my ($class, %arg) = @_;
552 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
553 pref => $arg{pref}, # Pref ref
554 glade => undef, # GladeXML ref
555 bconsole => undef, # Bconsole ref
558 my $console = $self->{bconsole} = get_bconsole($arg{pref});
563 # we load launch widget of $glade_file
564 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
567 # Connect signals magically
568 $glade->signal_autoconnect_from_package($self);
570 my $widget = $glade->get_widget('volumeview');
571 my $volview = Gtk2::SimpleList->new_from_treeview(
573 'InChanger' => 'pixbuf',
577 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
579 # we replace 0 and 1 by $noicon and $yesicon
580 for my $i (@{$infos}) {
582 $i->[0] = $DlgResto::noicon;
584 $i->[0] = $DlgResto::yesicon;
589 push @{ $volview->{data} }, @{$infos} ;
591 $console->prepare(qw/list_client list_job list_fileset list_storage/);
593 # fill client combobox (with director defined clients
594 my @clients = $console->list_client() ; # get from bconsole
595 if ($console->{error}) {
596 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
598 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
599 $self->{list_client} = DlgResto::init_combo($w, 'text');
600 DlgResto::fill_combo($self->{list_client},
601 $DlgResto::client_list_empty,
605 # fill fileset combobox
606 my @fileset = $console->list_fileset() ;
607 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
608 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
609 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
612 my @job = $console->list_job() ;
613 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
614 $self->{list_job} = DlgResto::init_combo($w, 'text');
615 DlgResto::fill_combo($self->{list_job}, '', @job);
617 # find default_restore_job in jobs list
618 my $default_restore_job = $arg{pref}->{default_restore_job} ;
622 if ($j =~ /$default_restore_job/io) {
628 $w->set_active($index);
630 # fill storage combobox
631 my @storage = $console->list_storage() ;
632 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
633 $self->{list_storage} = DlgResto::init_combo($w, 'text');
634 DlgResto::fill_combo($self->{list_storage}, '', @storage);
636 $glade->get_widget('dlg_launch')->show_all();
643 my ($self, $client, $jobid) = @_;
645 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
647 $self->on_cancel_resto_clicked();
650 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
651 "Your job have been submited to bacula.
652 To follow it, you must use bconsole (or install/configure bweb)");
658 sub on_cancel_resto_clicked
661 $self->{glade}->get_widget('dlg_launch')->destroy();
664 sub on_submit_resto_clicked
667 my $glade = $self->{glade};
669 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
672 new DlgWarn("Can't copy bsr file to director ($self->{error})");
676 my $fileset = $glade->get_widget('combo_launch_fileset')
679 my $storage = $glade->get_widget('combo_launch_storage')
682 my $where = $glade->get_widget('entry_launch_where')->get_text();
684 my $job = $glade->get_widget('combo_launch_job')
688 new DlgWarn("Can't use this job");
692 my $client = $glade->get_widget('combo_launch_client')
695 if (! $client or $client eq $DlgResto::client_list_empty) {
696 new DlgWarn("Can't use this client ($client)");
700 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
702 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
703 $replace=($replace)?'always':'never';
705 my $jobid = $self->{bconsole}->run(job => $job,
714 $self->show_job($client, $jobid);
717 sub on_combo_storage_button_press_event
720 print "on_combo_storage_button_press_event()\n";
723 sub on_combo_fileset_button_press_event
726 print "on_combo_fileset_button_press_event()\n";
730 sub on_combo_job_button_press_event
733 print "on_combo_job_button_press_event()\n";
736 sub get_volume_inchanger
738 my ($dbh, $vols) = @_;
740 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
742 my $rq = "SELECT InChanger, VolumeName
744 WHERE VolumeName IN ($lst)
747 my $res = $dbh->selectall_arrayref($rq);
748 return $res; # [ [ 1, VolName].. ]
752 use File::Copy qw/copy/;
753 use File::Basename qw/basename/;
755 # We must kown the path+filename destination
756 # $self->{error} contains error message
757 # it return 0/1 if fail/success
760 my ($self, $src, $dst) = @_ ;
761 print "$src => $dst\n"
772 if ($dst =~ m!file:/(/.+)!) {
773 $ret = copy($src, $1);
775 $dstfile = "$1/" . basename($src) ;
777 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
778 $err = `scp $src $1 2>&1` ;
780 $dstfile = "$2/" . basename($src) ;
784 $err = "$dst not implemented yet";
785 File::Copy::copy($src, \*STDOUT);
788 $self->{error} = $err;
791 $self->{error} = $err;
800 ################################################################
808 unless ($about_widget) {
809 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
810 $about_widget = $glade_box->get_widget("dlg_about") ;
811 $glade_box->signal_autoconnect_from_package('DlgAbout');
813 $about_widget->show() ;
816 sub on_about_okbutton_clicked
818 $about_widget->hide() ;
823 ################################################################
833 # Kept as is from the perl-gtk example. Draws the pretty icons
839 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
840 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
841 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
842 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
845 # init combo (and create ListStore object)
848 my ($widget, @type) = @_ ;
849 my %type_info = ('text' => 'Glib::String',
850 'markup' => 'Glib::String',
853 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
855 $widget->set_model($lst);
859 if ($t eq 'text' or $t eq 'markup') {
860 $cell = new Gtk2::CellRendererText();
862 $widget->pack_start($cell, 1);
863 $widget->add_attribute($cell, $t, $i++);
868 # fill simple combo (one element per row)
871 my ($list, @what) = @_;
875 foreach my $w (@what)
878 my $i = $list->append();
879 $list->set($i, 0, $w);
886 my @unit = qw(b Kb Mb Gb Tb);
889 my $format = '%i %s';
890 while ($val / 1024 > 1) {
894 $format = ($i>0)?'%0.1f %s':'%i %s';
895 return sprintf($format, $val, $unit[$i]);
898 sub get_wanted_job_status
905 return "'T', 'A', 'E'";
909 # This sub gives a full list of the EndTimes for a ClientId
910 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
911 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
912 sub get_all_endtimes_for_job
914 my ($self, $client, $ok_only)=@_;
915 my $status = get_wanted_job_status($ok_only);
917 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
918 FROM Job,Client,FileSet
919 WHERE Job.ClientId=Client.ClientId
920 AND Client.Name = '$client'
922 AND JobStatus IN ($status)
923 AND Job.FileSetId = FileSet.FileSetId
924 ORDER BY EndTime desc";
925 my $result = $self->dbh_selectall_arrayref($query);
932 my ($fileview) = shift;
933 my $fileview_target_entry = {target => 'STRING',
934 flags => ['GTK_TARGET_SAME_APP'],
937 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
938 ['copy'],$fileview_target_entry);
939 $fileview->get_selection->set_mode('multiple');
941 # set some useful SimpleList properties
942 $fileview->set_headers_clickable(0);
943 foreach ($fileview->get_columns())
945 $_->set_resizable(1);
946 $_->set_sizing('grow-only');
952 my ($class, $pref) = @_;
957 location => undef, # location entry widget
958 mainwin => undef, # mainwin widget
959 filelist_file_menu => undef, # file menu widget
960 filelist_dir_menu => undef, # dir menu widget
961 glade => undef, # glade object
962 status => undef, # status bar widget
963 dlg_pref => undef, # DlgPref object
964 fileattrib => {}, # cache file
965 fileview => undef, # fileview widget SimpleList
966 fileinfo => undef, # fileinfo widget SimpleList
968 client_combobox => undef, # client_combobox widget
969 restore_backup_combobox => undef, # date combobox widget
970 list_client => undef, # Gtk2::ListStore
971 list_backup => undef, # Gtk2::ListStore
972 cache_ppathid => {}, #
976 $self->{bvfs} = new Bvfs(conf => $pref);
978 # load menu (to use handler with self reference)
979 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
980 $glade->signal_autoconnect_from_package($self);
981 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
983 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
984 $glade->signal_autoconnect_from_package($self);
985 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
987 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
988 $glade->signal_autoconnect_from_package($self);
990 $self->{status} = $glade->get_widget('statusbar');
991 $self->{mainwin} = $glade->get_widget('dlg_resto');
992 $self->{location} = $glade->get_widget('entry_location');
993 $self->render_icons();
995 $self->{dlg_pref} = new DlgPref($pref);
997 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
998 $self->{list_client} = init_combo($c, 'text');
1000 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1001 $self->{list_backup} = init_combo($c, 'text', 'markup');
1003 # Connect glade-fileview to Gtk2::SimpleList
1004 # and set up drag n drop between $fileview and $restore_list
1006 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1007 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1009 my $widget = $glade->get_widget('fileview');
1010 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1012 'h_pathid' => 'hidden',
1013 'h_filenameid' => 'hidden',
1014 'h_name' => 'hidden',
1015 'h_jobid' => 'hidden',
1016 'h_type' => 'hidden',
1019 'File Name' => 'text',
1022 init_drag_drop($fileview);
1023 $fileview->set_search_column(6); # search on File Name
1025 # Connect glade-restore_list to Gtk2::SimpleList
1026 $widget = $glade->get_widget('restorelist');
1027 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1029 'h_pathid' => 'hidden', #0
1030 'h_filenameid' => 'hidden',
1031 'h_name' => 'hidden',
1032 'h_jobid' => 'hidden',
1033 'h_type' => 'hidden',
1034 'h_curjobid' => 'hidden', #5
1037 'File Name' => 'text',
1039 'FileIndex' => 'text',
1041 'Nb Files' => 'text', #10
1042 'Size' => 'text', #11
1043 'size_b' => 'hidden', #12
1046 my @restore_list_target_table = ({'target' => 'STRING',
1050 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1051 $restore_list->get_selection->set_mode('multiple');
1053 $widget = $glade->get_widget('infoview');
1054 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1056 'h_pathid' => 'hidden',
1057 'h_filenameid' => 'hidden',
1058 'h_name' => 'hidden',
1059 'h_jobid' => 'hidden',
1060 'h_type' => 'hidden',
1062 'InChanger' => 'pixbuf',
1069 init_drag_drop($infoview);
1071 $pref->connect_db() || $self->{dlg_pref}->display($self);
1074 $self->init_server_backup_combobox();
1075 $self->{bvfs}->create_brestore_tables();
1078 $self->set_status($pref->{error});
1081 # set status bar informations
1084 my ($self, $string) = @_;
1085 return unless ($string);
1087 my $context = $self->{status}->get_context_id('Main');
1088 $self->{status}->push($context, $string);
1091 sub on_time_select_changed
1099 my $c = $self->{glade}->get_widget('combo_time');
1100 return $c->get_active_text;
1103 # This sub returns all clients declared in DB
1107 my $query = "SELECT Name FROM Client ORDER BY Name";
1108 print STDERR $query,"\n" if $debug;
1110 my $result = $dbh->selectall_arrayref($query);
1112 return map { $_->[0] } @$result;
1115 # init infoview widget
1119 @{$self->{fileinfo}->{data}} = ();
1123 sub on_clear_clicked
1126 @{$self->{restore_list}->{data}} = ();
1129 sub on_estimate_clicked
1136 # TODO : If we get here, things could get lenghty ... draw a popup window .
1137 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1138 'destroy-with-parent',
1140 'Computing size...');
1144 my $title = "Computing size...\n";
1147 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1148 # undef, undef, undef, $dirfileindex);
1149 foreach my $entry (@{$self->{restore_list}->{data}})
1151 unless ($entry->[11]) {
1152 my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1153 $entry->[12] = $size;
1154 $entry->[11] = human($size);
1158 my $name = unpack('u', $entry->[2]);
1160 $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1161 $self->debug($title . $txt);
1162 $widget->set_markup($title . $txt);
1164 $size_total+=$entry->[12];
1165 $nb_total+=$entry->[10];
1169 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1170 $widget->set_markup("Size estimation :\n" . $txt);
1171 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1178 sub on_gen_bsr_clicked
1182 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1183 'gtk-save','ok', 'gtk-cancel', 'cancel');
1186 my $w = new Gtk2::FileChooserDialog ( @options );
1191 if ($a eq 'cancel') {
1196 my $f = $w->get_filename();
1198 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1199 'destroy-with-parent',
1200 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1201 if ($dlg->run() eq 'ok') {
1215 if (open(FP, ">$save")) {
1216 my $bsr = $self->create_filelist();
1219 $self->set_status("Dumping BSR to $save ok");
1221 $self->set_status("Can't dump BSR to $save: $!");
1227 use File::Temp qw/tempfile/;
1229 sub on_go_button_clicked
1232 unless (scalar(@{$self->{restore_list}->{data}})) {
1233 new DlgWarn("No file to restore");
1236 my $bsr = $self->create_filelist();
1237 my ($fh, $filename) = tempfile();
1240 chmod(0644, $filename);
1242 print "Dumping BSR info to $filename\n"
1245 # we get Volume list
1246 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1247 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1249 new DlgLaunch(pref => $self->{conf},
1251 bsr_file => $filename,
1257 our $client_list_empty = 'Clients list';
1258 our %type_markup = ('F' => '<b>$label F</b>',
1261 'B' => '<b>$label B</b>',
1263 'A' => '<span foreground=\"red\">$label</span>',
1265 'E' => '<span foreground=\"red\">$label</span>',
1268 sub on_list_client_changed
1270 my ($self, $widget) = @_;
1271 return 0 unless defined $self->{fileview};
1273 $self->{list_backup}->clear();
1275 if ($self->current_client eq $client_list_empty) {
1279 $self->{CurrentJobIds} = [
1280 set_job_ids_for_date($self->dbh(),
1281 $self->current_client,
1282 $self->current_date,
1283 $self->{pref}->{use_ok_bkp_only})
1286 my $fs = $self->{bvfs};
1287 $fs->set_curjobids(@{$self->{CurrentJobIds}});
1288 $fs->update_brestore_table(@{$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->{pref}->{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->{pref}->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->{pref}->{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->{pref}->{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);
2375 return undef unless ($self->{curjobids});
2377 my $inclause = $self->{curjobids};
2378 my $inlistpath = $self->{cwd};
2381 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2383 (SELECT Filename.Name, max(File.FileId) as id
2385 WHERE File.FilenameId = Filename.FilenameId
2386 AND Filename.Name != ''
2387 AND File.PathId IN ($inlistpath)
2388 AND File.JobId IN ($inclause)
2389 GROUP BY Filename.Name
2390 ORDER BY Filename.Name) AS listfiles,
2392 WHERE File.FileId = listfiles.id";
2394 $self->debug($query);
2395 my $result = $self->dbh_selectall_arrayref($query);
2396 $self->debug($result);
2401 # return ($dirid,$dir_basename,$lstat,$jobid)
2406 return undef unless ($self->{curjobids});
2408 my $pathid = $self->{cwd};
2409 my $jobclause = $self->{curjobids};
2411 # Let's retrieve the list of the visible dirs in this dir ...
2412 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2413 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2414 my $sth = $self->dbh_prepare($query);
2416 my $result = $sth->fetchrow_arrayref();
2418 my $dir_filenameid = $result->[0];
2420 # Then we get all the dir entries from File ...
2421 # It's ugly because there are records in brestore_missing_path ...
2423 SELECT PathId, Path, JobId, Lstat FROM(
2425 SELECT Path.PathId, Path.Path, lower(Path.Path),
2426 listfile.JobId, listfile.Lstat
2428 SELECT DISTINCT brestore_pathhierarchy.PathId
2429 FROM brestore_pathhierarchy
2431 ON (brestore_pathhierarchy.PathId = Path.PathId)
2432 JOIN brestore_pathvisibility
2433 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2434 WHERE brestore_pathhierarchy.PPathId = $pathid
2435 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2436 JOIN Path ON (listpath.PathId = Path.PathId)
2438 SELECT File.PathId, File.JobId, File.Lstat FROM File
2439 WHERE File.FilenameId = $dir_filenameid
2440 AND File.JobId IN ($jobclause)) AS listfile
2441 ON (listpath.PathId = listfile.PathId)
2443 SELECT brestore_missing_path.PathId, brestore_missing_path.Path,
2444 lower(brestore_missing_path.Path), listfile.JobId, listfile.Lstat
2446 SELECT DISTINCT brestore_pathhierarchy.PathId
2447 FROM brestore_pathhierarchy
2448 JOIN brestore_missing_path
2449 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
2450 JOIN brestore_pathvisibility
2451 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
2452 WHERE brestore_pathhierarchy.PPathId = $pathid
2453 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
2454 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
2456 SELECT File.PathId, File.JobId, File.Lstat FROM File
2457 WHERE File.FilenameId = $dir_filenameid
2458 AND File.JobId IN ($jobclause)) AS listfile
2459 ON (listpath.PathId = listfile.PathId))
2460 ORDER BY 2,3 DESC ) As a";
2461 $self->debug($query);
2462 $sth=$self->dbh_prepare($query);
2464 $result = $sth->fetchall_arrayref();
2467 foreach my $refrow (@{$result})
2469 my $dirid = $refrow->[0];
2470 my $dir = $refrow->[1];
2471 my $lstat = $refrow->[3];
2472 my $jobid = $refrow->[2] || 0;
2473 next if ($dirid eq $prev_dir);
2474 # We have to clean up this dirname ... we only want it's 'basename'
2478 my @temp = split ('/',$dir);
2479 $return_value = pop @temp;
2483 $return_value = '/';
2485 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2486 push @return_list,(\@return_array);
2489 $self->debug(\@return_list);
2490 return \@return_list;
2493 # Returns the list of media required for a list of jobids.
2494 # Input : self, jobid1, jobid2...
2495 # Output : reference to array of (joibd, inchanger)
2496 sub get_required_media_from_jobid
2498 my ($self, @jobids)=@_;
2499 my $inclause = join(',',@jobids);
2501 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2502 FROM JobMedia, Media
2503 WHERE JobMedia.MediaId=Media.MediaId
2504 AND JobId In ($inclause)
2506 my $result = $self->dbh_selectall_arrayref($query);
2510 # Returns the fileindex from dirname and jobid.
2511 # Input : self, dirid, jobid
2512 # Output : fileindex
2513 sub get_fileindex_from_dir_jobid
2515 my ($self, $dirid, $jobid)=@_;
2517 $query = "SELECT File.FileIndex
2519 WHERE File.FilenameId = Filename.FilenameId
2520 AND File.PathId = $dirid
2521 AND Filename.Name = ''
2522 AND File.JobId = '$jobid'
2525 $self->debug($query);
2526 my $result = $self->dbh_selectall_arrayref($query);
2527 return $result->[0]->[0];
2530 # Returns the fileindex from filename and jobid.
2531 # Input : self, dirid, filenameid, jobid
2532 # Output : fileindex
2533 sub get_fileindex_from_file_jobid
2535 my ($self, $dirid, $filenameid, $jobid)=@_;
2539 "SELECT File.FileIndex
2541 WHERE File.PathId = $dirid
2542 AND File.FilenameId = $filenameid
2543 AND File.JobId = $jobid";
2545 $self->debug($query);
2546 my $result = $self->dbh_selectall_arrayref($query);
2547 return $result->[0]->[0];
2550 # This function estimates the size to be restored for an entry of the restore
2552 # In : self,reference to the entry
2553 # Out : size in bytes, number of files
2554 sub estimate_restore_size
2556 # reminder : restore_list looks like this :
2557 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2558 # undef, undef, undef, $dirfileindex);
2559 my ($self, $entry, $refresh) = @_;
2561 if ($entry->[4] eq 'dir')
2563 my $dir = $entry->[0];
2565 my $inclause = $entry->[5]; #curjobids
2567 "SELECT Path.Path, File.FilenameId, File.LStat
2568 FROM File, Path, Job
2569 WHERE Path.PathId = File.PathId
2570 AND File.JobId = Job.JobId
2572 (SELECT Path || '%' FROM Path WHERE PathId IN ($dir)
2574 SELECT Path || '%' FROM brestore_missing_path WHERE PathId IN ($dir)
2576 AND File.JobId IN ($inclause)
2577 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2581 # It's a file. Great, we allready have most
2582 # of what is needed. Simple and efficient query
2583 my $dir = $entry->[0];
2584 my $fileid = $entry->[1];
2586 my $jobid = $entry->[3];
2587 my $fileindex = $entry->[9];
2588 my $inclause = $entry->[5]; # curjobids
2590 "SELECT Path.Path, File.FilenameId, File.Lstat
2592 WHERE Path.PathId = File.PathId
2593 AND Path.PathId = $dir
2594 AND File.FilenameId = $fileid
2595 AND File.JobId = $jobid";
2598 my ($path,$nameid,$lstat);
2599 my $sth = $self->dbh_prepare($query);
2601 $sth->bind_columns(\$path,\$nameid,\$lstat);
2611 while ($sth->fetchrow_arrayref())
2613 # Only the latest version of a file
2614 next if ($nameid eq $old_nameid and $path eq $old_path);
2616 if ($rcount > 15000) {
2623 # We get the size of this file
2624 my $size=lstat_attrib($lstat,'st_size');
2625 $total_size += $size;
2628 $old_nameid=$nameid;
2631 return ($total_size,$total_files);
2634 # Returns list of versions of a file that could be restored
2635 # returns an array of
2636 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2637 # there will be only one jobid in the array of jobids...
2638 sub get_all_file_versions
2640 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2642 defined $see_all or $see_all=0;
2647 "SELECT File.JobId, File.FileIndex, File.Lstat,
2648 File.Md5, Media.VolumeName, Media.InChanger
2649 FROM File, Job, Client, JobMedia, Media
2650 WHERE File.FilenameId = $fileid
2651 AND File.PathId=$pathid
2652 AND File.JobId = Job.JobId
2653 AND Job.ClientId = Client.ClientId
2654 AND Job.JobId = JobMedia.JobId
2655 AND File.FileIndex >= JobMedia.FirstIndex
2656 AND File.FileIndex <= JobMedia.LastIndex
2657 AND JobMedia.MediaId = Media.MediaId
2658 AND Client.Name = '$client'";
2660 $self->debug($query);
2662 my $result = $self->dbh_selectall_arrayref($query);
2664 foreach my $refrow (@$result)
2666 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2667 my @attribs = parse_lstat($lstat);
2668 my $mtime = array_attrib('st_mtime',\@attribs);
2669 my $size = array_attrib('st_size',\@attribs);
2671 my @list = ('FILE:',$pathid,$fileid,$jobid,
2672 $fileindex, $mtime, $size, $inchanger,
2674 push @versions, (\@list);
2677 # We have the list of all versions of this file.
2678 # We'll sort it by mtime desc, size, md5, inchanger desc
2679 # the rest of the algorithm will be simpler
2680 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2681 @versions = sort { $b->[5] <=> $a->[5]
2682 || $a->[6] <=> $b->[6]
2683 || $a->[8] cmp $a->[8]
2684 || $b->[7] <=> $a->[7]} @versions;
2688 my %allready_seen_by_mtime;
2689 my %allready_seen_by_md5;
2690 # Now we should create a new array with only the interesting records
2691 foreach my $ref (@versions)
2695 # The file has a md5. We compare his md5 to other known md5...
2696 # We take size into account. It may happen that 2 files
2697 # have the same md5sum and are different. size is a supplementary
2700 # If we allready have a (better) version
2701 next if ( (not $see_all)
2702 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2704 # we never met this one before...
2705 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2707 # Even if it has a md5, we should also work with mtimes
2708 # We allready have a (better) version
2709 next if ( (not $see_all)
2710 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2711 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2713 # We reached there. The file hasn't been seen.
2714 push @good_versions,($ref);
2717 # To be nice with the user, we re-sort good_versions by
2718 # inchanger desc, mtime desc
2719 @good_versions = sort { $b->[5] <=> $a->[5]
2720 || $b->[3] <=> $a->[3]} @good_versions;
2722 return @good_versions;
2726 sub update_brestore_table
2728 my ($self, @jobs) = @_;
2730 foreach my $job (sort {$a <=> $b} @jobs)
2732 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2733 my $retour = $self->dbh_selectrow_arrayref($query);
2734 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2736 print STDERR "Inserting path records for JobId $job\n";
2737 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2738 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2740 $self->dbh_do($query);
2742 # Now we have to do the directory recursion stuff to determine missing visibility
2743 # We try to avoid recursion, to be as fast as possible
2744 # We also only work on not allready hierarchised directories...
2746 print STDERR "Creating missing recursion paths for $job\n";
2748 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2749 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2750 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2751 WHERE brestore_pathvisibility.JobId = $job
2752 AND brestore_pathhierarchy.PathId IS NULL
2755 my $sth = $self->dbh_prepare($query);
2757 my $pathid; my $path;
2758 $sth->bind_columns(\$pathid,\$path);
2762 $self->build_path_hierarchy($path,$pathid);
2766 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2767 # This query gives all parent pathids for a given jobid that aren't stored.
2768 # It has to be called until no record is updated ...
2770 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2771 SELECT a.PathId,$job
2773 (SELECT DISTINCT h.PPathId AS PathId
2774 FROM brestore_pathhierarchy AS h
2775 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2776 WHERE p.JobId=$job) AS a
2779 FROM brestore_pathvisibility
2780 WHERE JobId=$job) AS b
2781 ON (a.PathId = b.PathId)
2782 WHERE b.PathId IS NULL)";
2785 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2787 print STDERR "Recursively adding $rows_affected records from $job\n";
2790 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2791 $self->dbh_do($query);
2795 sub cleanup_brestore_table
2799 my $query = "SELECT JobId from brestore_knownjobid";
2800 my @jobs = @{$self->dbh_selectall_arrayref($query)};
2802 foreach my $jobentry (@jobs)
2804 my $job = $jobentry->[0];
2805 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2806 my $result = $self->dbh_selectall_arrayref($query);
2807 if (scalar(@{$result}))
2809 # There are still files for this jobid
2810 print STDERR "$job still exists. Not cleaning...\n";
2813 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2814 $self->dbh_do($query);
2815 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2816 $self->dbh_do($query);
2829 # Root Windows case :
2830 if ($path =~ /^[a-z]+:\/$/i)
2835 my @tmp = split('/',$path);
2836 # We remove the last ...
2838 my $tmp = join ('/',@tmp) . '/';
2842 sub build_path_hierarchy
2844 my ($self, $path,$pathid)=@_;
2845 # Does the ppathid exist for this ? we use a memory cache...
2846 # In order to avoid the full loop, we consider that if a dir is allready in the
2847 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2850 if (! $self->{cache_ppathid}->{$pathid})
2852 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2853 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2854 $sth2->execute($pathid);
2855 # Do we have a result ?
2856 if (my $refrow = $sth2->fetchrow_arrayref)
2858 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2860 # This dir was in the db ...
2861 # It means we can leave, the tree has allready been built for
2866 # We have to create the record ...
2867 # What's the current p_path ?
2868 my $ppath = parent_dir($path);
2869 my $ppathid = $self->return_pathid_from_path($ppath);
2870 $self->{cache_ppathid}->{$pathid}= $ppathid;
2872 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2873 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2874 $sth2->execute($pathid,$ppathid);
2880 # It's allready in the cache.
2881 # We can leave, no time to waste here, all the parent dirs have allready
2890 sub return_pathid_from_path
2892 my ($self, $path) = @_;
2893 my $query = "SELECT PathId FROM Path WHERE Path = ?
2895 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2896 #print STDERR $query,"\n" if $debug;
2897 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2898 $sth->execute($path,$path);
2899 my $result =$sth->fetchrow_arrayref();
2901 if (defined $result)
2903 return $result->[0];
2906 # A bit dirty : we insert into path AND missing_path, to be sure
2907 # we aren't deleted by a purge. We still need to insert into path to get
2908 # the pathid, because of mysql
2909 $query = "INSERT INTO Path (Path) VALUES (?)";
2910 #print STDERR $query,"\n" if $debug;
2911 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2912 $sth->execute($path);
2915 $query = " INSERT INTO brestore_missing_path (PathId,Path)
2916 SELECT PathId,Path FROM Path WHERE Path = ?";
2917 #print STDERR $query,"\n" if $debug;
2918 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2919 $sth->execute($path);
2921 $query = " DELETE FROM Path WHERE Path = ?";
2922 #print STDERR $query,"\n" if $debug;
2923 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2924 $sth->execute($path);
2926 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2927 #print STDERR $query,"\n" if $debug;
2928 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2929 $sth->execute($path);
2930 $result = $sth->fetchrow_arrayref();
2932 return $result->[0];
2937 sub create_brestore_tables
2941 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2943 unless ($self->dbh_do($verif)) {
2944 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2946 $self->{error} = "Creating internal brestore tables";
2948 CREATE TABLE brestore_knownjobid
2950 JobId int4 NOT NULL,
2951 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2953 $self->dbh_do($req);
2956 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2957 unless ($self->dbh_do($verif)) {
2959 CREATE TABLE brestore_pathhierarchy
2961 PathId int4 NOT NULL,
2962 PPathId int4 NOT NULL,
2963 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2965 $self->dbh_do($req);
2968 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2969 ON brestore_pathhierarchy (PPathId)";
2970 $self->dbh_do($req);
2973 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2974 unless ($self->dbh_do($verif)) {
2976 CREATE TABLE brestore_pathvisibility
2978 PathId int4 NOT NULL,
2979 JobId int4 NOT NULL,
2980 Size int8 DEFAULT 0,
2981 Files int4 DEFAULT 0,
2982 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2984 $self->dbh_do($req);
2986 $req = "CREATE INDEX brestore_pathvisibility_jobid
2987 ON brestore_pathvisibility (JobId)";
2988 $self->dbh_do($req);
2991 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2992 unless ($self->dbh_do($verif)) {
2994 CREATE TABLE brestore_missing_path
2996 PathId int4 NOT NULL,
2998 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
3000 $self->dbh_do($req);
3002 $req = "CREATE INDEX brestore_missing_path_path
3003 ON brestore_missing_path (Path)";
3004 $self->dbh_do($req);
3010 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3011 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3012 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3013 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3014 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3015 'data_stream' => 15);;
3018 my ($attrib,$ref_attrib)=@_;
3019 return $ref_attrib->[$attrib_name_id{$attrib}];
3023 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3025 my ($file, $attrib)=@_;
3027 if (defined $attrib_name_id{$attrib}) {
3029 my @d = split(' ', $file->[3]) ; # TODO : cache this
3031 return from_base64($d[$attrib_name_id{$attrib}]);
3033 } elsif ($attrib eq 'jobid') {
3037 } elsif ($attrib eq 'name') {
3042 die "Attribute not known : $attrib.\n";
3048 my ($lstat,$attrib)=@_;
3049 if ($lstat and defined $attrib_name_id{$attrib})
3051 my @d = split(' ', $lstat) ; # TODO : cache this
3052 return from_base64($d[$attrib_name_id{$attrib}]);
3059 # Base 64 functions, directly from recover.pl.
3061 # Karl Hakimian <hakimian@aha.com>
3062 # This section is also under GPL v2 or later.
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 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3072 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3073 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3075 @base64_map = (0) x 128;
3077 for (my $i=0; $i<64; $i++) {
3078 $base64_map[ord($base64_digits[$i])] = $i;
3093 if (substr($where, 0, 1) eq '-') {
3095 $where = substr($where, 1);
3098 while ($where ne '') {
3100 my $d = substr($where, 0, 1);
3101 $val += $base64_map[ord(substr($where, 0, 1))];
3102 $where = substr($where, 1);
3110 my @attribs = split(' ',$lstat);
3111 foreach my $element (@attribs)
3113 $element = from_base64($element);
3121 ################################################################
3122 package BwebConsole;
3124 use HTTP::Request::Common;
3128 my ($class, %arg) = @_;
3131 pref => $arg{pref}, # Pref object
3132 timeout => $arg{timeout} || 20,
3133 debug => $arg{debug} || 0,
3135 'list_client' => '',
3136 'list_fileset' => '',
3137 'list_storage' => '',
3146 my ($self, @what) = @_;
3147 my $ua = LWP::UserAgent->new();
3148 $ua->agent("Brestore/$VERSION");
3149 my $req = POST($self->{pref}->{bconsole},
3150 Content_Type => 'form-data',
3151 Content => [ map { (action => $_) } @what ]);
3152 #$req->authorization_basic('eric', 'test');
3154 my $res = $ua->request($req);
3156 if ($res->is_success) {
3157 foreach my $l (split(/\n/, $res->content)) {
3158 my ($k, $c) = split(/=/,$l,2);
3162 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3163 new DlgWarn($self->{error});
3169 my ($self, %arg) = @_;
3171 my $ua = LWP::UserAgent->new();
3172 $ua->agent("Brestore/$VERSION");
3173 my $req = POST($self->{pref}->{bconsole},
3174 Content_Type => 'form-data',
3175 Content => [ job => $arg{job},
3176 client => $arg{client},
3177 storage => $arg{storage} || '',
3178 fileset => $arg{fileset} || '',
3179 where => $arg{where},
3180 replace => $arg{replace},
3181 priority=> $arg{prio} || '',
3184 bootstrap => [$arg{bootstrap}],
3186 #$req->authorization_basic('eric', 'test');
3188 my $res = $ua->request($req);
3190 if ($res->is_success) {
3191 foreach my $l (split(/\n/, $res->content)) {
3192 my ($k, $c) = split(/=/,$l,2);
3197 if (!$self->{run}) {
3198 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3201 unlink($arg{bootstrap});
3203 return $self->{run};
3209 return sort split(/;/, $self->{'list_job'});
3215 return sort split(/;/, $self->{'list_fileset'});
3221 return sort split(/;/, $self->{'list_storage'});
3226 return sort split(/;/, $self->{'list_client'});
3237 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3241 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3244 GetOptions("conf=s" => \$file_conf,
3245 "batch" => \$batch_mod,
3247 "help" => \&HELP_MESSAGE) ;
3249 my $p = new Pref($file_conf);
3251 if (! -f $file_conf) {
3256 my $vfs = new Bvfs(conf => $p);
3257 if ($p->connect_db()) {
3258 $vfs->update_cache();
3263 $glade_file = $p->{glade_file};
3265 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3266 if (-f "$path/$glade_file") {
3267 $glade_file = "$path/$glade_file" ;
3272 # gtk have lots of warning on stderr
3273 if ($^O eq 'MSWin32')
3276 open(STDERR, ">stderr.log");
3281 if ( -f $glade_file) {
3282 my $w = new DlgResto($p);
3285 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3286 "Can't find your brestore.glade (glade_file => '$glade_file')
3287 Please, edit your $file_conf to setup it." );
3289 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3294 Gtk2->main; # Start Gtk2 main loop
3303 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3305 $p->connect_db() || print $p->{error};
3307 my $bvfs = new Bvfs(conf => $p);
3309 $bvfs->debug($bvfs->get_root());
3310 $bvfs->ch_dir($bvfs->get_root());
3312 $bvfs->set_curjobids(268,178,282,281,279);
3314 my $dirs = $bvfs->ls_dirs();
3315 $bvfs->ch_dir(123496);
3316 $dirs = $bvfs->ls_dirs();
3318 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);