4 # path to your brestore.glade
5 my $glade_file = 'brestore.glade' ;
9 brestore.pl - A Perl/Gtk console for Bacula
17 Setup ~/.brestore.conf to find your brestore.glade
19 On debian like system, you need :
20 - libgtk2-gladexml-perl
21 - libdbd-mysql-perl or libdbd-pg-perl
24 You have to add brestore_xxx tables to your catalog.
26 To speed up database query you have to create theses indexes
27 - CREATE INDEX file_pathid on File(PathId);
30 To follow restore job, you must have a running Bweb installation.
34 Copyright (C) 2006 Marc Cousin and Eric Bollengier
36 This library is free software; you can redistribute it and/or
37 modify it under the terms of the GNU Lesser General Public
38 License as published by the Free Software Foundation; either
39 version 2 of the License, or (at your option) any later version.
41 This library is distributed in the hope that it will be useful,
42 but WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 Lesser General Public License for more details.
46 You should have received a copy of the GNU Lesser General Public
47 License along with this library; if not, write to the
48 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
49 Boston, MA 02111-1307, USA.
51 Base 64 functions from Karl Hakimian <hakimian@aha.com>
52 Integrally copied from recover.pl from bacula source distribution.
56 use File::Spec; # portable path manipulations
57 use Gtk2; # auto-initialize Gtk2
59 use Gtk2::SimpleList; # easy wrapper for list views
60 use Gtk2::Gdk::Keysyms; # keyboard code constants
61 use Data::Dumper qw/Dumper/;
63 my $debug=0; # can be on brestore.conf
65 ################################################################
67 package DlgFileVersion;
69 sub on_versions_close_clicked
71 my ($self, $widget)=@_;
72 $self->{version}->destroy();
75 sub on_selection_button_press_event
77 print STDERR "on_selection_button_press_event()\n";
82 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
84 DlgResto::drag_set_info($widget,
91 my ($class, $dbh, $client, $path, $file) = @_;
94 version => undef, # main window
97 # we load version widget of $glade_file
98 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
100 # Connect signals magically
101 $glade_box->signal_autoconnect_from_package($self);
103 $glade_box->get_widget("version_label")
104 ->set_markup("<b>File revisions : $client:$path/$file</b>");
106 my $widget = $glade_box->get_widget('version_fileview');
107 my $fileview = Gtk2::SimpleList->new_from_treeview(
109 'h_name' => 'hidden',
110 'h_jobid' => 'hidden',
111 'h_type' => 'hidden',
113 'InChanger' => 'pixbuf',
120 DlgResto::init_drag_drop($fileview);
122 my @v = DlgResto::get_all_file_versions($dbh,
128 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
130 my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
132 DlgResto::listview_push($fileview,
133 $file, $jobid, 'file',
134 $icon, $volname, $jobid,DlgResto::human($size),
135 scalar(localtime($mtime)), $md5);
138 $self->{version} = $glade_box->get_widget('dlg_version');
139 $self->{version}->show();
144 sub on_forward_keypress
150 ################################################################
155 my ($package, $text) = @_;
159 my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
161 # Connect signals magically
162 $glade->signal_autoconnect_from_package($self);
163 $glade->get_widget('label_warn')->set_text($text);
165 print STDERR "$text\n";
167 $self->{window} = $glade->get_widget('dlg_warn');
168 $self->{window}->show_all();
175 $self->{window}->destroy();
179 ################################################################
185 # %arg = (bsr_file => '/path/to/bsr', # on director
186 # volumes => [ '00001', '00004']
192 my ($class, %arg) = @_;
195 bsr_file => $arg{bsr_file}, # /path/to/bsr on director
196 pref => $arg{pref}, # Pref ref
197 glade => undef, # GladeXML ref
198 bconsole => undef, # Bconsole ref
201 # we load launch widget of $glade_file
202 my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
205 # Connect signals magically
206 $glade->signal_autoconnect_from_package($self);
208 my $widget = $glade->get_widget('volumeview');
209 my $volview = Gtk2::SimpleList->new_from_treeview(
211 'InChanger' => 'pixbuf',
215 my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
217 # we replace 0 and 1 by $noicon and $yesicon
218 for my $i (@{$infos}) {
220 $i->[0] = $DlgResto::noicon;
222 $i->[0] = $DlgResto::yesicon;
227 push @{ $volview->{data} }, @{$infos} ;
229 my $console = $self->{bconsole} = new Bconsole(pref => $arg{pref});
231 # fill client combobox (with director defined clients
232 my @clients = $console->list_client() ; # get from bconsole
233 if ($console->{error}) {
234 new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ;
236 my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ;
237 $self->{list_client} = DlgResto::init_combo($w, 'text');
238 DlgResto::fill_combo($self->{list_client},
239 $DlgResto::client_list_empty,
243 # fill fileset combobox
244 my @fileset = $console->list_fileset() ;
245 $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ;
246 $self->{list_fileset} = DlgResto::init_combo($w, 'text');
247 DlgResto::fill_combo($self->{list_fileset}, '', @fileset);
250 my @job = $console->list_job() ;
251 $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ;
252 $self->{list_job} = DlgResto::init_combo($w, 'text');
253 DlgResto::fill_combo($self->{list_job}, '', @job);
255 # find default_restore_job in jobs list
256 my $default_restore_job = $arg{pref}->{default_restore_job} ;
260 if ($j =~ /$default_restore_job/io) {
266 $w->set_active($index);
268 # fill storage combobox
269 my @storage = $console->list_storage() ;
270 $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ;
271 $self->{list_storage} = DlgResto::init_combo($w, 'text');
272 DlgResto::fill_combo($self->{list_storage}, '', @storage);
274 $glade->get_widget('dlg_launch')->show_all();
281 my ($self, $client, $jobid) = @_;
283 my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
286 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
287 "Your job have been submited to bacula.
288 To follow it, you must use bconsole (or install/configure bweb)");
293 $self->on_cancel_resto_clicked();
296 sub on_cancel_resto_clicked
299 $self->{glade}->get_widget('dlg_launch')->destroy();
302 sub on_submit_resto_clicked
305 my $glade = $self->{glade};
307 my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
310 new DlgWarn("Can't copy bsr file to director ($self->{error})");
314 my $fileset = $glade->get_widget('combo_launch_fileset')
317 my $storage = $glade->get_widget('combo_launch_storage')
320 my $where = $glade->get_widget('entry_launch_where')->get_text();
322 my $job = $glade->get_widget('combo_launch_job')
326 new DlgWarn("Can't use this job");
330 my $client = $glade->get_widget('combo_launch_client')
333 if (! $client or $client eq $DlgResto::client_list_empty) {
334 new DlgWarn("Can't use this client ($client)");
338 my $prio = $glade->get_widget('spin_launch_priority')->get_value();
340 my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
341 $replace=($replace)?'always':'never';
343 my $jobid = $self->{bconsole}->run(job => $job,
352 $self->show_job($client, $jobid);
355 sub on_combo_storage_button_press_event
358 print "on_combo_storage_button_press_event()\n";
361 sub on_combo_fileset_button_press_event
364 print "on_combo_fileset_button_press_event()\n";
368 sub on_combo_job_button_press_event
371 print "on_combo_job_button_press_event()\n";
374 sub get_volume_inchanger
376 my ($dbh, $vols) = @_;
378 my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
380 my $rq = "SELECT InChanger, VolumeName
382 WHERE VolumeName IN ($lst)
385 my $res = $dbh->selectall_arrayref($rq);
386 return $res; # [ [ 1, VolName].. ]
390 use File::Copy qw/copy/;
391 use File::Basename qw/basename/;
393 # We must kown the path+filename destination
394 # $self->{error} contains error message
395 # it return 0/1 if fail/success
398 my ($self, $src, $dst) = @_ ;
399 print "$src => $dst\n"
406 if ($dst =~ m!file:/(/.+)!) {
407 $ret = copy($src, $1);
409 $dstfile = "$1/" . basename($src) ;
411 } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
412 $err = `scp $src $1 2>&1` ;
414 $dstfile = "$2/" . basename($src) ;
418 $err = "$dst not implemented yet";
419 File::Copy::copy($src, \*STDOUT);
422 $self->{error} = $err;
425 $self->{error} = $err;
434 ################################################################
442 unless ($about_widget) {
443 my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ;
444 $about_widget = $glade_box->get_widget("dlg_about") ;
445 $glade_box->signal_autoconnect_from_package('DlgAbout');
447 $about_widget->show() ;
450 sub on_about_okbutton_clicked
452 $about_widget->hide() ;
457 ################################################################
463 my ($class, $config_file) = @_;
466 config_file => $config_file,
467 password => '', # db passwd
468 username => '', # db username
469 connection_string => '',# db connection string
470 bconsole => 'bconsole', # path and arg to bconsole
471 bsr_dest => '', # destination url for bsr files
472 debug => 0, # debug level 0|1
473 use_ok_bkp_only => 1, # dont use bad backup
474 bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
475 glade_file => $glade_file,
476 see_all_versions => 0, # display all file versions in FileInfo
477 mozilla => 'mozilla', # mozilla bin
478 default_restore_job => 'restore', # regular expression to select default
481 # keywords that are used to fill DlgPref
482 chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
483 entry_keyword => [ qw/username password bweb mozilla
484 connection_string default_restore_job
485 bconsole bsr_dest glade_file/],
488 $self->read_config();
497 # We read the parameters. They come from the configuration files
498 my $cfgfile ; my $tmpbuffer;
499 if (open FICCFG, $self->{config_file})
501 while(read FICCFG,$tmpbuffer,4096)
503 $cfgfile .= $tmpbuffer;
507 no strict; # I have no idea of the contents of the file
508 eval '$refparams' . " = $cfgfile";
511 for my $p (keys %{$refparams}) {
512 $self->{$p} = $refparams->{$p};
515 if (defined $self->{debug}) {
516 $debug = $self->{debug} ;
519 # TODO : Force dumb default values and display a message
529 for my $k (@{ $self->{entry_keyword} }) {
530 $parameters{$k} = $self->{$k};
533 for my $k (@{ $self->{chk_keyword} }) {
534 $parameters{$k} = $self->{$k};
537 if (open FICCFG,">$self->{config_file}")
539 print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
544 # TODO : Display a message
553 $self->{dbh}->disconnect() ;
557 delete $self->{error};
559 if (not $self->{connection_string})
561 # The parameters have not been set. Maybe the conf
562 # file is empty for now
563 $self->{error} = "No configuration found for database connection. " .
564 "Please set this up.";
569 $self->{dbh} = DBI->connect($self->{connection_string},
574 $self->{error} = "Can't open bacula database. " .
575 "Database connect string '" .
576 $self->{connection_string} ."' $!";
579 $self->{dbh}->{RowCacheSize}=100;
585 my ($self, $url, $msg) = @_;
587 unless ($self->{mozilla} and $self->{bweb}) {
588 new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
592 system("$self->{mozilla} -remote 'Ping()'");
594 new DlgWarn("Warning, you must have a running $self->{mozilla} to $msg");
598 my $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
606 ################################################################
610 # my $pref = new Pref(config_file => 'brestore.conf');
611 # my $dlg = new DlgPref($pref);
612 # my $dlg_resto = new DlgResto($pref);
613 # $dlg->display($dlg_resto);
616 my ($class, $pref) = @_;
619 pref => $pref, # Pref ref
620 dlgresto => undef, # DlgResto ref
628 my ($self, $dlgresto) = @_ ;
630 unless ($self->{glade}) {
631 $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
632 $self->{glade}->signal_autoconnect_from_package($self);
635 $self->{dlgresto} = $dlgresto;
637 my $g = $self->{glade};
638 my $p = $self->{pref};
640 for my $k (@{ $p->{entry_keyword} }) {
641 $g->get_widget("entry_$k")->set_text($p->{$k}) ;
644 for my $k (@{ $p->{chk_keyword} }) {
645 $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
648 $g->get_widget("dlg_pref")->show_all() ;
651 sub on_applybutton_clicked
654 my $glade = $self->{glade};
655 my $pref = $self->{pref};
657 for my $k (@{ $pref->{entry_keyword} }) {
658 my $w = $glade->get_widget("entry_$k") ;
659 $pref->{$k} = $w->get_text();
662 for my $k (@{ $pref->{chk_keyword} }) {
663 my $w = $glade->get_widget("chkbp_$k") ;
664 $pref->{$k} = $w->get_active();
667 $pref->write_config();
668 if ($pref->connect_db()) {
669 $self->{dlgresto}->set_dbh($pref->{dbh});
670 $self->{dlgresto}->set_status('Preferences updated');
671 $self->{dlgresto}->init_server_backup_combobox();
672 $self->{dlgresto}->create_brestore_tables();
673 $self->{dlgresto}->set_status($pref->{error});
675 $self->{dlgresto}->set_status($pref->{error});
679 # Handle prefs ok click (apply/dismiss dialog)
680 sub on_okbutton_clicked
683 $self->on_applybutton_clicked();
685 unless ($self->{pref}->{error}) {
686 $self->on_cancelbutton_clicked();
689 sub on_dialog_delete_event
692 $self->on_cancelbutton_clicked();
696 sub on_cancelbutton_clicked
699 $self->{glade}->get_widget('dlg_pref')->hide();
700 delete $self->{dlgresto};
704 ################################################################
714 # Kept as is from the perl-gtk example. Draws the pretty icons
720 $diricon = $self->{mainwin}->render_icon('gtk-open', $size);
721 $fileicon = $self->{mainwin}->render_icon('gtk-new', $size);
722 $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size);
723 $noicon = $self->{mainwin}->render_icon('gtk-no', $size);
727 # init combo (and create ListStore object)
730 my ($widget, @type) = @_ ;
731 my %type_info = ('text' => 'Glib::String',
732 'markup' => 'Glib::String',
735 my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
737 $widget->set_model($lst);
741 if ($t eq 'text' or $t eq 'markup') {
742 $cell = new Gtk2::CellRendererText();
744 $widget->pack_start($cell, 1);
745 $widget->add_attribute($cell, $t, $i++);
750 # fill simple combo (one element per row)
753 my ($list, @what) = @_;
757 foreach my $w (@what)
760 my $i = $list->append();
761 $list->set($i, 0, $w);
768 my @unit = qw(b Kb Mb Gb Tb);
771 my $format = '%i %s';
772 while ($val / 1024 > 1) {
776 $format = ($i>0)?'%0.1f %s':'%i %s';
777 return sprintf($format, $val, $unit[$i]);
782 my ($self, $dbh) = @_;
788 my ($fileview) = shift;
789 my $fileview_target_entry = {target => 'STRING',
790 flags => ['GTK_TARGET_SAME_APP'],
793 $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
794 ['copy'],$fileview_target_entry);
795 $fileview->get_selection->set_mode('multiple');
797 # set some useful SimpleList properties
798 $fileview->set_headers_clickable(0);
799 foreach ($fileview->get_columns())
801 $_->set_resizable(1);
802 $_->set_sizing('grow-only');
808 my ($self, $what) = @_;
812 print Data::Dumper::Dumper($what);
813 } elsif (defined $what) {
821 my ($self, $query) = @_;
822 $self->debug($query);
823 return $self->{dbh}->prepare($query);
828 my ($self, $query) = @_;
829 $self->debug($query);
830 return $self->{dbh}->do($query);
833 sub dbh_selectall_arrayref
835 my ($self, $query) = @_;
836 $self->debug($query);
837 return $self->{dbh}->selectall_arrayref($query);
840 sub dbh_selectrow_arrayref
842 my ($self, $query) = @_;
843 $self->debug($query);
844 return $self->{dbh}->selectrow_arrayref($query);
849 my ($class, $pref) = @_;
854 location => undef, # location entry widget
855 mainwin => undef, # mainwin widget
856 filelist_file_menu => undef, # file menu widget
857 filelist_dir_menu => undef, # dir menu widget
858 glade => undef, # glade object
859 status => undef, # status bar widget
860 dlg_pref => undef, # DlgPref object
861 fileattrib => {}, # cache file
862 fileview => undef, # fileview widget SimpleList
863 fileinfo => undef, # fileinfo widget SimpleList
865 client_combobox => undef, # client_combobox widget
866 restore_backup_combobox => undef, # date combobox widget
867 list_client => undef, # Gtk2::ListStore
868 list_backup => undef, # Gtk2::ListStore
869 cache_ppathid => {}, #
872 # load menu (to use handler with self reference)
873 my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
874 $glade->signal_autoconnect_from_package($self);
875 $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu");
877 $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu");
878 $glade->signal_autoconnect_from_package($self);
879 $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu");
881 $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
882 $glade->signal_autoconnect_from_package($self);
884 $self->{status} = $glade->get_widget('statusbar');
885 $self->{mainwin} = $glade->get_widget('dlg_resto');
886 $self->{location} = $glade->get_widget('entry_location');
887 $self->render_icons();
889 $self->{dlg_pref} = new DlgPref($pref);
891 my $c = $self->{client_combobox} = $glade->get_widget('combo_client');
892 $self->{list_client} = init_combo($c, 'text');
894 $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
895 $self->{list_backup} = init_combo($c, 'text', 'markup');
897 # Connect glade-fileview to Gtk2::SimpleList
898 # and set up drag n drop between $fileview and $restore_list
900 # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings
901 # we use an hidden field uuencoded to bypass theses bugs (h_name)
903 my $widget = $glade->get_widget('fileview');
904 my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
906 'h_name' => 'hidden',
907 'h_jobid' => 'hidden',
908 'h_type' => 'hidden',
911 'File Name' => 'text',
914 init_drag_drop($fileview);
915 $fileview->set_search_column(4); # search on File Name
917 # Connect glade-restore_list to Gtk2::SimpleList
918 $widget = $glade->get_widget('restorelist');
919 my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
921 'h_name' => 'hidden',
922 'h_jobid' => 'hidden',
923 'h_type' => 'hidden',
924 'h_curjobid' => 'hidden',
927 'File Name' => 'text',
929 'FileIndex' => 'text',
931 'Nb Files' => 'text', #8
933 'size_b' => 'hidden', #10
936 my @restore_list_target_table = ({'target' => 'STRING',
940 $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
941 $restore_list->get_selection->set_mode('multiple');
943 $widget = $glade->get_widget('infoview');
944 my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
946 'h_name' => 'hidden',
947 'h_jobid' => 'hidden',
948 'h_type' => 'hidden',
950 'InChanger' => 'pixbuf',
957 init_drag_drop($infoview);
959 $pref->connect_db() || $self->{dlg_pref}->display($self);
962 $self->{dbh} = $pref->{dbh};
963 $self->init_server_backup_combobox();
964 $self->create_brestore_tables();
967 $self->set_status($pref->{error});
970 # set status bar informations
973 my ($self, $string) = @_;
974 return unless ($string);
976 my $context = $self->{status}->get_context_id('Main');
977 $self->{status}->push($context, $string);
980 sub on_time_select_changed
988 my $c = $self->{glade}->get_widget('combo_time');
989 return $c->get_active_text;
992 # This sub returns all clients declared in DB
996 my $query = "SELECT Name FROM Client ORDER BY Name";
997 print STDERR $query,"\n" if $debug;
999 my $result = $dbh->selectall_arrayref($query);
1001 return map { $_->[0] } @$result;
1004 sub get_wanted_job_status
1011 return "'T', 'A', 'E'";
1015 # This sub gives a full list of the EndTimes for a ClientId
1016 # ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
1017 # ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
1018 sub get_all_endtimes_for_job
1020 my ($dbh, $client, $ok_only)=@_;
1021 my $status = get_wanted_job_status($ok_only);
1023 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
1024 FROM Job,Client,FileSet
1025 WHERE Job.ClientId=Client.ClientId
1026 AND Client.Name = '$client'
1028 AND JobStatus IN ($status)
1029 AND Job.FileSetId = FileSet.FileSetId
1030 ORDER BY EndTime desc";
1031 print STDERR $query,"\n" if $debug;
1032 my $result = $dbh->selectall_arrayref($query);
1038 # init infoview widget
1042 @{$self->{fileinfo}->{data}} = ();
1046 sub on_clear_clicked
1049 @{$self->{restore_list}->{data}} = ();
1052 sub on_estimate_clicked
1059 # TODO : If we get here, things could get lenghty ... draw a popup window .
1060 my $widget = Gtk2::MessageDialog->new($self->{mainwin},
1061 'destroy-with-parent',
1063 'Computing size...');
1067 my $title = "Computing size...\n";
1069 foreach my $entry (@{$self->{restore_list}->{data}})
1071 unless ($entry->[9]) {
1072 my ($size, $nb) = $self->estimate_restore_size($entry);
1073 $entry->[10] = $size;
1074 $entry->[9] = human($size);
1078 my $name = unpack('u', $entry->[0]);
1080 $txt .= "\n<i>$name</i> : " . $entry->[8] . " file(s)/" . $entry->[9] ;
1081 $widget->set_markup($title . $txt);
1083 $size_total+=$entry->[10];
1084 $nb_total+=$entry->[8];
1088 $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1089 $widget->set_markup("Size estimation :\n" . $txt);
1090 $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1095 sub on_gen_bsr_clicked
1099 my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
1100 'gtk-save','ok', 'gtk-cancel', 'cancel');
1103 my $w = new Gtk2::FileChooserDialog ( @options );
1108 if ($a eq 'cancel') {
1113 my $f = $w->get_filename();
1115 my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
1116 'destroy-with-parent',
1117 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1118 if ($dlg->run() eq 'ok') {
1132 if (open(FP, ">$save")) {
1133 my $bsr = $self->create_filelist();
1136 $self->set_status("Dumping BSR to $save ok");
1138 $self->set_status("Can't dump BSR to $save: $!");
1143 use File::Temp qw/tempfile/;
1145 sub on_go_button_clicked
1148 my $bsr = $self->create_filelist();
1149 my ($fh, $filename) = tempfile();
1152 chmod(0644, $filename);
1154 print "Dumping BSR info to $filename\n"
1157 # we get Volume list
1158 my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1159 my $vol = [ keys %a ] ; # need only one occurrence of each volume
1161 new DlgLaunch(pref => $self->{pref},
1163 bsr_file => $filename,
1168 our $client_list_empty = 'Clients list';
1169 our %type_markup = ('F' => '<b>$label F</b>',
1172 'B' => '<b>$label B</b>',
1174 'A' => '<span foreground=\"red\">$label</span>',
1176 'E' => '<span foreground=\"red\">$label</span>',
1179 sub on_list_client_changed
1181 my ($self, $widget) = @_;
1182 return 0 unless defined $self->{fileview};
1183 my $dbh = $self->{dbh};
1185 $self->{list_backup}->clear();
1187 if ($self->current_client eq $client_list_empty) {
1191 my @endtimes=get_all_endtimes_for_job($dbh,
1192 $self->current_client,
1193 $self->{pref}->{use_ok_bkp_only});
1194 foreach my $endtime (@endtimes)
1196 my $i = $self->{list_backup}->append();
1198 my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1199 eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1200 eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1202 $self->{list_backup}->set($i,
1207 $self->{restore_backup_combobox}->set_active(0);
1209 $self->{CurrentJobIds} = [
1210 set_job_ids_for_date($dbh,
1211 $self->current_client,
1212 $self->current_date,
1213 $self->{pref}->{use_ok_bkp_only})
1216 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1219 $self->refresh_fileview();
1223 sub fill_server_list
1225 my ($dbh, $combo, $list) = @_;
1227 my @clients=get_all_clients($dbh);
1231 my $i = $list->append();
1232 $list->set($i, 0, $client_list_empty);
1234 foreach my $client (@clients)
1236 $i = $list->append();
1237 $list->set($i, 0, $client);
1239 $combo->set_active(0);
1242 sub init_server_backup_combobox
1245 fill_server_list($self->{dbh},
1246 $self->{client_combobox},
1247 $self->{list_client}) ;
1250 #----------------------------------------------------------------------
1251 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1252 #data isn't. There is additionnal complexity for dirs (visibility problems),
1253 #so the @CurrentJobIds is not sufficient.
1254 sub refresh_fileview
1257 my $fileview = $self->{fileview};
1258 my $client_combobox = $self->{client_combobox};
1259 my $cwd = $self->{cwd};
1261 @{$fileview->{data}} = ();
1263 $self->clear_infoview();
1265 my $client_name = $self->current_client;
1267 if (!$client_name or ($client_name eq $client_list_empty)) {
1268 $self->set_status("Client list empty");
1272 my @list_dirs = $self->list_dirs($cwd,$client_name);
1273 # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1274 my $files = $self->list_files($cwd);
1275 print "CWD : $cwd\n" if ($debug);
1277 my $file_count = 0 ;
1278 my $total_bytes = 0;
1280 # Add directories to view
1281 foreach my $dir_entry (@list_dirs) {
1282 #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
1283 my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime'));
1284 my $dir = $dir_entry->[0];
1285 $total_bytes += 4096;
1288 listview_push($fileview,
1290 $self->dir_attrib("$cwd/$dir",'jobid'),
1300 foreach my $file (@$files)
1302 my $size = file_attrib($file,'st_size');
1303 my $time = localtime(file_attrib($file,'st_mtime'));
1304 $total_bytes += $size;
1306 # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
1308 listview_push($fileview,
1315 human($size), $time);
1318 $self->set_status("$file_count files/" . human($total_bytes));
1320 # set a decent default selection (makes keyboard nav easy)
1321 $fileview->select(0);
1325 sub on_about_activate
1327 DlgAbout::display();
1332 my ($tree, $path, $data) = @_;
1334 my @items = listview_get_all($tree) ;
1336 foreach my $i (@items)
1338 my @file_info = @{$i};
1341 # Ok, we have a corner case :
1346 $file = pack("u", $file_info[0]);
1350 $file = pack("u", $path . '/' . $file_info[0]);
1352 push @ret, join(" ; ", $file,
1353 $file_info[1], # $jobid
1354 $file_info[2], # $type
1358 my $data_get = join(" :: ", @ret);
1360 $data->set_text($data_get,-1);
1363 sub fileview_data_get
1365 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1366 drag_set_info($widget, $self->{cwd}, $data);
1369 sub fileinfo_data_get
1371 my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1372 drag_set_info($widget, $self->{cwd}, $data);
1375 sub restore_list_data_received
1377 my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1380 if ($info eq 40 || $info eq 0) # patch for display!=:0
1382 foreach my $elt (split(/ :: /, $data->data()))
1385 my ($file, $jobid, $type) =
1387 $file = unpack("u", $file);
1389 $self->add_selected_file_to_list($file, $jobid, $type);
1394 sub on_back_button_clicked {
1398 sub on_location_go_button_clicked
1401 $self->ch_dir($self->{location}->get_text());
1403 sub on_quit_activate {Gtk2->main_quit;}
1404 sub on_preferences_activate
1407 $self->{dlg_pref}->display($self) ;
1409 sub on_main_delete_event {Gtk2->main_quit;}
1410 sub on_bweb_activate
1413 $self->set_status("Open bweb on your browser");
1414 $self->{pref}->go_bweb('', "go on bweb");
1417 # Change to parent directory
1421 if ($self->{cwd} eq '/')
1425 my @dirs = File::Spec->splitdir ($self->{cwd});
1427 $self->ch_dir(File::Spec->catdir(@dirs));
1430 # Change the current working directory
1431 # * Updates fileview, location, and selection
1436 $self->{cwd} = shift;
1438 $self->refresh_fileview();
1439 $self->{location}->set_text($self->{cwd});
1444 # Handle dialog 'close' (window-decoration induced close)
1445 # * Just hide the dialog, and tell Gtk not to do anything else
1449 my ($self, $w) = @_;
1452 1; # consume this event!
1455 # Handle key presses in location text edit control
1456 # * Translate a Return/Enter key into a 'Go' command
1457 # * All other key presses left for GTK
1459 sub on_location_entry_key_release_event
1465 my $keypress = $event->keyval;
1466 if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1467 $keypress == $Gtk2::Gdk::Keysyms{Return})
1469 $self->ch_dir($widget->get_text());
1471 return 1; # consume keypress
1474 return 0; # let gtk have the keypress
1477 sub on_fileview_key_press_event
1479 my ($self, $widget, $event) = @_;
1483 sub listview_get_first
1486 my @selected = $list->get_selected_indices();
1487 if (@selected > 0) {
1488 my ($name, @other) = @{$list->{data}->[$selected[0]]};
1489 return (unpack('u', $name), @other);
1495 sub listview_get_all
1499 my @selected = $list->get_selected_indices();
1501 for my $i (@selected) {
1502 my ($name, @other) = @{$list->{data}->[$i]};
1503 push @ret, [unpack('u', $name), @other];
1511 my ($list, $name, @other) = @_;
1512 push @{$list->{data}}, [pack('u', $name), @other];
1515 #----------------------------------------------------------------------
1516 # Handle keypress in file-view
1517 # * Translates backspace into a 'cd ..' command
1518 # * All other key presses left for GTK
1520 sub on_fileview_key_release_event
1522 my ($self, $widget, $event) = @_;
1523 if (not $event->keyval)
1527 if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1529 return 1; # eat keypress
1532 return 0; # let gtk have keypress
1535 sub on_forward_keypress
1540 #----------------------------------------------------------------------
1541 # Handle double-click (or enter) on file-view
1542 # * Translates into a 'cd <dir>' command
1544 sub on_fileview_row_activated
1546 my ($self, $widget) = @_;
1548 my ($name, undef, $type, undef) = listview_get_first($widget);
1552 if ($self->{cwd} eq '')
1554 $self->ch_dir($name);
1556 elsif ($self->{cwd} eq '/')
1558 $self->ch_dir('/' . $name);
1562 $self->ch_dir($self->{cwd} . '/' . $name);
1566 $self->fill_infoview($self->{cwd}, $name);
1569 return 1; # consume event
1574 my ($self, $path, $file) = @_;
1575 $self->clear_infoview();
1576 my @v = get_all_file_versions($self->{dbh},
1579 $self->current_client,
1580 $self->{pref}->{see_all_versions});
1582 my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
1584 my $icon = ($inchanger)?$yesicon:$noicon;
1586 $mtime = localtime($mtime) ;
1588 listview_push($self->{fileinfo},
1589 $file, $jobid, 'file',
1590 $icon, $volname, $jobid, human($size), $mtime, $md5);
1597 return $self->{restore_backup_combobox}->get_active_text;
1603 return $self->{client_combobox}->get_active_text;
1606 sub on_list_backups_changed
1608 my ($self, $widget) = @_;
1609 return 0 unless defined $self->{fileview};
1611 $self->{CurrentJobIds} = [
1612 set_job_ids_for_date($self->{dbh},
1613 $self->current_client,
1614 $self->current_date,
1615 $self->{pref}->{use_ok_bkp_only})
1617 $self->update_brestore_table(@{$self->{CurrentJobIds}});
1619 $self->refresh_fileview();
1623 sub on_restore_list_keypress
1625 my ($self, $widget, $event) = @_;
1626 if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1628 my @sel = $widget->get_selected_indices;
1629 foreach my $elt (reverse(sort {$a <=> $b} @sel))
1631 splice @{$self->{restore_list}->{data}},$elt,1;
1636 sub on_fileview_button_press_event
1638 my ($self,$widget,$event) = @_;
1639 if ($event->button == 3)
1641 $self->on_right_click_filelist($widget,$event);
1645 if ($event->button == 2)
1647 $self->on_see_all_version();
1654 sub on_see_all_version
1658 my @lst = listview_get_all($self->{fileview});
1661 my ($name, undef) = @{$i};
1663 new DlgFileVersion($self->{dbh},
1664 $self->current_client,
1665 $self->{cwd}, $name);
1669 sub on_right_click_filelist
1671 my ($self,$widget,$event) = @_;
1672 # I need to know what's selected
1673 my @sel = listview_get_all($self->{fileview});
1678 $type = $sel[0]->[2]; # $type
1683 if (@sel >=2 or $type eq 'dir')
1685 # We have selected more than one or it is a directories
1686 $w = $self->{filelist_dir_menu};
1690 $w = $self->{filelist_file_menu};
1696 $event->button, $event->time);
1699 sub context_add_to_filelist
1703 my @sel = listview_get_all($self->{fileview});
1705 foreach my $i (@sel)
1707 my ($file, $jobid, $type, undef) = @{$i};
1708 $file = $self->{cwd} . '/' . $file;
1709 $self->add_selected_file_to_list($file, $jobid, $type);
1713 # Adds a file to the filelist
1714 sub add_selected_file_to_list
1716 my ($self, $name, $jobid, $type)=@_;
1718 my $dbh = $self->{dbh};
1719 my $restore_list = $self->{restore_list};
1721 my $curjobids=join(',', @{$self->{CurrentJobIds}});
1728 if ($name and substr $name,-1 ne '/')
1730 $name .= '/'; # For bacula
1732 my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
1733 listview_push($restore_list,
1734 $name, $jobid, 'dir', $curjobids,
1735 $diricon, $name,$curjobids,$dirfileindex);
1737 elsif ($type eq 'file')
1739 my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
1741 listview_push($restore_list,
1742 $name, $jobid, 'file', $curjobids,
1743 $fileicon, $name, $jobid, $fileindex );
1747 # TODO : we want be able to restore files from a bad ended backup
1748 # we have JobStatus IN ('T', 'A', 'E') and we must
1750 # Data acces subs from here. Interaction with SGBD and caching
1752 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1753 # GUI and stores them in @CurrentJobIds
1754 sub set_job_ids_for_date
1756 my ($dbh, $client, $date, $only_ok)=@_;
1758 if (!$client or !$date) {
1762 my $status = get_wanted_job_status($only_ok);
1764 # The algorithm : for a client, we get all the backups for each
1765 # fileset, in reverse order Then, for each fileset, we store the 'good'
1766 # incrementals and differentials until we have found a full so it goes
1767 # like this : store all incrementals until we have found a differential
1768 # or a full, then find the full #
1770 my $query = "SELECT JobId, FileSet, Level, JobStatus
1771 FROM Job, Client, FileSet
1772 WHERE Job.ClientId = Client.ClientId
1773 AND FileSet.FileSetId = Job.FileSetId
1774 AND EndTime <= '$date'
1775 AND Client.Name = '$client'
1777 AND JobStatus IN ($status)
1778 ORDER BY FileSet, JobTDate DESC";
1780 print STDERR $query,"\n" if $debug;
1782 my $result = $dbh->selectall_arrayref($query);
1784 foreach my $refrow (@$result)
1786 my $jobid = $refrow->[0];
1787 my $fileset = $refrow->[1];
1788 my $level = $refrow->[2];
1790 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1792 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1796 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1797 push @CurrentJobIds,($jobid);
1799 elsif ($level eq 'D')
1801 next if $progress{$fileset} eq 'D'; # We allready have a differential
1802 push @CurrentJobIds,($jobid);
1804 elsif ($level eq 'F')
1806 push @CurrentJobIds,($jobid);
1809 my $status = $refrow->[3] ;
1810 if ($status eq 'T') { # good end of job
1811 $progress{$fileset} = $level;
1814 print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
1816 return @CurrentJobIds;
1819 # Lists all directories contained inside a directory.
1820 # Uses the current dir, the client name, and CurrentJobIds for visibility.
1821 # Returns an array of dirs
1824 my ($self,$dir,$client)=@_;
1826 print "list_dirs(<$dir>, <$client>)\n" if $debug;
1828 if ($dir ne '' and substr $dir,-1 ne '/')
1830 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1833 my $dbh = $self->{dbh};
1834 my $query = "SELECT PathId FROM Path WHERE Path = ?
1835 UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?";
1836 my $sth = $dbh->prepare($query);
1837 $sth->execute($dir,$dir);
1838 my $result = $sth->fetchrow_arrayref();
1840 my $pathid = $result->[0];
1841 my @jobids = @{$self->{CurrentJobIds}};
1842 my $jobclause = join (',', @jobids);
1843 # Let's retrieve the list of the visible dirs in this dir ...
1844 # First, I need the empty filenameid to locate efficiently the dirs in the file table
1845 $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
1846 $sth = $dbh->prepare($query);
1848 $result = $sth->fetchrow_arrayref();
1850 my $dir_filenameid = $result->[0];
1852 # Then we get all the dir entries from File ...
1853 # It's ugly because there are records in brestore_missing_path ...
1855 SELECT Path, JobId, Lstat FROM(
1857 SELECT Path.Path, lower(Path.Path),
1858 listfile.JobId, listfile.Lstat
1860 SELECT DISTINCT brestore_pathhierarchy.PathId
1861 FROM brestore_pathhierarchy
1863 ON (brestore_pathhierarchy.PathId = Path.PathId)
1864 JOIN brestore_pathvisibility
1865 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
1866 WHERE brestore_pathhierarchy.PPathId = $pathid
1867 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
1868 JOIN Path ON (listpath.PathId = Path.PathId)
1870 SELECT File.PathId, File.JobId, File.Lstat FROM File
1871 WHERE File.FilenameId = $dir_filenameid
1872 AND File.JobId IN ($jobclause)) AS listfile
1873 ON (listpath.PathId = listfile.PathId)
1875 SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path),
1876 listfile.JobId, listfile.Lstat
1878 SELECT DISTINCT brestore_pathhierarchy.PathId
1879 FROM brestore_pathhierarchy
1880 JOIN brestore_missing_path
1881 ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId)
1882 JOIN brestore_pathvisibility
1883 ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId)
1884 WHERE brestore_pathhierarchy.PPathId = $pathid
1885 AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath
1886 JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId)
1888 SELECT File.PathId, File.JobId, File.Lstat FROM File
1889 WHERE File.FilenameId = $dir_filenameid
1890 AND File.JobId IN ($jobclause)) AS listfile
1891 ON (listpath.PathId = listfile.PathId))
1892 ORDER BY 2,3 DESC ) As a";
1893 print STDERR "$query\n" if $debug;
1894 $sth=$dbh->prepare($query);
1896 $result = $sth->fetchall_arrayref();
1899 foreach my $refrow (@{$result})
1901 my $dir = $refrow->[0];
1902 my $jobid = $refrow->[1];
1903 my $lstat = $refrow->[2];
1904 next if ($dir eq $prev_dir);
1905 # We have to clean up this dirname ... we only want it's 'basename'
1909 my @temp = split ('/',$dir);
1910 $return_value = pop @temp;
1914 $return_value = '/';
1916 my @return_array = ($return_value,$lstat);
1917 push @return_list,(\@return_array);
1920 return @return_list;
1924 # List all files in a directory. dir as parameter, CurrentJobIds for visibility
1925 # Returns an array of dirs
1928 my ($self, $dir)=@_;
1929 my $dbh = $self->{dbh};
1933 print "list_files($dir)\n" if $debug;
1935 if ($dir ne '' and substr $dir,-1 ne '/')
1937 $dir .= '/'; # In the db, there is a / at the end of the dirs ...
1940 my $query = "SELECT Path.PathId
1942 WHERE Path.Path = '$dir'
1944 SELECT brestore_missing_path.PathId
1945 FROM brestore_missing_path
1946 WHERE brestore_missing_path.Path = '$dir'";
1947 print $query,"\n" if $debug;
1949 my $result = $dbh->selectall_arrayref($query);
1950 foreach my $refrow (@$result)
1952 push @list_pathid,($refrow->[0]);
1955 if (@list_pathid == 0)
1957 print "No pathid found for $dir\n" if $debug;
1961 my $inlistpath = join (',', @list_pathid);
1962 my $inclause = join (',', @{$self->{CurrentJobIds}});
1963 if ($inclause eq '')
1969 "SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
1971 (SELECT Filename.Name, max(File.FileId) as id
1973 WHERE File.FilenameId = Filename.FilenameId
1974 AND Filename.Name != ''
1975 AND File.PathId IN ($inlistpath)
1976 AND File.JobId IN ($inclause)
1977 GROUP BY Filename.Name
1978 ORDER BY Filename.Name) AS listfiles,
1980 WHERE File.FileId = listfiles.id";
1982 print STDERR $query,"\n" if $debug;
1983 $result = $dbh->selectall_arrayref($query);
1990 Gtk2->main_iteration while (Gtk2->events_pending);
1993 sub create_brestore_tables
1997 my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
1999 unless ($self->dbh_do($verif)) {
2000 new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2002 $self->{error} = "Creating internal brestore tables";
2004 CREATE TABLE brestore_knownjobid
2006 JobId int4 NOT NULL,
2007 CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2009 $self->dbh_do($req);
2012 $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2013 unless ($self->dbh_do($verif)) {
2015 CREATE TABLE brestore_pathhierarchy
2017 PathId int4 NOT NULL,
2018 PPathId int4 NOT NULL,
2019 CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2021 $self->dbh_do($req);
2024 $req = "CREATE INDEX brestore_pathhierarchy_ppathid
2025 ON brestore_pathhierarchy (PPathId)";
2026 $self->dbh_do($req);
2029 $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2030 unless ($self->dbh_do($verif)) {
2032 CREATE TABLE brestore_pathvisibility
2034 PathId int4 NOT NULL,
2035 JobId int4 NOT NULL,
2036 CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2038 $self->dbh_do($req);
2040 $req = "CREATE INDEX brestore_pathvisibility_jobid
2041 ON brestore_pathvisibility (JobId)";
2042 $self->dbh_do($req);
2045 $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2046 unless ($self->dbh_do($verif)) {
2048 CREATE TABLE brestore_missing_path
2050 PathId int4 NOT NULL,
2052 CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
2054 $self->dbh_do($req);
2056 $req = "CREATE INDEX brestore_missing_path_path
2057 ON brestore_missing_path (Path)";
2058 $self->dbh_do($req);
2062 # Recursive function to calculate the visibility of each directory in the cache
2063 # tree Working with references to save time and memory
2064 # For each directory, we want to propagate it's visible jobids onto it's
2065 # parents directory.
2066 # A tree is visible if
2067 # - it's been in a backup pointed by the CurrentJobIds
2068 # - one of it's subdirs is in a backup pointed by the CurrentJobIds
2069 # In the second case, the directory is visible but has no metadata.
2070 # We symbolize this with lstat = 1 for this jobid in the cache.
2072 # Input : reference directory
2073 # Output : visibility of this dir. Has to know visibility of all subdirs
2074 # to know it's visibility, hence the recursing.
2080 # Get the subdirs array references list
2081 my @list_ref_subdirs;
2082 while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
2084 push @list_ref_subdirs,($ref_subdir);
2087 # Now lets recurse over these subdirs and retrieve the reference of a hash
2088 # containing the jobs where they are visible
2089 foreach my $ref_subdir (@list_ref_subdirs)
2091 my $ref_list_jobs = list_visible($ref_subdir);
2092 foreach my $jobid (keys %$ref_list_jobs)
2094 $visibility{$jobid}=1;
2098 # Ok. Now, we've got the list of those jobs. We are going to update our
2099 # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
2100 # the lstat for the known jobids. Put 1 in the new elements... But first,
2101 # let's store the current jobids
2103 foreach my $jobid (keys %{$refdir->[1]})
2105 push @known_jobids,($jobid);
2109 foreach my $jobid (keys %visibility)
2111 next if ($refdir->[1]->{$jobid});
2112 $refdir->[1]->{$jobid} = 1;
2114 # Add the known_jobids to %visibility
2115 foreach my $jobid (@known_jobids)
2117 $visibility{$jobid}=1;
2119 return \%visibility;
2122 # Returns the list of media required for a list of jobids.
2123 # Input : dbh, jobid1, jobid2...
2124 # Output : reference to array of (joibd, inchanger)
2125 sub get_required_media_from_jobid
2127 my ($dbh, @jobids)=@_;
2128 my $inclause = join(',',@jobids);
2130 SELECT DISTINCT JobMedia.MediaId, Media.InChanger
2131 FROM JobMedia, Media
2132 WHERE JobMedia.MediaId=Media.MediaId
2133 AND JobId In ($inclause)
2135 my $result = $dbh->selectall_arrayref($query);
2139 # Returns the fileindex from dirname and jobid.
2140 # Input : dbh, dirname, jobid
2141 # Output : fileindex
2142 sub get_fileindex_from_dir_jobid
2144 my ($dbh, $dirname, $jobid)=@_;
2146 $query = "SELECT File.FileIndex
2147 FROM File, Filename, Path
2148 WHERE File.FilenameId = Filename.FilenameId
2149 AND File.PathId = Path.PathId
2150 AND Filename.Name = ''
2151 AND Path.Path = '$dirname'
2152 AND File.JobId = '$jobid'
2155 print STDERR $query,"\n" if $debug;
2156 my $result = $dbh->selectall_arrayref($query);
2157 return $result->[0]->[0];
2160 # Returns the fileindex from filename and jobid.
2161 # Input : dbh, filename, jobid
2162 # Output : fileindex
2163 sub get_fileindex_from_file_jobid
2165 my ($dbh, $filename, $jobid)=@_;
2167 my @dirs = File::Spec->splitdir ($filename);
2168 $filename=pop(@dirs);
2169 my $dirname = File::Spec->catdir(@dirs) . '/';
2174 "SELECT File.FileIndex
2175 FROM File, Filename, Path
2176 WHERE File.FilenameId = Filename.FilenameId
2177 AND File.PathId = Path.PathId
2178 AND Filename.Name = '$filename'
2179 AND Path.Path = '$dirname'
2180 AND File.JobId = '$jobid'";
2182 print STDERR $query,"\n" if $debug;
2183 my $result = $dbh->selectall_arrayref($query);
2184 return $result->[0]->[0];
2188 # Returns list of versions of a file that could be restored
2189 # returns an array of
2190 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2191 # It's the same as entries of restore_list (hidden) + mtime and size and inchanger
2192 # and volname and md5
2193 # and of course, there will be only one jobid in the array of jobids...
2194 sub get_all_file_versions
2196 my ($dbh,$path,$file,$client,$see_all)=@_;
2198 defined $see_all or $see_all=0;
2203 "SELECT File.JobId, File.FileIndex, File.Lstat,
2204 File.Md5, Media.VolumeName, Media.InChanger
2205 FROM File, Filename, Path, Job, Client, JobMedia, Media
2206 WHERE File.FilenameId = Filename.FilenameId
2207 AND File.PathId=Path.PathId
2208 AND File.JobId = Job.JobId
2209 AND Job.ClientId = Client.ClientId
2210 AND Job.JobId = JobMedia.JobId
2211 AND File.FileIndex >= JobMedia.FirstIndex
2212 AND File.FileIndex <= JobMedia.LastIndex
2213 AND JobMedia.MediaId = Media.MediaId
2214 AND Path.Path = '$path'
2215 AND Filename.Name = '$file'
2216 AND Client.Name = '$client'";
2218 print STDERR $query if $debug;
2220 my $result = $dbh->selectall_arrayref($query);
2222 foreach my $refrow (@$result)
2224 my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2225 my @attribs = parse_lstat($lstat);
2226 my $mtime = array_attrib('st_mtime',\@attribs);
2227 my $size = array_attrib('st_size',\@attribs);
2229 my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
2230 $inchanger, $md5, $volname);
2231 push @versions, (\@list);
2234 # We have the list of all versions of this file.
2235 # We'll sort it by mtime desc, size, md5, inchanger desc
2236 # the rest of the algorithm will be simpler
2237 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2238 @versions = sort { $b->[4] <=> $a->[4]
2239 || $a->[5] <=> $b->[5]
2240 || $a->[7] cmp $a->[7]
2241 || $b->[6] <=> $a->[6]} @versions;
2244 my %allready_seen_by_mtime;
2245 my %allready_seen_by_md5;
2246 # Now we should create a new array with only the interesting records
2247 foreach my $ref (@versions)
2251 # The file has a md5. We compare his md5 to other known md5...
2252 # We take size into account. It may happen that 2 files
2253 # have the same md5sum and are different. size is a supplementary
2256 # If we allready have a (better) version
2257 next if ( (not $see_all)
2258 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
2260 # we never met this one before...
2261 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
2263 # Even if it has a md5, we should also work with mtimes
2264 # We allready have a (better) version
2265 next if ( (not $see_all)
2266 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
2267 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
2269 # We reached there. The file hasn't been seen.
2270 push @good_versions,($ref);
2273 # To be nice with the user, we re-sort good_versions by
2274 # inchanger desc, mtime desc
2275 @good_versions = sort { $b->[4] <=> $a->[4]
2276 || $b->[2] <=> $a->[2]} @good_versions;
2278 return @good_versions;
2281 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
2282 # This sub creates a BSR from the information in the restore_list
2283 # Returns the BSR as a string
2288 # This query gets all jobid/jobmedia/media combination.
2290 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
2291 JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
2292 JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
2293 Media.Volumename, Media.MediaType
2294 FROM Job, JobMedia, Media
2295 WHERE Job.JobId = JobMedia.JobId
2296 AND JobMedia.MediaId = Media.MediaId
2297 ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
2300 my $result = $self->dbh_selectall_arrayref($query);
2302 # We will store everything hashed by jobid.
2304 foreach my $refrow (@$result)
2306 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
2307 $firstindex, $lastindex, $startblock, $endblock,
2308 $volindex, $volumename, $mediatype) = @{$refrow};
2310 # We just have to deal with the case where starfile != endfile
2311 # In this case, we concatenate both, for the bsr
2312 if ($startfile != $endfile) {
2313 $startfile = $startfile . '-' . $endfile;
2317 ($jobid, $volsessionid, $volsessiontime, $startfile,
2318 $firstindex, $lastindex, $startblock .'-'. $endblock,
2319 $volindex, $volumename, $mediatype);
2321 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2325 # reminder : restore_list looks like this :
2326 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2328 # Here, we retrieve every file/dir that could be in the restore
2329 # We do as simple as possible for the SQL engine (no crazy joins,
2330 # no pseudo join (>= FirstIndex ...), etc ...
2331 # We do a SQL union of all the files/dirs specified in the restore_list
2333 foreach my $entry (@{$self->{restore_list}->{data}})
2335 if ($entry->[2] eq 'dir')
2337 my $dir = unpack('u', $entry->[0]);
2338 my $inclause = $entry->[3]; #curjobids
2341 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2342 FROM File, Path, Filename
2343 WHERE Path.PathId = File.PathId
2344 AND File.FilenameId = Filename.FilenameId
2345 AND Path.Path LIKE '$dir%'
2346 AND File.JobId IN ($inclause) )";
2347 push @select_queries,($query);
2351 # It's a file. Great, we allready have most
2352 # of what is needed. Simple and efficient query
2353 my $file = unpack('u', $entry->[0]);
2354 my @file = split '/',$file;
2356 my $dir = join('/',@file);
2358 my $jobid = $entry->[1];
2359 my $fileindex = $entry->[7];
2360 my $inclause = $entry->[3]; # curjobids
2362 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2363 FROM File, Path, Filename
2364 WHERE Path.PathId = File.PathId
2365 AND File.FilenameId = Filename.FilenameId
2366 AND Path.Path = '$dir/'
2367 AND Filename.Name = '$file'
2368 AND File.JobId = $jobid)";
2369 push @select_queries,($query);
2372 $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2374 print STDERR $query,"\n" if $debug;
2376 #Now we run the query and parse the result...
2377 # there may be a lot of records, so we better be efficient
2378 # We use the bind column method, working with references...
2380 my $sth = $self->dbh_prepare($query);
2383 my ($path,$name,$fileindex,$jobid);
2384 $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2386 # The temp place we're going to save all file
2387 # list to before the real list
2391 while ($sth->fetchrow_arrayref())
2393 # This may look dumb, but we're going to do a join by ourselves,
2394 # to save memory and avoid sending a complex query to mysql
2395 my $complete_path = $path . $name;
2403 # Remove trailing slash (normalize file and dir name)
2404 $complete_path =~ s/\/$//;
2406 # Let's find the ref(s) for the %mediainfo element(s)
2407 # containing the data for this file
2408 # There can be several matches. It is the pseudo join.
2410 my $max_elt=@{$mediainfos{$jobid}}-1;
2412 while($med_idx <= $max_elt)
2414 my $ref = $mediainfos{$jobid}->[$med_idx];
2415 # First, can we get rid of the first elements of the
2416 # array ? (if they don't contain valuable records
2418 if ($fileindex > $ref->[5])
2420 # It seems we don't need anymore
2421 # this entry in %mediainfo (the input data
2424 shift @{$mediainfos{$jobid}};
2428 # We will do work on this elt. We can ++
2429 # $med_idx for next loop
2432 # %mediainfo row looks like :
2433 # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2434 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2437 # We are in range. We store and continue looping
2439 if ($fileindex >= $ref->[4])
2441 my @data = ($complete_path,$is_dir,
2443 push @temp_list,(\@data);
2447 # We are not in range. No point in continuing looping
2448 # We go to next record.
2452 # Now we have the array.
2453 # We're going to sort it, by
2454 # path, volsessiontime DESC (get the most recent file...)
2455 # The array rows look like this :
2456 # complete_path,is_dir,fileindex,
2457 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2458 # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2459 @temp_list = sort {$a->[0] cmp $b->[0]
2460 || $b->[3]->[2] <=> $a->[3]->[2]
2464 my $prev_complete_path='////'; # Sure not to match
2468 while (my $refrow = shift @temp_list)
2470 # For the sake of readability, we load $refrow
2471 # contents in real scalars
2472 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2473 my $jobid= $refother->[0]; # We don't need the rest...
2475 # We skip this entry.
2476 # We allready have a newer one and this
2477 # isn't a continuation of the same file
2478 next if ($complete_path eq $prev_complete_path
2479 and $jobid != $prev_jobid);
2483 and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2485 # We would be recursing inside a file.
2486 # Just what we don't want (dir replaced by file
2487 # between two backups
2493 push @restore_list,($refrow);
2495 $prev_complete_path = $complete_path;
2496 $prev_jobid = $jobid;
2502 push @restore_list,($refrow);
2504 $prev_complete_path = $complete_path;
2505 $prev_jobid = $jobid;
2509 # We get rid of @temp_list... save memory
2512 # Ok everything is in the list. Let's sort it again in another way.
2513 # This time it will be in the bsr file order
2515 # we sort the results by
2516 # volsessiontime, volsessionid, volindex, fileindex
2517 # to get all files in right order...
2518 # Reminder : The array rows look like this :
2519 # complete_path,is_dir,fileindex,
2520 # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2521 # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2523 @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
2524 || $a->[3]->[1] <=> $b->[3]->[1]
2525 || $a->[3]->[7] <=> $b->[3]->[7]
2526 || $a->[2] <=> $b->[2] }
2529 # Now that everything is ready, we create the bsr
2530 my $prev_fileindex=-1;
2531 my $prev_volsessionid=-1;
2532 my $prev_volsessiontime=-1;
2533 my $prev_volumename=-1;
2534 my $prev_volfile=-1;
2538 my $first_of_current_range=0;
2539 my @fileindex_ranges;
2542 foreach my $refrow (@restore_list)
2544 my (undef,undef,$fileindex,$refother)=@{$refrow};
2545 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2546 $volblocks,undef,$volumename,$mediatype)=@{$refother};
2548 # We can specifiy the number of files in each section of the
2549 # bsr to speedup restore (bacula can then jump over the
2550 # end of tape files.
2554 if ($prev_volumename eq '-1')
2556 # We only have to start the new range...
2557 $first_of_current_range=$fileindex;
2559 elsif ($prev_volsessionid != $volsessionid
2560 or $prev_volsessiontime != $volsessiontime
2561 or $prev_volumename ne $volumename
2562 or $prev_volfile ne $volfile)
2564 # We have to create a new section in the bsr...
2565 # We print the previous one ...
2566 # (before that, save the current range ...)
2567 if ($first_of_current_range != $prev_fileindex)
2570 push @fileindex_ranges,
2571 ("$first_of_current_range-$prev_fileindex");
2575 # We are out of a range,
2576 # but there is only one element in the range
2577 push @fileindex_ranges,
2578 ("$first_of_current_range");
2581 $bsr.=print_bsr_section(\@fileindex_ranges,
2583 $prev_volsessiontime,
2590 # Reset for next loop
2591 @fileindex_ranges=();
2592 $first_of_current_range=$fileindex;
2594 elsif ($fileindex-1 != $prev_fileindex)
2596 # End of a range of fileindexes
2597 if ($first_of_current_range != $prev_fileindex)
2600 push @fileindex_ranges,
2601 ("$first_of_current_range-$prev_fileindex");
2605 # We are out of a range,
2606 # but there is only one element in the range
2607 push @fileindex_ranges,
2608 ("$first_of_current_range");
2610 $first_of_current_range=$fileindex;
2612 $prev_fileindex=$fileindex;
2613 $prev_volsessionid = $volsessionid;
2614 $prev_volsessiontime = $volsessiontime;
2615 $prev_volumename = $volumename;
2616 $prev_volfile=$volfile;
2617 $prev_mediatype=$mediatype;
2618 $prev_volblocks=$volblocks;
2622 # Ok, we're out of the loop. Alas, there's still the last record ...
2623 if ($first_of_current_range != $prev_fileindex)
2626 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2631 # We are out of a range,
2632 # but there is only one element in the range
2633 push @fileindex_ranges,("$first_of_current_range");
2636 $bsr.=print_bsr_section(\@fileindex_ranges,
2638 $prev_volsessiontime,
2648 sub print_bsr_section
2650 my ($ref_fileindex_ranges,$volsessionid,
2651 $volsessiontime,$volumename,$volfile,
2652 $mediatype,$volblocks,$count)=@_;
2655 $bsr .= "Volume=\"$volumename\"\n";
2656 $bsr .= "MediaType=\"$mediatype\"\n";
2657 $bsr .= "VolSessionId=$volsessionid\n";
2658 $bsr .= "VolSessionTime=$volsessiontime\n";
2659 $bsr .= "VolFile=$volfile\n";
2660 $bsr .= "VolBlock=$volblocks\n";
2662 foreach my $range (@{$ref_fileindex_ranges})
2664 $bsr .= "FileIndex=$range\n";
2667 $bsr .= "Count=$count\n";
2671 # This function estimates the size to be restored for an entry of the restore
2673 # In : self,reference to the entry
2674 # Out : size in bytes, number of files
2675 sub estimate_restore_size
2677 # reminder : restore_list looks like this :
2678 # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
2682 if ($entry->[2] eq 'dir')
2684 my $dir = unpack('u', $entry->[0]);
2685 my $inclause = $entry->[3]; #curjobids
2687 "SELECT Path.Path, File.FilenameId, File.LStat
2688 FROM File, Path, Job
2689 WHERE Path.PathId = File.PathId
2690 AND File.JobId = Job.JobId
2691 AND Path.Path LIKE '$dir%'
2692 AND File.JobId IN ($inclause)
2693 ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2697 # It's a file. Great, we allready have most
2698 # of what is needed. Simple and efficient query
2699 my $file = unpack('u', $entry->[0]);
2700 my @file = split '/',$file;
2702 my $dir = join('/',@file);
2704 my $jobid = $entry->[1];
2705 my $fileindex = $entry->[7];
2706 my $inclause = $entry->[3]; # curjobids
2708 "SELECT Path.Path, File.FilenameId, File.Lstat
2709 FROM File, Path, Filename
2710 WHERE Path.PathId = File.PathId
2711 AND Path.Path = '$dir/'
2712 AND Filename.Name = '$file'
2713 AND File.JobId = $jobid
2714 AND Filename.FilenameId = File.FilenameId";
2717 print STDERR $query,"\n" if $debug;
2718 my ($path,$nameid,$lstat);
2719 my $sth = $self->dbh_prepare($query);
2721 $sth->bind_columns(\$path,\$nameid,\$lstat);
2731 while ($sth->fetchrow_arrayref())
2733 # Only the latest version of a file
2734 next if ($nameid eq $old_nameid and $path eq $old_path);
2736 if ($rcount > 15000) {
2743 # We get the size of this file
2744 my $size=lstat_attrib($lstat,'st_size');
2745 $total_size += $size;
2748 $old_nameid=$nameid;
2750 return ($total_size,$total_files);
2753 sub update_brestore_table
2755 my ($self, @jobs) = @_;
2756 my $dbh = $self->{dbh};
2758 foreach my $job (sort {$a <=> $b} @jobs)
2760 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2761 my $retour = $self->dbh_selectrow_arrayref($query);
2762 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2764 print STDERR "Inserting path records for JobId $job\n";
2765 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
2766 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2768 $self->dbh_do($query);
2770 # Now we have to do the directory recursion stuff to determine missing visibility
2771 # We try to avoid recursion, to be as fast as possible
2772 # We also only work on not allready hierarchised directories...
2774 print STDERR "Creating missing recursion paths for $job\n";
2776 $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
2777 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2778 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2779 WHERE brestore_pathvisibility.JobId = $job
2780 AND brestore_pathhierarchy.PathId IS NULL
2783 my $sth = $self->dbh_prepare($query);
2785 my $pathid; my $path;
2786 $sth->bind_columns(\$pathid,\$path);
2790 $self->build_path_hierarchy($path,$pathid);
2794 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2795 # This query gives all parent pathids for a given jobid that aren't stored.
2796 # It has to be called until no record is updated ...
2798 INSERT INTO brestore_pathvisibility (PathId, JobId) (
2799 SELECT a.PathId,$job
2801 (SELECT DISTINCT h.PPathId AS PathId
2802 FROM brestore_pathhierarchy AS h
2803 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2804 WHERE p.JobId=$job) AS a
2807 FROM brestore_pathvisibility
2808 WHERE JobId=$job) AS b
2809 ON (a.PathId = b.PathId)
2810 WHERE b.PathId IS NULL)";
2811 print STDERR $query,"\n" if ($debug);
2813 while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/))
2815 print STDERR "Recursively adding $rows_affected records from $job\n";
2818 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2823 sub cleanup_brestore_table
2826 my $dbh = $self->{dbh};
2828 my $query = "SELECT JobId from brestore_knownjobid";
2829 my @jobs = @{$dbh->selectall_arrayref($query)};
2831 foreach my $jobentry (@jobs)
2833 my $job = $jobentry->[0];
2834 $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2835 my $result = $dbh->selectall_arrayref($query);
2836 if (scalar(@{$result}))
2838 # There are still files for this jobid
2839 print STDERR "$job still exists. Not cleaning...\n";
2842 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2844 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2850 sub build_path_hierarchy
2852 my ($self, $path,$pathid)=@_;
2853 # Does the ppathid exist for this ? we use a memory cache...
2854 # In order to avoid the full loop, we consider that if a dir is allready in the
2855 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2858 #print STDERR "$path\n" if $debug;
2859 if (! $self->{cache_ppathid}->{$pathid})
2861 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2862 my $sth2 = $self->{dbh}->prepare_cached($query);
2863 $sth2->execute($pathid);
2864 # Do we have a result ?
2865 if (my $refrow = $sth2->fetchrow_arrayref)
2867 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2869 # This dir was in the db ...
2870 # It means we can leave, the tree has allready been built for
2875 # We have to create the record ...
2876 # What's the current p_path ?
2877 my $ppath = parent_dir($path);
2878 my $ppathid = $self->return_pathid_from_path($ppath);
2879 $self->{cache_ppathid}->{$pathid}= $ppathid;
2881 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2882 $sth2 = $self->{dbh}->prepare_cached($query);
2883 $sth2->execute($pathid,$ppathid);
2889 # It's allready in the cache.
2890 # We can leave, no time to waste here, all the parent dirs have allready
2898 sub return_pathid_from_path
2900 my ($self, $path) = @_;
2901 my $query = "SELECT PathId FROM Path WHERE Path = ?
2903 SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2904 #print STDERR $query,"\n" if $debug;
2905 my $sth = $self->{dbh}->prepare_cached($query);
2906 $sth->execute($path,$path);
2907 my $result =$sth->fetchrow_arrayref();
2909 if (defined $result)
2911 return $result->[0];
2914 # A bit dirty : we insert into path AND missing_path, to be sure
2915 # we aren't deleted by a purge. We still need to insert into path to get
2916 # the pathid, because of mysql
2917 $query = "INSERT INTO Path (Path) VALUES (?)";
2918 #print STDERR $query,"\n" if $debug;
2919 $sth = $self->{dbh}->prepare_cached($query);
2920 $sth->execute($path);
2923 $query = " INSERT INTO brestore_missing_path (PathId,Path)
2924 SELECT PathId,Path FROM Path WHERE Path = ?";
2925 #print STDERR $query,"\n" if $debug;
2926 $sth = $self->{dbh}->prepare_cached($query);
2927 $sth->execute($path);
2929 $query = " DELETE FROM Path WHERE Path = ?";
2930 #print STDERR $query,"\n" if $debug;
2931 $sth = $self->{dbh}->prepare_cached($query);
2932 $sth->execute($path);
2934 $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2935 #print STDERR $query,"\n" if $debug;
2936 $sth = $self->{dbh}->prepare_cached($query);
2937 $sth->execute($path);
2938 $result = $sth->fetchrow_arrayref();
2940 return $result->[0];
2952 # Root Windows case :
2953 if ($path =~ /^[a-z]+:\/$/i)
2958 my @tmp = split('/',$path);
2959 # We remove the last ...
2961 my $tmp = join ('/',@tmp) . '/';
2967 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
2968 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
2969 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
2970 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
2971 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
2972 'data_stream' => 15);;
2975 my ($attrib,$ref_attrib)=@_;
2976 return $ref_attrib->[$attrib_name_id{$attrib}];
2980 { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
2982 my ($file, $attrib)=@_;
2984 if (defined $attrib_name_id{$attrib}) {
2986 my @d = split(' ', $file->[2]) ; # TODO : cache this
2988 return from_base64($d[$attrib_name_id{$attrib}]);
2990 } elsif ($attrib eq 'jobid') {
2994 } elsif ($attrib eq 'name') {
2999 die "Attribute not known : $attrib.\n";
3003 # Return the jobid or attribute asked for a dir
3006 my ($self,$dir,$attrib)=@_;
3008 my @dir = split('/',$dir,-1);
3009 my $refdir=$self->{dirtree}->{$self->current_client};
3011 if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
3013 die "Attribute not known : $attrib.\n";
3016 foreach my $subdir (@dir)
3018 $refdir = $refdir->[0]->{$subdir};
3021 # $refdir is now the reference to the dir's array
3022 # Is the a jobid in @CurrentJobIds where the lstat is
3023 # defined (we'll search in reverse order)
3024 foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
3026 if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
3028 if ($attrib eq 'jobid')
3034 my @attribs = parse_lstat($refdir->[1]->{$jobid});
3035 return $attribs[$attrib_name_id{$attrib}+1];
3040 return 0; # We cannot get a good attribute.
3041 # This directory is here for the sake of visibility
3046 my ($lstat,$attrib)=@_;
3047 if ($lstat and defined $attrib_name_id{$attrib})
3049 my @d = split(' ', $lstat) ; # TODO : cache this
3050 return from_base64($d[$attrib_name_id{$attrib}]);
3057 # Base 64 functions, directly from recover.pl.
3059 # Karl Hakimian <hakimian@aha.com>
3060 # This section is also under GPL v2 or later.
3067 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3068 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3069 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3070 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3071 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3073 @base64_map = (0) x 128;
3075 for (my $i=0; $i<64; $i++) {
3076 $base64_map[ord($base64_digits[$i])] = $i;
3091 if (substr($where, 0, 1) eq '-') {
3093 $where = substr($where, 1);
3096 while ($where ne '') {
3098 my $d = substr($where, 0, 1);
3099 $val += $base64_map[ord(substr($where, 0, 1))];
3100 $where = substr($where, 1);
3108 my @attribs = split(' ',$lstat);
3109 foreach my $element (@attribs)
3111 $element = from_base64($element);
3120 ################################################################
3123 use base qw/DlgResto/;
3127 my ($class, $conf) = @_;
3128 my $self = bless {info => $conf}, $class;
3130 $self->{dbh} = $conf->{dbh};
3139 my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
3140 my $jobs = $self->dbh_selectall_arrayref($query);
3142 $self->update_brestore_table(map { $_->[0] } @$jobs);
3153 print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3157 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3160 GetOptions("conf=s" => \$file_conf,
3161 "batch" => \$batch_mod,
3163 "help" => \&HELP_MESSAGE) ;
3165 my $p = new Pref($file_conf);
3167 if (! -f $file_conf) {
3172 my $b = new Batch($p);
3173 if ($p->connect_db()) {
3174 $b->set_dbh($p->{dbh});
3180 $glade_file = $p->{glade_file};
3182 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3183 if (-f "$path/$glade_file") {
3184 $glade_file = "$path/$glade_file" ;
3191 if ( -f $glade_file) {
3192 my $w = new DlgResto($p);
3195 my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
3196 "Can't find your brestore.glade (glade_file => '$glade_file')
3197 Please, edit your $file_conf to setup it." );
3199 $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3204 Gtk2->main; # Start Gtk2 main loop
3216 # Code pour trier les colonnes
3217 my $mod = $fileview->get_model();
3218 $mod->set_default_sort_func(sub {
3219 my ($model, $item1, $item2) = @_;
3220 my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
3221 my $b = $model->get($item2, 1); # colonne (indice 1)
3226 $fileview->set_headers_clickable(1);
3227 my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
3228 $col->signal_connect('clicked', sub {
3229 my ($colonne, $model) = @_;
3230 $model->set_sort_column_id (1, 'ascending');