]> git.sur5r.net Git - bacula/bacula/blob - gui/brestore/brestore.pl
33f17328b1861d2272aea57f17c98c324a22d1f1
[bacula/bacula] / gui / brestore / brestore.pl
1 #!/usr/bin/perl -w
2
3 use strict;
4
5 # path to your brestore.glade
6 my $glade_file = 'brestore.glade' ;
7
8 =head1 NAME
9
10     brestore.pl - A Perl/Gtk console for Bacula
11
12 =head1 VERSION
13
14     $Id$
15
16 =head1 INSTALL
17   
18   Setup ~/.brestore.conf to find your brestore.glade
19
20   On debian like system, you need :
21     - libgtk2-gladexml-perl
22     - libdbd-mysql-perl or libdbd-pg-perl
23     - libexpect-perl
24
25   You have to add brestore_xxx tables to your catalog.
26
27   To speed up database query you have to create theses indexes
28     - CREATE INDEX file_pathid on File(PathId);
29     - ...
30
31   To follow restore job, you must have a running Bweb installation.
32
33 =head1 COPYRIGHT
34
35    Bacula® - The Network Backup Solution
36
37    Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
38
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.
42
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.
47
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.
52
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
56    02110-1301, USA.
57
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.
62
63    Base 64 functions from Karl Hakimian <hakimian@aha.com>
64    Integrally copied from recover.pl from bacula source distribution.
65
66 =cut
67
68 use Gtk2;               # auto-initialize Gtk2
69 use Gtk2::GladeXML;
70 use Gtk2::SimpleList;           # easy wrapper for list views
71 use Gtk2::Gdk::Keysyms;         # keyboard code constants
72 use Data::Dumper qw/Dumper/;
73 my $debug=0;                    # can be on brestore.conf
74 our ($VERSION) = ('$Revision$' =~ /(\d+)/);
75
76 package Pref;
77 use DBI;
78 sub new
79 {
80     my ($class, $config_file) = @_;
81     
82     my $self = bless {
83         config_file => $config_file,
84         password => '',         # db passwd
85         username => '',         # db username
86         connection_string => '',# db connection string
87         bconsole => 'bconsole', # path and arg to bconsole
88         bsr_dest => '',         # destination url for bsr files
89         debug    => 0,          # debug level 0|1
90         use_ok_bkp_only => 1,   # dont use bad backup
91         bweb     => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
92         see_all_versions => 0,  # display all file versions in FileInfo
93         mozilla  => 'mozilla',  # mozilla bin
94         default_restore_job => 'restore', # regular expression to select default
95                                    # restore job
96
97         # keywords that are used to fill DlgPref
98         chk_keyword =>  [ qw/use_ok_bkp_only debug see_all_versions/ ],
99         entry_keyword => [ qw/username password bweb mozilla
100                           connection_string default_restore_job
101                           bconsole bsr_dest glade_file/],
102     };
103
104     $self->read_config();
105
106     return $self;
107 }
108
109 sub read_config
110 {
111     my ($self) = @_;
112
113     # We read the parameters. They come from the configuration files
114     my $cfgfile ; my $tmpbuffer;
115     if (open FICCFG, $self->{config_file})
116     {
117         while(read FICCFG,$tmpbuffer,4096)
118         {
119             $cfgfile .= $tmpbuffer;
120         }
121         close FICCFG;
122         my $refparams;
123         no strict; # I have no idea of the contents of the file
124         eval '$refparams' . " = $cfgfile";
125         use strict;
126         
127         for my $p (keys %{$refparams}) {
128             $self->{$p} = $refparams->{$p};
129         }
130
131     } else {
132         # TODO : Force dumb default values and display a message
133     }
134 }
135
136 sub write_config
137 {
138     my ($self) = @_;
139     
140     $self->{error} = '';
141     my %parameters;
142
143     for my $k (@{ $self->{entry_keyword} }) { 
144         $parameters{$k} = $self->{$k};
145     }
146
147     for my $k (@{ $self->{chk_keyword} }) { 
148         $parameters{$k} = $self->{$k};
149     }
150
151     if (open FICCFG,">$self->{config_file}")
152     {
153         print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
154         close FICCFG;
155     }
156     else
157     {
158         $self->{error} = "Can't write configuration $!";
159     }
160     return $self->{error};
161 }
162
163 sub connect_db
164 {
165     my $self = shift ;
166
167     if ($self->{dbh}) {
168         $self->{dbh}->disconnect() ;
169     }
170
171     delete $self->{dbh};
172     delete $self->{error};
173
174     if (not $self->{connection_string})
175     {
176         # The parameters have not been set. Maybe the conf
177         # file is empty for now
178         $self->{error} = "No configuration found for database connection. " .
179                          "Please set this up.";
180         return 0;
181     }
182     
183     if (not eval {
184         $self->{dbh} = DBI->connect($self->{connection_string}, 
185                                     $self->{username},
186                                     $self->{password})
187         })
188     {
189         $self->{error} = "Can't open bacula database. " . 
190                          "Database connect string '" . 
191                          $self->{connection_string} ."' $!";
192         return 0;
193     }
194     $self->{is_mysql} = ($self->{connection_string} =~ m/dbi:mysql/i);
195     $self->{dbh}->{RowCacheSize}=100;
196     return 1;
197 }
198
199 sub go_bweb
200 {    
201     my ($self, $url, $msg) = @_;
202
203     unless ($self->{mozilla} and $self->{bweb}) {
204         new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
205         return -1;
206     }
207
208     if ($^O eq 'MSWin32') {
209         system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
210
211     } elsif (!fork()) {
212         system("$self->{mozilla} -remote 'Ping()'");
213         my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
214         if ($? == 0) {
215             $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
216         }
217         exec($cmd);
218         exit 1;
219     } 
220     return ($? == 0);
221 }
222
223 1;
224
225 ################################################################
226
227 package Bbase;
228
229 sub new
230 {
231     my ($class, %arg) = @_;
232
233     my $self = bless {
234         conf => $arg{conf},
235     }, $class;
236
237     return $self;
238 }
239
240 use Data::Dumper;
241
242 sub debug
243 {
244     my ($self, $what, %arg) = @_;
245     
246     if ($self->{conf}->{debug} and defined $what) {
247         my $level=0;
248         if ($arg{up}) {
249             $level++;
250         }
251         my $line = (caller($level))[2];
252         my $func = (caller($level+1))[3] || 'main';
253         print "$func:$line\t";  
254         if (ref $what) {
255             print Data::Dumper::Dumper($what);
256         } elsif ($arg{md5}) {
257             print "MD5=", md5_base64($what), " str=", $what,"\n";
258         } else {
259             print $what, "\n";
260         }
261     }
262 }
263
264 sub dbh_strcat
265 {
266     my ($self, @what) = @_;
267     if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
268         return join(' || ', @what);
269     } else {
270         return 'CONCAT(' . join(',', @what) . ')' ;
271     }
272 }
273
274 sub dbh_prepare
275 {
276     my ($self, $query) = @_;
277     $self->debug($query, up => 1);
278     return $self->{conf}->{dbh}->prepare($query);    
279 }
280
281 sub dbh_do
282 {
283     my ($self, $query) = @_;
284     $self->debug($query, up => 1);
285     return $self->{conf}->{dbh}->do($query);
286 }
287
288 sub dbh_selectall_arrayref
289 {
290     my ($self, $query) = @_;
291     $self->debug($query, up => 1);
292     return $self->{conf}->{dbh}->selectall_arrayref($query);
293 }
294
295 sub dbh_selectrow_arrayref
296 {
297     my ($self, $query) = @_;
298     $self->debug($query, up => 1);
299     return $self->{conf}->{dbh}->selectrow_arrayref($query);
300 }
301
302 sub dbh
303 {
304     my ($self) = @_;
305     return $self->{conf}->{dbh};
306 }
307
308 1;
309
310 ################################################################
311 # Manage preference
312 package DlgPref;
313
314 # my $pref = new Pref(config_file => 'brestore.conf');
315 # my $dlg = new DlgPref($pref);
316 # my $dlg_resto = new DlgResto($pref);
317 # $dlg->display($dlg_resto);
318 sub new
319 {
320     my ($class, $pref) = @_;
321
322     my $self = bless {
323         pref => $pref,          # Pref ref
324         dlgresto => undef,      # DlgResto ref
325         };
326
327     return $self;
328 }
329
330 sub display
331 {
332     my ($self, $dlgresto) = @_ ;
333
334     unless ($self->{glade}) {
335         $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
336         $self->{glade}->signal_autoconnect_from_package($self);
337     }
338
339     $self->{dlgresto} = $dlgresto;
340
341     my $g = $self->{glade};
342     my $p = $self->{pref};
343
344     for my $k (@{ $p->{entry_keyword} }) {
345         $g->get_widget("entry_$k")->set_text($p->{$k}) ;
346     }
347
348     for my $k (@{ $p->{chk_keyword} }) {
349         $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
350     }
351
352     $g->get_widget("dlg_pref")->show_all() ;
353 }
354
355 sub on_applybutton_clicked
356 {
357     my ($self) = @_;
358     my $glade = $self->{glade};
359     my $pref  = $self->{pref};
360
361     for my $k (@{ $pref->{entry_keyword} }) {
362         my $w = $glade->get_widget("entry_$k") ;
363         $pref->{$k} = $w->get_text();
364     }
365
366     for my $k (@{ $pref->{chk_keyword} }) {
367         my $w = $glade->get_widget("chkbp_$k") ;
368         $pref->{$k} = $w->get_active();
369     }
370
371     if (!$pref->write_config() && $pref->connect_db()) {
372         $self->{dlgresto}->set_status('Preferences updated');
373         $self->{dlgresto}->init_server_backup_combobox();
374         $self->{dlgresto}->set_status($pref->{error});
375
376     } else {
377         $self->{dlgresto}->set_status($pref->{error});
378     }
379 }
380
381 # Handle prefs ok click (apply/dismiss dialog)
382 sub on_okbutton_clicked 
383 {
384     my ($self) = @_;
385     $self->on_applybutton_clicked();
386
387     unless ($self->{pref}->{error}) {
388         $self->on_cancelbutton_clicked();
389     }
390 }
391 sub on_dialog_delete_event
392 {
393     my ($self) = @_;
394     $self->on_cancelbutton_clicked();
395     1;
396 }
397
398 sub on_cancelbutton_clicked
399 {
400     my ($self) = @_;
401     $self->{glade}->get_widget('dlg_pref')->hide();
402     delete $self->{dlgresto};
403 }
404 1;
405
406 ################################################################
407
408 package DlgFileVersion;
409
410 sub on_versions_close_clicked
411 {
412     my ($self, $widget)=@_;
413     $self->{version}->destroy();
414 }
415
416 sub on_selection_button_press_event
417 {
418     print STDERR "on_selection_button_press_event()\n";
419 }
420
421 sub fileview_data_get
422 {
423     my ($self, $widget, $context, $data, $info, $time,$string) = @_;
424
425     DlgResto::drag_set_info($widget, 
426                             $self->{cwd},
427                             $data);
428 }
429
430 sub new
431 {
432     my ($class, $bvfs, $client, $path, $file, $cwd, $fn) = @_;
433     my $self = bless {
434         cwd       => $cwd,
435         version   => undef, # main window
436         };
437
438     # we load version widget of $glade_file
439     my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
440
441     # Connect signals magically
442     $glade_box->signal_autoconnect_from_package($self);
443
444     $glade_box->get_widget("version_label")
445         ->set_markup("<b>File revisions : $client:$cwd$fn</b>");
446
447     my $widget = $glade_box->get_widget('version_fileview');
448     my $fileview = Gtk2::SimpleList->new_from_treeview(
449                    $widget,
450                    'h_pathid'      => 'hidden',
451                    'h_filenameid'  => 'hidden',
452                    'h_name'        => 'hidden',
453                    'h_jobid'       => 'hidden',
454                    'h_type'        => 'hidden',
455
456                    'InChanger'     => 'pixbuf',
457                    'Volume'        => 'text',
458                    'JobId'         => 'text',
459                    'Size'          => 'text',
460                    'Date'          => 'text',
461                    'MD5'           => 'text',
462                                                        );
463     DlgResto::init_drag_drop($fileview);
464
465     my @v = $bvfs->get_all_file_versions($path, 
466                                          $file,
467                                          $client,
468                                          1);
469     for my $ver (@v) {
470         my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
471             $inchanger,$md5,$volname) = @{$ver};
472         my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
473
474         DlgResto::listview_push($fileview,$pid,$fid,
475                                 $fn, $jobid, 'file', 
476                                 $icon, $volname, $jobid,DlgResto::human($size),
477                                 scalar(localtime($mtime)), $md5);
478     }
479
480     $self->{version} = $glade_box->get_widget('dlg_version');
481     $self->{version}->show();
482     
483     return $self;
484 }
485
486 sub on_forward_keypress
487 {
488     return 0;
489 }
490
491 1;
492 ################################################################
493 package DlgWarn;
494
495 sub new
496 {
497     my ($package, $text) = @_;
498
499     my $self = bless {};
500
501     my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
502
503     # Connect signals magically
504     $glade->signal_autoconnect_from_package($self);
505     $glade->get_widget('label_warn')->set_text($text);
506
507     print STDERR "$text\n";
508
509     $self->{window} = $glade->get_widget('dlg_warn');
510     $self->{window}->show_all();
511     return $self;
512 }
513
514 sub on_close_clicked
515 {
516     my ($self) = @_;
517     $self->{window}->destroy();
518 }
519 1;
520
521 ################################################################
522
523 package DlgLaunch;
524
525 #use Bconsole;
526
527 # %arg = (bsr_file => '/path/to/bsr',       # on director
528 #         volumes  => [ '00001', '00004']
529 #         pref     => ref Pref
530 #         );
531
532 sub get_bconsole
533 {
534     my ($pref) = (@_);
535
536     if ($pref->{bconsole} =~ /^http/) {
537         return new BwebConsole(pref => $pref);
538     } else {
539         if (eval { require Bconsole; }) {
540             return new Bconsole(pref => $pref);
541         } else {
542             new DlgWarn("Can't use bconsole, verify your setup");
543             return undef;
544         }
545     }
546 }
547
548 sub new
549 {
550     my ($class, %arg) = @_;
551
552     my $self = bless {
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
557     };
558
559     my $console = $self->{bconsole} = get_bconsole($arg{pref});
560     unless ($console) {
561         return undef;
562     }
563
564     # we load launch widget of $glade_file
565     my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, 
566                                                      "dlg_launch");
567
568     # Connect signals magically
569     $glade->signal_autoconnect_from_package($self);
570
571     my $widget = $glade->get_widget('volumeview');
572     my $volview = Gtk2::SimpleList->new_from_treeview(
573                    $widget,
574                    'InChanger'     => 'pixbuf',
575                    'Volume'        => 'text', 
576                    );       
577
578     my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
579     
580     # we replace 0 and 1 by $noicon and $yesicon
581     for my $i (@{$infos}) {
582         if ($i->[0] == 0) {
583             $i->[0] = $DlgResto::noicon;
584         } else {
585             $i->[0] = $DlgResto::yesicon;
586         }
587     }
588
589     # fill volume view
590     push @{ $volview->{data} }, @{$infos} ;
591     
592     $console->prepare(qw/list_client list_job list_fileset list_storage/);
593
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}") ;
598     }
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,
603                          @clients);
604     $w->set_active(0);
605
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); 
611
612     # fill job combobox
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);
617     
618     # find default_restore_job in jobs list
619     my $default_restore_job = $arg{pref}->{default_restore_job} ;
620     my $index=0;
621     my $i=1;                    # 0 is ''
622     for my $j (@job) {
623         if ($j =~ /$default_restore_job/io) {
624             $index=$i;
625             last;
626         }
627         $i++;
628     }
629     $w->set_active($index);
630
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);
636
637     $glade->get_widget('dlg_launch')->show_all();
638
639     return $self;
640 }
641
642 sub show_job
643 {
644     my ($self, $client, $jobid) = @_;
645
646     my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
647
648     $self->on_cancel_resto_clicked();
649
650     if ($ret == -1) {
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)");
654         $widget->run;
655         $widget->destroy();
656     }
657 }
658
659 sub on_use_regexp_toggled
660 {
661     my ($self,$widget) = @_;
662     my $act = $widget->get_active();
663
664     foreach my $w ('entry_launch_where') {
665         $self->{glade}->get_widget($w)->set_sensitive(!$act);
666     }
667
668     foreach my $w ('entry_add_prefix', 'entry_strip_prefix', 
669                    'entry_add_suffix','entry_rwhere','chk_use_regexp')
670     {
671         $self->{glade}->get_widget($w)->set_sensitive($act);
672     }
673
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);
677     }
678 }
679
680
681 sub on_use_rwhere_toggled
682 {
683     my ($self,$widget) = @_;
684     my $act = $widget->get_active();
685
686     foreach my $w ('entry_rwhere') {
687         $self->{glade}->get_widget($w)->set_sensitive($act);
688     }
689
690     foreach my $w ('entry_add_prefix', 'entry_strip_prefix', 
691                    'entry_add_suffix')
692     {
693         $self->{glade}->get_widget($w)->set_sensitive(!$act);
694     }
695 }
696
697 sub on_cancel_resto_clicked
698 {
699     my ($self) = @_ ;
700     $self->{glade}->get_widget('dlg_launch')->destroy();
701 }
702
703 sub get_where
704 {
705     my ($self) = @_ ;
706
707     if ($self->{glade}->get_widget('chk_file_relocation')->get_active()) {
708         # using regexp
709         if ($self->{glade}->get_widget('chk_use_regexp')->get_active()) {
710
711             return ('rwhere', 
712                     $self->{glade}->get_widget('entry_rwhere')->get_active());
713         }
714             
715         # using regexp utils
716         my @ret;
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());
721             
722         if ($strip_prefix) {
723             push @ret,"!$strip_prefix!!i";
724         }
725         
726         if ($add_prefix) {
727             push @ret,"!^!$add_prefix!";
728         }
729
730         if ($add_suffix) {
731             push @ret,"!([^/])$!\$1$add_prefix!";
732         }
733
734         return ('rwhere', join(',', @ret));
735
736     } else { # using where
737         return ('where', 
738                 $self->{glade}->get_widget('entry_launch_where')->get_text());
739     }
740 }
741
742 sub on_submit_resto_clicked
743 {
744     my ($self) = @_ ;
745     my $glade = $self->{glade};
746
747     my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ;
748     
749     unless ($r) {
750         new DlgWarn("Can't copy bsr file to director ($self->{error})");
751         return;
752     }
753
754     my $fileset = $glade->get_widget('combo_launch_fileset')
755                                ->get_active_text();
756
757     my $storage = $glade->get_widget('combo_launch_storage')
758                                ->get_active_text();
759
760     my ($where_cmd, $where) = $self->get_where();
761     print "$where_cmd => $where\n";
762
763     my $job = $glade->get_widget('combo_launch_job')
764                                ->get_active_text();
765
766     if (! $job) {
767         new DlgWarn("Can't use this job");
768         return;
769     }
770
771     my $client = $glade->get_widget('combo_launch_client')
772                                ->get_active_text();
773
774     if (! $client or $client eq $DlgResto::client_list_empty) {
775         new DlgWarn("Can't use this client ($client)");
776         return;
777     }
778
779     my $prio = $glade->get_widget('spin_launch_priority')->get_value();
780
781     my $replace = $glade->get_widget('chkbp_launch_replace')->get_active();
782     $replace=($replace)?'always':'never';    
783
784     my $jobid = $self->{bconsole}->run(job => $job,
785                                        client  => $client,
786                                        storage => $storage,
787                                        fileset => $fileset,
788                                        $where_cmd => $where,
789                                        replace => $replace,
790                                        priority=> $prio,
791                                        bootstrap => $r);
792
793     $self->show_job($client, $jobid);
794 }
795
796 sub on_combo_storage_button_press_event
797 {
798     my ($self) = @_;
799     print "on_combo_storage_button_press_event()\n";
800 }
801
802 sub on_combo_fileset_button_press_event
803 {
804     my ($self) = @_;
805     print "on_combo_fileset_button_press_event()\n";
806
807 }
808
809 sub on_combo_job_button_press_event
810 {
811     my ($self) = @_;
812     print "on_combo_job_button_press_event()\n";
813 }
814
815 sub get_volume_inchanger
816 {
817     my ($dbh, $vols) = @_;
818
819     my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ;
820
821     my $rq = "SELECT InChanger, VolumeName 
822                FROM  Media  
823                WHERE VolumeName IN ($lst)
824              ";
825
826     my $res = $dbh->selectall_arrayref($rq);
827     return $res;                # [ [ 1, VolName].. ]
828 }
829
830
831 use File::Copy qw/copy/;
832 use File::Basename qw/basename/; 
833
834 # We must kown the path+filename destination
835 # $self->{error} contains error message
836 # it return 0/1 if fail/success
837 sub copy_bsr
838 {
839     my ($self, $src, $dst) = @_ ;
840     print "$src => $dst\n"
841         if ($debug);
842
843     if (!$dst) {
844         return $src;
845     }
846
847     my $ret=0 ;
848     my $err ; 
849     my $dstfile;
850
851     if ($dst =~ m!file:/(/.+)!) {
852         $ret = copy($src, $1);
853         $err = $!;
854         $dstfile = "$1/" . basename($src) ;
855
856     } elsif ($dst =~ m!scp://([^:]+:(.+))!) {
857         $err = `scp $src $1 2>&1` ;
858         $ret = ($? == 0) ;
859         $dstfile = "$2/" . basename($src) ;
860
861     } else {
862         $ret = 0;
863         $err = "$dst not implemented yet";
864         File::Copy::copy($src, \*STDOUT);
865     }
866
867     $self->{error} = $err;
868
869     if ($ret == 0) {
870         $self->{error} = $err;
871         return '';
872
873     } else {
874         return $dstfile;
875     }
876 }
877 1;
878
879 ################################################################
880
881 package DlgAbout;
882
883 my $about_widget;
884
885 sub display
886 {
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');
891     }
892     $about_widget->show() ;
893 }
894
895 sub on_about_okbutton_clicked
896 {
897     $about_widget->hide() ;
898 }
899
900 1;
901
902 ################################################################
903
904 package DlgResto;
905 use base qw/Bbase/;
906
907 our $diricon;
908 our $fileicon;
909 our $yesicon;
910 our $noicon;
911
912 # Kept as is from the perl-gtk example. Draws the pretty icons
913 sub render_icons 
914 {
915     my $self = shift;
916     unless ($diricon) {
917         my $size = 'button';
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);
922     }
923 }
924 # init combo (and create ListStore object)
925 sub init_combo
926 {
927     my ($widget, @type) = @_ ;
928     my %type_info = ('text' => 'Glib::String',
929                      'markup' => 'Glib::String',
930                      ) ;
931     
932     my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type );
933
934     $widget->set_model($lst);
935     my $i=0;
936     for my $t (@type) {
937         my $cell;
938         if ($t eq 'text' or $t eq 'markup') {
939             $cell = new Gtk2::CellRendererText();
940         }
941         $widget->pack_start($cell, 1);
942         $widget->add_attribute($cell, $t, $i++);
943     }
944     return $lst;
945 }
946
947 # fill simple combo (one element per row)
948 sub fill_combo
949 {
950     my ($list, @what) = @_;
951
952     $list->clear();
953     
954     foreach my $w (@what)
955     {
956         chomp($w);
957         my $i = $list->append();
958         $list->set($i, 0, $w);
959     }
960 }
961
962 # display Mb/Gb/Kb
963 sub human
964 {
965     my @unit = qw(b Kb Mb Gb Tb);
966     my $val = shift;
967     my $i=0;
968     my $format = '%i %s';
969     while ($val / 1024 > 1) {
970         $i++;
971         $val /= 1024;
972     }
973     $format = ($i>0)?'%0.1f %s':'%i %s';
974     return sprintf($format, $val, $unit[$i]);
975 }
976
977 sub get_wanted_job_status
978 {
979     my ($ok_only) = @_;
980
981     if ($ok_only) {
982         return "'T'";
983     } else {
984         return "'T', 'A', 'E'";
985     }
986 }
987
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
992 {
993     my ($self, $client, $ok_only)=@_;
994     my $status = get_wanted_job_status($ok_only);
995     my $query = "
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'
1000   AND Job.Type = 'B'
1001   AND JobStatus IN ($status)
1002   AND Job.FileSetId = FileSet.FileSetId
1003   ORDER BY EndTime desc";
1004     my $result = $self->dbh_selectall_arrayref($query);
1005
1006     return @$result;
1007 }
1008
1009 sub init_drag_drop
1010 {
1011     my ($fileview) = shift;
1012     my $fileview_target_entry = {target => 'STRING',
1013                                  flags => ['GTK_TARGET_SAME_APP'],
1014                                  info => 40 };
1015
1016     $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
1017                                         ['copy'],$fileview_target_entry);
1018     $fileview->get_selection->set_mode('multiple');
1019
1020     # set some useful SimpleList properties    
1021     $fileview->set_headers_clickable(0);
1022     foreach ($fileview->get_columns()) 
1023     {
1024         $_->set_resizable(1);
1025         $_->set_sizing('grow-only');
1026     }
1027 }
1028
1029 sub new
1030 {
1031     my ($class, $pref) = @_;
1032     my $self = bless { 
1033         conf => $pref,
1034         dirtree => undef,
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
1046         cwd   => '/',
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,
1053     };
1054
1055     $self->{bvfs} = new Bvfs(conf => $pref);
1056
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");
1061
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");
1065
1066     $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto");
1067     $glade->signal_autoconnect_from_package($self);
1068
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();
1073
1074     $self->{dlg_pref} = new DlgPref($pref);
1075
1076     my $c = $self->{client_combobox} = $glade->get_widget('combo_client');    
1077     $self->{list_client} = init_combo($c, 'text');
1078
1079     $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups');
1080     $self->{list_backup} = init_combo($c, 'text', 'markup');
1081  
1082     # Connect glade-fileview to Gtk2::SimpleList
1083     # and set up drag n drop between $fileview and $restore_list
1084
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)
1087
1088     my $widget = $glade->get_widget('fileview');
1089     my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
1090                                               $widget,
1091                                               'h_pathid'      => 'hidden',
1092                                               'h_filenameid'  => 'hidden',
1093                                               'h_name'        => 'hidden',
1094                                               'h_jobid'       => 'hidden',
1095                                               'h_type'        => 'hidden',
1096
1097                                               ''              => 'pixbuf',
1098                                               'File Name'     => 'text',
1099                                               'Size'          => 'text',
1100                                               'Date'          => 'text');
1101     init_drag_drop($fileview);
1102     $fileview->set_search_column(6); # search on File Name
1103
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(
1107                                               $widget,
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
1114
1115                                               ''            => 'pixbuf',
1116                                               'File Name'   => 'text',
1117                                               'JobId'       => 'text',
1118                                               'FileIndex'   => 'text',
1119
1120                                               'Nb Files'    => 'text', #10
1121                                               'Size'        => 'text', #11
1122                                               'size_b'      => 'hidden', #12
1123                                               );
1124
1125     my @restore_list_target_table = ({'target' => 'STRING',
1126                                       'flags' => [], 
1127                                       'info' => 40 });  
1128
1129     $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table);
1130     $restore_list->get_selection->set_mode('multiple');
1131     
1132     $widget = $glade->get_widget('infoview');
1133     my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
1134                    $widget,
1135                    'h_pathid'      => 'hidden',
1136                    'h_filenameid'  => 'hidden',
1137                    'h_name'        => 'hidden',
1138                    'h_jobid'       => 'hidden',
1139                    'h_type'        => 'hidden',
1140
1141                    'InChanger'     => 'pixbuf',
1142                    'Volume'        => 'text',
1143                    'JobId'         => 'text',
1144                    'Size'          => 'text',
1145                    'Date'          => 'text',
1146                    'MD5'           => 'text');
1147
1148     init_drag_drop($infoview);
1149
1150     $pref->connect_db() ||  $self->{dlg_pref}->display($self);
1151
1152     if ($pref->{dbh}) {
1153         $self->init_server_backup_combobox();
1154         $self->{bvfs}->create_brestore_tables();
1155     }
1156
1157     $self->set_status($pref->{error});
1158 }
1159
1160 # set status bar informations
1161 sub set_status
1162 {
1163     my ($self, $string) = @_;
1164     return unless ($string);
1165
1166     my $context = $self->{status}->get_context_id('Main');
1167     $self->{status}->push($context, $string);
1168 }
1169
1170 sub on_time_select_changed
1171 {
1172     my ($self) = @_;
1173 }
1174
1175 sub get_active_time
1176 {
1177     my ($self) = @_;
1178     my $c = $self->{glade}->get_widget('combo_time');
1179     return $c->get_active_text;
1180 }
1181
1182 # This sub returns all clients declared in DB
1183 sub get_all_clients
1184 {
1185     my $dbh = shift;
1186     my $query = "SELECT Name FROM Client ORDER BY Name";
1187     print STDERR $query,"\n" if $debug;
1188
1189     my $result = $dbh->selectall_arrayref($query);
1190
1191     return map { $_->[0] } @$result;
1192 }
1193
1194 # init infoview widget
1195 sub clear_infoview
1196 {
1197     my $self = shift;
1198     @{$self->{fileinfo}->{data}} = ();
1199 }
1200
1201 # init restore_list
1202 sub on_clear_clicked
1203 {
1204     my $self = shift;
1205     @{$self->{restore_list}->{data}} = ();
1206 }
1207
1208 sub on_estimate_clicked
1209 {
1210     my ($self) = @_;
1211
1212     my $size_total=0;
1213     my $nb_total=0;
1214
1215     # TODO : If we get here, things could get lenghty ... draw a popup window .
1216     my $widget = Gtk2::MessageDialog->new($self->{mainwin}, 
1217                                           'destroy-with-parent', 
1218                                           'info', 'close', 
1219                                           'Computing size...');
1220     $widget->show;
1221     refresh_screen();
1222
1223     my $title = "Computing size...\n";
1224     my $txt="";
1225
1226     # ($pid,$fid,$name,$jobid,'file',$curjobids,
1227     #   undef, undef, undef, $dirfileindex);
1228     foreach my $entry (@{$self->{restore_list}->{data}})
1229     {
1230         unless ($entry->[11]) {
1231             my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
1232             $entry->[12] = $size;
1233             $entry->[11] = human($size);
1234             $entry->[10] = $nb;
1235         }
1236
1237         my $name = unpack('u', $entry->[2]);
1238
1239         $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
1240         $self->debug($title . $txt);
1241         $widget->set_markup($title . $txt);
1242         
1243         $size_total+=$entry->[12];
1244         $nb_total+=$entry->[10];
1245         refresh_screen();
1246     }
1247     
1248     $txt .= "\n\n<b>Total</b> : $nb_total file(s)/" . human($size_total);
1249     $widget->set_markup("Size estimation :\n" . $txt);
1250     $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();});
1251
1252     return 0;
1253 }
1254
1255
1256
1257 sub on_gen_bsr_clicked
1258 {
1259     my ($self) = @_;
1260     
1261     my @options = ("Choose a bsr file", $self->{mainwin}, 'save', 
1262                    'gtk-save','ok', 'gtk-cancel', 'cancel');
1263
1264     
1265     my $w = new Gtk2::FileChooserDialog ( @options );
1266     my $ok = 0;
1267     my $save;
1268     while (!$ok) {
1269         my $a = $w->run();
1270         if ($a eq 'cancel') {
1271             $ok = 1;
1272         }
1273
1274         if ($a eq 'ok') {
1275             my $f = $w->get_filename();
1276             if (-f $f) {
1277                 my $dlg = Gtk2::MessageDialog->new($self->{mainwin}, 
1278                                                    'destroy-with-parent', 
1279                                                    'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
1280                 if ($dlg->run() eq 'ok') {
1281                     $save = $f;
1282                 }
1283                 $dlg->destroy();
1284             } else {
1285                 $save = $f;
1286             }
1287             $ok = 1;
1288         }
1289     }
1290
1291     $w->destroy();
1292     
1293     if ($save) {
1294         if (open(FP, ">$save")) {
1295             my $bsr = $self->create_filelist();
1296             print FP $bsr;
1297             close(FP);
1298             $self->set_status("Dumping BSR to $save ok");
1299         } else {
1300             $self->set_status("Can't dump BSR to $save: $!");
1301         }
1302     }
1303 }
1304
1305
1306 use File::Temp qw/tempfile/;
1307
1308 sub on_go_button_clicked 
1309 {
1310     my $self = shift;
1311     unless (scalar(@{$self->{restore_list}->{data}})) {
1312         new DlgWarn("No file to restore");
1313         return 0;
1314     }
1315     my $bsr = $self->create_filelist();
1316     my ($fh, $filename) = tempfile();
1317     $fh->print($bsr);
1318     close($fh);
1319     chmod(0644, $filename);
1320
1321     print "Dumping BSR info to $filename\n"
1322         if ($debug);
1323
1324     # we get Volume list
1325     my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
1326     my $vol = [ keys %a ] ;     # need only one occurrence of each volume
1327
1328     new DlgLaunch(pref     => $self->{conf},
1329                   volumes  => $vol,
1330                   bsr_file => $filename,
1331                   );
1332
1333 }
1334
1335
1336 our $client_list_empty = 'Clients list'; 
1337 our %type_markup = ('F' => '<b>$label F</b>',
1338                     'D' => '$label D',
1339                     'I' => '$label I',
1340                     'B' => '<b>$label B</b>',
1341
1342                     'A' => '<span foreground=\"red\">$label</span>',
1343                     'T' => '$label',
1344                     'E' => '<span foreground=\"red\">$label</span>',
1345                     );
1346
1347 sub on_list_client_changed 
1348 {
1349     my ($self, $widget) = @_;
1350     return 0 unless defined $self->{fileview};
1351
1352     $self->{list_backup}->clear();
1353
1354     if ($self->current_client eq $client_list_empty) {
1355         return 0 ;
1356     }
1357
1358     $self->{CurrentJobIds} = [
1359                               set_job_ids_for_date($self->dbh(),
1360                                                    $self->current_client,
1361                                                    $self->current_date,
1362                                                    $self->{conf}->{use_ok_bkp_only})
1363                               ];
1364
1365     my $fs = $self->{bvfs};
1366     $fs->set_curjobids(@{$self->{CurrentJobIds}});
1367     $fs->ch_dir($fs->get_root());
1368     # refresh_fileview will be done by list_backup_changed
1369
1370
1371     my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1372                                                  $self->{conf}->{use_ok_bkp_only});
1373
1374     foreach my $endtime (@endtimes)
1375     {
1376         my $i = $self->{list_backup}->append();
1377
1378         my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1379         eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1380         eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1381
1382         $self->{list_backup}->set($i, 
1383                                   0, $endtime->[0],
1384                                   1, $label,
1385                                   );
1386     }
1387     $self->{restore_backup_combobox}->set_active(0);
1388     0;
1389 }
1390
1391
1392 sub fill_server_list
1393 {
1394     my ($dbh, $combo, $list) = @_;
1395
1396     my @clients=get_all_clients($dbh);
1397
1398     $list->clear();
1399     
1400     my $i = $list->append();
1401     $list->set($i, 0, $client_list_empty);
1402     
1403     foreach my $client (@clients)
1404     {
1405         $i = $list->append();
1406         $list->set($i, 0, $client);
1407     }
1408     $combo->set_active(0);
1409 }
1410
1411 sub init_server_backup_combobox
1412 {
1413     my $self = shift ;
1414     fill_server_list($self->{conf}->{dbh}, 
1415                      $self->{client_combobox},
1416                      $self->{list_client}) ;
1417 }
1418
1419 #----------------------------------------------------------------------
1420 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1421 #data isn't.  There is additionnal complexity for dirs (visibility problems),
1422 #so the @CurrentJobIds is not sufficient.
1423 sub refresh_fileview 
1424 {
1425     my ($self) = @_;
1426     my $fileview = $self->{fileview};
1427     my $client_combobox = $self->{client_combobox};
1428     my $bvfs = $self->{bvfs};
1429
1430     @{$fileview->{data}} = ();
1431
1432     $self->clear_infoview();
1433     
1434     my $client_name = $self->current_client;
1435
1436     if (!$client_name or ($client_name eq $client_list_empty)) {
1437         $self->set_status("Client list empty");
1438         return;
1439     }
1440
1441     # [ [dirid, dir_basename, File.LStat, jobid]..]
1442     my $list_dirs = $bvfs->ls_dirs();
1443     # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1444     my $files     = $bvfs->ls_files(); 
1445     
1446     my $file_count = 0 ;
1447     my $total_bytes = 0;
1448     
1449     # Add directories to view
1450     foreach my $dir_entry (@$list_dirs) {
1451         my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1452         $total_bytes += 4096;
1453         $file_count++;
1454
1455         listview_push($fileview,
1456                       $dir_entry->[0],
1457                       0,
1458                       $dir_entry->[1],
1459                       # TODO: voir ce que l'on met la
1460                       $dir_entry->[3],
1461                       'dir',
1462
1463                       $diricon, 
1464                       $dir_entry->[1], 
1465                       "4 Kb", 
1466                       $time);
1467     }
1468     
1469     # Add files to view 
1470     foreach my $file (@$files) 
1471     {
1472         my $size = Bvfs::file_attrib($file,'st_size');
1473         my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1474         $total_bytes += $size;
1475         $file_count++;
1476         # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1477         listview_push($fileview,
1478                       $bvfs->{cwd},
1479                       $file->[0],
1480                       $file->[2],
1481                       $file->[4],
1482                       'file',
1483                       
1484                       $fileicon, 
1485                       $file->[2], 
1486                       human($size), $time);
1487     }
1488     
1489     $self->set_status("$file_count files/" . human($total_bytes));
1490     $self->{cwd} = $self->{bvfs}->pwd();
1491     $self->{location}->set_text($self->{cwd});
1492     # set a decent default selection (makes keyboard nav easy)
1493     $fileview->select(0);
1494 }
1495
1496
1497 sub on_about_activate
1498 {
1499     DlgAbout::display();
1500 }
1501
1502 sub drag_set_info
1503 {
1504     my ($tree, $path, $data) = @_;
1505
1506     my @items = listview_get_all($tree) ;
1507     my @ret;
1508     foreach my $i (@items)
1509     {
1510         my @file_info = @{$i};
1511
1512         # doc ligne 93
1513         # Ok, we have a corner case :
1514         # path can be empty
1515         my $file = pack("u", $path . $file_info[2]);
1516
1517         push @ret, join(" ; ", $file,
1518                         $file_info[0], # $pathid
1519                         $file_info[1], # $filenameid
1520                         $file_info[3], # $jobid
1521                         $file_info[4], # $type
1522                         );
1523     }
1524
1525     my $data_get = join(" :: ", @ret);
1526     
1527     $data->set_text($data_get,-1);
1528 }
1529
1530
1531
1532 sub fileview_data_get
1533 {
1534     my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1535     drag_set_info($widget, $self->{cwd}, $data);
1536 }
1537
1538 sub fileinfo_data_get
1539 {
1540     my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1541     drag_set_info($widget, $self->{cwd}, $data);
1542 }
1543
1544 sub restore_list_data_received
1545 {
1546     my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1547     my @ret;
1548     $self->debug("start\n");
1549     if  ($info eq 40 || $info eq 0) # patch for display!=:0
1550     {
1551         foreach my $elt (split(/ :: /, $data->data()))
1552         {
1553             my ($file, $pathid, $filenameid, $jobid, $type) = 
1554                 split(/ ; /, $elt);
1555             $file = unpack("u", $file);
1556             
1557             $self->add_selected_file_to_list($pathid,$filenameid,
1558                                              $file, $jobid, $type);
1559         }
1560     }
1561     $self->debug("end\n");
1562 }
1563
1564 sub on_back_button_clicked {
1565     my $self = shift;
1566     $self->{bvfs}->up_dir();
1567     $self->refresh_fileview();
1568 }
1569 sub on_location_go_button_clicked 
1570 {
1571     my $self = shift; 
1572     $self->ch_dir($self->{location}->get_text());
1573 }
1574 sub on_quit_activate {Gtk2->main_quit;}
1575 sub on_preferences_activate
1576 {
1577     my $self = shift; 
1578     $self->{dlg_pref}->display($self) ;
1579 }
1580 sub on_main_delete_event {Gtk2->main_quit;}
1581 sub on_bweb_activate
1582 {
1583     my $self = shift; 
1584     $self->set_status("Open bweb on your browser");
1585     $self->{conf}->go_bweb('', "go on bweb");
1586 }
1587
1588 # Change the current working directory
1589 #   * Updates fileview, location, and selection
1590 #
1591 sub ch_dir 
1592 {
1593     my ($self,$l) = @_;
1594
1595     my $p = $self->{bvfs}->get_pathid($l);
1596     if ($p) {
1597         $self->{bvfs}->ch_dir($p);
1598         $self->refresh_fileview();
1599     } else {
1600         $self->set_status("Can't find $l");
1601     }
1602     1;
1603 }
1604
1605 # Handle dialog 'close' (window-decoration induced close)
1606 #   * Just hide the dialog, and tell Gtk not to do anything else
1607 #
1608 sub on_delete_event 
1609 {
1610     my ($self, $w) = @_;
1611     $w->hide; 
1612     Gtk2::main_quit();
1613     1; # consume this event!
1614 }
1615
1616 # Handle key presses in location text edit control
1617 #   * Translate a Return/Enter key into a 'Go' command
1618 #   * All other key presses left for GTK
1619 #
1620 sub on_location_entry_key_release_event 
1621 {
1622     my $self = shift;
1623     my $widget = shift;
1624     my $event = shift;
1625     
1626     my $keypress = $event->keyval;
1627     if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1628         $keypress == $Gtk2::Gdk::Keysyms{Return}) 
1629     {
1630         $self->ch_dir($widget->get_text());
1631         
1632         return 1; # consume keypress
1633     }
1634
1635     return 0; # let gtk have the keypress
1636 }
1637
1638 sub on_fileview_key_press_event
1639 {
1640     my ($self, $widget, $event) = @_;
1641     return 0;
1642 }
1643
1644 sub listview_get_first
1645 {
1646     my ($list) = shift; 
1647     my @selected = $list->get_selected_indices();
1648     if (@selected > 0) {
1649         my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1650         return ($pid,$fid,unpack('u', $name), @other);
1651     } else {
1652         return undef;
1653     }
1654 }
1655
1656 sub listview_get_all
1657 {
1658     my ($list) = shift; 
1659
1660     my @selected = $list->get_selected_indices();
1661     my @ret;
1662     for my $i (@selected) {
1663         my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1664         push @ret, [$pid,$fid,unpack('u', $name), @other];
1665     } 
1666     return @ret;
1667 }
1668
1669 sub listview_push
1670 {
1671     my ($list, $pid, $fid, $name, @other) = @_;
1672     push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1673 }
1674
1675 #-----------------------------------------------------------------
1676 # Handle keypress in file-view
1677 #   * Translates backspace into a 'cd ..' command 
1678 #   * All other key presses left for GTK
1679 #
1680 sub on_fileview_key_release_event 
1681 {
1682     my ($self, $widget, $event) = @_;
1683     if (not $event->keyval)
1684     {
1685         return 0;
1686     }
1687     if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1688         $self->on_back_button_clicked();
1689         return 1; # eat keypress
1690     }
1691
1692     return 0; # let gtk have keypress
1693 }
1694
1695 sub on_forward_keypress
1696 {
1697     return 0;
1698 }
1699
1700 #-------------------------------------------------------------------
1701 # Handle double-click (or enter) on file-view
1702 #   * Translates into a 'cd <dir>' command
1703 #
1704 sub on_fileview_row_activated 
1705 {
1706     my ($self, $widget) = @_;
1707     
1708     my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1709
1710     if ($type eq 'dir')
1711     {
1712         $self->{bvfs}->ch_dir($pid);
1713         $self->refresh_fileview();
1714     } else {
1715         $self->fill_infoview($pid,$fid,$name);
1716     }
1717     
1718     return 1; # consume event
1719 }
1720
1721 sub fill_infoview
1722 {
1723     my ($self, $path, $file, $fn) = @_;
1724     $self->clear_infoview();
1725     my @v = $self->{bvfs}->get_all_file_versions($path, 
1726                                                  $file,
1727                                                  $self->current_client,
1728                                                  $self->{conf}->{see_all_versions});
1729     for my $ver (@v) {
1730         my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1731             $size,$inchanger,$md5,$volname) = @{$ver};
1732         my $icon = ($inchanger)?$yesicon:$noicon;
1733
1734         $mtime = localtime($mtime) ;
1735
1736         listview_push($self->{fileinfo},$pid,$fid,
1737                       $fn, $jobid, 'file', 
1738                       $icon, $volname, $jobid, human($size), $mtime, $md5);
1739     }
1740 }
1741
1742 sub current_date
1743 {
1744     my $self = shift ;
1745     return $self->{restore_backup_combobox}->get_active_text;
1746 }
1747
1748 sub current_client
1749 {
1750     my $self = shift ;
1751     return $self->{client_combobox}->get_active_text;
1752 }
1753
1754 sub on_list_backups_changed 
1755 {
1756     my ($self, $widget) = @_;
1757     return 0 unless defined $self->{fileview};
1758
1759     $self->{CurrentJobIds} = [
1760                               set_job_ids_for_date($self->dbh(),
1761                                                    $self->current_client,
1762                                                    $self->current_date,
1763                                                    $self->{conf}->{use_ok_bkp_only})
1764                               ];
1765     $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1766     $self->refresh_fileview();
1767     0;
1768 }
1769
1770 sub on_restore_list_keypress
1771 {
1772     my ($self, $widget, $event) = @_;
1773     if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1774     {
1775         my @sel = $widget->get_selected_indices;
1776         foreach my $elt (reverse(sort {$a <=> $b} @sel))
1777         {
1778             splice @{$self->{restore_list}->{data}},$elt,1;
1779         }
1780     }
1781 }
1782
1783 sub on_fileview_button_press_event
1784 {
1785     my ($self,$widget,$event) = @_;
1786     if ($event->button == 3)
1787     {
1788         $self->on_right_click_filelist($widget,$event);
1789         return 1;
1790     }
1791     
1792     if ($event->button == 2)
1793     {
1794         $self->on_see_all_version();
1795         return 1;
1796     }
1797
1798     return 0;
1799 }
1800
1801 sub on_see_all_version
1802 {
1803     my ($self) = @_;
1804     
1805     my @lst = listview_get_all($self->{fileview});
1806
1807     for my $i (@lst) {
1808         my ($pid,$fid,$name, undef) = @{$i};
1809
1810         new DlgFileVersion($self->{bvfs}, 
1811                            $self->current_client, 
1812                            $pid,$fid,$self->{cwd},$name);
1813     }
1814 }
1815
1816 sub on_right_click_filelist
1817 {
1818     my ($self,$widget,$event) = @_;
1819     # I need to know what's selected
1820     my @sel = listview_get_all($self->{fileview});
1821     
1822     my $type = '';
1823
1824     if (@sel == 1) {
1825         $type = $sel[0]->[4];   # $type
1826     }
1827
1828     my $w;
1829
1830     if (@sel >=2 or $type eq 'dir')
1831     {
1832         # We have selected more than one or it is a directories
1833         $w = $self->{filelist_dir_menu};
1834     }
1835     else
1836     {
1837         $w = $self->{filelist_file_menu};
1838     }
1839     $w->popup(undef,
1840               undef,
1841               undef,
1842               undef,
1843               $event->button, $event->time);
1844 }
1845
1846 sub context_add_to_filelist
1847 {
1848     my ($self) = @_;
1849
1850     my @sel = listview_get_all($self->{fileview});
1851
1852     foreach my $i (@sel)
1853     {
1854         my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1855         $file = $self->{cwd} . '/' . $file;
1856         $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1857     }
1858 }
1859
1860 # Adds a file to the filelist
1861 sub add_selected_file_to_list
1862 {
1863     my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1864
1865     my $restore_list = $self->{restore_list};
1866
1867     my $curjobids=join(',', @{$self->{CurrentJobIds}});
1868
1869     if ($type eq 'dir')
1870     {
1871         # dirty hack
1872         $name =~ s!^//+!/!;
1873
1874         if ($name and substr $name,-1 ne '/')
1875         {
1876                 $name .= '/'; # For bacula
1877         }
1878         my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1879         listview_push($restore_list,$pid,0, 
1880                       $name, $jobid, 'dir', $curjobids,
1881                       $diricon, $name,$curjobids,$dirfileindex);
1882     }
1883     elsif ($type eq 'file')
1884     {
1885         my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1886
1887         listview_push($restore_list,$pid,$fid,
1888                       $name, $jobid, 'file', $curjobids,
1889                       $fileicon, $name, $jobid, $fileindex );
1890     }
1891 }
1892
1893 # TODO : we want be able to restore files from a bad ended backup
1894 # we have JobStatus IN ('T', 'A', 'E') and we must 
1895
1896 # Data acces subs from here. Interaction with SGBD and caching
1897
1898 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1899 # GUI and stores them in @CurrentJobIds
1900 sub set_job_ids_for_date
1901 {
1902     my ($dbh, $client, $date, $only_ok)=@_;
1903
1904     if (!$client or !$date) {
1905         return ();
1906     }
1907     
1908     my $status = get_wanted_job_status($only_ok);
1909         
1910     # The algorithm : for a client, we get all the backups for each
1911     # fileset, in reverse order Then, for each fileset, we store the 'good'
1912     # incrementals and differentials until we have found a full so it goes
1913     # like this : store all incrementals until we have found a differential
1914     # or a full, then find the full #
1915
1916     my $query = "SELECT JobId, FileSet, Level, JobStatus
1917                 FROM Job, Client, FileSet
1918                 WHERE Job.ClientId = Client.ClientId
1919                 AND FileSet.FileSetId = Job.FileSetId
1920                 AND EndTime <= '$date'
1921                 AND Client.Name = '$client'
1922                 AND Type IN ('B')
1923                 AND JobStatus IN ($status)
1924                 ORDER BY FileSet, JobTDate DESC";
1925         
1926     my @CurrentJobIds;
1927     my $result = $dbh->selectall_arrayref($query);
1928     my %progress;
1929     foreach my $refrow (@$result)
1930     {
1931         my $jobid = $refrow->[0];
1932         my $fileset = $refrow->[1];
1933         my $level = $refrow->[2];
1934                 
1935         defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1936                 
1937         next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1938                 
1939         if ($level eq 'I')
1940         {
1941             next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1942             push @CurrentJobIds,($jobid);
1943         }
1944         elsif ($level eq 'D')
1945         {
1946             next if $progress{$fileset} eq 'D'; # We allready have a differential
1947             push @CurrentJobIds,($jobid);
1948         }
1949         elsif ($level eq 'F')
1950         {
1951             push @CurrentJobIds,($jobid);
1952         }
1953
1954         my $status = $refrow->[3] ;
1955         if ($status eq 'T') {              # good end of job
1956             $progress{$fileset} = $level;
1957         }
1958     }
1959
1960     return @CurrentJobIds;
1961 }
1962
1963 sub refresh_screen
1964 {
1965     Gtk2->main_iteration while (Gtk2->events_pending);
1966 }
1967
1968 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1969 # This sub creates a BSR from the information in the restore_list
1970 # Returns the BSR as a string
1971 sub create_filelist
1972 {
1973         my $self = shift;
1974         my %mediainfos;
1975         # This query gets all jobid/jobmedia/media combination.
1976         my $query = "
1977 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile, 
1978        JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1979        JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex, 
1980        Media.Volumename, Media.MediaType
1981 FROM Job, JobMedia, Media
1982 WHERE Job.JobId = JobMedia.JobId
1983   AND JobMedia.MediaId = Media.MediaId
1984   ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1985         
1986
1987         my $result = $self->dbh_selectall_arrayref($query);
1988
1989         # We will store everything hashed by jobid.
1990
1991         foreach my $refrow (@$result)
1992         {
1993                 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1994                 $firstindex, $lastindex, $startblock, $endblock,
1995                 $volindex, $volumename, $mediatype) = @{$refrow};
1996
1997                 # We just have to deal with the case where starfile != endfile
1998                 # In this case, we concatenate both, for the bsr
1999                 if ($startfile != $endfile) { 
2000                       $startfile = $startfile . '-' . $endfile;
2001                 }
2002
2003                 my @tmparray = 
2004                 ($jobid, $volsessionid, $volsessiontime, $startfile, 
2005                 $firstindex, $lastindex, $startblock .'-'. $endblock,
2006                 $volindex, $volumename, $mediatype);
2007                 
2008                 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
2009         }
2010
2011         
2012         # reminder : restore_list looks like this : 
2013         # ($pid,$fid,$name,$jobid,'file',$curjobids,
2014         #   undef, undef, undef, $dirfileindex);
2015         
2016         # Here, we retrieve every file/dir that could be in the restore
2017         # We do as simple as possible for the SQL engine (no crazy joins,
2018         # no pseudo join (>= FirstIndex ...), etc ...
2019         # We do a SQL union of all the files/dirs specified in the restore_list
2020         my @select_queries;
2021         foreach my $entry (@{$self->{restore_list}->{data}})
2022         {
2023                 if ($entry->[4] eq 'dir')
2024                 {
2025                         my $dirid = $entry->[0];
2026                         my $inclause = $entry->[5]; #curjobids
2027         
2028                         my $query = 
2029 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2030   FROM File, Path, Filename
2031   WHERE Path.PathId = File.PathId
2032   AND File.FilenameId = Filename.FilenameId
2033   AND Path.Path LIKE 
2034         (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path 
2035           WHERE PathId IN ($dirid)
2036         )
2037   AND File.JobId IN ($inclause) )";
2038                         push @select_queries,($query);
2039                 }
2040                 else
2041                 {
2042                         # It's a file. Great, we allready have most 
2043                         # of what is needed. Simple and efficient query
2044                         my $dir = $entry->[0];
2045                         my $file = $entry->[1];
2046                         
2047                         my $jobid = $entry->[3];
2048                         my $fileindex = $entry->[9];
2049                         my $inclause = $entry->[5]; # curjobids
2050                         my $query = 
2051 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
2052   FROM File,Path,Filename
2053   WHERE File.PathId = $dir
2054   AND File.PathId = Path.PathId
2055   AND File.FilenameId = $file
2056   AND File.FilenameId = Filename.FilenameId
2057   AND File.JobId = $jobid
2058  )
2059 ";
2060                         push @select_queries,($query);
2061                 }
2062         }
2063         $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
2064
2065         #Now we run the query and parse the result...
2066         # there may be a lot of records, so we better be efficient
2067         # We use the bind column method, working with references...
2068
2069         my $sth = $self->dbh_prepare($query);
2070         $sth->execute;
2071
2072         my ($path,$name,$fileindex,$jobid);
2073         $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
2074         
2075         # The temp place we're going to save all file
2076         # list to before the real list
2077         my @temp_list;
2078
2079         RECORD_LOOP:
2080         while ($sth->fetchrow_arrayref())
2081         {
2082                 # This may look dumb, but we're going to do a join by ourselves,
2083                 # to save memory and avoid sending a complex query to mysql
2084                 my $complete_path = $path . $name;
2085                 my $is_dir = 0;
2086                 
2087                 if ( $name eq '')
2088                 {
2089                         $is_dir = 1;
2090                 }
2091                 
2092                 # Remove trailing slash (normalize file and dir name)
2093                 $complete_path =~ s/\/$//;
2094                 
2095                 # Let's find the ref(s) for the %mediainfo element(s) 
2096                 # containing the data for this file
2097                 # There can be several matches. It is the pseudo join.
2098                 my $med_idx=0;
2099                 my $max_elt=@{$mediainfos{$jobid}}-1;
2100                 MEDIA_LOOP:
2101                 while($med_idx <= $max_elt)
2102                 {
2103                         my $ref = $mediainfos{$jobid}->[$med_idx];
2104                         # First, can we get rid of the first elements of the
2105                         # array ? (if they don't contain valuable records
2106                         # anymore
2107                         if ($fileindex > $ref->[5])
2108                         {
2109                                 # It seems we don't need anymore
2110                                 # this entry in %mediainfo (the input data
2111                                 # is sorted...)
2112                                 # We get rid of it.
2113                                 shift @{$mediainfos{$jobid}};
2114                                 $max_elt--;
2115                                 next MEDIA_LOOP;
2116                         }
2117                         # We will do work on this elt. We can ++
2118                         # $med_idx for next loop
2119                         $med_idx++;
2120
2121                         # %mediainfo row looks like : 
2122                         # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2123                         # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2124                         # MediaType)
2125                         
2126                         # We are in range. We store and continue looping
2127                         # in the medias
2128                         if ($fileindex >= $ref->[4])
2129                         {
2130                                 my @data = ($complete_path,$is_dir,
2131                                             $fileindex,$ref);
2132                                 push @temp_list,(\@data);
2133                                 next MEDIA_LOOP;
2134                         }
2135                         
2136                         # We are not in range. No point in continuing looping
2137                         # We go to next record.
2138                         next RECORD_LOOP;
2139                 }
2140         }
2141         # Now we have the array.
2142         # We're going to sort it, by 
2143         # path, volsessiontime DESC (get the most recent file...)
2144         # The array rows look like this :
2145         # complete_path,is_dir,fileindex,
2146         #\81 ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2147         #       LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2148         @temp_list = sort {$a->[0] cmp $b->[0]
2149                         || $b->[3]->[2] <=> $a->[3]->[2]
2150                           } @temp_list;
2151
2152         my @restore_list;
2153         my $prev_complete_path='////'; # Sure not to match
2154         my $prev_is_file=1;
2155         my $prev_jobid;
2156
2157         while (my $refrow = shift @temp_list)
2158         {
2159                 # For the sake of readability, we load $refrow 
2160                 # contents in real scalars
2161                 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2162                 my $jobid= $refother->[0]; # We don't need the rest...
2163
2164                 # We skip this entry.
2165                 # We allready have a newer one and this 
2166                 # isn't a continuation of the same file
2167                 next if ($complete_path eq $prev_complete_path 
2168                          and $jobid != $prev_jobid);
2169                 
2170                 
2171                 if ($prev_is_file 
2172                     and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2173                 {
2174                         # We would be recursing inside a file.
2175                         # Just what we don't want (dir replaced by file
2176                         # between two backups
2177                         next;
2178                 }
2179                 elsif ($is_dir)
2180                 {
2181                         # It is a directory
2182                         push @restore_list,($refrow);
2183                         
2184                         $prev_complete_path = $complete_path;
2185                         $prev_jobid = $jobid;
2186                         $prev_is_file = 0;
2187                 }
2188                 else
2189                 {
2190                         # It is a file
2191                         push @restore_list,($refrow);
2192                         
2193                         $prev_complete_path = $complete_path;
2194                         $prev_jobid = $jobid;
2195                         $prev_is_file = 1;
2196                 }
2197         }
2198         # We get rid of @temp_list... save memory
2199         @temp_list=();
2200
2201         # Ok everything is in the list. Let's sort it again in another way.
2202         # This time it will be in the bsr file order
2203
2204         # we sort the results by 
2205         # volsessiontime, volsessionid, volindex, fileindex 
2206         # to get all files in right order...
2207         # Reminder : The array rows look like this :
2208         # complete_path,is_dir,fileindex,
2209         # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2210         #       StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2211
2212         @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2] 
2213                            || $a->[3]->[1] <=> $b->[3]->[1] 
2214                            || $a->[3]->[7] <=> $b->[3]->[7] 
2215                            || $a->[2] <=> $b->[2] } 
2216                                 @restore_list;
2217
2218         # Now that everything is ready, we create the bsr
2219         my $prev_fileindex=-1;
2220         my $prev_volsessionid=-1;
2221         my $prev_volsessiontime=-1;
2222         my $prev_volumename=-1;
2223         my $prev_volfile=-1;
2224         my $prev_mediatype;
2225         my $prev_volblocks;
2226         my $count=0;
2227         my $first_of_current_range=0;
2228         my @fileindex_ranges;
2229         my $bsr='';
2230
2231         foreach my $refrow (@restore_list)
2232         {
2233                 my (undef,undef,$fileindex,$refother)=@{$refrow};
2234                 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2235                     $volblocks,undef,$volumename,$mediatype)=@{$refother};
2236                 
2237                 # We can specifiy the number of files in each section of the
2238                 # bsr to speedup restore (bacula can then jump over the
2239                 # end of tape files.
2240                 $count++;
2241                 
2242                 
2243                 if ($prev_volumename eq '-1')
2244                 {
2245                         # We only have to start the new range...
2246                         $first_of_current_range=$fileindex;
2247                 }
2248                 elsif ($prev_volsessionid != $volsessionid 
2249                        or $prev_volsessiontime != $volsessiontime 
2250                        or $prev_volumename ne $volumename 
2251                        or $prev_volfile ne $volfile)
2252                 {
2253                         # We have to create a new section in the bsr...
2254                         #\81Â\81 We print the previous one ... 
2255                         # (before that, save the current range ...)
2256                         if ($first_of_current_range != $prev_fileindex)
2257                         {
2258                                 #\81Â\81 we are in a range
2259                                 push @fileindex_ranges,
2260                                     ("$first_of_current_range-$prev_fileindex");
2261                         }
2262                         else
2263                         {
2264                                  # We are out of a range,
2265                                  # but there is only one element in the range
2266                                 push @fileindex_ranges,
2267                                     ("$first_of_current_range");
2268                         }
2269                         
2270                         $bsr.=print_bsr_section(\@fileindex_ranges,
2271                                                 $prev_volsessionid,
2272                                                 $prev_volsessiontime,
2273                                                 $prev_volumename,
2274                                                 $prev_volfile,
2275                                                 $prev_mediatype,
2276                                                 $prev_volblocks,
2277                                                 $count-1);
2278                         $count=1;
2279                         # Reset for next loop
2280                         @fileindex_ranges=();
2281                         $first_of_current_range=$fileindex;
2282                 }
2283                 elsif ($fileindex-1 != $prev_fileindex)
2284                 {
2285                         # End of a range of fileindexes
2286                         if ($first_of_current_range != $prev_fileindex)
2287                         {
2288                                 #we are in a range
2289                                 push @fileindex_ranges,
2290                                     ("$first_of_current_range-$prev_fileindex");
2291                         }
2292                         else
2293                         {
2294                                  # We are out of a range,
2295                                  # but there is only one element in the range
2296                                 push @fileindex_ranges,
2297                                     ("$first_of_current_range");
2298                         }
2299                         $first_of_current_range=$fileindex;
2300                 }
2301                 $prev_fileindex=$fileindex;
2302                 $prev_volsessionid = $volsessionid;
2303                 $prev_volsessiontime = $volsessiontime;
2304                 $prev_volumename = $volumename;
2305                 $prev_volfile=$volfile;
2306                 $prev_mediatype=$mediatype;
2307                 $prev_volblocks=$volblocks;
2308
2309         }
2310
2311         # Ok, we're out of the loop. Alas, there's still the last record ...
2312         if ($first_of_current_range != $prev_fileindex)
2313         {
2314                 # we are in a range
2315                 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2316                 
2317         }
2318         else
2319         {
2320                 # We are out of a range,
2321                 # but there is only one element in the range
2322                 push @fileindex_ranges,("$first_of_current_range");
2323                 
2324         }
2325         $bsr.=print_bsr_section(\@fileindex_ranges,
2326                                 $prev_volsessionid,
2327                                 $prev_volsessiontime,
2328                                 $prev_volumename,
2329                                 $prev_volfile,
2330                                 $prev_mediatype,
2331                                 $prev_volblocks,
2332                                 $count);
2333         
2334         return $bsr;
2335 }
2336
2337 sub print_bsr_section
2338 {
2339     my ($ref_fileindex_ranges,$volsessionid,
2340         $volsessiontime,$volumename,$volfile,
2341         $mediatype,$volblocks,$count)=@_;
2342     
2343     my $bsr='';
2344     $bsr .= "Volume=\"$volumename\"\n";
2345     $bsr .= "MediaType=\"$mediatype\"\n";
2346     $bsr .= "VolSessionId=$volsessionid\n";
2347     $bsr .= "VolSessionTime=$volsessiontime\n";
2348     $bsr .= "VolFile=$volfile\n";
2349     $bsr .= "VolBlock=$volblocks\n";
2350     
2351     foreach my $range (@{$ref_fileindex_ranges})
2352     {
2353         $bsr .= "FileIndex=$range\n";
2354     }
2355     
2356     $bsr .= "Count=$count\n";
2357     return $bsr;
2358 }
2359
2360 1;
2361
2362 ################################################################
2363
2364 package Bvfs;
2365 use base qw/Bbase/;
2366
2367 sub get_pathid
2368 {
2369     my ($self, $dir) = @_;
2370     my $query = 
2371         "SELECT PathId FROM Path WHERE Path = ?";
2372     my $sth = $self->dbh_prepare($query);
2373     $sth->execute($dir);
2374     my $result = $sth->fetchall_arrayref();
2375     $sth->finish();
2376     
2377     return join(',', map { $_->[0] } @$result);
2378 }
2379
2380
2381 sub update_cache
2382 {
2383     my ($self) = @_;
2384
2385     my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
2386     my $jobs = $self->dbh_selectall_arrayref($query);
2387
2388     $self->update_brestore_table(map { $_->[0] } @$jobs);
2389 }
2390
2391 sub get_root
2392 {
2393     my ($self, $dir) = @_;
2394     return $self->get_pathid('');
2395 }
2396
2397 sub ch_dir
2398 {
2399     my ($self, $pathid) = @_;
2400     $self->{cwd} = $pathid;
2401 }
2402
2403 sub up_dir
2404 {
2405     my ($self) = @_ ;
2406     my $query = 
2407   "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) ";
2408
2409     my $all = $self->dbh_selectall_arrayref($query);
2410     return unless ($all);       # already at root
2411
2412     my $dir = join(',', map { $_->[0] } @$all);
2413     if ($dir) {
2414         $self->{cwd} = $dir;
2415     }
2416 }
2417
2418 sub pwd
2419 {
2420     my ($self) = @_;
2421     return $self->get_path($self->{cwd});
2422 }
2423
2424 sub get_path
2425 {
2426     my ($self, $pathid) = @_;
2427     $self->debug("Call with pathid = $pathid");
2428     my $query = 
2429         "SELECT Path FROM Path WHERE PathId IN (?)";
2430
2431     my $sth = $self->dbh_prepare($query);
2432     $sth->execute($pathid);
2433     my $result = $sth->fetchrow_arrayref();
2434     $sth->finish();
2435     return $result->[0];    
2436 }
2437
2438 sub set_curjobids
2439 {
2440     my ($self, @jobids) = @_;
2441     $self->{curjobids} = join(',', @jobids);
2442     $self->update_brestore_table(@jobids);
2443 }
2444
2445 sub ls_files
2446 {
2447     my ($self) = @_;
2448
2449     return undef unless ($self->{curjobids});
2450
2451     my $inclause   = $self->{curjobids};
2452     my $inlistpath = $self->{cwd};
2453
2454     my $query = 
2455 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
2456  FROM
2457         (SELECT Filename.Name, max(File.FileId) as id
2458          FROM File, Filename
2459          WHERE File.FilenameId = Filename.FilenameId
2460            AND Filename.Name != ''
2461            AND File.PathId IN ($inlistpath)
2462            AND File.JobId IN ($inclause)
2463          GROUP BY Filename.Name
2464          ORDER BY Filename.Name) AS listfiles,
2465 File
2466 WHERE File.FileId = listfiles.id";
2467         
2468     $self->debug($query);
2469     my $result = $self->dbh_selectall_arrayref($query);
2470     $self->debug($result);
2471         
2472     return $result;
2473 }
2474
2475 # return ($dirid,$dir_basename,$lstat,$jobid)
2476 sub ls_dirs
2477 {
2478     my ($self) = @_;
2479
2480     return undef unless ($self->{curjobids});
2481
2482     my $pathid = $self->{cwd};
2483     my $jobclause = $self->{curjobids};
2484
2485     # Let's retrieve the list of the visible dirs in this dir ...
2486     # First, I need the empty filenameid to locate efficiently the dirs in the file table
2487     my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
2488     my $sth = $self->dbh_prepare($query);
2489     $sth->execute();
2490     my $result = $sth->fetchrow_arrayref();
2491     $sth->finish();
2492     my $dir_filenameid = $result->[0];
2493      
2494     # Then we get all the dir entries from File ...
2495     $query = "
2496 SELECT PathId, Path, JobId, Lstat FROM (
2497     
2498     SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
2499            listfile1.JobId, listfile1.Lstat
2500     FROM (
2501         SELECT DISTINCT brestore_pathhierarchy1.PathId
2502         FROM brestore_pathhierarchy AS brestore_pathhierarchy1
2503         JOIN Path AS Path2
2504             ON (brestore_pathhierarchy1.PathId = Path2.PathId)
2505         JOIN brestore_pathvisibility AS brestore_pathvisibility1
2506             ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
2507         WHERE brestore_pathhierarchy1.PPathId = $pathid
2508         AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
2509     JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
2510     LEFT JOIN (
2511         SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
2512         WHERE File1.FilenameId = $dir_filenameid
2513         AND File1.JobId IN ($jobclause)) AS listfile1
2514         ON (listpath1.PathId = listfile1.PathId)
2515      ) AS A ORDER BY 2,3 DESC
2516 ";
2517     $self->debug($query);
2518     $sth=$self->dbh_prepare($query);
2519     $sth->execute();
2520     $result = $sth->fetchall_arrayref();
2521     my @return_list;
2522     my $prev_dir='';
2523     foreach my $refrow (@{$result})
2524     {
2525         my $dirid = $refrow->[0];
2526         my $dir = $refrow->[1];
2527         my $lstat = $refrow->[3];
2528         my $jobid = $refrow->[2] || 0;
2529         next if ($dirid eq $prev_dir);
2530         # We have to clean up this dirname ... we only want it's 'basename'
2531         my $return_value;
2532         if ($dir ne '/')
2533         {
2534             my @temp = split ('/',$dir);
2535             $return_value = pop @temp;
2536         }
2537         else
2538         {
2539             $return_value = '/';
2540         }
2541         my @return_array = ($dirid,$return_value,$lstat,$jobid);
2542         push @return_list,(\@return_array);
2543         $prev_dir = $dirid;
2544     }
2545     $self->debug(\@return_list);
2546     return \@return_list;    
2547 }
2548
2549 # Returns the list of media required for a list of jobids.
2550 # Input : self, jobid1, jobid2...
2551 # Output : reference to array of (joibd, inchanger)
2552 sub get_required_media_from_jobid
2553 {
2554     my ($self, @jobids)=@_;
2555     my $inclause = join(',',@jobids);
2556     my $query = "
2557 SELECT DISTINCT JobMedia.MediaId, Media.InChanger 
2558 FROM JobMedia, Media 
2559 WHERE JobMedia.MediaId=Media.MediaId 
2560 AND JobId In ($inclause)
2561 ORDER BY MediaId";
2562     my $result = $self->dbh_selectall_arrayref($query);
2563     return $result;
2564 }
2565
2566 # Returns the fileindex from dirname and jobid.
2567 # Input : self, dirid, jobid
2568 # Output : fileindex
2569 sub get_fileindex_from_dir_jobid
2570 {
2571     my ($self, $dirid, $jobid)=@_;
2572     my $query;
2573     $query = "SELECT File.FileIndex
2574                 FROM File, Filename
2575                 WHERE File.FilenameId = Filename.FilenameId
2576                 AND File.PathId = $dirid
2577                 AND Filename.Name = ''
2578                 AND File.JobId = '$jobid'
2579                 ";
2580                 
2581     $self->debug($query);
2582     my $result = $self->dbh_selectall_arrayref($query);
2583     return $result->[0]->[0];
2584 }
2585
2586 # Returns the fileindex from filename and jobid.
2587 # Input : self, dirid, filenameid, jobid
2588 # Output : fileindex
2589 sub get_fileindex_from_file_jobid
2590 {
2591     my ($self, $dirid, $filenameid, $jobid)=@_;
2592     
2593     my $query;
2594     $query = 
2595 "SELECT File.FileIndex
2596  FROM File
2597  WHERE File.PathId = $dirid
2598    AND File.FilenameId = $filenameid
2599    AND File.JobId = $jobid";
2600                 
2601     $self->debug($query);
2602     my $result = $self->dbh_selectall_arrayref($query);
2603     return $result->[0]->[0];
2604 }
2605
2606 # This function estimates the size to be restored for an entry of the restore
2607 # list
2608 # In : self,reference to the entry
2609 # Out : size in bytes, number of files
2610 sub estimate_restore_size
2611 {
2612     # reminder : restore_list looks like this : 
2613     # ($pid,$fid,$name,$jobid,'file',$curjobids, 
2614     #  undef, undef, undef, $dirfileindex);
2615     my ($self, $entry, $refresh) = @_;
2616     my $query;
2617     if ($entry->[4] eq 'dir')
2618     {
2619         my $dir = $entry->[0];
2620
2621         my $inclause = $entry->[5]; #curjobids
2622         $query = 
2623 "SELECT Path.Path, File.FilenameId, File.LStat
2624   FROM File, Path, Job
2625   WHERE Path.PathId = File.PathId
2626   AND File.JobId = Job.JobId
2627   AND Path.Path LIKE 
2628         (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
2629         )
2630   AND File.JobId IN ($inclause)
2631   ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
2632     }
2633     else
2634     {
2635         # It's a file. Great, we allready have most 
2636         # of what is needed. Simple and efficient query
2637         my $dir = $entry->[0];
2638         my $fileid = $entry->[1];
2639         
2640         my $jobid = $entry->[3];
2641         my $fileindex = $entry->[9];
2642         my $inclause = $entry->[5]; # curjobids
2643         $query = 
2644 "SELECT Path.Path, File.FilenameId, File.Lstat
2645   FROM File, Path
2646   WHERE Path.PathId = File.PathId
2647   AND Path.PathId = $dir
2648   AND File.FilenameId = $fileid
2649   AND File.JobId = $jobid";
2650     }
2651
2652     my ($path,$nameid,$lstat);
2653     my $sth = $self->dbh_prepare($query);
2654     $sth->execute;
2655     $sth->bind_columns(\$path,\$nameid,\$lstat);
2656     my $old_path='';
2657     my $old_nameid='';
2658     my $total_size=0;
2659     my $total_files=0;
2660
2661     &$refresh();
2662
2663     my $rcount=0;
2664     # We fetch all rows
2665     while ($sth->fetchrow_arrayref())
2666     {
2667         # Only the latest version of a file
2668         next if ($nameid eq $old_nameid and $path eq $old_path);
2669
2670         if ($rcount > 15000) {
2671             &$refresh();
2672             $rcount=0;
2673         } else {
2674             $rcount++;
2675         }
2676
2677         # We get the size of this file
2678         my $size=lstat_attrib($lstat,'st_size');
2679         $total_size += $size;
2680         $total_files++;
2681         $old_path=$path;
2682         $old_nameid=$nameid;
2683     }
2684
2685     return ($total_size,$total_files);
2686 }
2687
2688 # Returns list of versions of a file that could be restored
2689 # returns an array of 
2690 # ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
2691 # there will be only one jobid in the array of jobids...
2692 sub get_all_file_versions
2693 {
2694     my ($self,$pathid,$fileid,$client,$see_all)=@_;
2695     
2696     defined $see_all or $see_all=0;
2697     
2698     my @versions;
2699     my $query;
2700     $query = 
2701 "SELECT File.JobId, File.FileIndex, File.Lstat, 
2702         File.Md5, Media.VolumeName, Media.InChanger
2703  FROM File, Job, Client, JobMedia, Media
2704  WHERE File.FilenameId = $fileid
2705    AND File.PathId=$pathid
2706    AND File.JobId = Job.JobId
2707    AND Job.ClientId = Client.ClientId
2708    AND Job.JobId = JobMedia.JobId
2709    AND File.FileIndex >= JobMedia.FirstIndex
2710    AND File.FileIndex <= JobMedia.LastIndex
2711    AND JobMedia.MediaId = Media.MediaId
2712    AND Client.Name = '$client'";
2713         
2714     $self->debug($query);
2715         
2716     my $result = $self->dbh_selectall_arrayref($query);
2717         
2718     foreach my $refrow (@$result)
2719     {
2720         my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
2721         my @attribs = parse_lstat($lstat);
2722         my $mtime = array_attrib('st_mtime',\@attribs);
2723         my $size = array_attrib('st_size',\@attribs);
2724                 
2725         my @list = ('FILE:',$pathid,$fileid,$jobid,
2726                     $fileindex, $mtime, $size, $inchanger,
2727                     $md5, $volname);
2728         push @versions, (\@list);
2729     }
2730         
2731     # We have the list of all versions of this file.
2732     # We'll sort it by mtime desc, size, md5, inchanger desc
2733     # the rest of the algorithm will be simpler
2734     # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
2735     @versions = sort { $b->[5] <=> $a->[5] 
2736                     || $a->[6] <=> $b->[6] 
2737                     || $a->[8] cmp $a->[8] 
2738                     || $b->[7] <=> $a->[7]} @versions;
2739
2740         
2741     my @good_versions;
2742     my %allready_seen_by_mtime;
2743     my %allready_seen_by_md5;
2744     # Now we should create a new array with only the interesting records
2745     foreach my $ref (@versions)
2746     {   
2747         if ($ref->[8])
2748         {
2749             # The file has a md5. We compare his md5 to other known md5...
2750             #\81 We take size into account. It may happen that 2 files
2751             # have the same md5sum and are different. size is a supplementary
2752             # criterion
2753             
2754             # If we allready have a (better) version
2755             next if ( (not $see_all) 
2756                       and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}); 
2757
2758             # we never met this one before...
2759             $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
2760         }
2761         #\81 Even if it has a md5, we should also work with mtimes
2762         # We allready have a (better) version
2763         next if ( (not $see_all)
2764                   and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]}); 
2765         $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
2766         
2767         # We reached there. The file hasn't been seen.
2768         push @good_versions,($ref);
2769     }
2770         
2771     # To be nice with the user, we re-sort good_versions by
2772     # inchanger desc, mtime desc
2773     @good_versions = sort { $b->[5] <=> $a->[5] 
2774                          || $b->[3] <=> $a->[3]} @good_versions;
2775         
2776     return @good_versions;
2777 }
2778
2779
2780 sub update_brestore_table
2781 {
2782     my ($self, @jobs) = @_;
2783
2784     $self->debug(\@jobs);
2785
2786     foreach my $job (sort {$a <=> $b} @jobs)
2787     {
2788         my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2789         my $retour = $self->dbh_selectrow_arrayref($query);
2790         next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2791
2792         print STDERR "Inserting path records for JobId $job\n";
2793         $query = "INSERT INTO brestore_pathvisibility (PathId, JobId) 
2794                    (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2795
2796         $self->dbh_do($query);
2797
2798         # Now we have to do the directory recursion stuff to determine missing visibility
2799         # We try to avoid recursion, to be as fast as possible
2800         # We also only work on not allready hierarchised directories...
2801
2802         print STDERR "Creating missing recursion paths for $job\n";
2803
2804         $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility 
2805                   JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2806                   LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2807                   WHERE brestore_pathvisibility.JobId = $job
2808                   AND brestore_pathhierarchy.PathId IS NULL
2809                   ORDER BY Path";
2810
2811         my $sth = $self->dbh_prepare($query);
2812         $sth->execute();
2813         my $pathid; my $path;
2814         $sth->bind_columns(\$pathid,\$path);
2815         
2816         while ($sth->fetch)
2817         {
2818             $self->build_path_hierarchy($path,$pathid);
2819         }
2820         $sth->finish();
2821
2822         # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2823         # This query gives all parent pathids for a given jobid that aren't stored.
2824         # It has to be called until no record is updated ...
2825         $query = "
2826         INSERT INTO brestore_pathvisibility (PathId, JobId) (
2827         SELECT a.PathId,$job
2828         FROM
2829                 (SELECT DISTINCT h.PPathId AS PathId
2830                 FROM brestore_pathhierarchy AS h
2831                 JOIN  brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2832                 WHERE p.JobId=$job) AS a
2833                 LEFT JOIN
2834                 (SELECT PathId
2835                 FROM brestore_pathvisibility
2836                 WHERE JobId=$job) AS b
2837                 ON (a.PathId = b.PathId)
2838         WHERE b.PathId IS NULL)";
2839
2840         my $rows_affected;
2841         while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2842         {
2843             print STDERR "Recursively adding $rows_affected records from $job\n";
2844         }
2845         # Job's done
2846         $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2847         $self->dbh_do($query);
2848     }
2849 }
2850
2851 sub cleanup_brestore_table
2852 {
2853     my ($self) = @_;
2854
2855     my $query = "SELECT JobId from brestore_knownjobid";
2856     my @jobs = @{$self->dbh_selectall_arrayref($query)};
2857
2858     foreach my $jobentry (@jobs)
2859     {
2860         my $job = $jobentry->[0];
2861         $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2862         my $result = $self->dbh_selectall_arrayref($query);
2863         if (scalar(@{$result}))
2864         {
2865             # There are still files for this jobid
2866             print STDERR "$job still exists. Not cleaning...\n";
2867
2868         } else {
2869                 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2870                 $self->dbh_do($query);
2871                 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2872                 $self->dbh_do($query);
2873         }
2874     }
2875 }
2876
2877 sub parent_dir
2878 {
2879     my ($path) = @_;
2880     # Root Unix case :
2881     if ($path eq '/')
2882     {
2883         return '';
2884     }
2885     # Root Windows case :
2886     if ($path =~ /^[a-z]+:\/$/i)
2887     {
2888         return '';
2889     }
2890     # Split
2891     my @tmp = split('/',$path);
2892     # We remove the last ...
2893     pop @tmp;
2894     my $tmp = join ('/',@tmp) . '/';
2895     return $tmp;
2896 }
2897
2898 sub build_path_hierarchy
2899 {
2900     my ($self, $path,$pathid)=@_;
2901     # Does the ppathid exist for this ? we use a memory cache...
2902     # In order to avoid the full loop, we consider that if a dir is allready in the
2903     # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2904     while ($path ne '')
2905     {
2906         if (! $self->{cache_ppathid}->{$pathid})
2907         {
2908             my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2909             my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2910             $sth2->execute($pathid);
2911             # Do we have a result ?
2912             if (my $refrow = $sth2->fetchrow_arrayref)
2913             {
2914                 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2915                 $sth2->finish();
2916                 # This dir was in the db ...
2917                 # It means we can leave, the tree has allready been built for
2918                 # this dir
2919                 return 1;
2920             } else {
2921                 $sth2->finish();
2922                 # We have to create the record ...
2923                 # What's the current p_path ?
2924                 my $ppath = parent_dir($path);
2925                 my $ppathid = $self->return_pathid_from_path($ppath);
2926                 $self->{cache_ppathid}->{$pathid}= $ppathid;
2927                 
2928                 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2929                 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2930                 $sth2->execute($pathid,$ppathid);
2931                 $sth2->finish();
2932                 $path = $ppath;
2933                 $pathid = $ppathid;
2934             }
2935         } else {
2936            # It's allready in the cache.
2937            # We can leave, no time to waste here, all the parent dirs have allready
2938            # been done
2939            return 1;
2940         }
2941     }
2942     return 1;
2943 }
2944
2945
2946 sub return_pathid_from_path
2947 {
2948     my ($self, $path) = @_;
2949     my $query = "SELECT PathId FROM Path WHERE Path = ?";
2950
2951     #print STDERR $query,"\n" if $debug;
2952     my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2953     $sth->execute($path);
2954     my $result =$sth->fetchrow_arrayref();
2955     $sth->finish();
2956     if (defined $result)
2957     {
2958         return $result->[0];
2959
2960     } else {
2961         # A bit dirty : we insert into path, and we have to be sure
2962         # we aren't deleted by a purge. We still need to insert into path to get
2963         # the pathid, because of mysql
2964         $query = "INSERT INTO Path (Path) VALUES (?)";
2965         #print STDERR $query,"\n" if $debug;
2966         $sth = $self->{conf}->{dbh}->prepare_cached($query);
2967         $sth->execute($path);
2968         $sth->finish();
2969         
2970         $query = "SELECT PathId FROM Path WHERE Path = ?";
2971         #print STDERR $query,"\n" if $debug;
2972         $sth = $self->{conf}->{dbh}->prepare_cached($query);
2973         $sth->execute($path);
2974         $result = $sth->fetchrow_arrayref();
2975         $sth->finish();
2976         return $result->[0];
2977     }
2978 }
2979
2980
2981 sub create_brestore_tables
2982 {
2983     my ($self) = @_;
2984
2985     my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2986
2987     unless ($self->dbh_do($verif)) {
2988         new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2989
2990         $self->{error} = "Creating internal brestore tables";
2991         my $req = "
2992     CREATE TABLE brestore_knownjobid
2993     (
2994      JobId int4 NOT NULL,
2995      CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2996     )";
2997         $self->dbh_do($req);
2998     }
2999     
3000     $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
3001     unless ($self->dbh_do($verif)) {
3002         my $req = "
3003    CREATE TABLE brestore_pathhierarchy
3004    (
3005      PathId int4 NOT NULL,
3006      PPathId int4 NOT NULL,
3007      CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
3008    )";
3009         $self->dbh_do($req);
3010
3011
3012         $req = "CREATE INDEX brestore_pathhierarchy_ppathid 
3013                           ON brestore_pathhierarchy (PPathId)";
3014         $self->dbh_do($req);
3015     }
3016     
3017     $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
3018     unless ($self->dbh_do($verif)) {
3019         my $req = "
3020     CREATE TABLE brestore_pathvisibility
3021     (
3022       PathId int4 NOT NULL,
3023       JobId int4 NOT NULL,
3024       Size int8 DEFAULT 0,
3025       Files int4 DEFAULT 0,
3026       CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
3027     )";
3028         $self->dbh_do($req);
3029
3030         $req = "CREATE INDEX brestore_pathvisibility_jobid
3031                           ON brestore_pathvisibility (JobId)";
3032         $self->dbh_do($req);
3033     }
3034 }
3035
3036 # Get metadata
3037 {
3038     my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3039                           'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3040                           'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3041                           'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3042                           'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3043                           'data_stream' => 15);;
3044     sub array_attrib
3045     {
3046         my ($attrib,$ref_attrib)=@_;
3047         return $ref_attrib->[$attrib_name_id{$attrib}];
3048     }
3049         
3050     sub file_attrib
3051     {   # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3052
3053         my ($file, $attrib)=@_;
3054         
3055         if (defined $attrib_name_id{$attrib}) {
3056
3057             my @d = split(' ', $file->[3]) ; # TODO : cache this
3058             
3059             return from_base64($d[$attrib_name_id{$attrib}]);
3060
3061         } elsif ($attrib eq 'jobid') {
3062
3063             return $file->[4];
3064
3065         } elsif ($attrib eq 'name') {
3066
3067             return $file->[2];
3068             
3069         } else  {
3070             die "Attribute not known : $attrib.\n";
3071         }
3072     }
3073     
3074     sub lstat_attrib
3075     {
3076         my ($lstat,$attrib)=@_;
3077         if ($lstat and defined $attrib_name_id{$attrib}) 
3078         {
3079             my @d = split(' ', $lstat) ; # TODO : cache this
3080             return from_base64($d[$attrib_name_id{$attrib}]);
3081         }
3082         return 0;
3083     }
3084 }
3085
3086 {
3087     # Base 64 functions, directly from recover.pl.
3088     # Thanks to
3089     # Karl Hakimian <hakimian@aha.com>
3090     # This section is also under GPL v2 or later.
3091     my @base64_digits;
3092     my @base64_map;
3093     my $is_init=0;
3094     sub init_base64
3095     {
3096         @base64_digits = (
3097         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3098         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3099         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3100         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3101         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3102                           );
3103         @base64_map = (0) x 128;
3104         
3105         for (my $i=0; $i<64; $i++) {
3106             $base64_map[ord($base64_digits[$i])] = $i;
3107         }
3108         $is_init = 1;
3109     }
3110
3111     sub from_base64 {
3112         if(not $is_init)
3113         {
3114             init_base64();
3115         }
3116         my $where = shift;
3117         my $val = 0;
3118         my $i = 0;
3119         my $neg = 0;
3120         
3121         if (substr($where, 0, 1) eq '-') {
3122             $neg = 1;
3123             $where = substr($where, 1);
3124         }
3125         
3126         while ($where ne '') {
3127             $val *= 64;
3128             my $d = substr($where, 0, 1);
3129             $val += $base64_map[ord(substr($where, 0, 1))];
3130             $where = substr($where, 1);
3131         }
3132         
3133         return $val;
3134     }
3135
3136     sub parse_lstat {
3137         my ($lstat)=@_;
3138         my @attribs = split(' ',$lstat);
3139         foreach my $element (@attribs)
3140         {
3141             $element = from_base64($element);
3142         }
3143         return @attribs;
3144     }
3145 }
3146
3147 1;
3148
3149 ################################################################
3150 package BwebConsole;
3151 use LWP::UserAgent;
3152 use HTTP::Request::Common;
3153
3154 sub new
3155 {
3156     my ($class, %arg) = @_;
3157
3158     my $self = bless {
3159         pref => $arg{pref},     # Pref object
3160         timeout => $arg{timeout} || 20,
3161         debug   => $arg{debug} || 0,
3162         'list_job'     => '',
3163         'list_client'  => '',
3164         'list_fileset' => '',
3165         'list_storage' => '',
3166         'run'          => '',
3167     };
3168
3169     return $self;
3170 }
3171
3172 sub prepare
3173 {
3174     my ($self, @what) = @_;
3175     my $ua = LWP::UserAgent->new();
3176     $ua->agent("Brestore/$VERSION");
3177     my $req = POST($self->{pref}->{bconsole},
3178                    Content_Type => 'form-data',
3179                    Content => [ map { (action => $_) } @what ]);
3180     #$req->authorization_basic('eric', 'test');
3181
3182     my $res = $ua->request($req);
3183
3184     if ($res->is_success) {
3185         foreach my $l (split(/\n/, $res->content)) {
3186             my ($k, $c) = split(/=/,$l,2);
3187             $self->{$k} = $c;
3188         }
3189     } else {
3190         $self->{error} = "Can't connect to bweb : " . $res->status_line;
3191         new DlgWarn($self->{error});
3192     }
3193 }
3194
3195 sub run
3196 {
3197     my ($self, %arg) = @_;
3198
3199     my $ua = LWP::UserAgent->new();
3200     $ua->agent("Brestore/$VERSION");
3201     my $req = POST($self->{pref}->{bconsole},
3202                    Content_Type => 'form-data',
3203                    Content => [ job     => $arg{job},
3204                                 client  => $arg{client},
3205                                 storage => $arg{storage} || '',
3206                                 fileset => $arg{fileset} || '',
3207                                 where   => $arg{where}   || '',
3208                                 rwhere  => $arg{rwhere}  || '',
3209                                 priority=> $arg{prio}    || '',
3210                                 replace => $arg{replace},
3211                                 action  => 'run',
3212                                 timeout => 10,
3213                                 bootstrap => [$arg{bootstrap}],
3214                                 ]);
3215     #$req->authorization_basic('eric', 'test');
3216
3217     my $res = $ua->request($req);
3218
3219     if ($res->is_success) {
3220         foreach my $l (split(/\n/, $res->content)) {
3221             my ($k, $c) = split(/=/,$l,2);
3222             $self->{$k} = $c;
3223         }
3224     } 
3225
3226     if (!$self->{run}) {
3227         new DlgWarn("Can't connect to bweb : " . $res->status_line);
3228     } 
3229
3230     unlink($arg{bootstrap});
3231
3232     return $self->{run};
3233 }
3234
3235 sub list_job
3236 {
3237     my ($self) = @_;
3238     return sort split(/;/, $self->{'list_job'});
3239 }
3240
3241 sub list_fileset
3242 {
3243     my ($self) = @_;
3244     return sort split(/;/, $self->{'list_fileset'});
3245 }
3246
3247 sub list_storage
3248 {
3249     my ($self) = @_;
3250     return sort split(/;/, $self->{'list_storage'});
3251 }
3252 sub list_client
3253 {
3254     my ($self) = @_;
3255     return sort split(/;/, $self->{'list_client'});
3256 }
3257
3258 1;
3259
3260 package main;
3261
3262 use Getopt::Long ;
3263
3264 sub HELP_MESSAGE
3265 {
3266     print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3267     exit 1;
3268 }
3269
3270 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3271 my $batch_mod;
3272
3273 GetOptions("conf=s"   => \$file_conf,
3274            "batch"    => \$batch_mod,
3275            "debug"    => \$debug,
3276            "help"     => \&HELP_MESSAGE) ;
3277
3278 my $p = new Pref($file_conf);
3279
3280 if (! -f $file_conf) {
3281     $p->write_config();
3282 }
3283
3284 if ($batch_mod) {
3285     my $vfs = new Bvfs(conf => $p);
3286     if ($p->connect_db()) {
3287         $vfs->update_cache();
3288     }
3289     exit (0);
3290 }
3291
3292 $glade_file = $p->{glade_file};
3293
3294 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3295     if (-f "$path/$glade_file") {
3296         $glade_file = "$path/$glade_file" ;
3297         last;
3298     }
3299 }
3300
3301 # gtk have lots of warning on stderr
3302 if ($^O eq 'MSWin32')
3303 {
3304     close(STDERR);
3305     open(STDERR, ">stderr.log");
3306 }
3307
3308 Gtk2->init();
3309
3310 if ( -f $glade_file) {
3311     my $w = new DlgResto($p);
3312
3313 } else {
3314     my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close', 
3315 "Can't find your brestore.glade (glade_file => '$glade_file')
3316 Please, edit your $file_conf to setup it." );
3317  
3318     $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3319     $widget->run;
3320     exit 1;
3321 }
3322
3323 Gtk2->main; # Start Gtk2 main loop      
3324
3325 # that's it!
3326
3327 exit 0;
3328
3329 __END__
3330 package main;
3331
3332 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3333 $p->{debug} = 1;
3334 $p->connect_db() || print $p->{error};
3335
3336 my $bvfs = new Bvfs(conf => $p);
3337
3338 $bvfs->debug($bvfs->get_root());
3339 $bvfs->ch_dir($bvfs->get_root());
3340 $bvfs->up_dir();
3341 $bvfs->set_curjobids(268,178,282,281,279);
3342 $bvfs->ls_files();
3343 my $dirs = $bvfs->ls_dirs();
3344 $bvfs->ch_dir(123496);
3345 $dirs = $bvfs->ls_dirs();
3346 $bvfs->ls_files();
3347 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);