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 Data::Dumper qw/Dumper/;
69 my $debug=0; # can be on brestore.conf
70 our ($VERSION) = ('$Revision$' =~ /(\d+)/);
76 my ($class, $config_file) = @_;
79 config_file => $config_file,
80 password => '', # db passwd
81 username => '', # db username
82 connection_string => '',# db connection string
83 bconsole => 'bconsole', # path and arg to bconsole
84 bsr_dest => '', # destination url for bsr files
85 debug => 0, # debug level 0|1
86 use_ok_bkp_only => 1, # dont use bad backup
87 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
88 see_all_versions => 0, # display all file versions in FileInfo
89 mozilla => 'mozilla', # mozilla bin
90 default_restore_job => 'restore', # regular expression to select default
93 # keywords that are used to fill DlgPref
94 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
95 entry_keyword => [ qw/username password bweb mozilla
96 connection_string default_restore_job
97 bconsole bsr_dest glade_file/],
100 $self->read_config();
109 # We read the parameters. They come from the configuration files
110 my $cfgfile ; my $tmpbuffer;
111 if (open FICCFG, $self->{config_file})
113 while(read FICCFG,$tmpbuffer,4096)
115 $cfgfile .= $tmpbuffer;
119 no strict; # I have no idea of the contents of the file
120 eval '$refparams' . " = $cfgfile";
123 for my $p (keys %{$refparams}) {
124 $self->{$p} = $refparams->{$p};
128 # TODO : Force dumb default values and display a message
139 for my $k (@{ $self->{entry_keyword} }) {
140 $parameters{$k} = $self->{$k};
143 for my $k (@{ $self->{chk_keyword} }) {
144 $parameters{$k} = $self->{$k};
147 if (open FICCFG,">$self->{config_file}")
149 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
154 $self->{error} = "Can't write configuration $!";
156 return $self->{error};
164 $self->{dbh}->disconnect() ;
168 delete $self->{error};
170 if (not $self->{connection_string})
172 # The parameters have not been set. Maybe the conf
173 # file is empty for now
174 $self->{error} = "No configuration found for database connection. " .
175 "Please set this up.";
180 $self->{dbh} = DBI->connect($self->{connection_string},
185 $self->{error} = "Can't open bacula database. " .
186 "Database connect string '" .
187 $self->{connection_string} ."' $!";
190 $self->{is_mysql} = ($self->{connection_string} =~ m/dbi:mysql/i);
191 $self->{dbh}->{RowCacheSize}=100;
197 my ($self, $url, $msg) = @_;
199 unless ($self->{mozilla} and $self->{bweb}) {
200 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
204 if ($^O eq 'MSWin32') {
205 system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
208 system("$self->{mozilla} -remote 'Ping()'");
209 my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
211 $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
221 ################################################################
227 my ($class, %arg) = @_;
240 my ($self, $what, %arg) = @_;
242 if ($self->{conf}->{debug} and defined $what) {
247 my $line = (caller($level))[2];
248 my $func = (caller($level+1))[3] || 'main';
249 print "$func:$line\t";
251 print Data::Dumper::Dumper($what);
252 } elsif ($arg{md5}) {
253 print "MD5=", md5_base64($what), " str=", $what,"\n";
262 my ($self, @what) = @_;
263 if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
264 return join(' || ', @what);
266 return 'CONCAT(' . join(',', @what) . ')' ;
272 my ($self, $query) = @_;
273 $self->debug($query, up => 1);
274 return $self->{conf}->{dbh}->prepare($query);
279 my ($self, $query) = @_;
280 $self->debug($query, up => 1);
281 return $self->{conf}->{dbh}->do($query);
284 sub dbh_selectall_arrayref
286 my ($self, $query) = @_;
287 $self->debug($query, up => 1);
288 return $self->{conf}->{dbh}->selectall_arrayref($query);
291 sub dbh_selectrow_arrayref
293 my ($self, $query) = @_;
294 $self->debug($query, up => 1);
295 return $self->{conf}->{dbh}->selectrow_arrayref($query);
301 return $self->{conf}->{dbh};
306 ################################################################
310 # my $pref = new Pref(config_file => 'brestore.conf');
311 # my $dlg = new DlgPref($pref);
312 # my $dlg_resto = new DlgResto($pref);
313 # $dlg->display($dlg_resto);
316 my ($class, $pref) = @_;
319 pref => $pref, # Pref ref
320 dlgresto => undef, # DlgResto ref
328 my ($self, $dlgresto) = @_ ;
330 unless ($self->{glade}) {
331 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
332 $self->{glade}->signal_autoconnect_from_package($self);
335 $self->{dlgresto} = $dlgresto;
337 my $g = $self->{glade};
338 my $p = $self->{pref};
340 for my $k (@{ $p->{entry_keyword} }) {
341 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
344 for my $k (@{ $p->{chk_keyword} }) {
345 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
348 $g->get_widget("dlg_pref")->show_all() ;
351 sub on_applybutton_clicked
354 my $glade = $self->{glade};
355 my $pref = $self->{pref};
357 for my $k (@{ $pref->{entry_keyword} }) {
358 my $w = $glade->get_widget("entry_$k") ;
359 $pref->{$k} = $w->get_text();
362 for my $k (@{ $pref->{chk_keyword} }) {
363 my $w = $glade->get_widget("chkbp_$k") ;
364 $pref->{$k} = $w->get_active();
367 if (!$pref->write_config() && $pref->connect_db()) {
368 $self->{dlgresto}->set_status('Preferences updated');
369 $self->{dlgresto}->init_server_backup_combobox();
370 $self->{dlgresto}->set_status($pref->{error});
373 $self->{dlgresto}->set_status($pref->{error});
377 # Handle prefs ok click (apply/dismiss dialog)
378 sub on_okbutton_clicked
381 $self->on_applybutton_clicked();
383 unless ($self->{pref}->{error}) {
384 $self->on_cancelbutton_clicked();
387 sub on_dialog_delete_event
390 $self->on_cancelbutton_clicked();
394 sub on_cancelbutton_clicked
397 $self->{glade}->get_widget('dlg_pref')->hide();
398 delete $self->{dlgresto};
402 ################################################################
403 # Display all file revision in a separated window
404 package DlgFileVersion;
406 sub on_versions_close_clicked
408 my ($self, $widget)=@_;
409 $self->{version}->destroy();
412 sub on_selection_button_press_event
414 print STDERR "on_selection_button_press_event()\n";
417 sub fileview_data_get
419 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
421 DlgResto::drag_set_info($widget,
427 # new DlgFileVersion(Bvfs, "client", pathid, fileid, "/path/to/", "filename");
431 my ($class, $bvfs, $client, $pathid, $fileid, $path, $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:$path$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($pathid,
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 ################################################################
492 # Display a warning message
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_use_regexp_toggled
661 my ($self,$widget) = @_;
662 my $act = $widget->get_active();
664 foreach my $w ('entry_launch_where') {
665 $self->{glade}->get_widget($w)->set_sensitive(!$act);
668 foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
669 'entry_add_suffix','entry_rwhere','chk_use_regexp')
671 $self->{glade}->get_widget($w)->set_sensitive($act);
674 if ($act) { # if we activate file relocation, we reset use_regexp
675 $self->{glade}->get_widget('entry_rwhere')->set_sensitive(0);
676 $self->{glade}->get_widget('chk_use_regexp')->set_active(0);
681 sub on_use_rwhere_toggled
683 my ($self,$widget) = @_;
684 my $act = $widget->get_active();
686 foreach my $w ('entry_rwhere') {
687 $self->{glade}->get_widget($w)->set_sensitive($act);
690 foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
693 $self->{glade}->get_widget($w)->set_sensitive(!$act);
697 sub on_cancel_resto_clicked
700 $self->{glade}->get_widget('dlg_launch')->destroy();
707 if ($self->{glade}->get_widget('chk_file_relocation')->get_active()) {
709 if ($self->{glade}->get_widget('chk_use_regexp')->get_active()) {
711 return ('regexwhere',
712 $self->{glade}->get_widget('entry_rwhere')->get_active());
717 my ($strip_prefix, $add_prefix, $add_suffix) =
718 ($self->{glade}->get_widget('entry_strip_prefix')->get_text(),
719 $self->{glade}->get_widget('entry_add_prefix')->get_text(),
720 $self->{glade}->get_widget('entry_add_suffix')->get_text());
723 push @ret,"!$strip_prefix!!i";
727 push @ret,"!^!$add_prefix!";
731 push @ret,"!([^/])\$!\$1$add_suffix!";
734 return ('regexwhere', join(',', @ret));
736 } else { # using where
738 $self->{glade}->get_widget('entry_launch_where')->get_text());
742 sub on_submit_resto_clicked
745 my $glade = $self->{glade};
747 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
750 new DlgWarn("Can't copy bsr file to director ($self->{error})");
754 my $fileset = $glade->get_widget('combo_launch_fileset')
757 my $storage = $glade->get_widget('combo_launch_storage')
760 my ($where_cmd, $where) = $self->get_where();
761 print "$where_cmd => $where\n";
763 my $job = $glade->get_widget('combo_launch_job')
767 new DlgWarn("Can't use this job");
771 my $client = $glade->get_widget('combo_launch_client')
774 if (! $client or $client eq $DlgResto::client_list_empty) {
775 new DlgWarn("Can't use this client ($client)");
779 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
781 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
782 $replace=($replace)?'always':'never';
784 my $jobid = $self->{bconsole}->run(job => $job,
788 $where_cmd => $where,
793 $self->show_job($client, $jobid);
796 sub on_combo_storage_button_press_event
799 print "on_combo_storage_button_press_event()\n";
802 sub on_combo_fileset_button_press_event
805 print "on_combo_fileset_button_press_event()\n";
809 sub on_combo_job_button_press_event
812 print "on_combo_job_button_press_event()\n";
815 sub get_volume_inchanger
817 my ($dbh, $vols) = @_;
819 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
821 my $rq = "SELECT InChanger, VolumeName
823 WHERE VolumeName IN ($lst)
826 my $res = $dbh->selectall_arrayref($rq);
827 return $res; # [ [ 1, VolName].. ]
831 use File::Copy qw/copy/;
832 use File::Basename qw/basename/;
834 # We must kown the path+filename destination
835 # $self->{error} contains error message
836 # it return 0/1 if fail/success
839 my ($self, $src, $dst) = @_ ;
840 print "$src => $dst\n"
851 if ($dst =~ m!file:/(/.+)!) {
852 $ret = copy($src, $1);
854 $dstfile = "$1/" . basename($src) ;
856 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
857 $err = `scp $src $1 2>&1` ;
859 $dstfile = "$2/" . basename($src) ;
863 $err = "$dst not implemented yet";
864 File::Copy::copy($src, \*STDOUT);
867 $self->{error} = $err;
870 $self->{error} = $err;
879 ################################################################
887 unless ($about_widget) {
888 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
889 $about_widget = $glade_box->get_widget("dlg_about") ;
890 $glade_box->signal_autoconnect_from_package('DlgAbout');
892 $about_widget->show() ;
895 sub on_about_okbutton_clicked
897 $about_widget->hide() ;
902 ################################################################
912 # Kept as is from the perl-gtk example. Draws the pretty icons
918 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
919 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
920 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
921 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
924 # init combo (and create ListStore object)
927 my ($widget, @type) = @_ ;
928 my %type_info = ('text' => 'Glib::String',
929 'markup' => 'Glib::String',
932 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
934 $widget->set_model($lst);
938 if ($t eq 'text' or $t eq 'markup') {
939 $cell = new Gtk2::CellRendererText();
941 $widget->pack_start($cell, 1);
942 $widget->add_attribute($cell, $t, $i++);
947 # fill simple combo (one element per row)
950 my ($list, @what) = @_;
954 foreach my $w (@what)
957 my $i = $list->append();
958 $list->set($i, 0, $w);
965 my @unit = qw(B KB MB GB TB);
968 my $format = '%i %s';
969 while ($val / 1024 > 1) {
973 $format = ($i>0)?'%0.1f %s':'%i %s';
974 return sprintf($format, $val, $unit[$i]);
977 sub get_wanted_job_status
984 return "'T', 'A', 'E'";
988 # This sub gives a full list of the EndTimes for a ClientId
989 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
990 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
991 sub get_all_endtimes_for_job
993 my ($self, $client, $ok_only)=@_;
994 my $status = get_wanted_job_status($ok_only);
996 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
997 FROM Job,Client,FileSet
998 WHERE Job.ClientId=Client.ClientId
999 AND Client.Name = '$client'
1001 AND JobStatus IN ($status)
1002 AND Job.FileSetId = FileSet.FileSetId
1003 ORDER BY EndTime desc";
1004 my $result = $self->dbh_selectall_arrayref($query);
1011 my ($fileview) = shift;
1012 my $fileview_target_entry = {target => 'STRING',
1013 flags => ['GTK_TARGET_SAME_APP'],
1016 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
1017 ['copy'],$fileview_target_entry);
1018 $fileview->get_selection->set_mode('multiple');
1020 # set some useful SimpleList properties
1021 $fileview->set_headers_clickable(0);
1022 foreach ($fileview->get_columns())
1024 $_->set_resizable(1);
1025 $_->set_sizing('grow-only');
1031 my ($class, $pref) = @_;
1035 CurrentJobIds => [],
1036 location => undef, # location entry widget
1037 mainwin => undef, # mainwin widget
1038 filelist_file_menu => undef, # file menu widget
1039 filelist_dir_menu => undef, # dir menu widget
1040 glade => undef, # glade object
1041 status => undef, # status bar widget
1042 dlg_pref => undef, # DlgPref object
1043 fileattrib => {}, # cache file
1044 fileview => undef, # fileview widget SimpleList
1045 fileinfo => undef, # fileinfo widget SimpleList
1047 client_combobox => undef, # client_combobox widget
1048 restore_backup_combobox => undef, # date combobox widget
1049 list_client => undef, # Gtk2::ListStore
1050 list_backup => undef, # Gtk2::ListStore
1051 cache_ppathid => {}, #
1052 bvfs => undef, # Bfvs object
1055 $self->{bvfs} = new Bvfs(conf => $pref);
1057 # load menu (to use handler with self reference)
1058 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
1059 $glade->signal_autoconnect_from_package($self);
1060 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
1062 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
1063 $glade->signal_autoconnect_from_package($self);
1064 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
1066 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1067 $glade->signal_autoconnect_from_package($self);
1069 $self->{status} = $glade->get_widget('statusbar');
1070 $self->{mainwin} = $glade->get_widget('dlg_resto');
1071 $self->{location} = $glade->get_widget('entry_location');
1072 $self->render_icons();
1074 $self->{dlg_pref} = new DlgPref($pref);
1076 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
1077 $self->{list_client} = init_combo($c, 'text');
1079 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1080 $self->{list_backup} = init_combo($c, 'text', 'markup');
1082 # Connect glade-fileview to Gtk2::SimpleList
1083 # and set up drag n drop between $fileview and $restore_list
1085 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
1086 # we use an hidden field uuencoded to bypass theses bugs (h_name)
1088 my $widget = $glade->get_widget('fileview');
1089 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1091 'h_pathid' => 'hidden',
1092 'h_filenameid' => 'hidden',
1093 'h_name' => 'hidden',
1094 'h_jobid' => 'hidden',
1095 'h_type' => 'hidden',
1098 'File Name' => 'text',
1101 init_drag_drop($fileview);
1102 $fileview->set_search_column(6); # search on File Name
1104 # Connect glade-restore_list to Gtk2::SimpleList
1105 $widget = $glade->get_widget('restorelist');
1106 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
1108 'h_pathid' => 'hidden', #0
1109 'h_filenameid' => 'hidden',
1110 'h_name' => 'hidden',
1111 'h_jobid' => 'hidden',
1112 'h_type' => 'hidden',
1113 'h_curjobid' => 'hidden', #5
1116 'File Name' => 'text',
1118 'FileIndex' => 'text',
1120 'Nb Files' => 'text', #10
1121 'Size' => 'text', #11
1122 'size_b' => 'hidden', #12
1125 my @restore_list_target_table = ({'target' => 'STRING',
1129 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1130 $restore_list->get_selection->set_mode('multiple');
1132 $widget = $glade->get_widget('infoview');
1133 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1135 'h_pathid' => 'hidden',
1136 'h_filenameid' => 'hidden',
1137 'h_name' => 'hidden',
1138 'h_jobid' => 'hidden',
1139 'h_type' => 'hidden',
1141 'InChanger' => 'pixbuf',
1148 init_drag_drop($infoview);
1150 $pref->connect_db() || $self->{dlg_pref}->display($self);
1153 $self->init_server_backup_combobox();
1154 if ($self->{bvfs}->create_brestore_tables()) {
1155 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
1159 $self->set_status($pref->{error});
1162 # set status bar informations
1165 my ($self, $string) = @_;
1166 return unless ($string);
1168 my $context = $self->{status}->get_context_id('Main');
1169 $self->{status}->push($context, $string);
1172 sub on_time_select_changed
1180 my $c = $self->{glade}->get_widget('combo_time');
1181 return $c->get_active_text;
1184 # This sub returns all clients declared in DB
1188 my $query = "SELECT Name FROM Client ORDER BY Name";
1189 print STDERR $query,"\n" if $debug;
1191 my $result = $dbh->selectall_arrayref($query);
1193 return map { $_->[0] } @$result;
1196 # init infoview widget
1200 @{$self->{fileinfo}->{data}} = ();
1204 sub on_clear_clicked
1207 @{$self->{restore_list}->{data}} = ();
1210 sub on_estimate_clicked
1217 # TODO : If we get here, things could get lenghty ... draw a popup window .
1218 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1219 'destroy-with-parent',
1221 'Computing size...');
1225 my $title = "Computing size...\n";
1228 # ($pid,$fid,$name,$jobid,'file',$curjobids,
1229 # undef, undef, undef, $dirfileindex);
1230 foreach my $entry (@{$self->{restore_list}->{data}})
1232 unless ($entry->[11]) {
1233 my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1234 $entry->[12] = $size;
1235 $entry->[11] = human($size);
1239 my $name = unpack('u', $entry->[2]);
1241 $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1242 $self->debug($title . $txt);
1243 $widget->set_markup($title . $txt);
1245 $size_total+=$entry->[12];
1246 $nb_total+=$entry->[10];
1250 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1251 $widget->set_markup("Size estimation :\n" . $txt);
1252 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1259 sub on_gen_bsr_clicked
1263 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1264 'gtk-save','ok', 'gtk-cancel', 'cancel');
1267 my $w = new Gtk2::FileChooserDialog ( @options );
1272 if ($a eq 'cancel') {
1277 my $f = $w->get_filename();
1279 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1280 'destroy-with-parent',
1281 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1282 if ($dlg->run() eq 'ok') {
1296 if (open(FP, ">$save")) {
1297 my $bsr = $self->create_filelist();
1300 $self->set_status("Dumping BSR to $save ok");
1302 $self->set_status("Can't dump BSR to $save: $!");
1308 use File::Temp qw/tempfile/;
1310 sub on_go_button_clicked
1313 unless (scalar(@{$self->{restore_list}->{data}})) {
1314 new DlgWarn("No file to restore");
1317 my $bsr = $self->create_filelist();
1318 my ($fh, $filename) = tempfile();
1321 chmod(0644, $filename);
1323 print "Dumping BSR info to $filename\n"
1326 # we get Volume list
1327 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1328 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1330 new DlgLaunch(pref => $self->{conf},
1332 bsr_file => $filename,
1338 our $client_list_empty = 'Clients list';
1339 our %type_markup = ('F' => '<b>$label F</b>',
1342 'B' => '<b>$label B</b>',
1344 'A' => '<span foreground=\"red\">$label</span>',
1346 'E' => '<span foreground=\"red\">$label</span>',
1349 sub on_list_client_changed
1351 my ($self, $widget) = @_;
1352 return 0 unless defined $self->{fileview};
1354 $self->{list_backup}->clear();
1356 if ($self->current_client eq $client_list_empty) {
1360 $self->{CurrentJobIds} = [
1361 set_job_ids_for_date($self->dbh(),
1362 $self->current_client,
1363 $self->current_date,
1364 $self->{conf}->{use_ok_bkp_only})
1367 my $fs = $self->{bvfs};
1368 $fs->set_curjobids(@{$self->{CurrentJobIds}});
1369 $fs->ch_dir($fs->get_root());
1370 # refresh_fileview will be done by list_backup_changed
1373 my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1374 $self->{conf}->{use_ok_bkp_only});
1376 foreach my $endtime (@endtimes)
1378 my $i = $self->{list_backup}->append();
1380 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1381 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1382 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1384 $self->{list_backup}->set($i,
1389 $self->{restore_backup_combobox}->set_active(0);
1394 sub fill_server_list
1396 my ($dbh, $combo, $list) = @_;
1398 my @clients=get_all_clients($dbh);
1402 my $i = $list->append();
1403 $list->set($i, 0, $client_list_empty);
1405 foreach my $client (@clients)
1407 $i = $list->append();
1408 $list->set($i, 0, $client);
1410 $combo->set_active(0);
1413 sub init_server_backup_combobox
1416 fill_server_list($self->{conf}->{dbh},
1417 $self->{client_combobox},
1418 $self->{list_client}) ;
1421 #----------------------------------------------------------------------
1422 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1423 #data isn't. There is additionnal complexity for dirs (visibility problems),
1424 #so the @CurrentJobIds is not sufficient.
1425 sub refresh_fileview
1428 my $fileview = $self->{fileview};
1429 my $client_combobox = $self->{client_combobox};
1430 my $bvfs = $self->{bvfs};
1432 @{$fileview->{data}} = ();
1434 $self->clear_infoview();
1436 my $client_name = $self->current_client;
1438 if (!$client_name or ($client_name eq $client_list_empty)) {
1439 $self->set_status("Client list empty");
1443 # [ [dirid, dir_basename, File.LStat, jobid]..]
1444 my $list_dirs = $bvfs->ls_dirs();
1445 # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1446 my $files = $bvfs->ls_files();
1448 my $file_count = 0 ;
1449 my $total_bytes = 0;
1451 # Add directories to view
1452 foreach my $dir_entry (@$list_dirs) {
1453 my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1454 $total_bytes += 4096;
1457 listview_push($fileview,
1461 # TODO: voir ce que l'on met la
1472 foreach my $file (@$files)
1474 my $size = Bvfs::file_attrib($file,'st_size');
1475 my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1476 $total_bytes += $size;
1478 # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1479 listview_push($fileview,
1488 human($size), $time);
1491 $self->set_status("$file_count files/" . human($total_bytes));
1492 $self->{cwd} = $self->{bvfs}->pwd();
1493 $self->{location}->set_text($self->{cwd});
1494 # set a decent default selection (makes keyboard nav easy)
1495 $fileview->select(0);
1499 sub on_about_activate
1501 DlgAbout::display();
1506 my ($tree, $path, $data) = @_;
1508 my @items = listview_get_all($tree) ;
1510 foreach my $i (@items)
1512 my @file_info = @{$i};
1515 # Ok, we have a corner case :
1517 my $file = pack("u", $path . $file_info[2]);
1519 push @ret, join(" ; ", $file,
1520 $file_info[0], # $pathid
1521 $file_info[1], # $filenameid
1522 $file_info[3], # $jobid
1523 $file_info[4], # $type
1527 my $data_get = join(" :: ", @ret);
1529 $data->set_text($data_get,-1);
1534 sub fileview_data_get
1536 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1537 drag_set_info($widget, $self->{cwd}, $data);
1540 sub fileinfo_data_get
1542 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1543 drag_set_info($widget, $self->{cwd}, $data);
1546 sub restore_list_data_received
1548 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1550 $self->debug("start\n");
1551 if ($info eq 40 || $info eq 0) # patch for display!=:0
1553 foreach my $elt (split(/ :: /, $data->data()))
1555 my ($file, $pathid, $filenameid, $jobid, $type) =
1557 $file = unpack("u", $file);
1559 $self->add_selected_file_to_list($pathid,$filenameid,
1560 $file, $jobid, $type);
1563 $self->debug("end\n");
1566 sub on_back_button_clicked {
1568 $self->{bvfs}->up_dir();
1569 $self->refresh_fileview();
1571 sub on_location_go_button_clicked
1574 $self->ch_dir($self->{location}->get_text());
1576 sub on_quit_activate {Gtk2->main_quit;}
1577 sub on_preferences_activate
1580 $self->{dlg_pref}->display($self) ;
1582 sub on_main_delete_event {Gtk2->main_quit;}
1583 sub on_bweb_activate
1586 $self->set_status("Open bweb on your browser");
1587 $self->{conf}->go_bweb('', "go on bweb");
1590 # Change the current working directory
1591 # * Updates fileview, location, and selection
1597 my $p = $self->{bvfs}->get_pathid($l);
1599 $self->{bvfs}->ch_dir($p);
1600 $self->refresh_fileview();
1602 $self->set_status("Can't find $l");
1607 # Handle dialog 'close' (window-decoration induced close)
1608 # * Just hide the dialog, and tell Gtk not to do anything else
1612 my ($self, $w) = @_;
1615 1; # consume this event!
1618 # Handle key presses in location text edit control
1619 # * Translate a Return/Enter key into a 'Go' command
1620 # * All other key presses left for GTK
1622 sub on_location_entry_key_release_event
1628 my $keypress = $event->keyval;
1629 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1630 $keypress == $Gtk2::Gdk::Keysyms{Return})
1632 $self->ch_dir($widget->get_text());
1634 return 1; # consume keypress
1637 return 0; # let gtk have the keypress
1640 sub on_fileview_key_press_event
1642 my ($self, $widget, $event) = @_;
1646 sub listview_get_first
1649 my @selected = $list->get_selected_indices();
1650 if (@selected > 0) {
1651 my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1652 return ($pid,$fid,unpack('u', $name), @other);
1658 sub listview_get_all
1662 my @selected = $list->get_selected_indices();
1664 for my $i (@selected) {
1665 my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1666 push @ret, [$pid,$fid,unpack('u', $name), @other];
1673 my ($list, $pid, $fid, $name, @other) = @_;
1674 push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1677 #-----------------------------------------------------------------
1678 # Handle keypress in file-view
1679 # * Translates backspace into a 'cd ..' command
1680 # * All other key presses left for GTK
1682 sub on_fileview_key_release_event
1684 my ($self, $widget, $event) = @_;
1685 if (not $event->keyval)
1689 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1690 $self->on_back_button_clicked();
1691 return 1; # eat keypress
1694 return 0; # let gtk have keypress
1697 sub on_forward_keypress
1702 #-------------------------------------------------------------------
1703 # Handle double-click (or enter) on file-view
1704 # * Translates into a 'cd <dir>' command
1706 sub on_fileview_row_activated
1708 my ($self, $widget) = @_;
1710 my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1714 $self->{bvfs}->ch_dir($pid);
1715 $self->refresh_fileview();
1717 $self->fill_infoview($pid,$fid,$name);
1720 return 1; # consume event
1725 my ($self, $path, $file, $fn) = @_;
1726 $self->clear_infoview();
1727 my @v = $self->{bvfs}->get_all_file_versions($path,
1729 $self->current_client,
1730 $self->{conf}->{see_all_versions});
1732 my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1733 $size,$inchanger,$md5,$volname) = @{$ver};
1734 my $icon = ($inchanger)?$yesicon:$noicon;
1736 $mtime = localtime($mtime) ;
1738 listview_push($self->{fileinfo},$pid,$fid,
1739 $fn, $jobid, 'file',
1740 $icon, $volname, $jobid, human($size), $mtime, $md5);
1747 return $self->{restore_backup_combobox}->get_active_text;
1753 return $self->{client_combobox}->get_active_text;
1756 sub on_list_backups_changed
1758 my ($self, $widget) = @_;
1759 return 0 unless defined $self->{fileview};
1761 $self->{CurrentJobIds} = [
1762 set_job_ids_for_date($self->dbh(),
1763 $self->current_client,
1764 $self->current_date,
1765 $self->{conf}->{use_ok_bkp_only})
1767 $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1768 $self->refresh_fileview();
1772 sub on_restore_list_keypress
1774 my ($self, $widget, $event) = @_;
1775 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1777 my @sel = $widget->get_selected_indices;
1778 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1780 splice @{$self->{restore_list}->{data}},$elt,1;
1785 sub on_fileview_button_press_event
1787 my ($self,$widget,$event) = @_;
1788 if ($event->button == 3)
1790 $self->on_right_click_filelist($widget,$event);
1794 if ($event->button == 2)
1796 $self->on_see_all_version();
1803 sub on_see_all_version
1807 my @lst = listview_get_all($self->{fileview});
1810 my ($pid,$fid,$name, undef) = @{$i};
1812 new DlgFileVersion($self->{bvfs},
1813 $self->current_client,
1814 $pid,$fid,$self->{cwd},$name);
1818 sub on_right_click_filelist
1820 my ($self,$widget,$event) = @_;
1821 # I need to know what's selected
1822 my @sel = listview_get_all($self->{fileview});
1827 $type = $sel[0]->[4]; # $type
1832 if (@sel >=2 or $type eq 'dir')
1834 # We have selected more than one or it is a directories
1835 $w = $self->{filelist_dir_menu};
1839 $w = $self->{filelist_file_menu};
1845 $event->button, $event->time);
1848 sub context_add_to_filelist
1852 my @sel = listview_get_all($self->{fileview});
1854 foreach my $i (@sel)
1856 my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1857 $file = $self->{cwd} . $file;
1858 $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1862 # Adds a file to the filelist
1863 sub add_selected_file_to_list
1865 my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1867 my $restore_list = $self->{restore_list};
1869 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1876 if ($name and substr $name,-1 ne '/')
1878 $name .= '/'; # For bacula
1880 my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1881 listview_push($restore_list,$pid,0,
1882 $name, $jobid, 'dir', $curjobids,
1883 $diricon, $name,$curjobids,$dirfileindex);
1885 elsif ($type eq 'file')
1887 my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1889 listview_push($restore_list,$pid,$fid,
1890 $name, $jobid, 'file', $curjobids,
1891 $fileicon, $name, $jobid, $fileindex );
1895 # TODO : we want be able to restore files from a bad ended backup
1896 # we have JobStatus IN ('T', 'A', 'E') and we must
1898 # Data acces subs from here. Interaction with SGBD and caching
1900 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1901 # GUI and stores them in @CurrentJobIds
1902 sub set_job_ids_for_date
1904 my ($dbh, $client, $date, $only_ok)=@_;
1906 if (!$client or !$date) {
1910 my $status = get_wanted_job_status($only_ok);
1912 # The algorithm : for a client, we get all the backups for each
1913 # fileset, in reverse order Then, for each fileset, we store the 'good'
1914 # incrementals and differentials until we have found a full so it goes
1915 # like this : store all incrementals until we have found a differential
1916 # or a full, then find the full #
1918 my $query = "SELECT JobId, FileSet, Level, JobStatus
1919 FROM Job, Client, FileSet
1920 WHERE Job.ClientId = Client.ClientId
1921 AND FileSet.FileSetId = Job.FileSetId
1922 AND EndTime <= '$date'
1923 AND Client.Name = '$client'
1925 AND JobStatus IN ($status)
1926 ORDER BY FileSet, JobTDate DESC";
1929 my $result = $dbh->selectall_arrayref($query);
1931 foreach my $refrow (@$result)
1933 my $jobid = $refrow->[0];
1934 my $fileset = $refrow->[1];
1935 my $level = $refrow->[2];
1937 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1939 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1943 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1944 push @CurrentJobIds,($jobid);
1946 elsif ($level eq 'D')
1948 next if $progress{$fileset} eq 'D'; # We allready have a differential
1949 push @CurrentJobIds,($jobid);
1951 elsif ($level eq 'F')
1953 push @CurrentJobIds,($jobid);
1956 my $status = $refrow->[3] ;
1957 if ($status eq 'T') { # good end of job
1958 $progress{$fileset} = $level;
1962 return @CurrentJobIds;
1967 Gtk2->main_iteration while (Gtk2->events_pending);
1970 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1971 # This sub creates a BSR from the information in the restore_list
1972 # Returns the BSR as a string
1977 # This query gets all jobid/jobmedia/media combination.
1979 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
1980 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1981 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
1982 Media.VolumeName, Media.MediaType
1983 FROM Job, JobMedia, Media
1984 WHERE Job.JobId = JobMedia.JobId
1985 AND JobMedia.MediaId = Media.MediaId
1986 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1989 my $result = $self->dbh_selectall_arrayref($query);
1991 # We will store everything hashed by jobid.
1993 foreach my $refrow (@$result)
1995 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1996 $firstindex, $lastindex, $startblock, $endblock,
1997 $volindex, $volumename, $mediatype) = @{$refrow};
1999 # We just have to deal with the case where starfile != endfile
2000 # In this case, we concatenate both, for the bsr
2001 if ($startfile != $endfile) {
2002 $startfile = $startfile . '-' . $endfile;
2006 ($jobid, $volsessionid, $volsessiontime, $startfile,
2007 $firstindex, $lastindex, $startblock .'-'. $endblock,
2008 $volindex, $volumename, $mediatype);
2010 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2014 # reminder : restore_list looks like this :
2015 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2016 # undef, undef, undef, $dirfileindex);
2018 # Here, we retrieve every file/dir that could be in the restore
2019 # We do as simple as possible for the SQL engine (no crazy joins,
2020 # no pseudo join (>= FirstIndex ...), etc ...
2021 # We do a SQL union of all the files/dirs specified in the restore_list
2023 foreach my $entry (@{$self->{restore_list}->{data}})
2025 if ($entry->[4] eq 'dir')
2027 my $dirid = $entry->[0];
2028 my $inclause = $entry->[5]; #curjobids
2031 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2032 FROM File, Path, Filename
2033 WHERE Path.PathId = File.PathId
2034 AND File.FilenameId = Filename.FilenameId
2036 (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
2037 WHERE PathId IN ($dirid)
2039 AND File.JobId IN ($inclause) )";
2040 push @select_queries,($query);
2044 # It's a file. Great, we allready have most
2045 # of what is needed. Simple and efficient query
2046 my $dir = $entry->[0];
2047 my $file = $entry->[1];
2049 my $jobid = $entry->[3];
2050 my $fileindex = $entry->[9];
2051 my $inclause = $entry->[5]; # curjobids
2053 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2054 FROM File,Path,Filename
2055 WHERE File.PathId = $dir
2056 AND File.PathId = Path.PathId
2057 AND File.FilenameId = $file
2058 AND File.FilenameId = Filename.FilenameId
2059 AND File.JobId = $jobid
2062 push @select_queries,($query);
2065 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2067 #Now we run the query and parse the result...
2068 # there may be a lot of records, so we better be efficient
2069 # We use the bind column method, working with references...
2071 my $sth = $self->dbh_prepare($query);
2074 my ($path,$name,$fileindex,$jobid);
2075 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2077 # The temp place we're going to save all file
2078 # list to before the real list
2082 while ($sth->fetchrow_arrayref())
2084 # This may look dumb, but we're going to do a join by ourselves,
2085 # to save memory and avoid sending a complex query to mysql
2086 my $complete_path = $path . $name;
2094 # Remove trailing slash (normalize file and dir name)
2095 $complete_path =~ s/\/$//;
2097 # Let's find the ref(s) for the %mediainfo element(s)
2098 # containing the data for this file
2099 # There can be several matches. It is the pseudo join.
2101 my $max_elt=@{$mediainfos{$jobid}}-1;
2103 while($med_idx <= $max_elt)
2105 my $ref = $mediainfos{$jobid}->[$med_idx];
2106 # First, can we get rid of the first elements of the
2107 # array ? (if they don't contain valuable records
2109 if ($fileindex > $ref->[5])
2111 # It seems we don't need anymore
2112 # this entry in %mediainfo (the input data
2115 shift @{$mediainfos{$jobid}};
2119 # We will do work on this elt. We can ++
2120 # $med_idx for next loop
2123 # %mediainfo row looks like :
2124 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2125 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2128 # We are in range. We store and continue looping
2130 if ($fileindex >= $ref->[4])
2132 my @data = ($complete_path,$is_dir,
2134 push @temp_list,(\@data);
2138 # We are not in range. No point in continuing looping
2139 # We go to next record.
2143 # Now we have the array.
2144 # We're going to sort it, by
2145 # path, volsessiontime DESC (get the most recent file...)
2146 # The array rows look like this :
2147 # complete_path,is_dir,fileindex,
2148 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2149 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2150 @temp_list = sort {$a->[0] cmp $b->[0]
2151 || $b->[3]->[2] <=> $a->[3]->[2]
2155 my $prev_complete_path='////'; # Sure not to match
2159 while (my $refrow = shift @temp_list)
2161 # For the sake of readability, we load $refrow
2162 # contents in real scalars
2163 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2164 my $jobid= $refother->[0]; # We don't need the rest...
2166 # We skip this entry.
2167 # We allready have a newer one and this
2168 # isn't a continuation of the same file
2169 next if ($complete_path eq $prev_complete_path
2170 and $jobid != $prev_jobid);
2174 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2176 # We would be recursing inside a file.
2177 # Just what we don't want (dir replaced by file
2178 # between two backups
2184 push @restore_list,($refrow);
2186 $prev_complete_path = $complete_path;
2187 $prev_jobid = $jobid;
2193 push @restore_list,($refrow);
2195 $prev_complete_path = $complete_path;
2196 $prev_jobid = $jobid;
2200 # We get rid of @temp_list... save memory
2203 # Ok everything is in the list. Let's sort it again in another way.
2204 # This time it will be in the bsr file order
2206 # we sort the results by
2207 # volsessiontime, volsessionid, volindex, fileindex
2208 # to get all files in right order...
2209 # Reminder : The array rows look like this :
2210 # complete_path,is_dir,fileindex,
2211 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2212 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2214 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2215 || $a->[3]->[1] <=> $b->[3]->[1]
2216 || $a->[3]->[7] <=> $b->[3]->[7]
2217 || $a->[2] <=> $b->[2] }
2220 # Now that everything is ready, we create the bsr
2221 my $prev_fileindex=-1;
2222 my $prev_volsessionid=-1;
2223 my $prev_volsessiontime=-1;
2224 my $prev_volumename=-1;
2225 my $prev_volfile=-1;
2229 my $first_of_current_range=0;
2230 my @fileindex_ranges;
2233 foreach my $refrow (@restore_list)
2235 my (undef,undef,$fileindex,$refother)=@{$refrow};
2236 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2237 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2239 # We can specifiy the number of files in each section of the
2240 # bsr to speedup restore (bacula can then jump over the
2241 # end of tape files.
2245 if ($prev_volumename eq '-1')
2247 # We only have to start the new range...
2248 $first_of_current_range=$fileindex;
2250 elsif ($prev_volsessionid != $volsessionid
2251 or $prev_volsessiontime != $volsessiontime
2252 or $prev_volumename ne $volumename
2253 or $prev_volfile ne $volfile)
2255 # We have to create a new section in the bsr...
2256 # We print the previous one ...
2257 # (before that, save the current range ...)
2258 if ($first_of_current_range != $prev_fileindex)
2261 push @fileindex_ranges,
2262 ("$first_of_current_range-$prev_fileindex");
2266 # We are out of a range,
2267 # but there is only one element in the range
2268 push @fileindex_ranges,
2269 ("$first_of_current_range");
2272 $bsr.=print_bsr_section(\@fileindex_ranges,
2274 $prev_volsessiontime,
2281 # Reset for next loop
2282 @fileindex_ranges=();
2283 $first_of_current_range=$fileindex;
2285 elsif ($fileindex-1 != $prev_fileindex)
2287 # End of a range of fileindexes
2288 if ($first_of_current_range != $prev_fileindex)
2291 push @fileindex_ranges,
2292 ("$first_of_current_range-$prev_fileindex");
2296 # We are out of a range,
2297 # but there is only one element in the range
2298 push @fileindex_ranges,
2299 ("$first_of_current_range");
2301 $first_of_current_range=$fileindex;
2303 $prev_fileindex=$fileindex;
2304 $prev_volsessionid = $volsessionid;
2305 $prev_volsessiontime = $volsessiontime;
2306 $prev_volumename = $volumename;
2307 $prev_volfile=$volfile;
2308 $prev_mediatype=$mediatype;
2309 $prev_volblocks=$volblocks;
2313 # Ok, we're out of the loop. Alas, there's still the last record ...
2314 if ($first_of_current_range != $prev_fileindex)
2317 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2322 # We are out of a range,
2323 # but there is only one element in the range
2324 push @fileindex_ranges,("$first_of_current_range");
2327 $bsr.=print_bsr_section(\@fileindex_ranges,
2329 $prev_volsessiontime,
2339 sub print_bsr_section
2341 my ($ref_fileindex_ranges,$volsessionid,
2342 $volsessiontime,$volumename,$volfile,
2343 $mediatype,$volblocks,$count)=@_;
2346 $bsr .= "Volume=\"$volumename\"\n";
2347 $bsr .= "MediaType=\"$mediatype\"\n";
2348 $bsr .= "VolSessionId=$volsessionid\n";
2349 $bsr .= "VolSessionTime=$volsessiontime\n";
2350 $bsr .= "VolFile=$volfile\n";
2351 $bsr .= "VolBlock=$volblocks\n";
2353 foreach my $range (@{$ref_fileindex_ranges})
2355 $bsr .= "FileIndex=$range\n";
2358 $bsr .= "Count=$count\n";
2364 ################################################################
2371 my ($self, $dir) = @_;
2373 "SELECT PathId FROM Path WHERE Path = ?";
2374 my $sth = $self->dbh_prepare($query);
2375 $sth->execute($dir);
2376 my $result = $sth->fetchall_arrayref();
2379 return join(',', map { $_->[0] } @$result);
2387 $self->{conf}->{dbh}->begin_work();
2390 SELECT JobId from Job
2391 WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) AND JobStatus IN ('T', 'f', 'A') ORDER BY JobId";
2392 my $jobs = $self->dbh_selectall_arrayref($query);
2394 $self->update_brestore_table(map { $_->[0] } @$jobs);
2396 $self->{conf}->{dbh}->commit();
2397 $self->{conf}->{dbh}->begin_work();
2399 print STDERR "Cleaning path visibility\n";
2401 my $nb = $self->dbh_do("
2402 DELETE FROM brestore_pathvisibility
2404 (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
2406 print STDERR "$nb rows affected\n";
2407 print STDERR "Cleaning known jobid\n";
2409 $nb = $self->dbh_do("
2410 DELETE FROM brestore_knownjobid
2412 (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
2414 print STDERR "$nb rows affected\n";
2416 $self->{conf}->{dbh}->commit();
2421 my ($self, $dir) = @_;
2422 return $self->get_pathid('');
2427 my ($self, $pathid) = @_;
2428 $self->{cwdid} = $pathid;
2436 FROM brestore_pathhierarchy
2437 WHERE PathId IN ($self->{cwdid}) ";
2439 my $all = $self->dbh_selectall_arrayref($query);
2440 return unless ($all); # already at root
2442 my $dir = join(',', map { $_->[0] } @$all);
2444 $self->ch_dir($dir);
2451 return $self->get_path($self->{cwdid});
2456 my ($self, $pathid) = @_;
2457 $self->debug("Call with pathid = $pathid");
2459 "SELECT Path FROM Path WHERE PathId IN (?)";
2461 my $sth = $self->dbh_prepare($query);
2462 $sth->execute($pathid);
2463 my $result = $sth->fetchrow_arrayref();
2465 return $result->[0];
2470 my ($self, @jobids) = @_;
2471 $self->{curjobids} = join(',', @jobids);
2472 $self->update_brestore_table(@jobids);
2479 return undef unless ($self->{curjobids});
2481 my $inclause = $self->{curjobids};
2482 my $inlistpath = $self->{cwdid};
2485 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2487 (SELECT Filename.Name, max(File.FileId) as id
2489 WHERE File.FilenameId = Filename.FilenameId
2490 AND Filename.Name != ''
2491 AND File.PathId IN ($inlistpath)
2492 AND File.JobId IN ($inclause)
2493 GROUP BY Filename.Name
2494 ORDER BY Filename.Name) AS listfiles,
2496 WHERE File.FileId = listfiles.id";
2498 $self->debug($query);
2499 my $result = $self->dbh_selectall_arrayref($query);
2500 $self->debug($result);
2505 # return ($dirid,$dir_basename,$lstat,$jobid)
2510 return undef unless ($self->{curjobids});
2512 my $pathid = $self->{cwdid};
2513 my $jobclause = $self->{curjobids};
2515 # Let's retrieve the list of the visible dirs in this dir ...
2516 # First, I need the empty filenameid to locate efficiently the dirs in the file table
2517 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2518 my $sth = $self->dbh_prepare($query);
2520 my $result = $sth->fetchrow_arrayref();
2522 my $dir_filenameid = $result->[0];
2524 # Then we get all the dir entries from File ...
2526 SELECT PathId, Path, JobId, Lstat FROM (
2528 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2529 listfile1.JobId, listfile1.Lstat
2531 SELECT DISTINCT brestore_pathhierarchy1.PathId
2532 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2534 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2535 JOIN brestore_pathvisibility AS brestore_pathvisibility1
2536 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2537 WHERE brestore_pathhierarchy1.PPathId = $pathid
2538 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2539 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2541 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2542 WHERE File1.FilenameId = $dir_filenameid
2543 AND File1.JobId IN ($jobclause)) AS listfile1
2544 ON (listpath1.PathId = listfile1.PathId)
2545 ) AS A ORDER BY 2,3 DESC
2547 $self->debug($query);
2548 $sth=$self->dbh_prepare($query);
2550 $result = $sth->fetchall_arrayref();
2553 foreach my $refrow (@{$result})
2555 my $dirid = $refrow->[0];
2556 my $dir = $refrow->[1];
2557 my $lstat = $refrow->[3];
2558 my $jobid = $refrow->[2] || 0;
2559 next if ($dirid eq $prev_dir);
2560 # We have to clean up this dirname ... we only want it's 'basename'
2564 my @temp = split ('/',$dir);
2565 $return_value = pop @temp;
2569 $return_value = '/';
2571 my @return_array = ($dirid,$return_value,$lstat,$jobid);
2572 push @return_list,(\@return_array);
2575 $self->debug(\@return_list);
2576 return \@return_list;
2579 # Returns the list of media required for a list of jobids.
2580 # Input : self, jobid1, jobid2...
2581 # Output : reference to array of (joibd, inchanger)
2582 sub get_required_media_from_jobid
2584 my ($self, @jobids)=@_;
2585 my $inclause = join(',',@jobids);
2587 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2588 FROM JobMedia, Media
2589 WHERE JobMedia.MediaId=Media.MediaId
2590 AND JobId In ($inclause)
2592 my $result = $self->dbh_selectall_arrayref($query);
2596 # Returns the fileindex from dirname and jobid.
2597 # Input : self, dirid, jobid
2598 # Output : fileindex
2599 sub get_fileindex_from_dir_jobid
2601 my ($self, $dirid, $jobid)=@_;
2603 $query = "SELECT File.FileIndex
2605 WHERE File.FilenameId = Filename.FilenameId
2606 AND File.PathId = $dirid
2607 AND Filename.Name = ''
2608 AND File.JobId = '$jobid'
2611 $self->debug($query);
2612 my $result = $self->dbh_selectall_arrayref($query);
2613 return $result->[0]->[0];
2616 # Returns the fileindex from filename and jobid.
2617 # Input : self, dirid, filenameid, jobid
2618 # Output : fileindex
2619 sub get_fileindex_from_file_jobid
2621 my ($self, $dirid, $filenameid, $jobid)=@_;
2625 "SELECT File.FileIndex
2627 WHERE File.PathId = $dirid
2628 AND File.FilenameId = $filenameid
2629 AND File.JobId = $jobid";
2631 $self->debug($query);
2632 my $result = $self->dbh_selectall_arrayref($query);
2633 return $result->[0]->[0];
2636 # This function estimates the size to be restored for an entry of the restore
2638 # In : self,reference to the entry
2639 # Out : size in bytes, number of files
2640 sub estimate_restore_size
2642 # reminder : restore_list looks like this :
2643 # ($pid,$fid,$name,$jobid,'file',$curjobids,
2644 # undef, undef, undef, $dirfileindex);
2645 my ($self, $entry, $refresh) = @_;
2647 if ($entry->[4] eq 'dir')
2649 my $dir = $entry->[0];
2651 my $inclause = $entry->[5]; #curjobids
2653 "SELECT Path.Path, File.FilenameId, File.LStat
2654 FROM File, Path, Job
2655 WHERE Path.PathId = File.PathId
2656 AND File.JobId = Job.JobId
2658 (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2660 AND File.JobId IN ($inclause)
2661 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2665 # It's a file. Great, we allready have most
2666 # of what is needed. Simple and efficient query
2667 my $dir = $entry->[0];
2668 my $fileid = $entry->[1];
2670 my $jobid = $entry->[3];
2671 my $fileindex = $entry->[9];
2672 my $inclause = $entry->[5]; # curjobids
2674 "SELECT Path.Path, File.FilenameId, File.Lstat
2676 WHERE Path.PathId = File.PathId
2677 AND Path.PathId = $dir
2678 AND File.FilenameId = $fileid
2679 AND File.JobId = $jobid";
2682 my ($path,$nameid,$lstat);
2683 my $sth = $self->dbh_prepare($query);
2685 $sth->bind_columns(\$path,\$nameid,\$lstat);
2695 while ($sth->fetchrow_arrayref())
2697 # Only the latest version of a file
2698 next if ($nameid eq $old_nameid and $path eq $old_path);
2700 if ($rcount > 15000) {
2707 # We get the size of this file
2708 my $size=lstat_attrib($lstat,'st_size');
2709 $total_size += $size;
2712 $old_nameid=$nameid;
2715 return ($total_size,$total_files);
2718 # Returns list of versions of a file that could be restored
2719 # returns an array of
2720 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2721 # there will be only one jobid in the array of jobids...
2722 sub get_all_file_versions
2724 my ($self,$pathid,$fileid,$client,$see_all)=@_;
2726 defined $see_all or $see_all=0;
2731 "SELECT File.JobId, File.FileIndex, File.Lstat,
2732 File.Md5, Media.VolumeName, Media.InChanger
2733 FROM File, Job, Client, JobMedia, Media
2734 WHERE File.FilenameId = $fileid
2735 AND File.PathId=$pathid
2736 AND File.JobId = Job.JobId
2737 AND Job.ClientId = Client.ClientId
2738 AND Job.JobId = JobMedia.JobId
2739 AND File.FileIndex >= JobMedia.FirstIndex
2740 AND File.FileIndex <= JobMedia.LastIndex
2741 AND JobMedia.MediaId = Media.MediaId
2742 AND Client.Name = '$client'";
2744 $self->debug($query);
2746 my $result = $self->dbh_selectall_arrayref($query);
2748 foreach my $refrow (@$result)
2750 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2751 my @attribs = parse_lstat($lstat);
2752 my $mtime = array_attrib('st_mtime',\@attribs);
2753 my $size = array_attrib('st_size',\@attribs);
2755 my @list = ('FILE:',$pathid,$fileid,$jobid,
2756 $fileindex, $mtime, $size, $inchanger,
2758 push @versions, (\@list);
2761 # We have the list of all versions of this file.
2762 # We'll sort it by mtime desc, size, md5, inchanger desc
2763 # the rest of the algorithm will be simpler
2764 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2765 @versions = sort { $b->[5] <=> $a->[5]
2766 || $a->[6] <=> $b->[6]
2767 || $a->[8] cmp $a->[8]
2768 || $b->[7] <=> $a->[7]} @versions;
2772 my %allready_seen_by_mtime;
2773 my %allready_seen_by_md5;
2774 # Now we should create a new array with only the interesting records
2775 foreach my $ref (@versions)
2779 # The file has a md5. We compare his md5 to other known md5...
2780 # We take size into account. It may happen that 2 files
2781 # have the same md5sum and are different. size is a supplementary
2784 # If we allready have a (better) version
2785 next if ( (not $see_all)
2786 and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
2788 # we never met this one before...
2789 $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2791 # Even if it has a md5, we should also work with mtimes
2792 # We allready have a (better) version
2793 next if ( (not $see_all)
2794 and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
2795 $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2797 # We reached there. The file hasn't been seen.
2798 push @good_versions,($ref);
2801 # To be nice with the user, we re-sort good_versions by
2802 # inchanger desc, mtime desc
2803 @good_versions = sort { $b->[5] <=> $a->[5]
2804 || $b->[3] <=> $a->[3]} @good_versions;
2806 return @good_versions;
2810 sub update_brestore_table
2812 my ($self, @jobs) = @_;
2814 $self->debug(\@jobs);
2816 foreach my $job (sort {$a <=> $b} @jobs)
2818 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2819 my $retour = $self->dbh_selectrow_arrayref($query);
2820 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2822 print STDERR "Inserting path records for JobId $job\n";
2823 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2824 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2826 $self->dbh_do($query);
2828 # Now we have to do the directory recursion stuff to determine missing visibility
2829 # We try to avoid recursion, to be as fast as possible
2830 # We also only work on not allready hierarchised directories...
2832 print STDERR "Creating missing recursion paths for $job\n";
2834 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2835 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2836 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2837 WHERE brestore_pathvisibility.JobId = $job
2838 AND brestore_pathhierarchy.PathId IS NULL
2841 my $sth = $self->dbh_prepare($query);
2843 my $pathid; my $path;
2844 $sth->bind_columns(\$pathid,\$path);
2848 $self->build_path_hierarchy($path,$pathid);
2852 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2853 # This query gives all parent pathids for a given jobid that aren't stored.
2854 # It has to be called until no record is updated ...
2856 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2857 SELECT a.PathId,$job
2859 (SELECT DISTINCT h.PPathId AS PathId
2860 FROM brestore_pathhierarchy AS h
2861 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2862 WHERE p.JobId=$job) AS a
2865 FROM brestore_pathvisibility
2866 WHERE JobId=$job) AS b
2867 ON (a.PathId = b.PathId)
2868 WHERE b.PathId IS NULL)";
2871 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2873 print STDERR "Recursively adding $rows_affected records from $job\n";
2876 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2877 $self->dbh_do($query);
2889 # Root Windows case :
2890 if ($path =~ /^[a-z]+:\/$/i)
2895 my @tmp = split('/',$path);
2896 # We remove the last ...
2898 my $tmp = join ('/',@tmp) . '/';
2902 sub build_path_hierarchy
2904 my ($self, $path,$pathid)=@_;
2905 # Does the ppathid exist for this ? we use a memory cache...
2906 # In order to avoid the full loop, we consider that if a dir is allready in the
2907 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2910 if (! $self->{cache_ppathid}->{$pathid})
2912 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2913 my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2914 $sth2->execute($pathid);
2915 # Do we have a result ?
2916 if (my $refrow = $sth2->fetchrow_arrayref)
2918 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2920 # This dir was in the db ...
2921 # It means we can leave, the tree has allready been built for
2926 # We have to create the record ...
2927 # What's the current p_path ?
2928 my $ppath = parent_dir($path);
2929 my $ppathid = $self->return_pathid_from_path($ppath);
2930 $self->{cache_ppathid}->{$pathid}= $ppathid;
2932 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2933 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2934 $sth2->execute($pathid,$ppathid);
2940 # It's allready in the cache.
2941 # We can leave, no time to waste here, all the parent dirs have allready
2950 sub return_pathid_from_path
2952 my ($self, $path) = @_;
2953 my $query = "SELECT PathId FROM Path WHERE Path = ?";
2955 #print STDERR $query,"\n" if $debug;
2956 my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2957 $sth->execute($path);
2958 my $result =$sth->fetchrow_arrayref();
2960 if (defined $result)
2962 return $result->[0];
2965 # A bit dirty : we insert into path, and we have to be sure
2966 # we aren't deleted by a purge. We still need to insert into path to get
2967 # the pathid, because of mysql
2968 $query = "INSERT INTO Path (Path) VALUES (?)";
2969 #print STDERR $query,"\n" if $debug;
2970 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2971 $sth->execute($path);
2974 $query = "SELECT PathId FROM Path WHERE Path = ?";
2975 #print STDERR $query,"\n" if $debug;
2976 $sth = $self->{conf}->{dbh}->prepare_cached($query);
2977 $sth->execute($path);
2978 $result = $sth->fetchrow_arrayref();
2980 return $result->[0];
2985 sub create_brestore_tables
2989 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2991 unless ($self->dbh_do($verif)) {
2995 CREATE TABLE brestore_knownjobid
2997 JobId int4 NOT NULL,
2998 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
3000 $self->dbh_do($req);
3003 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
3004 unless ($self->dbh_do($verif)) {
3007 CREATE TABLE brestore_pathhierarchy
3009 PathId int4 NOT NULL,
3010 PPathId int4 NOT NULL,
3011 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
3013 $self->dbh_do($req);
3016 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
3017 ON brestore_pathhierarchy (PPathId)";
3018 $self->dbh_do($req);
3021 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
3022 unless ($self->dbh_do($verif)) {
3025 CREATE TABLE brestore_pathvisibility
3027 PathId int4 NOT NULL,
3028 JobId int4 NOT NULL,
3029 Size int8 DEFAULT 0,
3030 Files int4 DEFAULT 0,
3031 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
3033 $self->dbh_do($req);
3035 $req = "CREATE INDEX brestore_pathvisibility_jobid
3036 ON brestore_pathvisibility (JobId)";
3037 $self->dbh_do($req);
3044 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3045 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3046 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3047 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3048 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3049 'data_stream' => 15);;
3052 my ($attrib,$ref_attrib)=@_;
3053 return $ref_attrib->[$attrib_name_id{$attrib}];
3057 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3059 my ($file, $attrib)=@_;
3061 if (defined $attrib_name_id{$attrib}) {
3063 my @d = split(' ', $file->[3]) ; # TODO : cache this
3065 return from_base64($d[$attrib_name_id{$attrib}]);
3067 } elsif ($attrib eq 'jobid') {
3071 } elsif ($attrib eq 'name') {
3076 die "Attribute not known : $attrib.\n";
3082 my ($lstat,$attrib)=@_;
3083 if ($lstat and defined $attrib_name_id{$attrib})
3085 my @d = split(' ', $lstat) ; # TODO : cache this
3086 return from_base64($d[$attrib_name_id{$attrib}]);
3093 # Base 64 functions, directly from recover.pl.
3095 # Karl Hakimian <hakimian@aha.com>
3096 # This section is also under GPL v2 or later.
3103 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3104 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3105 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3106 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3107 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3109 @base64_map = (0) x 128;
3111 for (my $i=0; $i<64; $i++) {
3112 $base64_map[ord($base64_digits[$i])] = $i;
3127 if (substr($where, 0, 1) eq '-') {
3129 $where = substr($where, 1);
3132 while ($where ne '') {
3134 my $d = substr($where, 0, 1);
3135 $val += $base64_map[ord(substr($where, 0, 1))];
3136 $where = substr($where, 1);
3144 my @attribs = split(' ',$lstat);
3145 foreach my $element (@attribs)
3147 $element = from_base64($element);
3155 ################################################################
3156 package BwebConsole;
3158 use HTTP::Request::Common;
3162 my ($class, %arg) = @_;
3165 pref => $arg{pref}, # Pref object
3166 timeout => $arg{timeout} || 20,
3167 debug => $arg{debug} || 0,
3169 'list_client' => '',
3170 'list_fileset' => '',
3171 'list_storage' => '',
3180 my ($self, @what) = @_;
3181 my $ua = LWP::UserAgent->new();
3182 $ua->agent("Brestore/$VERSION");
3183 my $req = POST($self->{pref}->{bconsole},
3184 Content_Type => 'form-data',
3185 Content => [ map { (action => $_) } @what ]);
3186 #$req->authorization_basic('eric', 'test');
3188 my $res = $ua->request($req);
3190 if ($res->is_success) {
3191 foreach my $l (split(/\n/, $res->content)) {
3192 my ($k, $c) = split(/=/,$l,2);
3196 $self->{error} = "Can't connect to bweb : " . $res->status_line;
3197 new DlgWarn($self->{error});
3203 my ($self, %arg) = @_;
3205 my $ua = LWP::UserAgent->new();
3206 $ua->agent("Brestore/$VERSION");
3207 my $req = POST($self->{pref}->{bconsole},
3208 Content_Type => 'form-data',
3209 Content => [ job => $arg{job},
3210 client => $arg{client},
3211 storage => $arg{storage} || '',
3212 fileset => $arg{fileset} || '',
3213 where => $arg{where} || '',
3214 regexwhere => $arg{regexwhere} || '',
3215 priority=> $arg{prio} || '',
3216 replace => $arg{replace},
3219 bootstrap => [$arg{bootstrap}],
3221 #$req->authorization_basic('eric', 'test');
3223 my $res = $ua->request($req);
3225 if ($res->is_success) {
3226 foreach my $l (split(/\n/, $res->content)) {
3227 my ($k, $c) = split(/=/,$l,2);
3232 if (!$self->{run}) {
3233 new DlgWarn("Can't connect to bweb : " . $res->status_line);
3236 unlink($arg{bootstrap});
3238 return $self->{run};
3244 return sort split(/;/, $self->{'list_job'});
3250 return sort split(/;/, $self->{'list_fileset'});
3256 return sort split(/;/, $self->{'list_storage'});
3261 return sort split(/;/, $self->{'list_client'});
3265 ################################################################
3273 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3277 my $file_conf = (exists $ENV{HOME})? "$ENV{HOME}/.brestore.conf" : undef ;
3280 GetOptions("conf=s" => \$file_conf,
3281 "batch" => \$batch_mod,
3283 "help" => \&HELP_MESSAGE) ;
3285 if (! defined $file_conf) {
3286 print STDERR "Could not detect default config and no config file specified\n";
3290 my $p = new Pref($file_conf);
3292 if (! -f $file_conf) {
3297 my $vfs = new Bvfs(conf => $p);
3298 if ($p->connect_db()) {
3299 if ($vfs->create_brestore_tables()) {
3300 print "Creating brestore tables\n";
3302 $vfs->update_cache();
3307 # load Gtk libraries only in non batch mode
3311 use Gtk2::SimpleList;
3312 use Gtk2::Gdk::Keysyms;
3315 $glade_file = $p->{glade_file} || $glade_file;
3317 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3318 if (-f "$path/$glade_file") {
3319 $glade_file = "$path/$glade_file" ;
3324 # gtk have lots of warning on stderr
3325 if ($^O eq 'MSWin32')
3328 open(STDERR, ">stderr.log");
3333 if ( -f $glade_file) {
3334 my $w = new DlgResto($p);
3337 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3338 "Can't find your brestore.glade (glade_file => '$glade_file')
3339 Please, edit your $file_conf to setup it." );
3341 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3346 Gtk2->main; # Start Gtk2 main loop
3355 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3357 $p->connect_db() || print $p->{error};
3359 my $bvfs = new Bvfs(conf => $p);
3361 $bvfs->debug($bvfs->get_root());
3362 $bvfs->ch_dir($bvfs->get_root());
3364 $bvfs->set_curjobids(268,178,282,281,279);
3366 my $dirs = $bvfs->ls_dirs();
3367 $bvfs->ch_dir(123496);
3368 $dirs = $bvfs->ls_dirs();
3370 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwdid},312433, "exw3srv3", 1);