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->ch_dir($fs->get_root());
1289 # refresh_fileview will be done by list_backup_changed
1292 my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1293 $self->{pref}->{use_ok_bkp_only});
1295 foreach my $endtime (@endtimes)
1297 my $i = $self->{list_backup}->append();
1299 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1300 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1301 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1303 $self->{list_backup}->set($i,
1308 $self->{restore_backup_combobox}->set_active(0);
1313 sub fill_server_list
1315 my ($dbh, $combo, $list) = @_;
1317 my @clients=get_all_clients($dbh);
1321 my $i = $list->append();
1322 $list->set($i, 0, $client_list_empty);
1324 foreach my $client (@clients)
1326 $i = $list->append();
1327 $list->set($i, 0, $client);
1329 $combo->set_active(0);
1332 sub init_server_backup_combobox
1335 fill_server_list($self->{conf}->{dbh},
1336 $self->{client_combobox},
1337 $self->{list_client}) ;
1340 #----------------------------------------------------------------------
1341 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1342 #data isn't. There is additionnal complexity for dirs (visibility problems),
1343 #so the @CurrentJobIds is not sufficient.
1344 sub refresh_fileview
1347 my $fileview = $self->{fileview};
1348 my $client_combobox = $self->{client_combobox};
1349 my $bvfs = $self->{bvfs};
1351 @{$fileview->{data}} = ();
1353 $self->clear_infoview();
1355 my $client_name = $self->current_client;
1357 if (!$client_name or ($client_name eq $client_list_empty)) {
1358 $self->set_status("Client list empty");
1362 # [ [dirid, dir_basename, File.LStat, jobid]..]
1363 my $list_dirs = $bvfs->ls_dirs();
1364 # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1365 my $files = $bvfs->ls_files();
1367 my $file_count = 0 ;
1368 my $total_bytes = 0;
1370 # Add directories to view
1371 foreach my $dir_entry (@$list_dirs) {
1372 my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1373 $total_bytes += 4096;
1376 listview_push($fileview,
1380 # TODO: voir ce que l'on met la
1391 foreach my $file (@$files)
1393 my $size = Bvfs::file_attrib($file,'st_size');
1394 my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1395 $total_bytes += $size;
1397 # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1398 listview_push($fileview,
1407 human($size), $time);
1410 $self->set_status("$file_count files/" . human($total_bytes));
1411 $self->{cwd} = $self->{bvfs}->pwd();
1412 $self->{location}->set_text($self->{cwd});
1413 # set a decent default selection (makes keyboard nav easy)
1414 $fileview->select(0);
1418 sub on_about_activate
1420 DlgAbout::display();
1425 my ($tree, $path, $data) = @_;
1427 my @items = listview_get_all($tree) ;
1429 foreach my $i (@items)
1431 my @file_info = @{$i};
1434 # Ok, we have a corner case :
1436 my $file = pack("u", $path . $file_info[2]);
1438 push @ret, join(" ; ", $file,
1439 $file_info[0], # $pathid
1440 $file_info[1], # $filenameid
1441 $file_info[3], # $jobid
1442 $file_info[4], # $type
1446 my $data_get = join(" :: ", @ret);
1448 $data->set_text($data_get,-1);
1453 sub fileview_data_get
1455 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1456 drag_set_info($widget, $self->{cwd}, $data);
1459 sub fileinfo_data_get
1461 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1462 drag_set_info($widget, $self->{cwd}, $data);
1465 sub restore_list_data_received
1467 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1469 $self->debug("start\n");
1470 if ($info eq 40 || $info eq 0) # patch for display!=:0
1472 foreach my $elt (split(/ :: /, $data->data()))
1474 my ($file, $pathid, $filenameid, $jobid, $type) =
1476 $file = unpack("u", $file);
1478 $self->add_selected_file_to_list($pathid,$filenameid,
1479 $file, $jobid, $type);
1482 $self->debug("end\n");
1485 sub on_back_button_clicked {
1487 $self->{bvfs}->up_dir();
1488 $self->refresh_fileview();
1490 sub on_location_go_button_clicked
1493 $self->ch_dir($self->{location}->get_text());
1495 sub on_quit_activate {Gtk2->main_quit;}
1496 sub on_preferences_activate
1499 $self->{dlg_pref}->display($self) ;
1501 sub on_main_delete_event {Gtk2->main_quit;}
1502 sub on_bweb_activate
1505 $self->set_status("Open bweb on your browser");
1506 $self->{pref}->go_bweb('', "go on bweb");
1509 # Change the current working directory
1510 # * Updates fileview, location, and selection
1516 my $p = $self->{bvfs}->get_pathid($l);
1518 $self->{bvfs}->ch_dir($p);
1519 $self->refresh_fileview();
1521 $self->set_status("Can't find $l");
1526 # Handle dialog 'close' (window-decoration induced close)
1527 # * Just hide the dialog, and tell Gtk not to do anything else
1531 my ($self, $w) = @_;
1534 1; # consume this event!
1537 # Handle key presses in location text edit control
1538 # * Translate a Return/Enter key into a 'Go' command
1539 # * All other key presses left for GTK
1541 sub on_location_entry_key_release_event
1547 my $keypress = $event->keyval;
1548 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1549 $keypress == $Gtk2::Gdk::Keysyms{Return})
1551 $self->ch_dir($widget->get_text());
1553 return 1; # consume keypress
1556 return 0; # let gtk have the keypress
1559 sub on_fileview_key_press_event
1561 my ($self, $widget, $event) = @_;
1565 sub listview_get_first
1568 my @selected = $list->get_selected_indices();
1569 if (@selected > 0) {
1570 my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1571 return ($pid,$fid,unpack('u', $name), @other);
1577 sub listview_get_all
1581 my @selected = $list->get_selected_indices();
1583 for my $i (@selected) {
1584 my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1585 push @ret, [$pid,$fid,unpack('u', $name), @other];
1592 my ($list, $pid, $fid, $name, @other) = @_;
1593 push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1596 #-----------------------------------------------------------------
1597 # Handle keypress in file-view
1598 # * Translates backspace into a 'cd ..' command
1599 # * All other key presses left for GTK
1601 sub on_fileview_key_release_event
1603 my ($self, $widget, $event) = @_;
1604 if (not $event->keyval)
1608 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1609 $self->on_back_button_clicked();
1610 return 1; # eat keypress
1613 return 0; # let gtk have keypress
1616 sub on_forward_keypress
1621 #-------------------------------------------------------------------
1622 # Handle double-click (or enter) on file-view
1623 # * Translates into a 'cd <dir>' command
1625 sub on_fileview_row_activated
1627 my ($self, $widget) = @_;
1629 my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1633 $self->{bvfs}->ch_dir($pid);
1634 $self->refresh_fileview();
1636 $self->fill_infoview($pid,$fid,$name);
1639 return 1; # consume event
1644 my ($self, $path, $file, $fn) = @_;
1645 $self->clear_infoview();
1646 my @v = $self->{bvfs}->get_all_file_versions($path,
1648 $self->current_client,
1649 $self->{pref}->{see_all_versions});
1651 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1652 $size,$inchanger,$md5,$volname) = @{$ver};
1653 my $icon = ($inchanger)?$yesicon:$noicon;
1655 $mtime = localtime($mtime) ;
1657 listview_push($self->{fileinfo},$pid,$fid,
1658 $fn, $jobid, 'file',
1659 $icon, $volname, $jobid, human($size), $mtime, $md5);
1666 return $self->{restore_backup_combobox}->get_active_text;
1672 return $self->{client_combobox}->get_active_text;
1675 sub on_list_backups_changed
1677 my ($self, $widget) = @_;
1678 return 0 unless defined $self->{fileview};
1680 $self->{CurrentJobIds} = [
1681 set_job_ids_for_date($self->dbh(),
1682 $self->current_client,
1683 $self->current_date,
1684 $self->{pref}->{use_ok_bkp_only})
1686 $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1687 $self->refresh_fileview();
1691 sub on_restore_list_keypress
1693 my ($self, $widget, $event) = @_;
1694 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1696 my @sel = $widget->get_selected_indices;
1697 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1699 splice @{$self->{restore_list}->{data}},$elt,1;
1704 sub on_fileview_button_press_event
1706 my ($self,$widget,$event) = @_;
1707 if ($event->button == 3)
1709 $self->on_right_click_filelist($widget,$event);
1713 if ($event->button == 2)
1715 $self->on_see_all_version();
1722 sub on_see_all_version
1726 my @lst = listview_get_all($self->{fileview});
1729 my ($pid,$fid,$name, undef) = @{$i};
1731 new DlgFileVersion($self->{bvfs},
1732 $self->current_client,
1733 $pid,$fid,$self->{cwd},$name);
1737 sub on_right_click_filelist
1739 my ($self,$widget,$event) = @_;
1740 # I need to know what's selected
1741 my @sel = listview_get_all($self->{fileview});
1746 $type = $sel[0]->[4]; # $type
1751 if (@sel >=2 or $type eq 'dir')
1753 # We have selected more than one or it is a directories
1754 $w = $self->{filelist_dir_menu};
1758 $w = $self->{filelist_file_menu};
1764 $event->button, $event->time);
1767 sub context_add_to_filelist
1771 my @sel = listview_get_all($self->{fileview});
1773 foreach my $i (@sel)
1775 my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1776 $file = $self->{cwd} . '/' . $file;
1777 $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1781 # Adds a file to the filelist
1782 sub add_selected_file_to_list
1784 my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1786 my $restore_list = $self->{restore_list};
1788 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1795 if ($name and substr $name,-1 ne '/')
1797 $name .= '/'; # For bacula
1799 my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1800 listview_push($restore_list,$pid,0,
1801 $name, $jobid, 'dir', $curjobids,
1802 $diricon, $name,$curjobids,$dirfileindex);
1804 elsif ($type eq 'file')
1806 my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1808 listview_push($restore_list,$pid,$fid,
1809 $name, $jobid, 'file', $curjobids,
1810 $fileicon, $name, $jobid, $fileindex );
1814 # TODO : we want be able to restore files from a bad ended backup
1815 # we have JobStatus IN ('T', 'A', 'E') and we must
1817 # Data acces subs from here. Interaction with SGBD and caching
1819 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1820 # GUI and stores them in @CurrentJobIds
1821 sub set_job_ids_for_date
1823 my ($dbh, $client, $date, $only_ok)=@_;
1825 if (!$client or !$date) {
1829 my $status = get_wanted_job_status($only_ok);
1831 # The algorithm : for a client, we get all the backups for each
1832 # fileset, in reverse order Then, for each fileset, we store the 'good'
1833 # incrementals and differentials until we have found a full so it goes
1834 # like this : store all incrementals until we have found a differential
1835 # or a full, then find the full #
1837 my $query = "SELECT JobId, FileSet, Level, JobStatus
1838 FROM Job, Client, FileSet
1839 WHERE Job.ClientId = Client.ClientId
1840 AND FileSet.FileSetId = Job.FileSetId
1841 AND EndTime <= '$date'
1842 AND Client.Name = '$client'
1844 AND JobStatus IN ($status)
1845 ORDER BY FileSet, JobTDate DESC";
1848 my $result = $dbh->selectall_arrayref($query);
1850 foreach my $refrow (@$result)
1852 my $jobid = $refrow->[0];
1853 my $fileset = $refrow->[1];
1854 my $level = $refrow->[2];
1856 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1858 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1862 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1863 push @CurrentJobIds,($jobid);
1865 elsif ($level eq 'D')
1867 next if $progress{$fileset} eq 'D'; # We allready have a differential
1868 push @CurrentJobIds,($jobid);
1870 elsif ($level eq 'F')
1872 push @CurrentJobIds,($jobid);
1875 my $status = $refrow->[3] ;
1876 if ($status eq 'T') { # good end of job
1877 $progress{$fileset} = $level;
1881 return @CurrentJobIds;
1886 Gtk2->main_iteration while (Gtk2->events_pending);
1889 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1890 # This sub creates a BSR from the information in the restore_list
1891 # Returns the BSR as a string
1896 # This query gets all jobid/jobmedia/media combination.
1898 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
1899 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1900 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
1901 Media.Volumename, Media.MediaType
1902 FROM Job, JobMedia, Media
1903 WHERE Job.JobId = JobMedia.JobId
1904 AND JobMedia.MediaId = Media.MediaId
1905 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1908 my $result = $self->dbh_selectall_arrayref($query);
1910 # We will store everything hashed by jobid.
1912 foreach my $refrow (@$result)
1914 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1915 $firstindex, $lastindex, $startblock, $endblock,
1916 $volindex, $volumename, $mediatype) = @{$refrow};
1918 # We just have to deal with the case where starfile != endfile
1919 # In this case, we concatenate both, for the bsr
1920 if ($startfile != $endfile) {
1921 $startfile = $startfile . '-' . $endfile;
1925 ($jobid, $volsessionid, $volsessiontime, $startfile,
1926 $firstindex, $lastindex, $startblock .'-'. $endblock,
1927 $volindex, $volumename, $mediatype);
1929 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
1933 # reminder : restore_list looks like this :
1934 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1935 # undef, undef, undef, $dirfileindex);
1937 # Here, we retrieve every file/dir that could be in the restore
1938 # We do as simple as possible for the SQL engine (no crazy joins,
1939 # no pseudo join (>= FirstIndex ...), etc ...
1940 # We do a SQL union of all the files/dirs specified in the restore_list
1942 foreach my $entry (@{$self->{restore_list}->{data}})
1944 if ($entry->[4] eq 'dir')
1946 my $dirid = $entry->[0];
1947 my $inclause = $entry->[5]; #curjobids
1950 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1951 FROM File, Path, Filename
1952 WHERE Path.PathId = File.PathId
1953 AND File.FilenameId = Filename.FilenameId
1955 (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
1956 WHERE PathId IN ($dirid)
1958 SELECT " . $self->dbh_strcat('Path',"'\%'") ." FROM brestore_missing_path WHERE PathId IN ($dirid)
1960 AND File.JobId IN ($inclause) )";
1961 push @select_queries,($query);
1965 # It's a file. Great, we allready have most
1966 # of what is needed. Simple and efficient query
1967 my $dir = $entry->[0];
1968 my $file = $entry->[1];
1970 my $jobid = $entry->[3];
1971 my $fileindex = $entry->[9];
1972 my $inclause = $entry->[5]; # curjobids
1974 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1975 FROM File,Path,Filename
1976 WHERE File.PathId = $dir
1977 AND File.PathId = Path.PathId
1978 AND File.FilenameId = $file
1979 AND File.FilenameId = Filename.FilenameId
1980 AND File.JobId = $jobid
1983 push @select_queries,($query);
1986 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
1988 #Now we run the query and parse the result...
1989 # there may be a lot of records, so we better be efficient
1990 # We use the bind column method, working with references...
1992 my $sth = $self->dbh_prepare($query);
1995 my ($path,$name,$fileindex,$jobid);
1996 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
1998 # The temp place we're going to save all file
1999 # list to before the real list
2003 while ($sth->fetchrow_arrayref())
2005 # This may look dumb, but we're going to do a join by ourselves,
2006 # to save memory and avoid sending a complex query to mysql
2007 my $complete_path = $path . $name;
2015 # Remove trailing slash (normalize file and dir name)
2016 $complete_path =~ s/\/$//;
2018 # Let's find the ref(s) for the %mediainfo element(s)
2019 # containing the data for this file
2020 # There can be several matches. It is the pseudo join.
2022 my $max_elt=@{$mediainfos{$jobid}}-1;
2024 while($med_idx <= $max_elt)
2026 my $ref = $mediainfos{$jobid}->[$med_idx];
2027 # First, can we get rid of the first elements of the
2028 # array ? (if they don't contain valuable records
2030 if ($fileindex > $ref->[5])
2032 # It seems we don't need anymore
2033 # this entry in %mediainfo (the input data
2036 shift @{$mediainfos{$jobid}};
2040 # We will do work on this elt. We can ++
2041 # $med_idx for next loop
2044 # %mediainfo row looks like :
2045 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2046 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2049 # We are in range. We store and continue looping
2051 if ($fileindex >= $ref->[4])
2053 my @data = ($complete_path,$is_dir,
2055 push @temp_list,(\@data);
2059 # We are not in range. No point in continuing looping
2060 # We go to next record.
2064 # Now we have the array.
2065 # We're going to sort it, by
2066 # path, volsessiontime DESC (get the most recent file...)
2067 # The array rows look like this :
2068 # complete_path,is_dir,fileindex,
2069 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2070 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2071 @temp_list = sort {$a->[0] cmp $b->[0]
2072 || $b->[3]->[2] <=> $a->[3]->[2]
2076 my $prev_complete_path='////'; # Sure not to match
2080 while (my $refrow = shift @temp_list)
2082 # For the sake of readability, we load $refrow
2083 # contents in real scalars
2084 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2085 my $jobid= $refother->[0]; # We don't need the rest...
2087 # We skip this entry.
2088 # We allready have a newer one and this
2089 # isn't a continuation of the same file
2090 next if ($complete_path eq $prev_complete_path
2091 and $jobid != $prev_jobid);
2095 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2097 # We would be recursing inside a file.
2098 # Just what we don't want (dir replaced by file
2099 # between two backups
2105 push @restore_list,($refrow);
2107 $prev_complete_path = $complete_path;
2108 $prev_jobid = $jobid;
2114 push @restore_list,($refrow);
2116 $prev_complete_path = $complete_path;
2117 $prev_jobid = $jobid;
2121 # We get rid of @temp_list... save memory
2124 # Ok everything is in the list. Let's sort it again in another way.
2125 # This time it will be in the bsr file order
2127 # we sort the results by
2128 # volsessiontime, volsessionid, volindex, fileindex
2129 # to get all files in right order...
2130 # Reminder : The array rows look like this :
2131 # complete_path,is_dir,fileindex,
2132 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2133 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2135 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2136 || $a->[3]->[1] <=> $b->[3]->[1]
2137 || $a->[3]->[7] <=> $b->[3]->[7]
2138 || $a->[2] <=> $b->[2] }
2141 # Now that everything is ready, we create the bsr
2142 my $prev_fileindex=-1;
2143 my $prev_volsessionid=-1;
2144 my $prev_volsessiontime=-1;
2145 my $prev_volumename=-1;
2146 my $prev_volfile=-1;
2150 my $first_of_current_range=0;
2151 my @fileindex_ranges;
2154 foreach my $refrow (@restore_list)
2156 my (undef,undef,$fileindex,$refother)=@{$refrow};
2157 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2158 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2160 # We can specifiy the number of files in each section of the
2161 # bsr to speedup restore (bacula can then jump over the
2162 # end of tape files.
2166 if ($prev_volumename eq '-1')
2168 # We only have to start the new range...
2169 $first_of_current_range=$fileindex;
2171 elsif ($prev_volsessionid != $volsessionid
2172 or $prev_volsessiontime != $volsessiontime
2173 or $prev_volumename ne $volumename
2174 or $prev_volfile ne $volfile)
2176 # We have to create a new section in the bsr...
2177 # We print the previous one ...
2178 # (before that, save the current range ...)
2179 if ($first_of_current_range != $prev_fileindex)
2182 push @fileindex_ranges,
2183 ("$first_of_current_range-$prev_fileindex");
2187 # We are out of a range,
2188 # but there is only one element in the range
2189 push @fileindex_ranges,
2190 ("$first_of_current_range");
2193 $bsr.=print_bsr_section(\@fileindex_ranges,
2195 $prev_volsessiontime,
2202 # Reset for next loop
2203 @fileindex_ranges=();
2204 $first_of_current_range=$fileindex;
2206 elsif ($fileindex-1 != $prev_fileindex)
2208 # End of a range of fileindexes
2209 if ($first_of_current_range != $prev_fileindex)
2212 push @fileindex_ranges,
2213 ("$first_of_current_range-$prev_fileindex");
2217 # We are out of a range,
2218 # but there is only one element in the range
2219 push @fileindex_ranges,
2220 ("$first_of_current_range");
2222 $first_of_current_range=$fileindex;
2224 $prev_fileindex=$fileindex;
2225 $prev_volsessionid = $volsessionid;
2226 $prev_volsessiontime = $volsessiontime;
2227 $prev_volumename = $volumename;
2228 $prev_volfile=$volfile;
2229 $prev_mediatype=$mediatype;
2230 $prev_volblocks=$volblocks;
2234 # Ok, we're out of the loop. Alas, there's still the last record ...
2235 if ($first_of_current_range != $prev_fileindex)
2238 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2243 # We are out of a range,
2244 # but there is only one element in the range
2245 push @fileindex_ranges,("$first_of_current_range");
2248 $bsr.=print_bsr_section(\@fileindex_ranges,
2250 $prev_volsessiontime,
2260 sub print_bsr_section
2262 my ($ref_fileindex_ranges,$volsessionid,
2263 $volsessiontime,$volumename,$volfile,
2264 $mediatype,$volblocks,$count)=@_;
2267 $bsr .= "Volume=\"$volumename\"\n";
2268 $bsr .= "MediaType=\"$mediatype\"\n";
2269 $bsr .= "VolSessionId=$volsessionid\n";
2270 $bsr .= "VolSessionTime=$volsessiontime\n";
2271 $bsr .= "VolFile=$volfile\n";
2272 $bsr .= "VolBlock=$volblocks\n";
2274 foreach my $range (@{$ref_fileindex_ranges})
2276 $bsr .= "FileIndex=$range\n";
2279 $bsr .= "Count=$count\n";
2285 ################################################################
2292 my ($self, $dir) = @_;
2294 "SELECT PathId FROM Path WHERE Path = ?
2296 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2297 my $sth = $self->dbh_prepare($query);
2298 $sth->execute($dir,$dir);
2299 my $result = $sth->fetchall_arrayref();
2302 return join(',', map { $_->[0] } @$result);
2310 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
2311 my $jobs = $self->dbh_selectall_arrayref($query);
2313 $self->update_brestore_table(map { $_->[0] } @$jobs);
2318 my ($self, $dir) = @_;
2319 return $self->get_pathid('');
2324 my ($self, $pathid) = @_;
2325 $self->{cwd} = $pathid;
2332 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) ";
2334 my $all = $self->dbh_selectall_arrayref($query);
2335 return unless ($all); # already at root
2337 my $dir = join(',', map { $_->[0] } @$all);
2339 $self->{cwd} = $dir;
2346 return $self->get_path($self->{cwd});
2351 my ($self, $pathid) = @_;
2352 $self->debug("Call with pathid = $pathid");
2354 "SELECT Path FROM Path WHERE PathId IN (?)
2356 SELECT Path FROM brestore_missing_path WHERE PathId IN (?)";
2357 my $sth = $self->dbh_prepare($query);
2358 $sth->execute($pathid,$pathid);
2359 my $result = $sth->fetchrow_arrayref();
2361 return $result->[0];
2366 my ($self, @jobids) = @_;
2367 $self->{curjobids} = join(',', @jobids);
2368 $self->update_brestore_table(@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 $self->debug(\@jobs);
2732 foreach my $job (sort {$a <=> $b} @jobs)
2734 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2735 my $retour = $self->dbh_selectrow_arrayref($query);
2736 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2738 print STDERR "Inserting path records for JobId $job\n";
2739 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2740 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2742 $self->dbh_do($query);
2744 # Now we have to do the directory recursion stuff to determine missing visibility
2745 # We try to avoid recursion, to be as fast as possible
2746 # We also only work on not allready hierarchised directories...
2748 print STDERR "Creating missing recursion paths for $job\n";
2750 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2751 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2752 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2753 WHERE brestore_pathvisibility.JobId = $job
2754 AND brestore_pathhierarchy.PathId IS NULL
2757 my $sth = $self->dbh_prepare($query);
2759 my $pathid; my $path;
2760 $sth->bind_columns(\$pathid,\$path);
2764 $self->build_path_hierarchy($path,$pathid);
2768 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2769 # This query gives all parent pathids for a given jobid that aren't stored.
2770 # It has to be called until no record is updated ...
2772 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2773 SELECT a.PathId,$job
2775 (SELECT DISTINCT h.PPathId AS PathId
2776 FROM brestore_pathhierarchy AS h
2777 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2778 WHERE p.JobId=$job) AS a
2781 FROM brestore_pathvisibility
2782 WHERE JobId=$job) AS b
2783 ON (a.PathId = b.PathId)
2784 WHERE b.PathId IS NULL)";
2787 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2789 print STDERR "Recursively adding $rows_affected records from $job\n";
2792 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2793 $self->dbh_do($query);
2797 sub cleanup_brestore_table
2801 my $query = "SELECT JobId from brestore_knownjobid";
2802 my @jobs = @{$self->dbh_selectall_arrayref($query)};
2804 foreach my $jobentry (@jobs)
2806 my $job = $jobentry->[0];
2807 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2808 my $result = $self->dbh_selectall_arrayref($query);
2809 if (scalar(@{$result}))
2811 # There are still files for this jobid
2812 print STDERR "$job still exists. Not cleaning...\n";
2815 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2816 $self->dbh_do($query);
2817 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2818 $self->dbh_do($query);
2831 # Root Windows case :
2832 if ($path =~ /^[a-z]+:\/$/i)
2837 my @tmp = split('/',$path);
2838 # We remove the last ...
2840 my $tmp = join ('/',@tmp) . '/';
2844 sub build_path_hierarchy
2846 my ($self, $path,$pathid)=@_;
2847 # Does the ppathid exist for this ? we use a memory cache...
2848 # In order to avoid the full loop, we consider that if a dir is allready in the
2849 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2852 if (! $self->{cache_ppathid}->{$pathid})
2854 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2855 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2856 $sth2->execute($pathid);
2857 # Do we have a result ?
2858 if (my $refrow = $sth2->fetchrow_arrayref)
2860 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2862 # This dir was in the db ...
2863 # It means we can leave, the tree has allready been built for
2868 # We have to create the record ...
2869 # What's the current p_path ?
2870 my $ppath = parent_dir($path);
2871 my $ppathid = $self->return_pathid_from_path($ppath);
2872 $self->{cache_ppathid}->{$pathid}= $ppathid;
2874 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2875 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2876 $sth2->execute($pathid,$ppathid);
2882 # It's allready in the cache.
2883 # We can leave, no time to waste here, all the parent dirs have allready
2892 sub return_pathid_from_path
2894 my ($self, $path) = @_;
2895 my $query = "SELECT PathId FROM Path WHERE Path = ?
2897 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2898 #print STDERR $query,"\n" if $debug;
2899 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2900 $sth->execute($path,$path);
2901 my $result =$sth->fetchrow_arrayref();
2903 if (defined $result)
2905 return $result->[0];
2908 # A bit dirty : we insert into path AND missing_path, to be sure
2909 # we aren't deleted by a purge. We still need to insert into path to get
2910 # the pathid, because of mysql
2911 $query = "INSERT INTO Path (Path) VALUES (?)";
2912 #print STDERR $query,"\n" if $debug;
2913 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2914 $sth->execute($path);
2917 $query = " INSERT INTO brestore_missing_path (PathId,Path)
2918 SELECT PathId,Path FROM Path WHERE Path = ?";
2919 #print STDERR $query,"\n" if $debug;
2920 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2921 $sth->execute($path);
2923 $query = " DELETE FROM Path WHERE Path = ?";
2924 #print STDERR $query,"\n" if $debug;
2925 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2926 $sth->execute($path);
2928 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2929 #print STDERR $query,"\n" if $debug;
2930 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2931 $sth->execute($path);
2932 $result = $sth->fetchrow_arrayref();
2934 return $result->[0];
2939 sub create_brestore_tables
2943 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2945 unless ($self->dbh_do($verif)) {
2946 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2948 $self->{error} = "Creating internal brestore tables";
2950 CREATE TABLE brestore_knownjobid
2952 JobId int4 NOT NULL,
2953 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2955 $self->dbh_do($req);
2958 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2959 unless ($self->dbh_do($verif)) {
2961 CREATE TABLE brestore_pathhierarchy
2963 PathId int4 NOT NULL,
2964 PPathId int4 NOT NULL,
2965 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2967 $self->dbh_do($req);
2970 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2971 ON brestore_pathhierarchy (PPathId)";
2972 $self->dbh_do($req);
2975 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2976 unless ($self->dbh_do($verif)) {
2978 CREATE TABLE brestore_pathvisibility
2980 PathId int4 NOT NULL,
2981 JobId int4 NOT NULL,
2982 Size int8 DEFAULT 0,
2983 Files int4 DEFAULT 0,
2984 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2986 $self->dbh_do($req);
2988 $req = "CREATE INDEX brestore_pathvisibility_jobid
2989 ON brestore_pathvisibility (JobId)";
2990 $self->dbh_do($req);
2993 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2994 unless ($self->dbh_do($verif)) {
2996 CREATE TABLE brestore_missing_path
2998 PathId int4 NOT NULL,
3000 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
3002 $self->dbh_do($req);
3004 $req = "CREATE INDEX brestore_missing_path_path
3005 ON brestore_missing_path (Path)";
3006 $self->dbh_do($req);
3012 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3013 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3014 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3015 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3016 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3017 'data_stream' => 15);;
3020 my ($attrib,$ref_attrib)=@_;
3021 return $ref_attrib->[$attrib_name_id{$attrib}];
3025 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3027 my ($file, $attrib)=@_;
3029 if (defined $attrib_name_id{$attrib}) {
3031 my @d = split(' ', $file->[3]) ; # TODO : cache this
3033 return from_base64($d[$attrib_name_id{$attrib}]);
3035 } elsif ($attrib eq 'jobid') {
3039 } elsif ($attrib eq 'name') {
3044 die "Attribute not known : $attrib.\n";
3050 my ($lstat,$attrib)=@_;
3051 if ($lstat and defined $attrib_name_id{$attrib})
3053 my @d = split(' ', $lstat) ; # TODO : cache this
3054 return from_base64($d[$attrib_name_id{$attrib}]);
3061 # Base 64 functions, directly from recover.pl.
3063 # Karl Hakimian <hakimian@aha.com>
3064 # This section is also under GPL v2 or later.
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 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3074 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3075 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3077 @base64_map = (0) x 128;
3079 for (my $i=0; $i<64; $i++) {
3080 $base64_map[ord($base64_digits[$i])] = $i;
3095 if (substr($where, 0, 1) eq '-') {
3097 $where = substr($where, 1);
3100 while ($where ne '') {
3102 my $d = substr($where, 0, 1);
3103 $val += $base64_map[ord(substr($where, 0, 1))];
3104 $where = substr($where, 1);
3112 my @attribs = split(' ',$lstat);
3113 foreach my $element (@attribs)
3115 $element = from_base64($element);
3123 ################################################################
3124 package BwebConsole;
3126 use HTTP::Request::Common;
3130 my ($class, %arg) = @_;
3133 pref => $arg{pref}, # Pref object
3134 timeout => $arg{timeout} || 20,
3135 debug => $arg{debug} || 0,
3137 'list_client' => '',
3138 'list_fileset' => '',
3139 'list_storage' => '',
3148 my ($self, @what) = @_;
3149 my $ua = LWP::UserAgent->new();
3150 $ua->agent("Brestore/$VERSION");
3151 my $req = POST($self->{pref}->{bconsole},
3152 Content_Type => 'form-data',
3153 Content => [ map { (action => $_) } @what ]);
3154 #$req->authorization_basic('eric', 'test');
3156 my $res = $ua->request($req);
3158 if ($res->is_success) {
3159 foreach my $l (split(/\n/, $res->content)) {
3160 my ($k, $c) = split(/=/,$l,2);
3164 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3165 new DlgWarn($self->{error});
3171 my ($self, %arg) = @_;
3173 my $ua = LWP::UserAgent->new();
3174 $ua->agent("Brestore/$VERSION");
3175 my $req = POST($self->{pref}->{bconsole},
3176 Content_Type => 'form-data',
3177 Content => [ job => $arg{job},
3178 client => $arg{client},
3179 storage => $arg{storage} || '',
3180 fileset => $arg{fileset} || '',
3181 where => $arg{where},
3182 replace => $arg{replace},
3183 priority=> $arg{prio} || '',
3186 bootstrap => [$arg{bootstrap}],
3188 #$req->authorization_basic('eric', 'test');
3190 my $res = $ua->request($req);
3192 if ($res->is_success) {
3193 foreach my $l (split(/\n/, $res->content)) {
3194 my ($k, $c) = split(/=/,$l,2);
3199 if (!$self->{run}) {
3200 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3203 unlink($arg{bootstrap});
3205 return $self->{run};
3211 return sort split(/;/, $self->{'list_job'});
3217 return sort split(/;/, $self->{'list_fileset'});
3223 return sort split(/;/, $self->{'list_storage'});
3228 return sort split(/;/, $self->{'list_client'});
3239 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3243 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3246 GetOptions("conf=s" => \$file_conf,
3247 "batch" => \$batch_mod,
3249 "help" => \&HELP_MESSAGE) ;
3251 my $p = new Pref($file_conf);
3253 if (! -f $file_conf) {
3258 my $vfs = new Bvfs(conf => $p);
3259 if ($p->connect_db()) {
3260 $vfs->update_cache();
3265 $glade_file = $p->{glade_file};
3267 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3268 if (-f "$path/$glade_file") {
3269 $glade_file = "$path/$glade_file" ;
3274 # gtk have lots of warning on stderr
3275 if ($^O eq 'MSWin32')
3278 open(STDERR, ">stderr.log");
3283 if ( -f $glade_file) {
3284 my $w = new DlgResto($p);
3287 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3288 "Can't find your brestore.glade (glade_file => '$glade_file')
3289 Please, edit your $file_conf to setup it." );
3291 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3296 Gtk2->main; # Start Gtk2 main loop
3305 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3307 $p->connect_db() || print $p->{error};
3309 my $bvfs = new Bvfs(conf => $p);
3311 $bvfs->debug($bvfs->get_root());
3312 $bvfs->ch_dir($bvfs->get_root());
3314 $bvfs->set_curjobids(268,178,282,281,279);
3316 my $dirs = $bvfs->ls_dirs();
3317 $bvfs->ch_dir(123496);
3318 $dirs = $bvfs->ls_dirs();
3320 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);