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