5 # path to your brestore.glade
6 my $glade_file = 'brestore.glade' ;
10 brestore.pl - A Perl/Gtk console for Bacula
18 Setup ~/.brestore.conf to find your brestore.glade
20 On debian like system, you need :
21 - libgtk2-gladexml-perl
22 - libdbd-mysql-perl or libdbd-pg-perl
25 You have to add brestore_xxx tables to your catalog.
27 To speed up database query you have to create theses indexes
28 - CREATE INDEX file_pathid on File(PathId);
31 To follow restore job, you must have a running Bweb installation.
35 Bacula® - The Network Backup Solution
37 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
39 Brestore authors are Marc Cousin and Eric Bollengier.
40 The main author of Bacula is Kern Sibbald, with contributions from
41 many others, a complete list can be found in the file AUTHORS.
43 This program is Free Software; you can redistribute it and/or
44 modify it under the terms of version two of the GNU General Public
45 License as published by the Free Software Foundation plus additions
46 that are listed in the file LICENSE.
48 This program is distributed in the hope that it will be useful, but
49 WITHOUT ANY WARRANTY; without even the implied warranty of
50 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
51 General Public License for more details.
53 You should have received a copy of the GNU General Public License
54 along with this program; if not, write to the Free Software
55 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
58 Bacula® is a registered trademark of John Walker.
59 The licensor of Bacula is the Free Software Foundation Europe
60 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
61 Switzerland, email:ftf@fsfeurope.org.
63 Base 64 functions from Karl Hakimian <hakimian@aha.com>
64 Integrally copied from recover.pl from bacula source distribution.
68 use Gtk2; # auto-initialize Gtk2
70 use Gtk2::SimpleList; # easy wrapper for list views
71 use Gtk2::Gdk::Keysyms; # keyboard code constants
72 use Data::Dumper qw/Dumper/;
73 my $debug=0; # can be on brestore.conf
74 our ($VERSION) = ('$Revision$' =~ /(\d+\.\d+)/);
80 my ($class, $config_file) = @_;
83 config_file => $config_file,
84 password => '', # db passwd
85 username => '', # db username
86 connection_string => '',# db connection string
87 bconsole => 'bconsole', # path and arg to bconsole
88 bsr_dest => '', # destination url for bsr files
89 debug => 0, # debug level 0|1
90 use_ok_bkp_only => 1, # dont use bad backup
91 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
92 see_all_versions => 0, # display all file versions in FileInfo
93 mozilla => 'mozilla', # mozilla bin
94 default_restore_job => 'restore', # regular expression to select default
97 # keywords that are used to fill DlgPref
98 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
99 entry_keyword => [ qw/username password bweb mozilla
100 connection_string default_restore_job
101 bconsole bsr_dest glade_file/],
104 $self->read_config();
113 # We read the parameters. They come from the configuration files
114 my $cfgfile ; my $tmpbuffer;
115 if (open FICCFG, $self->{config_file})
117 while(read FICCFG,$tmpbuffer,4096)
119 $cfgfile .= $tmpbuffer;
123 no strict; # I have no idea of the contents of the file
124 eval '$refparams' . " = $cfgfile";
127 for my $p (keys %{$refparams}) {
128 $self->{$p} = $refparams->{$p};
132 # TODO : Force dumb default values and display a message
143 for my $k (@{ $self->{entry_keyword} }) {
144 $parameters{$k} = $self->{$k};
147 for my $k (@{ $self->{chk_keyword} }) {
148 $parameters{$k} = $self->{$k};
151 if (open FICCFG,">$self->{config_file}")
153 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
158 $self->{error} = "Can't write configuration $!";
160 return $self->{error};
168 $self->{dbh}->disconnect() ;
172 delete $self->{error};
174 if (not $self->{connection_string})
176 # The parameters have not been set. Maybe the conf
177 # file is empty for now
178 $self->{error} = "No configuration found for database connection. " .
179 "Please set this up.";
184 $self->{dbh} = DBI->connect($self->{connection_string},
189 $self->{error} = "Can't open bacula database. " .
190 "Database connect string '" .
191 $self->{connection_string} ."' $!";
194 $self->{is_mysql} = ($self->{connection_string} =~ m/dbi:mysql/i);
195 $self->{dbh}->{RowCacheSize}=100;
201 my ($self, $url, $msg) = @_;
203 unless ($self->{mozilla} and $self->{bweb}) {
204 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
208 if ($^O eq 'MSWin32') {
209 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
212 system("$self->{mozilla} -remote 'Ping()'");
213 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
215 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
225 ################################################################
231 my ($class, %arg) = @_;
244 my ($self, $what, %arg) = @_;
246 if ($self->{conf}->{debug} and defined $what) {
251 my $line = (caller($level))[2];
252 my $func = (caller($level+1))[3] || 'main';
253 print "$func:$line\t";
255 print Data::Dumper::Dumper($what);
256 } elsif ($arg{md5}) {
257 print "MD5=", md5_base64($what), " str=", $what,"\n";
266 my ($self, @what) = @_;
267 if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
268 return join(' || ', @what);
270 return 'CONCAT(' . join(',', @what) . ')' ;
276 my ($self, $query) = @_;
277 $self->debug($query, up => 1);
278 return $self->{conf}->{dbh}->prepare($query);
283 my ($self, $query) = @_;
284 $self->debug($query, up => 1);
285 return $self->{conf}->{dbh}->do($query);
288 sub dbh_selectall_arrayref
290 my ($self, $query) = @_;
291 $self->debug($query, up => 1);
292 return $self->{conf}->{dbh}->selectall_arrayref($query);
295 sub dbh_selectrow_arrayref
297 my ($self, $query) = @_;
298 $self->debug($query, up => 1);
299 return $self->{conf}->{dbh}->selectrow_arrayref($query);
305 return $self->{conf}->{dbh};
310 ################################################################
314 # my $pref = new Pref(config_file => 'brestore.conf');
315 # my $dlg = new DlgPref($pref);
316 # my $dlg_resto = new DlgResto($pref);
317 # $dlg->display($dlg_resto);
320 my ($class, $pref) = @_;
323 pref => $pref, # Pref ref
324 dlgresto => undef, # DlgResto ref
332 my ($self, $dlgresto) = @_ ;
334 unless ($self->{glade}) {
335 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
336 $self->{glade}->signal_autoconnect_from_package($self);
339 $self->{dlgresto} = $dlgresto;
341 my $g = $self->{glade};
342 my $p = $self->{pref};
344 for my $k (@{ $p->{entry_keyword} }) {
345 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
348 for my $k (@{ $p->{chk_keyword} }) {
349 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
352 $g->get_widget("dlg_pref")->show_all() ;
355 sub on_applybutton_clicked
358 my $glade = $self->{glade};
359 my $pref = $self->{pref};
361 for my $k (@{ $pref->{entry_keyword} }) {
362 my $w = $glade->get_widget("entry_$k") ;
363 $pref->{$k} = $w->get_text();
366 for my $k (@{ $pref->{chk_keyword} }) {
367 my $w = $glade->get_widget("chkbp_$k") ;
368 $pref->{$k} = $w->get_active();
371 if (!$pref->write_config() && $pref->connect_db()) {
372 $self->{dlgresto}->set_status('Preferences updated');
373 $self->{dlgresto}->init_server_backup_combobox();
374 $self->{dlgresto}->set_status($pref->{error});
377 $self->{dlgresto}->set_status($pref->{error});
381 # Handle prefs ok click (apply/dismiss dialog)
382 sub on_okbutton_clicked
385 $self->on_applybutton_clicked();
387 unless ($self->{pref}->{error}) {
388 $self->on_cancelbutton_clicked();
391 sub on_dialog_delete_event
394 $self->on_cancelbutton_clicked();
398 sub on_cancelbutton_clicked
401 $self->{glade}->get_widget('dlg_pref')->hide();
402 delete $self->{dlgresto};
406 ################################################################
408 package DlgFileVersion;
410 sub on_versions_close_clicked
412 my ($self, $widget)=@_;
413 $self->{version}->destroy();
416 sub on_selection_button_press_event
418 print STDERR "on_selection_button_press_event()\n";
421 sub fileview_data_get
423 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
425 DlgResto::drag_set_info($widget,
432 my ($class, $bvfs, $client, $path, $file, $cwd, $fn) = @_;
435 version => undef, # main window
438 # we load version widget of $glade_file
439 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
441 # Connect signals magically
442 $glade_box->signal_autoconnect_from_package($self);
444 $glade_box->get_widget("version_label")
445 ->set_markup("<b>File revisions : $client:$cwd$fn</b>");
447 my $widget = $glade_box->get_widget('version_fileview');
448 my $fileview = Gtk2::SimpleList->new_from_treeview(
450 'h_pathid' => 'hidden',
451 'h_filenameid' => 'hidden',
452 'h_name' => 'hidden',
453 'h_jobid' => 'hidden',
454 'h_type' => 'hidden',
456 'InChanger' => 'pixbuf',
463 DlgResto::init_drag_drop($fileview);
465 my @v = $bvfs->get_all_file_versions($path,
470 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
471 $inchanger,$md5,$volname) = @{$ver};
472 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
474 DlgResto::listview_push($fileview,$pid,$fid,
476 $icon, $volname, $jobid,DlgResto::human($size),
477 scalar(localtime($mtime)), $md5);
480 $self->{version} = $glade_box->get_widget('dlg_version');
481 $self->{version}->show();
486 sub on_forward_keypress
492 ################################################################
497 my ($package, $text) = @_;
501 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
503 # Connect signals magically
504 $glade->signal_autoconnect_from_package($self);
505 $glade->get_widget('label_warn')->set_text($text);
507 print STDERR "$text\n";
509 $self->{window} = $glade->get_widget('dlg_warn');
510 $self->{window}->show_all();
517 $self->{window}->destroy();
521 ################################################################
527 # %arg = (bsr_file => '/path/to/bsr', # on director
528 # volumes => [ '00001', '00004']
536 if ($pref->{bconsole} =~ /^http/) {
537 return new BwebConsole(pref => $pref);
539 if (eval { require Bconsole; }) {
540 return new Bconsole(pref => $pref);
542 new DlgWarn("Can't use bconsole, verify your setup");
550 my ($class, %arg) = @_;
553 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
554 pref => $arg{pref}, # Pref ref
555 glade => undef, # GladeXML ref
556 bconsole => undef, # Bconsole ref
559 my $console = $self->{bconsole} = get_bconsole($arg{pref});
564 # we load launch widget of $glade_file
565 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
568 # Connect signals magically
569 $glade->signal_autoconnect_from_package($self);
571 my $widget = $glade->get_widget('volumeview');
572 my $volview = Gtk2::SimpleList->new_from_treeview(
574 'InChanger' => 'pixbuf',
578 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
580 # we replace 0 and 1 by $noicon and $yesicon
581 for my $i (@{$infos}) {
583 $i->[0] = $DlgResto::noicon;
585 $i->[0] = $DlgResto::yesicon;
590 push @{ $volview->{data} }, @{$infos} ;
592 $console->prepare(qw/list_client list_job list_fileset list_storage/);
594 # fill client combobox (with director defined clients
595 my @clients = $console->list_client() ; # get from bconsole
596 if ($console->{error}) {
597 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
599 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
600 $self->{list_client} = DlgResto::init_combo($w, 'text');
601 DlgResto::fill_combo($self->{list_client},
602 $DlgResto::client_list_empty,
606 # fill fileset combobox
607 my @fileset = $console->list_fileset() ;
608 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
609 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
610 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
613 my @job = $console->list_job() ;
614 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
615 $self->{list_job} = DlgResto::init_combo($w, 'text');
616 DlgResto::fill_combo($self->{list_job}, '', @job);
618 # find default_restore_job in jobs list
619 my $default_restore_job = $arg{pref}->{default_restore_job} ;
623 if ($j =~ /$default_restore_job/io) {
629 $w->set_active($index);
631 # fill storage combobox
632 my @storage = $console->list_storage() ;
633 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
634 $self->{list_storage} = DlgResto::init_combo($w, 'text');
635 DlgResto::fill_combo($self->{list_storage}, '', @storage);
637 $glade->get_widget('dlg_launch')->show_all();
644 my ($self, $client, $jobid) = @_;
646 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
648 $self->on_cancel_resto_clicked();
651 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
652 "Your job have been submited to bacula.
653 To follow it, you must use bconsole (or install/configure bweb)");
659 sub on_cancel_resto_clicked
662 $self->{glade}->get_widget('dlg_launch')->destroy();
665 sub on_submit_resto_clicked
668 my $glade = $self->{glade};
670 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
673 new DlgWarn("Can't copy bsr file to director ($self->{error})");
677 my $fileset = $glade->get_widget('combo_launch_fileset')
680 my $storage = $glade->get_widget('combo_launch_storage')
683 my $where = $glade->get_widget('entry_launch_where')->get_text();
685 my $job = $glade->get_widget('combo_launch_job')
689 new DlgWarn("Can't use this job");
693 my $client = $glade->get_widget('combo_launch_client')
696 if (! $client or $client eq $DlgResto::client_list_empty) {
697 new DlgWarn("Can't use this client ($client)");
701 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
703 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
704 $replace=($replace)?'always':'never';
706 my $jobid = $self->{bconsole}->run(job => $job,
715 $self->show_job($client, $jobid);
718 sub on_combo_storage_button_press_event
721 print "on_combo_storage_button_press_event()\n";
724 sub on_combo_fileset_button_press_event
727 print "on_combo_fileset_button_press_event()\n";
731 sub on_combo_job_button_press_event
734 print "on_combo_job_button_press_event()\n";
737 sub get_volume_inchanger
739 my ($dbh, $vols) = @_;
741 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
743 my $rq = "SELECT InChanger, VolumeName
745 WHERE VolumeName IN ($lst)
748 my $res = $dbh->selectall_arrayref($rq);
749 return $res; # [ [ 1, VolName].. ]
753 use File::Copy qw/copy/;
754 use File::Basename qw/basename/;
756 # We must kown the path+filename destination
757 # $self->{error} contains error message
758 # it return 0/1 if fail/success
761 my ($self, $src, $dst) = @_ ;
762 print "$src => $dst\n"
773 if ($dst =~ m!file:/(/.+)!) {
774 $ret = copy($src, $1);
776 $dstfile = "$1/" . basename($src) ;
778 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
779 $err = `scp $src $1 2>&1` ;
781 $dstfile = "$2/" . basename($src) ;
785 $err = "$dst not implemented yet";
786 File::Copy::copy($src, \*STDOUT);
789 $self->{error} = $err;
792 $self->{error} = $err;
801 ################################################################
809 unless ($about_widget) {
810 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
811 $about_widget = $glade_box->get_widget("dlg_about") ;
812 $glade_box->signal_autoconnect_from_package('DlgAbout');
814 $about_widget->show() ;
817 sub on_about_okbutton_clicked
819 $about_widget->hide() ;
824 ################################################################
834 # Kept as is from the perl-gtk example. Draws the pretty icons
840 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
841 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
842 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
843 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
846 # init combo (and create ListStore object)
849 my ($widget, @type) = @_ ;
850 my %type_info = ('text' => 'Glib::String',
851 'markup' => 'Glib::String',
854 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
856 $widget->set_model($lst);
860 if ($t eq 'text' or $t eq 'markup') {
861 $cell = new Gtk2::CellRendererText();
863 $widget->pack_start($cell, 1);
864 $widget->add_attribute($cell, $t, $i++);
869 # fill simple combo (one element per row)
872 my ($list, @what) = @_;
876 foreach my $w (@what)
879 my $i = $list->append();
880 $list->set($i, 0, $w);
887 my @unit = qw(b Kb Mb Gb Tb);
890 my $format = '%i %s';
891 while ($val / 1024 > 1) {
895 $format = ($i>0)?'%0.1f %s':'%i %s';
896 return sprintf($format, $val, $unit[$i]);
899 sub get_wanted_job_status
906 return "'T', 'A', 'E'";
910 # This sub gives a full list of the EndTimes for a ClientId
911 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
912 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
913 sub get_all_endtimes_for_job
915 my ($self, $client, $ok_only)=@_;
916 my $status = get_wanted_job_status($ok_only);
918 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
919 FROM Job,Client,FileSet
920 WHERE Job.ClientId=Client.ClientId
921 AND Client.Name = '$client'
923 AND JobStatus IN ($status)
924 AND Job.FileSetId = FileSet.FileSetId
925 ORDER BY EndTime desc";
926 my $result = $self->dbh_selectall_arrayref($query);
933 my ($fileview) = shift;
934 my $fileview_target_entry = {target => 'STRING',
935 flags => ['GTK_TARGET_SAME_APP'],
938 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
939 ['copy'],$fileview_target_entry);
940 $fileview->get_selection->set_mode('multiple');
942 # set some useful SimpleList properties
943 $fileview->set_headers_clickable(0);
944 foreach ($fileview->get_columns())
946 $_->set_resizable(1);
947 $_->set_sizing('grow-only');
953 my ($class, $pref) = @_;
958 location => undef, # location entry widget
959 mainwin => undef, # mainwin widget
960 filelist_file_menu => undef, # file menu widget
961 filelist_dir_menu => undef, # dir menu widget
962 glade => undef, # glade object
963 status => undef, # status bar widget
964 dlg_pref => undef, # DlgPref object
965 fileattrib => {}, # cache file
966 fileview => undef, # fileview widget SimpleList
967 fileinfo => undef, # fileinfo widget SimpleList
969 client_combobox => undef, # client_combobox widget
970 restore_backup_combobox => undef, # date combobox widget
971 list_client => undef, # Gtk2::ListStore
972 list_backup => undef, # Gtk2::ListStore
973 cache_ppathid => {}, #
977 $self->{bvfs} = new Bvfs(conf => $pref);
979 # load menu (to use handler with self reference)
980 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
981 $glade->signal_autoconnect_from_package($self);
982 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
984 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
985 $glade->signal_autoconnect_from_package($self);
986 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
988 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
989 $glade->signal_autoconnect_from_package($self);
991 $self->{status} = $glade->get_widget('statusbar');
992 $self->{mainwin} = $glade->get_widget('dlg_resto');
993 $self->{location} = $glade->get_widget('entry_location');
994 $self->render_icons();
996 $self->{dlg_pref} = new DlgPref($pref);
998 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
999 $self->{list_client} = init_combo($c, 'text');
1001 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1002 $self->{list_backup} = init_combo($c, 'text', 'markup');
1004 # Connect glade-fileview to Gtk2::SimpleList
1005 # and set up drag n drop between $fileview and $restore_list
1007 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1008 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1010 my $widget = $glade->get_widget('fileview');
1011 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1013 'h_pathid' => 'hidden',
1014 'h_filenameid' => 'hidden',
1015 'h_name' => 'hidden',
1016 'h_jobid' => 'hidden',
1017 'h_type' => 'hidden',
1020 'File Name' => 'text',
1023 init_drag_drop($fileview);
1024 $fileview->set_search_column(6); # search on File Name
1026 # Connect glade-restore_list to Gtk2::SimpleList
1027 $widget = $glade->get_widget('restorelist');
1028 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1030 'h_pathid' => 'hidden', #0
1031 'h_filenameid' => 'hidden',
1032 'h_name' => 'hidden',
1033 'h_jobid' => 'hidden',
1034 'h_type' => 'hidden',
1035 'h_curjobid' => 'hidden', #5
1038 'File Name' => 'text',
1040 'FileIndex' => 'text',
1042 'Nb Files' => 'text', #10
1043 'Size' => 'text', #11
1044 'size_b' => 'hidden', #12
1047 my @restore_list_target_table = ({'target' => 'STRING',
1051 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1052 $restore_list->get_selection->set_mode('multiple');
1054 $widget = $glade->get_widget('infoview');
1055 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1057 'h_pathid' => 'hidden',
1058 'h_filenameid' => 'hidden',
1059 'h_name' => 'hidden',
1060 'h_jobid' => 'hidden',
1061 'h_type' => 'hidden',
1063 'InChanger' => 'pixbuf',
1070 init_drag_drop($infoview);
1072 $pref->connect_db() || $self->{dlg_pref}->display($self);
1075 $self->init_server_backup_combobox();
1076 $self->{bvfs}->create_brestore_tables();
1079 $self->set_status($pref->{error});
1082 # set status bar informations
1085 my ($self, $string) = @_;
1086 return unless ($string);
1088 my $context = $self->{status}->get_context_id('Main');
1089 $self->{status}->push($context, $string);
1092 sub on_time_select_changed
1100 my $c = $self->{glade}->get_widget('combo_time');
1101 return $c->get_active_text;
1104 # This sub returns all clients declared in DB
1108 my $query = "SELECT Name FROM Client ORDER BY Name";
1109 print STDERR $query,"\n" if $debug;
1111 my $result = $dbh->selectall_arrayref($query);
1113 return map { $_->[0] } @$result;
1116 # init infoview widget
1120 @{$self->{fileinfo}->{data}} = ();
1124 sub on_clear_clicked
1127 @{$self->{restore_list}->{data}} = ();
1130 sub on_estimate_clicked
1137 # TODO : If we get here, things could get lenghty ... draw a popup window .
1138 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1139 'destroy-with-parent',
1141 'Computing size...');
1145 my $title = "Computing size...\n";
1148 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1149 # undef, undef, undef, $dirfileindex);
1150 foreach my $entry (@{$self->{restore_list}->{data}})
1152 unless ($entry->[11]) {
1153 my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1154 $entry->[12] = $size;
1155 $entry->[11] = human($size);
1159 my $name = unpack('u', $entry->[2]);
1161 $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1162 $self->debug($title . $txt);
1163 $widget->set_markup($title . $txt);
1165 $size_total+=$entry->[12];
1166 $nb_total+=$entry->[10];
1170 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1171 $widget->set_markup("Size estimation :\n" . $txt);
1172 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1179 sub on_gen_bsr_clicked
1183 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1184 'gtk-save','ok', 'gtk-cancel', 'cancel');
1187 my $w = new Gtk2::FileChooserDialog ( @options );
1192 if ($a eq 'cancel') {
1197 my $f = $w->get_filename();
1199 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1200 'destroy-with-parent',
1201 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1202 if ($dlg->run() eq 'ok') {
1216 if (open(FP, ">$save")) {
1217 my $bsr = $self->create_filelist();
1220 $self->set_status("Dumping BSR to $save ok");
1222 $self->set_status("Can't dump BSR to $save: $!");
1228 use File::Temp qw/tempfile/;
1230 sub on_go_button_clicked
1233 unless (scalar(@{$self->{restore_list}->{data}})) {
1234 new DlgWarn("No file to restore");
1237 my $bsr = $self->create_filelist();
1238 my ($fh, $filename) = tempfile();
1241 chmod(0644, $filename);
1243 print "Dumping BSR info to $filename\n"
1246 # we get Volume list
1247 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1248 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1250 new DlgLaunch(pref => $self->{conf},
1252 bsr_file => $filename,
1258 our $client_list_empty = 'Clients list';
1259 our %type_markup = ('F' => '<b>$label F</b>',
1262 'B' => '<b>$label B</b>',
1264 'A' => '<span foreground=\"red\">$label</span>',
1266 'E' => '<span foreground=\"red\">$label</span>',
1269 sub on_list_client_changed
1271 my ($self, $widget) = @_;
1272 return 0 unless defined $self->{fileview};
1274 $self->{list_backup}->clear();
1276 if ($self->current_client eq $client_list_empty) {
1280 $self->{CurrentJobIds} = [
1281 set_job_ids_for_date($self->dbh(),
1282 $self->current_client,
1283 $self->current_date,
1284 $self->{conf}->{use_ok_bkp_only})
1287 my $fs = $self->{bvfs};
1288 $fs->set_curjobids(@{$self->{CurrentJobIds}});
1289 $fs->ch_dir($fs->get_root());
1290 # refresh_fileview will be done by list_backup_changed
1293 my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1294 $self->{conf}->{use_ok_bkp_only});
1296 foreach my $endtime (@endtimes)
1298 my $i = $self->{list_backup}->append();
1300 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1301 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1302 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1304 $self->{list_backup}->set($i,
1309 $self->{restore_backup_combobox}->set_active(0);
1314 sub fill_server_list
1316 my ($dbh, $combo, $list) = @_;
1318 my @clients=get_all_clients($dbh);
1322 my $i = $list->append();
1323 $list->set($i, 0, $client_list_empty);
1325 foreach my $client (@clients)
1327 $i = $list->append();
1328 $list->set($i, 0, $client);
1330 $combo->set_active(0);
1333 sub init_server_backup_combobox
1336 fill_server_list($self->{conf}->{dbh},
1337 $self->{client_combobox},
1338 $self->{list_client}) ;
1341 #----------------------------------------------------------------------
1342 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1343 #data isn't. There is additionnal complexity for dirs (visibility problems),
1344 #so the @CurrentJobIds is not sufficient.
1345 sub refresh_fileview
1348 my $fileview = $self->{fileview};
1349 my $client_combobox = $self->{client_combobox};
1350 my $bvfs = $self->{bvfs};
1352 @{$fileview->{data}} = ();
1354 $self->clear_infoview();
1356 my $client_name = $self->current_client;
1358 if (!$client_name or ($client_name eq $client_list_empty)) {
1359 $self->set_status("Client list empty");
1363 # [ [dirid, dir_basename, File.LStat, jobid]..]
1364 my $list_dirs = $bvfs->ls_dirs();
1365 # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1366 my $files = $bvfs->ls_files();
1368 my $file_count = 0 ;
1369 my $total_bytes = 0;
1371 # Add directories to view
1372 foreach my $dir_entry (@$list_dirs) {
1373 my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1374 $total_bytes += 4096;
1377 listview_push($fileview,
1381 # TODO: voir ce que l'on met la
1392 foreach my $file (@$files)
1394 my $size = Bvfs::file_attrib($file,'st_size');
1395 my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1396 $total_bytes += $size;
1398 # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1399 listview_push($fileview,
1408 human($size), $time);
1411 $self->set_status("$file_count files/" . human($total_bytes));
1412 $self->{cwd} = $self->{bvfs}->pwd();
1413 $self->{location}->set_text($self->{cwd});
1414 # set a decent default selection (makes keyboard nav easy)
1415 $fileview->select(0);
1419 sub on_about_activate
1421 DlgAbout::display();
1426 my ($tree, $path, $data) = @_;
1428 my @items = listview_get_all($tree) ;
1430 foreach my $i (@items)
1432 my @file_info = @{$i};
1435 # Ok, we have a corner case :
1437 my $file = pack("u", $path . $file_info[2]);
1439 push @ret, join(" ; ", $file,
1440 $file_info[0], # $pathid
1441 $file_info[1], # $filenameid
1442 $file_info[3], # $jobid
1443 $file_info[4], # $type
1447 my $data_get = join(" :: ", @ret);
1449 $data->set_text($data_get,-1);
1454 sub fileview_data_get
1456 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1457 drag_set_info($widget, $self->{cwd}, $data);
1460 sub fileinfo_data_get
1462 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1463 drag_set_info($widget, $self->{cwd}, $data);
1466 sub restore_list_data_received
1468 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1470 $self->debug("start\n");
1471 if ($info eq 40 || $info eq 0) # patch for display!=:0
1473 foreach my $elt (split(/ :: /, $data->data()))
1475 my ($file, $pathid, $filenameid, $jobid, $type) =
1477 $file = unpack("u", $file);
1479 $self->add_selected_file_to_list($pathid,$filenameid,
1480 $file, $jobid, $type);
1483 $self->debug("end\n");
1486 sub on_back_button_clicked {
1488 $self->{bvfs}->up_dir();
1489 $self->refresh_fileview();
1491 sub on_location_go_button_clicked
1494 $self->ch_dir($self->{location}->get_text());
1496 sub on_quit_activate {Gtk2->main_quit;}
1497 sub on_preferences_activate
1500 $self->{dlg_pref}->display($self) ;
1502 sub on_main_delete_event {Gtk2->main_quit;}
1503 sub on_bweb_activate
1506 $self->set_status("Open bweb on your browser");
1507 $self->{conf}->go_bweb('', "go on bweb");
1510 # Change the current working directory
1511 # * Updates fileview, location, and selection
1517 my $p = $self->{bvfs}->get_pathid($l);
1519 $self->{bvfs}->ch_dir($p);
1520 $self->refresh_fileview();
1522 $self->set_status("Can't find $l");
1527 # Handle dialog 'close' (window-decoration induced close)
1528 # * Just hide the dialog, and tell Gtk not to do anything else
1532 my ($self, $w) = @_;
1535 1; # consume this event!
1538 # Handle key presses in location text edit control
1539 # * Translate a Return/Enter key into a 'Go' command
1540 # * All other key presses left for GTK
1542 sub on_location_entry_key_release_event
1548 my $keypress = $event->keyval;
1549 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1550 $keypress == $Gtk2::Gdk::Keysyms{Return})
1552 $self->ch_dir($widget->get_text());
1554 return 1; # consume keypress
1557 return 0; # let gtk have the keypress
1560 sub on_fileview_key_press_event
1562 my ($self, $widget, $event) = @_;
1566 sub listview_get_first
1569 my @selected = $list->get_selected_indices();
1570 if (@selected > 0) {
1571 my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1572 return ($pid,$fid,unpack('u', $name), @other);
1578 sub listview_get_all
1582 my @selected = $list->get_selected_indices();
1584 for my $i (@selected) {
1585 my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1586 push @ret, [$pid,$fid,unpack('u', $name), @other];
1593 my ($list, $pid, $fid, $name, @other) = @_;
1594 push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1597 #-----------------------------------------------------------------
1598 # Handle keypress in file-view
1599 # * Translates backspace into a 'cd ..' command
1600 # * All other key presses left for GTK
1602 sub on_fileview_key_release_event
1604 my ($self, $widget, $event) = @_;
1605 if (not $event->keyval)
1609 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1610 $self->on_back_button_clicked();
1611 return 1; # eat keypress
1614 return 0; # let gtk have keypress
1617 sub on_forward_keypress
1622 #-------------------------------------------------------------------
1623 # Handle double-click (or enter) on file-view
1624 # * Translates into a 'cd <dir>' command
1626 sub on_fileview_row_activated
1628 my ($self, $widget) = @_;
1630 my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1634 $self->{bvfs}->ch_dir($pid);
1635 $self->refresh_fileview();
1637 $self->fill_infoview($pid,$fid,$name);
1640 return 1; # consume event
1645 my ($self, $path, $file, $fn) = @_;
1646 $self->clear_infoview();
1647 my @v = $self->{bvfs}->get_all_file_versions($path,
1649 $self->current_client,
1650 $self->{conf}->{see_all_versions});
1652 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1653 $size,$inchanger,$md5,$volname) = @{$ver};
1654 my $icon = ($inchanger)?$yesicon:$noicon;
1656 $mtime = localtime($mtime) ;
1658 listview_push($self->{fileinfo},$pid,$fid,
1659 $fn, $jobid, 'file',
1660 $icon, $volname, $jobid, human($size), $mtime, $md5);
1667 return $self->{restore_backup_combobox}->get_active_text;
1673 return $self->{client_combobox}->get_active_text;
1676 sub on_list_backups_changed
1678 my ($self, $widget) = @_;
1679 return 0 unless defined $self->{fileview};
1681 $self->{CurrentJobIds} = [
1682 set_job_ids_for_date($self->dbh(),
1683 $self->current_client,
1684 $self->current_date,
1685 $self->{conf}->{use_ok_bkp_only})
1687 $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1688 $self->refresh_fileview();
1692 sub on_restore_list_keypress
1694 my ($self, $widget, $event) = @_;
1695 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1697 my @sel = $widget->get_selected_indices;
1698 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1700 splice @{$self->{restore_list}->{data}},$elt,1;
1705 sub on_fileview_button_press_event
1707 my ($self,$widget,$event) = @_;
1708 if ($event->button == 3)
1710 $self->on_right_click_filelist($widget,$event);
1714 if ($event->button == 2)
1716 $self->on_see_all_version();
1723 sub on_see_all_version
1727 my @lst = listview_get_all($self->{fileview});
1730 my ($pid,$fid,$name, undef) = @{$i};
1732 new DlgFileVersion($self->{bvfs},
1733 $self->current_client,
1734 $pid,$fid,$self->{cwd},$name);
1738 sub on_right_click_filelist
1740 my ($self,$widget,$event) = @_;
1741 # I need to know what's selected
1742 my @sel = listview_get_all($self->{fileview});
1747 $type = $sel[0]->[4]; # $type
1752 if (@sel >=2 or $type eq 'dir')
1754 # We have selected more than one or it is a directories
1755 $w = $self->{filelist_dir_menu};
1759 $w = $self->{filelist_file_menu};
1765 $event->button, $event->time);
1768 sub context_add_to_filelist
1772 my @sel = listview_get_all($self->{fileview});
1774 foreach my $i (@sel)
1776 my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1777 $file = $self->{cwd} . '/' . $file;
1778 $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1782 # Adds a file to the filelist
1783 sub add_selected_file_to_list
1785 my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1787 my $restore_list = $self->{restore_list};
1789 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1796 if ($name and substr $name,-1 ne '/')
1798 $name .= '/'; # For bacula
1800 my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1801 listview_push($restore_list,$pid,0,
1802 $name, $jobid, 'dir', $curjobids,
1803 $diricon, $name,$curjobids,$dirfileindex);
1805 elsif ($type eq 'file')
1807 my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1809 listview_push($restore_list,$pid,$fid,
1810 $name, $jobid, 'file', $curjobids,
1811 $fileicon, $name, $jobid, $fileindex );
1815 # TODO : we want be able to restore files from a bad ended backup
1816 # we have JobStatus IN ('T', 'A', 'E') and we must
1818 # Data acces subs from here. Interaction with SGBD and caching
1820 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1821 # GUI and stores them in @CurrentJobIds
1822 sub set_job_ids_for_date
1824 my ($dbh, $client, $date, $only_ok)=@_;
1826 if (!$client or !$date) {
1830 my $status = get_wanted_job_status($only_ok);
1832 # The algorithm : for a client, we get all the backups for each
1833 # fileset, in reverse order Then, for each fileset, we store the 'good'
1834 # incrementals and differentials until we have found a full so it goes
1835 # like this : store all incrementals until we have found a differential
1836 # or a full, then find the full #
1838 my $query = "SELECT JobId, FileSet, Level, JobStatus
1839 FROM Job, Client, FileSet
1840 WHERE Job.ClientId = Client.ClientId
1841 AND FileSet.FileSetId = Job.FileSetId
1842 AND EndTime <= '$date'
1843 AND Client.Name = '$client'
1845 AND JobStatus IN ($status)
1846 ORDER BY FileSet, JobTDate DESC";
1849 my $result = $dbh->selectall_arrayref($query);
1851 foreach my $refrow (@$result)
1853 my $jobid = $refrow->[0];
1854 my $fileset = $refrow->[1];
1855 my $level = $refrow->[2];
1857 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1859 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1863 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1864 push @CurrentJobIds,($jobid);
1866 elsif ($level eq 'D')
1868 next if $progress{$fileset} eq 'D'; # We allready have a differential
1869 push @CurrentJobIds,($jobid);
1871 elsif ($level eq 'F')
1873 push @CurrentJobIds,($jobid);
1876 my $status = $refrow->[3] ;
1877 if ($status eq 'T') { # good end of job
1878 $progress{$fileset} = $level;
1882 return @CurrentJobIds;
1887 Gtk2->main_iteration while (Gtk2->events_pending);
1890 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1891 # This sub creates a BSR from the information in the restore_list
1892 # Returns the BSR as a string
1897 # This query gets all jobid/jobmedia/media combination.
1899 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
1900 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1901 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
1902 Media.Volumename, Media.MediaType
1903 FROM Job, JobMedia, Media
1904 WHERE Job.JobId = JobMedia.JobId
1905 AND JobMedia.MediaId = Media.MediaId
1906 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1909 my $result = $self->dbh_selectall_arrayref($query);
1911 # We will store everything hashed by jobid.
1913 foreach my $refrow (@$result)
1915 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1916 $firstindex, $lastindex, $startblock, $endblock,
1917 $volindex, $volumename, $mediatype) = @{$refrow};
1919 # We just have to deal with the case where starfile != endfile
1920 # In this case, we concatenate both, for the bsr
1921 if ($startfile != $endfile) {
1922 $startfile = $startfile . '-' . $endfile;
1926 ($jobid, $volsessionid, $volsessiontime, $startfile,
1927 $firstindex, $lastindex, $startblock .'-'. $endblock,
1928 $volindex, $volumename, $mediatype);
1930 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
1934 # reminder : restore_list looks like this :
1935 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1936 # undef, undef, undef, $dirfileindex);
1938 # Here, we retrieve every file/dir that could be in the restore
1939 # We do as simple as possible for the SQL engine (no crazy joins,
1940 # no pseudo join (>= FirstIndex ...), etc ...
1941 # We do a SQL union of all the files/dirs specified in the restore_list
1943 foreach my $entry (@{$self->{restore_list}->{data}})
1945 if ($entry->[4] eq 'dir')
1947 my $dirid = $entry->[0];
1948 my $inclause = $entry->[5]; #curjobids
1951 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1952 FROM File, Path, Filename
1953 WHERE Path.PathId = File.PathId
1954 AND File.FilenameId = Filename.FilenameId
1956 (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
1957 WHERE PathId IN ($dirid)
1959 AND File.JobId IN ($inclause) )";
1960 push @select_queries,($query);
1964 # It's a file. Great, we allready have most
1965 # of what is needed. Simple and efficient query
1966 my $dir = $entry->[0];
1967 my $file = $entry->[1];
1969 my $jobid = $entry->[3];
1970 my $fileindex = $entry->[9];
1971 my $inclause = $entry->[5]; # curjobids
1973 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1974 FROM File,Path,Filename
1975 WHERE File.PathId = $dir
1976 AND File.PathId = Path.PathId
1977 AND File.FilenameId = $file
1978 AND File.FilenameId = Filename.FilenameId
1979 AND File.JobId = $jobid
1982 push @select_queries,($query);
1985 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
1987 #Now we run the query and parse the result...
1988 # there may be a lot of records, so we better be efficient
1989 # We use the bind column method, working with references...
1991 my $sth = $self->dbh_prepare($query);
1994 my ($path,$name,$fileindex,$jobid);
1995 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
1997 # The temp place we're going to save all file
1998 # list to before the real list
2002 while ($sth->fetchrow_arrayref())
2004 # This may look dumb, but we're going to do a join by ourselves,
2005 # to save memory and avoid sending a complex query to mysql
2006 my $complete_path = $path . $name;
2014 # Remove trailing slash (normalize file and dir name)
2015 $complete_path =~ s/\/$//;
2017 # Let's find the ref(s) for the %mediainfo element(s)
2018 # containing the data for this file
2019 # There can be several matches. It is the pseudo join.
2021 my $max_elt=@{$mediainfos{$jobid}}-1;
2023 while($med_idx <= $max_elt)
2025 my $ref = $mediainfos{$jobid}->[$med_idx];
2026 # First, can we get rid of the first elements of the
2027 # array ? (if they don't contain valuable records
2029 if ($fileindex > $ref->[5])
2031 # It seems we don't need anymore
2032 # this entry in %mediainfo (the input data
2035 shift @{$mediainfos{$jobid}};
2039 # We will do work on this elt. We can ++
2040 # $med_idx for next loop
2043 # %mediainfo row looks like :
2044 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2045 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2048 # We are in range. We store and continue looping
2050 if ($fileindex >= $ref->[4])
2052 my @data = ($complete_path,$is_dir,
2054 push @temp_list,(\@data);
2058 # We are not in range. No point in continuing looping
2059 # We go to next record.
2063 # Now we have the array.
2064 # We're going to sort it, by
2065 # path, volsessiontime DESC (get the most recent file...)
2066 # The array rows look like this :
2067 # complete_path,is_dir,fileindex,
2068 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2069 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2070 @temp_list = sort {$a->[0] cmp $b->[0]
2071 || $b->[3]->[2] <=> $a->[3]->[2]
2075 my $prev_complete_path='////'; # Sure not to match
2079 while (my $refrow = shift @temp_list)
2081 # For the sake of readability, we load $refrow
2082 # contents in real scalars
2083 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2084 my $jobid= $refother->[0]; # We don't need the rest...
2086 # We skip this entry.
2087 # We allready have a newer one and this
2088 # isn't a continuation of the same file
2089 next if ($complete_path eq $prev_complete_path
2090 and $jobid != $prev_jobid);
2094 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2096 # We would be recursing inside a file.
2097 # Just what we don't want (dir replaced by file
2098 # between two backups
2104 push @restore_list,($refrow);
2106 $prev_complete_path = $complete_path;
2107 $prev_jobid = $jobid;
2113 push @restore_list,($refrow);
2115 $prev_complete_path = $complete_path;
2116 $prev_jobid = $jobid;
2120 # We get rid of @temp_list... save memory
2123 # Ok everything is in the list. Let's sort it again in another way.
2124 # This time it will be in the bsr file order
2126 # we sort the results by
2127 # volsessiontime, volsessionid, volindex, fileindex
2128 # to get all files in right order...
2129 # Reminder : The array rows look like this :
2130 # complete_path,is_dir,fileindex,
2131 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2132 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2134 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2135 || $a->[3]->[1] <=> $b->[3]->[1]
2136 || $a->[3]->[7] <=> $b->[3]->[7]
2137 || $a->[2] <=> $b->[2] }
2140 # Now that everything is ready, we create the bsr
2141 my $prev_fileindex=-1;
2142 my $prev_volsessionid=-1;
2143 my $prev_volsessiontime=-1;
2144 my $prev_volumename=-1;
2145 my $prev_volfile=-1;
2149 my $first_of_current_range=0;
2150 my @fileindex_ranges;
2153 foreach my $refrow (@restore_list)
2155 my (undef,undef,$fileindex,$refother)=@{$refrow};
2156 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2157 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2159 # We can specifiy the number of files in each section of the
2160 # bsr to speedup restore (bacula can then jump over the
2161 # end of tape files.
2165 if ($prev_volumename eq '-1')
2167 # We only have to start the new range...
2168 $first_of_current_range=$fileindex;
2170 elsif ($prev_volsessionid != $volsessionid
2171 or $prev_volsessiontime != $volsessiontime
2172 or $prev_volumename ne $volumename
2173 or $prev_volfile ne $volfile)
2175 # We have to create a new section in the bsr...
2176 # We print the previous one ...
2177 # (before that, save the current range ...)
2178 if ($first_of_current_range != $prev_fileindex)
2181 push @fileindex_ranges,
2182 ("$first_of_current_range-$prev_fileindex");
2186 # We are out of a range,
2187 # but there is only one element in the range
2188 push @fileindex_ranges,
2189 ("$first_of_current_range");
2192 $bsr.=print_bsr_section(\@fileindex_ranges,
2194 $prev_volsessiontime,
2201 # Reset for next loop
2202 @fileindex_ranges=();
2203 $first_of_current_range=$fileindex;
2205 elsif ($fileindex-1 != $prev_fileindex)
2207 # End of a range of fileindexes
2208 if ($first_of_current_range != $prev_fileindex)
2211 push @fileindex_ranges,
2212 ("$first_of_current_range-$prev_fileindex");
2216 # We are out of a range,
2217 # but there is only one element in the range
2218 push @fileindex_ranges,
2219 ("$first_of_current_range");
2221 $first_of_current_range=$fileindex;
2223 $prev_fileindex=$fileindex;
2224 $prev_volsessionid = $volsessionid;
2225 $prev_volsessiontime = $volsessiontime;
2226 $prev_volumename = $volumename;
2227 $prev_volfile=$volfile;
2228 $prev_mediatype=$mediatype;
2229 $prev_volblocks=$volblocks;
2233 # Ok, we're out of the loop. Alas, there's still the last record ...
2234 if ($first_of_current_range != $prev_fileindex)
2237 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2242 # We are out of a range,
2243 # but there is only one element in the range
2244 push @fileindex_ranges,("$first_of_current_range");
2247 $bsr.=print_bsr_section(\@fileindex_ranges,
2249 $prev_volsessiontime,
2259 sub print_bsr_section
2261 my ($ref_fileindex_ranges,$volsessionid,
2262 $volsessiontime,$volumename,$volfile,
2263 $mediatype,$volblocks,$count)=@_;
2266 $bsr .= "Volume=\"$volumename\"\n";
2267 $bsr .= "MediaType=\"$mediatype\"\n";
2268 $bsr .= "VolSessionId=$volsessionid\n";
2269 $bsr .= "VolSessionTime=$volsessiontime\n";
2270 $bsr .= "VolFile=$volfile\n";
2271 $bsr .= "VolBlock=$volblocks\n";
2273 foreach my $range (@{$ref_fileindex_ranges})
2275 $bsr .= "FileIndex=$range\n";
2278 $bsr .= "Count=$count\n";
2284 ################################################################
2291 my ($self, $dir) = @_;
2293 "SELECT PathId FROM Path WHERE Path = ?";
2294 my $sth = $self->dbh_prepare($query);
2295 $sth->execute($dir);
2296 my $result = $sth->fetchall_arrayref();
2299 return join(',', map { $_->[0] } @$result);
2307 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
2308 my $jobs = $self->dbh_selectall_arrayref($query);
2310 $self->update_brestore_table(map { $_->[0] } @$jobs);
2315 my ($self, $dir) = @_;
2316 return $self->get_pathid('');
2321 my ($self, $pathid) = @_;
2322 $self->{cwd} = $pathid;
2329 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) ";
2331 my $all = $self->dbh_selectall_arrayref($query);
2332 return unless ($all); # already at root
2334 my $dir = join(',', map { $_->[0] } @$all);
2336 $self->{cwd} = $dir;
2343 return $self->get_path($self->{cwd});
2348 my ($self, $pathid) = @_;
2349 $self->debug("Call with pathid = $pathid");
2351 "SELECT Path FROM Path WHERE PathId IN (?)";
2353 my $sth = $self->dbh_prepare($query);
2354 $sth->execute($pathid);
2355 my $result = $sth->fetchrow_arrayref();
2357 return $result->[0];
2362 my ($self, @jobids) = @_;
2363 $self->{curjobids} = join(',', @jobids);
2364 $self->update_brestore_table(@jobids);
2371 return undef unless ($self->{curjobids});
2373 my $inclause = $self->{curjobids};
2374 my $inlistpath = $self->{cwd};
2377 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2379 (SELECT Filename.Name, max(File.FileId) as id
2381 WHERE File.FilenameId = Filename.FilenameId
2382 AND Filename.Name != ''
2383 AND File.PathId IN ($inlistpath)
2384 AND File.JobId IN ($inclause)
2385 GROUP BY Filename.Name
2386 ORDER BY Filename.Name) AS listfiles,
2388 WHERE File.FileId = listfiles.id";
2390 $self->debug($query);
2391 my $result = $self->dbh_selectall_arrayref($query);
2392 $self->debug($result);
2397 # return ($dirid,$dir_basename,$lstat,$jobid)
2402 return undef unless ($self->{curjobids});
2404 my $pathid = $self->{cwd};
2405 my $jobclause = $self->{curjobids};
2407 # Let's retrieve the list of the visible dirs in this dir ...
2408 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2409 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2410 my $sth = $self->dbh_prepare($query);
2412 my $result = $sth->fetchrow_arrayref();
2414 my $dir_filenameid = $result->[0];
2416 # Then we get all the dir entries from File ...
2418 SELECT PathId, Path, JobId, Lstat FROM (
2420 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2421 listfile1.JobId, listfile1.Lstat
2423 SELECT DISTINCT brestore_pathhierarchy1.PathId
2424 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2426 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2427 JOIN brestore_pathvisibility AS brestore_pathvisibility1
2428 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2429 WHERE brestore_pathhierarchy1.PPathId = $pathid
2430 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2431 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2433 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2434 WHERE File1.FilenameId = $dir_filenameid
2435 AND File1.JobId IN ($jobclause)) AS listfile1
2436 ON (listpath1.PathId = listfile1.PathId)
2437 ) AS A ORDER BY 2,3 DESC
2439 $self->debug($query);
2440 $sth=$self->dbh_prepare($query);
2442 $result = $sth->fetchall_arrayref();
2445 foreach my $refrow (@{$result})
2447 my $dirid = $refrow->[0];
2448 my $dir = $refrow->[1];
2449 my $lstat = $refrow->[3];
2450 my $jobid = $refrow->[2] || 0;
2451 next if ($dirid eq $prev_dir);
2452 # We have to clean up this dirname ... we only want it's 'basename'
2456 my @temp = split ('/',$dir);
2457 $return_value = pop @temp;
2461 $return_value = '/';
2463 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2464 push @return_list,(\@return_array);
2467 $self->debug(\@return_list);
2468 return \@return_list;
2471 # Returns the list of media required for a list of jobids.
2472 # Input : self, jobid1, jobid2...
2473 # Output : reference to array of (joibd, inchanger)
2474 sub get_required_media_from_jobid
2476 my ($self, @jobids)=@_;
2477 my $inclause = join(',',@jobids);
2479 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2480 FROM JobMedia, Media
2481 WHERE JobMedia.MediaId=Media.MediaId
2482 AND JobId In ($inclause)
2484 my $result = $self->dbh_selectall_arrayref($query);
2488 # Returns the fileindex from dirname and jobid.
2489 # Input : self, dirid, jobid
2490 # Output : fileindex
2491 sub get_fileindex_from_dir_jobid
2493 my ($self, $dirid, $jobid)=@_;
2495 $query = "SELECT File.FileIndex
2497 WHERE File.FilenameId = Filename.FilenameId
2498 AND File.PathId = $dirid
2499 AND Filename.Name = ''
2500 AND File.JobId = '$jobid'
2503 $self->debug($query);
2504 my $result = $self->dbh_selectall_arrayref($query);
2505 return $result->[0]->[0];
2508 # Returns the fileindex from filename and jobid.
2509 # Input : self, dirid, filenameid, jobid
2510 # Output : fileindex
2511 sub get_fileindex_from_file_jobid
2513 my ($self, $dirid, $filenameid, $jobid)=@_;
2517 "SELECT File.FileIndex
2519 WHERE File.PathId = $dirid
2520 AND File.FilenameId = $filenameid
2521 AND File.JobId = $jobid";
2523 $self->debug($query);
2524 my $result = $self->dbh_selectall_arrayref($query);
2525 return $result->[0]->[0];
2528 # This function estimates the size to be restored for an entry of the restore
2530 # In : self,reference to the entry
2531 # Out : size in bytes, number of files
2532 sub estimate_restore_size
2534 # reminder : restore_list looks like this :
2535 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2536 # undef, undef, undef, $dirfileindex);
2537 my ($self, $entry, $refresh) = @_;
2539 if ($entry->[4] eq 'dir')
2541 my $dir = $entry->[0];
2543 my $inclause = $entry->[5]; #curjobids
2545 "SELECT Path.Path, File.FilenameId, File.LStat
2546 FROM File, Path, Job
2547 WHERE Path.PathId = File.PathId
2548 AND File.JobId = Job.JobId
2550 (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2552 AND File.JobId IN ($inclause)
2553 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2557 # It's a file. Great, we allready have most
2558 # of what is needed. Simple and efficient query
2559 my $dir = $entry->[0];
2560 my $fileid = $entry->[1];
2562 my $jobid = $entry->[3];
2563 my $fileindex = $entry->[9];
2564 my $inclause = $entry->[5]; # curjobids
2566 "SELECT Path.Path, File.FilenameId, File.Lstat
2568 WHERE Path.PathId = File.PathId
2569 AND Path.PathId = $dir
2570 AND File.FilenameId = $fileid
2571 AND File.JobId = $jobid";
2574 my ($path,$nameid,$lstat);
2575 my $sth = $self->dbh_prepare($query);
2577 $sth->bind_columns(\$path,\$nameid,\$lstat);
2587 while ($sth->fetchrow_arrayref())
2589 # Only the latest version of a file
2590 next if ($nameid eq $old_nameid and $path eq $old_path);
2592 if ($rcount > 15000) {
2599 # We get the size of this file
2600 my $size=lstat_attrib($lstat,'st_size');
2601 $total_size += $size;
2604 $old_nameid=$nameid;
2607 return ($total_size,$total_files);
2610 # Returns list of versions of a file that could be restored
2611 # returns an array of
2612 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2613 # there will be only one jobid in the array of jobids...
2614 sub get_all_file_versions
2616 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2618 defined $see_all or $see_all=0;
2623 "SELECT File.JobId, File.FileIndex, File.Lstat,
2624 File.Md5, Media.VolumeName, Media.InChanger
2625 FROM File, Job, Client, JobMedia, Media
2626 WHERE File.FilenameId = $fileid
2627 AND File.PathId=$pathid
2628 AND File.JobId = Job.JobId
2629 AND Job.ClientId = Client.ClientId
2630 AND Job.JobId = JobMedia.JobId
2631 AND File.FileIndex >= JobMedia.FirstIndex
2632 AND File.FileIndex <= JobMedia.LastIndex
2633 AND JobMedia.MediaId = Media.MediaId
2634 AND Client.Name = '$client'";
2636 $self->debug($query);
2638 my $result = $self->dbh_selectall_arrayref($query);
2640 foreach my $refrow (@$result)
2642 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2643 my @attribs = parse_lstat($lstat);
2644 my $mtime = array_attrib('st_mtime',\@attribs);
2645 my $size = array_attrib('st_size',\@attribs);
2647 my @list = ('FILE:',$pathid,$fileid,$jobid,
2648 $fileindex, $mtime, $size, $inchanger,
2650 push @versions, (\@list);
2653 # We have the list of all versions of this file.
2654 # We'll sort it by mtime desc, size, md5, inchanger desc
2655 # the rest of the algorithm will be simpler
2656 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2657 @versions = sort { $b->[5] <=> $a->[5]
2658 || $a->[6] <=> $b->[6]
2659 || $a->[8] cmp $a->[8]
2660 || $b->[7] <=> $a->[7]} @versions;
2664 my %allready_seen_by_mtime;
2665 my %allready_seen_by_md5;
2666 # Now we should create a new array with only the interesting records
2667 foreach my $ref (@versions)
2671 # The file has a md5. We compare his md5 to other known md5...
2672 # We take size into account. It may happen that 2 files
2673 # have the same md5sum and are different. size is a supplementary
2676 # If we allready have a (better) version
2677 next if ( (not $see_all)
2678 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2680 # we never met this one before...
2681 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2683 # Even if it has a md5, we should also work with mtimes
2684 # We allready have a (better) version
2685 next if ( (not $see_all)
2686 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2687 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2689 # We reached there. The file hasn't been seen.
2690 push @good_versions,($ref);
2693 # To be nice with the user, we re-sort good_versions by
2694 # inchanger desc, mtime desc
2695 @good_versions = sort { $b->[5] <=> $a->[5]
2696 || $b->[3] <=> $a->[3]} @good_versions;
2698 return @good_versions;
2702 sub update_brestore_table
2704 my ($self, @jobs) = @_;
2706 $self->debug(\@jobs);
2708 foreach my $job (sort {$a <=> $b} @jobs)
2710 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2711 my $retour = $self->dbh_selectrow_arrayref($query);
2712 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2714 print STDERR "Inserting path records for JobId $job\n";
2715 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2716 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2718 $self->dbh_do($query);
2720 # Now we have to do the directory recursion stuff to determine missing visibility
2721 # We try to avoid recursion, to be as fast as possible
2722 # We also only work on not allready hierarchised directories...
2724 print STDERR "Creating missing recursion paths for $job\n";
2726 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2727 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2728 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2729 WHERE brestore_pathvisibility.JobId = $job
2730 AND brestore_pathhierarchy.PathId IS NULL
2733 my $sth = $self->dbh_prepare($query);
2735 my $pathid; my $path;
2736 $sth->bind_columns(\$pathid,\$path);
2740 $self->build_path_hierarchy($path,$pathid);
2744 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2745 # This query gives all parent pathids for a given jobid that aren't stored.
2746 # It has to be called until no record is updated ...
2748 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2749 SELECT a.PathId,$job
2751 (SELECT DISTINCT h.PPathId AS PathId
2752 FROM brestore_pathhierarchy AS h
2753 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2754 WHERE p.JobId=$job) AS a
2757 FROM brestore_pathvisibility
2758 WHERE JobId=$job) AS b
2759 ON (a.PathId = b.PathId)
2760 WHERE b.PathId IS NULL)";
2763 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2765 print STDERR "Recursively adding $rows_affected records from $job\n";
2768 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2769 $self->dbh_do($query);
2773 sub cleanup_brestore_table
2777 my $query = "SELECT JobId from brestore_knownjobid";
2778 my @jobs = @{$self->dbh_selectall_arrayref($query)};
2780 foreach my $jobentry (@jobs)
2782 my $job = $jobentry->[0];
2783 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2784 my $result = $self->dbh_selectall_arrayref($query);
2785 if (scalar(@{$result}))
2787 # There are still files for this jobid
2788 print STDERR "$job still exists. Not cleaning...\n";
2791 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2792 $self->dbh_do($query);
2793 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2794 $self->dbh_do($query);
2807 # Root Windows case :
2808 if ($path =~ /^[a-z]+:\/$/i)
2813 my @tmp = split('/',$path);
2814 # We remove the last ...
2816 my $tmp = join ('/',@tmp) . '/';
2820 sub build_path_hierarchy
2822 my ($self, $path,$pathid)=@_;
2823 # Does the ppathid exist for this ? we use a memory cache...
2824 # In order to avoid the full loop, we consider that if a dir is allready in the
2825 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2828 if (! $self->{cache_ppathid}->{$pathid})
2830 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2831 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2832 $sth2->execute($pathid);
2833 # Do we have a result ?
2834 if (my $refrow = $sth2->fetchrow_arrayref)
2836 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2838 # This dir was in the db ...
2839 # It means we can leave, the tree has allready been built for
2844 # We have to create the record ...
2845 # What's the current p_path ?
2846 my $ppath = parent_dir($path);
2847 my $ppathid = $self->return_pathid_from_path($ppath);
2848 $self->{cache_ppathid}->{$pathid}= $ppathid;
2850 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2851 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2852 $sth2->execute($pathid,$ppathid);
2858 # It's allready in the cache.
2859 # We can leave, no time to waste here, all the parent dirs have allready
2868 sub return_pathid_from_path
2870 my ($self, $path) = @_;
2871 my $query = "SELECT PathId FROM Path WHERE Path = ?";
2873 #print STDERR $query,"\n" if $debug;
2874 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2875 $sth->execute($path);
2876 my $result =$sth->fetchrow_arrayref();
2878 if (defined $result)
2880 return $result->[0];
2883 # A bit dirty : we insert into path, and we have to be sure
2884 # we aren't deleted by a purge. We still need to insert into path to get
2885 # the pathid, because of mysql
2886 $query = "INSERT INTO Path (Path) VALUES (?)";
2887 #print STDERR $query,"\n" if $debug;
2888 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2889 $sth->execute($path);
2892 $query = "SELECT PathId FROM Path WHERE Path = ?";
2893 #print STDERR $query,"\n" if $debug;
2894 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2895 $sth->execute($path);
2896 $result = $sth->fetchrow_arrayref();
2898 return $result->[0];
2903 sub create_brestore_tables
2907 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2909 unless ($self->dbh_do($verif)) {
2910 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2912 $self->{error} = "Creating internal brestore tables";
2914 CREATE TABLE brestore_knownjobid
2916 JobId int4 NOT NULL,
2917 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2919 $self->dbh_do($req);
2922 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2923 unless ($self->dbh_do($verif)) {
2925 CREATE TABLE brestore_pathhierarchy
2927 PathId int4 NOT NULL,
2928 PPathId int4 NOT NULL,
2929 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2931 $self->dbh_do($req);
2934 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2935 ON brestore_pathhierarchy (PPathId)";
2936 $self->dbh_do($req);
2939 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2940 unless ($self->dbh_do($verif)) {
2942 CREATE TABLE brestore_pathvisibility
2944 PathId int4 NOT NULL,
2945 JobId int4 NOT NULL,
2946 Size int8 DEFAULT 0,
2947 Files int4 DEFAULT 0,
2948 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2950 $self->dbh_do($req);
2952 $req = "CREATE INDEX brestore_pathvisibility_jobid
2953 ON brestore_pathvisibility (JobId)";
2954 $self->dbh_do($req);
2960 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
2961 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
2962 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
2963 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
2964 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
2965 'data_stream' => 15);;
2968 my ($attrib,$ref_attrib)=@_;
2969 return $ref_attrib->[$attrib_name_id{$attrib}];
2973 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
2975 my ($file, $attrib)=@_;
2977 if (defined $attrib_name_id{$attrib}) {
2979 my @d = split(' ', $file->[3]) ; # TODO : cache this
2981 return from_base64($d[$attrib_name_id{$attrib}]);
2983 } elsif ($attrib eq 'jobid') {
2987 } elsif ($attrib eq 'name') {
2992 die "Attribute not known : $attrib.\n";
2998 my ($lstat,$attrib)=@_;
2999 if ($lstat and defined $attrib_name_id{$attrib})
3001 my @d = split(' ', $lstat) ; # TODO : cache this
3002 return from_base64($d[$attrib_name_id{$attrib}]);
3009 # Base 64 functions, directly from recover.pl.
3011 # Karl Hakimian <hakimian@aha.com>
3012 # This section is also under GPL v2 or later.
3019 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3020 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3021 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3022 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3023 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3025 @base64_map = (0) x 128;
3027 for (my $i=0; $i<64; $i++) {
3028 $base64_map[ord($base64_digits[$i])] = $i;
3043 if (substr($where, 0, 1) eq '-') {
3045 $where = substr($where, 1);
3048 while ($where ne '') {
3050 my $d = substr($where, 0, 1);
3051 $val += $base64_map[ord(substr($where, 0, 1))];
3052 $where = substr($where, 1);
3060 my @attribs = split(' ',$lstat);
3061 foreach my $element (@attribs)
3063 $element = from_base64($element);
3071 ################################################################
3072 package BwebConsole;
3074 use HTTP::Request::Common;
3078 my ($class, %arg) = @_;
3081 pref => $arg{pref}, # Pref object
3082 timeout => $arg{timeout} || 20,
3083 debug => $arg{debug} || 0,
3085 'list_client' => '',
3086 'list_fileset' => '',
3087 'list_storage' => '',
3096 my ($self, @what) = @_;
3097 my $ua = LWP::UserAgent->new();
3098 $ua->agent("Brestore/$VERSION");
3099 my $req = POST($self->{pref}->{bconsole},
3100 Content_Type => 'form-data',
3101 Content => [ map { (action => $_) } @what ]);
3102 #$req->authorization_basic('eric', 'test');
3104 my $res = $ua->request($req);
3106 if ($res->is_success) {
3107 foreach my $l (split(/\n/, $res->content)) {
3108 my ($k, $c) = split(/=/,$l,2);
3112 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3113 new DlgWarn($self->{error});
3119 my ($self, %arg) = @_;
3121 my $ua = LWP::UserAgent->new();
3122 $ua->agent("Brestore/$VERSION");
3123 my $req = POST($self->{pref}->{bconsole},
3124 Content_Type => 'form-data',
3125 Content => [ job => $arg{job},
3126 client => $arg{client},
3127 storage => $arg{storage} || '',
3128 fileset => $arg{fileset} || '',
3129 where => $arg{where},
3130 replace => $arg{replace},
3131 priority=> $arg{prio} || '',
3134 bootstrap => [$arg{bootstrap}],
3136 #$req->authorization_basic('eric', 'test');
3138 my $res = $ua->request($req);
3140 if ($res->is_success) {
3141 foreach my $l (split(/\n/, $res->content)) {
3142 my ($k, $c) = split(/=/,$l,2);
3147 if (!$self->{run}) {
3148 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3151 unlink($arg{bootstrap});
3153 return $self->{run};
3159 return sort split(/;/, $self->{'list_job'});
3165 return sort split(/;/, $self->{'list_fileset'});
3171 return sort split(/;/, $self->{'list_storage'});
3176 return sort split(/;/, $self->{'list_client'});
3187 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3191 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3194 GetOptions("conf=s" => \$file_conf,
3195 "batch" => \$batch_mod,
3197 "help" => \&HELP_MESSAGE) ;
3199 my $p = new Pref($file_conf);
3201 if (! -f $file_conf) {
3206 my $vfs = new Bvfs(conf => $p);
3207 if ($p->connect_db()) {
3208 $vfs->update_cache();
3213 $glade_file = $p->{glade_file};
3215 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3216 if (-f "$path/$glade_file") {
3217 $glade_file = "$path/$glade_file" ;
3222 # gtk have lots of warning on stderr
3223 if ($^O eq 'MSWin32')
3226 open(STDERR, ">stderr.log");
3231 if ( -f $glade_file) {
3232 my $w = new DlgResto($p);
3235 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3236 "Can't find your brestore.glade (glade_file => '$glade_file')
3237 Please, edit your $file_conf to setup it." );
3239 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3244 Gtk2->main; # Start Gtk2 main loop
3253 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3255 $p->connect_db() || print $p->{error};
3257 my $bvfs = new Bvfs(conf => $p);
3259 $bvfs->debug($bvfs->get_root());
3260 $bvfs->ch_dir($bvfs->get_root());
3262 $bvfs->set_curjobids(268,178,282,281,279);
3264 my $dirs = $bvfs->ls_dirs();
3265 $bvfs->ch_dir(123496);
3266 $dirs = $bvfs->ls_dirs();
3268 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);