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