]> git.sur5r.net Git - bacula/bacula/blob - gui/brestore/brestore.pl
ebl fix cache create bug
[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->ch_dir($fs->get_root());
1289     # refresh_fileview will be done by list_backup_changed
1290
1291
1292     my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
1293                                                  $self->{pref}->{use_ok_bkp_only});
1294
1295     foreach my $endtime (@endtimes)
1296     {
1297         my $i = $self->{list_backup}->append();
1298
1299         my $label = $endtime->[1] . " (" . $endtime->[4] . ")";
1300         eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type
1301         eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status
1302
1303         $self->{list_backup}->set($i, 
1304                                   0, $endtime->[0],
1305                                   1, $label,
1306                                   );
1307     }
1308     $self->{restore_backup_combobox}->set_active(0);
1309     0;
1310 }
1311
1312
1313 sub fill_server_list
1314 {
1315     my ($dbh, $combo, $list) = @_;
1316
1317     my @clients=get_all_clients($dbh);
1318
1319     $list->clear();
1320     
1321     my $i = $list->append();
1322     $list->set($i, 0, $client_list_empty);
1323     
1324     foreach my $client (@clients)
1325     {
1326         $i = $list->append();
1327         $list->set($i, 0, $client);
1328     }
1329     $combo->set_active(0);
1330 }
1331
1332 sub init_server_backup_combobox
1333 {
1334     my $self = shift ;
1335     fill_server_list($self->{conf}->{dbh}, 
1336                      $self->{client_combobox},
1337                      $self->{list_client}) ;
1338 }
1339
1340 #----------------------------------------------------------------------
1341 #Refreshes the file-view Redraws everything. The dir data is cached, the file
1342 #data isn't.  There is additionnal complexity for dirs (visibility problems),
1343 #so the @CurrentJobIds is not sufficient.
1344 sub refresh_fileview 
1345 {
1346     my ($self) = @_;
1347     my $fileview = $self->{fileview};
1348     my $client_combobox = $self->{client_combobox};
1349     my $bvfs = $self->{bvfs};
1350
1351     @{$fileview->{data}} = ();
1352
1353     $self->clear_infoview();
1354     
1355     my $client_name = $self->current_client;
1356
1357     if (!$client_name or ($client_name eq $client_list_empty)) {
1358         $self->set_status("Client list empty");
1359         return;
1360     }
1361
1362     # [ [dirid, dir_basename, File.LStat, jobid]..]
1363     my $list_dirs = $bvfs->ls_dirs();
1364     # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
1365     my $files     = $bvfs->ls_files(); 
1366     
1367     my $file_count = 0 ;
1368     my $total_bytes = 0;
1369     
1370     # Add directories to view
1371     foreach my $dir_entry (@$list_dirs) {
1372         my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
1373         $total_bytes += 4096;
1374         $file_count++;
1375
1376         listview_push($fileview,
1377                       $dir_entry->[0],
1378                       0,
1379                       $dir_entry->[1],
1380                       # TODO: voir ce que l'on met la
1381                       $dir_entry->[3],
1382                       'dir',
1383
1384                       $diricon, 
1385                       $dir_entry->[1], 
1386                       "4 Kb", 
1387                       $time);
1388     }
1389     
1390     # Add files to view 
1391     foreach my $file (@$files) 
1392     {
1393         my $size = Bvfs::file_attrib($file,'st_size');
1394         my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
1395         $total_bytes += $size;
1396         $file_count++;
1397         # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
1398         listview_push($fileview,
1399                       $bvfs->{cwd},
1400                       $file->[0],
1401                       $file->[2],
1402                       $file->[4],
1403                       'file',
1404                       
1405                       $fileicon, 
1406                       $file->[2], 
1407                       human($size), $time);
1408     }
1409     
1410     $self->set_status("$file_count files/" . human($total_bytes));
1411     $self->{cwd} = $self->{bvfs}->pwd();
1412     $self->{location}->set_text($self->{cwd});
1413     # set a decent default selection (makes keyboard nav easy)
1414     $fileview->select(0);
1415 }
1416
1417
1418 sub on_about_activate
1419 {
1420     DlgAbout::display();
1421 }
1422
1423 sub drag_set_info
1424 {
1425     my ($tree, $path, $data) = @_;
1426
1427     my @items = listview_get_all($tree) ;
1428     my @ret;
1429     foreach my $i (@items)
1430     {
1431         my @file_info = @{$i};
1432
1433         # doc ligne 93
1434         # Ok, we have a corner case :
1435         # path can be empty
1436         my $file = pack("u", $path . $file_info[2]);
1437
1438         push @ret, join(" ; ", $file,
1439                         $file_info[0], # $pathid
1440                         $file_info[1], # $filenameid
1441                         $file_info[3], # $jobid
1442                         $file_info[4], # $type
1443                         );
1444     }
1445
1446     my $data_get = join(" :: ", @ret);
1447     
1448     $data->set_text($data_get,-1);
1449 }
1450
1451
1452
1453 sub fileview_data_get
1454 {
1455     my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1456     drag_set_info($widget, $self->{cwd}, $data);
1457 }
1458
1459 sub fileinfo_data_get
1460 {
1461     my ($self, $widget, $context, $data, $info, $time,$string) = @_;
1462     drag_set_info($widget, $self->{cwd}, $data);
1463 }
1464
1465 sub restore_list_data_received
1466 {
1467     my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
1468     my @ret;
1469     $self->debug("start\n");
1470     if  ($info eq 40 || $info eq 0) # patch for display!=:0
1471     {
1472         foreach my $elt (split(/ :: /, $data->data()))
1473         {
1474             my ($file, $pathid, $filenameid, $jobid, $type) = 
1475                 split(/ ; /, $elt);
1476             $file = unpack("u", $file);
1477             
1478             $self->add_selected_file_to_list($pathid,$filenameid,
1479                                              $file, $jobid, $type);
1480         }
1481     }
1482     $self->debug("end\n");
1483 }
1484
1485 sub on_back_button_clicked {
1486     my $self = shift;
1487     $self->{bvfs}->up_dir();
1488     $self->refresh_fileview();
1489 }
1490 sub on_location_go_button_clicked 
1491 {
1492     my $self = shift; 
1493     $self->ch_dir($self->{location}->get_text());
1494 }
1495 sub on_quit_activate {Gtk2->main_quit;}
1496 sub on_preferences_activate
1497 {
1498     my $self = shift; 
1499     $self->{dlg_pref}->display($self) ;
1500 }
1501 sub on_main_delete_event {Gtk2->main_quit;}
1502 sub on_bweb_activate
1503 {
1504     my $self = shift; 
1505     $self->set_status("Open bweb on your browser");
1506     $self->{pref}->go_bweb('', "go on bweb");
1507 }
1508
1509 # Change the current working directory
1510 #   * Updates fileview, location, and selection
1511 #
1512 sub ch_dir 
1513 {
1514     my ($self,$l) = @_;
1515
1516     my $p = $self->{bvfs}->get_pathid($l);
1517     if ($p) {
1518         $self->{bvfs}->ch_dir($p);
1519         $self->refresh_fileview();
1520     } else {
1521         $self->set_status("Can't find $l");
1522     }
1523     1;
1524 }
1525
1526 # Handle dialog 'close' (window-decoration induced close)
1527 #   * Just hide the dialog, and tell Gtk not to do anything else
1528 #
1529 sub on_delete_event 
1530 {
1531     my ($self, $w) = @_;
1532     $w->hide; 
1533     Gtk2::main_quit();
1534     1; # consume this event!
1535 }
1536
1537 # Handle key presses in location text edit control
1538 #   * Translate a Return/Enter key into a 'Go' command
1539 #   * All other key presses left for GTK
1540 #
1541 sub on_location_entry_key_release_event 
1542 {
1543     my $self = shift;
1544     my $widget = shift;
1545     my $event = shift;
1546     
1547     my $keypress = $event->keyval;
1548     if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} ||
1549         $keypress == $Gtk2::Gdk::Keysyms{Return}) 
1550     {
1551         $self->ch_dir($widget->get_text());
1552         
1553         return 1; # consume keypress
1554     }
1555
1556     return 0; # let gtk have the keypress
1557 }
1558
1559 sub on_fileview_key_press_event
1560 {
1561     my ($self, $widget, $event) = @_;
1562     return 0;
1563 }
1564
1565 sub listview_get_first
1566 {
1567     my ($list) = shift; 
1568     my @selected = $list->get_selected_indices();
1569     if (@selected > 0) {
1570         my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
1571         return ($pid,$fid,unpack('u', $name), @other);
1572     } else {
1573         return undef;
1574     }
1575 }
1576
1577 sub listview_get_all
1578 {
1579     my ($list) = shift; 
1580
1581     my @selected = $list->get_selected_indices();
1582     my @ret;
1583     for my $i (@selected) {
1584         my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
1585         push @ret, [$pid,$fid,unpack('u', $name), @other];
1586     } 
1587     return @ret;
1588 }
1589
1590 sub listview_push
1591 {
1592     my ($list, $pid, $fid, $name, @other) = @_;
1593     push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
1594 }
1595
1596 #-----------------------------------------------------------------
1597 # Handle keypress in file-view
1598 #   * Translates backspace into a 'cd ..' command 
1599 #   * All other key presses left for GTK
1600 #
1601 sub on_fileview_key_release_event 
1602 {
1603     my ($self, $widget, $event) = @_;
1604     if (not $event->keyval)
1605     {
1606         return 0;
1607     }
1608     if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
1609         $self->on_back_button_clicked();
1610         return 1; # eat keypress
1611     }
1612
1613     return 0; # let gtk have keypress
1614 }
1615
1616 sub on_forward_keypress
1617 {
1618     return 0;
1619 }
1620
1621 #-------------------------------------------------------------------
1622 # Handle double-click (or enter) on file-view
1623 #   * Translates into a 'cd <dir>' command
1624 #
1625 sub on_fileview_row_activated 
1626 {
1627     my ($self, $widget) = @_;
1628     
1629     my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
1630
1631     if ($type eq 'dir')
1632     {
1633         $self->{bvfs}->ch_dir($pid);
1634         $self->refresh_fileview();
1635     } else {
1636         $self->fill_infoview($pid,$fid,$name);
1637     }
1638     
1639     return 1; # consume event
1640 }
1641
1642 sub fill_infoview
1643 {
1644     my ($self, $path, $file, $fn) = @_;
1645     $self->clear_infoview();
1646     my @v = $self->{bvfs}->get_all_file_versions($path, 
1647                                                  $file,
1648                                                  $self->current_client,
1649                                                  $self->{pref}->{see_all_versions});
1650     for my $ver (@v) {
1651         my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
1652             $size,$inchanger,$md5,$volname) = @{$ver};
1653         my $icon = ($inchanger)?$yesicon:$noicon;
1654
1655         $mtime = localtime($mtime) ;
1656
1657         listview_push($self->{fileinfo},$pid,$fid,
1658                       $fn, $jobid, 'file', 
1659                       $icon, $volname, $jobid, human($size), $mtime, $md5);
1660     }
1661 }
1662
1663 sub current_date
1664 {
1665     my $self = shift ;
1666     return $self->{restore_backup_combobox}->get_active_text;
1667 }
1668
1669 sub current_client
1670 {
1671     my $self = shift ;
1672     return $self->{client_combobox}->get_active_text;
1673 }
1674
1675 sub on_list_backups_changed 
1676 {
1677     my ($self, $widget) = @_;
1678     return 0 unless defined $self->{fileview};
1679
1680     $self->{CurrentJobIds} = [
1681                               set_job_ids_for_date($self->dbh(),
1682                                                    $self->current_client,
1683                                                    $self->current_date,
1684                                                    $self->{pref}->{use_ok_bkp_only})
1685                               ];
1686     $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
1687     $self->refresh_fileview();
1688     0;
1689 }
1690
1691 sub on_restore_list_keypress
1692 {
1693     my ($self, $widget, $event) = @_;
1694     if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete})
1695     {
1696         my @sel = $widget->get_selected_indices;
1697         foreach my $elt (reverse(sort {$a <=> $b} @sel))
1698         {
1699             splice @{$self->{restore_list}->{data}},$elt,1;
1700         }
1701     }
1702 }
1703
1704 sub on_fileview_button_press_event
1705 {
1706     my ($self,$widget,$event) = @_;
1707     if ($event->button == 3)
1708     {
1709         $self->on_right_click_filelist($widget,$event);
1710         return 1;
1711     }
1712     
1713     if ($event->button == 2)
1714     {
1715         $self->on_see_all_version();
1716         return 1;
1717     }
1718
1719     return 0;
1720 }
1721
1722 sub on_see_all_version
1723 {
1724     my ($self) = @_;
1725     
1726     my @lst = listview_get_all($self->{fileview});
1727
1728     for my $i (@lst) {
1729         my ($pid,$fid,$name, undef) = @{$i};
1730
1731         new DlgFileVersion($self->{bvfs}, 
1732                            $self->current_client, 
1733                            $pid,$fid,$self->{cwd},$name);
1734     }
1735 }
1736
1737 sub on_right_click_filelist
1738 {
1739     my ($self,$widget,$event) = @_;
1740     # I need to know what's selected
1741     my @sel = listview_get_all($self->{fileview});
1742     
1743     my $type = '';
1744
1745     if (@sel == 1) {
1746         $type = $sel[0]->[4];   # $type
1747     }
1748
1749     my $w;
1750
1751     if (@sel >=2 or $type eq 'dir')
1752     {
1753         # We have selected more than one or it is a directories
1754         $w = $self->{filelist_dir_menu};
1755     }
1756     else
1757     {
1758         $w = $self->{filelist_file_menu};
1759     }
1760     $w->popup(undef,
1761               undef,
1762               undef,
1763               undef,
1764               $event->button, $event->time);
1765 }
1766
1767 sub context_add_to_filelist
1768 {
1769     my ($self) = @_;
1770
1771     my @sel = listview_get_all($self->{fileview});
1772
1773     foreach my $i (@sel)
1774     {
1775         my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
1776         $file = $self->{cwd} . '/' . $file;
1777         $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
1778     }
1779 }
1780
1781 # Adds a file to the filelist
1782 sub add_selected_file_to_list
1783 {
1784     my ($self, $pid, $fid, $name, $jobid, $type)=@_;
1785
1786     my $restore_list = $self->{restore_list};
1787
1788     my $curjobids=join(',', @{$self->{CurrentJobIds}});
1789
1790     if ($type eq 'dir')
1791     {
1792         # dirty hack
1793         $name =~ s!^//+!/!;
1794
1795         if ($name and substr $name,-1 ne '/')
1796         {
1797                 $name .= '/'; # For bacula
1798         }
1799         my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
1800         listview_push($restore_list,$pid,0, 
1801                       $name, $jobid, 'dir', $curjobids,
1802                       $diricon, $name,$curjobids,$dirfileindex);
1803     }
1804     elsif ($type eq 'file')
1805     {
1806         my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
1807
1808         listview_push($restore_list,$pid,$fid,
1809                       $name, $jobid, 'file', $curjobids,
1810                       $fileicon, $name, $jobid, $fileindex );
1811     }
1812 }
1813
1814 # TODO : we want be able to restore files from a bad ended backup
1815 # we have JobStatus IN ('T', 'A', 'E') and we must 
1816
1817 # Data acces subs from here. Interaction with SGBD and caching
1818
1819 # This sub retrieves the list of jobs corresponding to the jobs selected in the
1820 # GUI and stores them in @CurrentJobIds
1821 sub set_job_ids_for_date
1822 {
1823     my ($dbh, $client, $date, $only_ok)=@_;
1824
1825     if (!$client or !$date) {
1826         return ();
1827     }
1828     
1829     my $status = get_wanted_job_status($only_ok);
1830         
1831     # The algorithm : for a client, we get all the backups for each
1832     # fileset, in reverse order Then, for each fileset, we store the 'good'
1833     # incrementals and differentials until we have found a full so it goes
1834     # like this : store all incrementals until we have found a differential
1835     # or a full, then find the full #
1836
1837     my $query = "SELECT JobId, FileSet, Level, JobStatus
1838                 FROM Job, Client, FileSet
1839                 WHERE Job.ClientId = Client.ClientId
1840                 AND FileSet.FileSetId = Job.FileSetId
1841                 AND EndTime <= '$date'
1842                 AND Client.Name = '$client'
1843                 AND Type IN ('B')
1844                 AND JobStatus IN ($status)
1845                 ORDER BY FileSet, JobTDate DESC";
1846         
1847     my @CurrentJobIds;
1848     my $result = $dbh->selectall_arrayref($query);
1849     my %progress;
1850     foreach my $refrow (@$result)
1851     {
1852         my $jobid = $refrow->[0];
1853         my $fileset = $refrow->[1];
1854         my $level = $refrow->[2];
1855                 
1856         defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
1857                 
1858         next if $progress{$fileset} eq 'F'; # It's over for this fileset...
1859                 
1860         if ($level eq 'I')
1861         {
1862             next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
1863             push @CurrentJobIds,($jobid);
1864         }
1865         elsif ($level eq 'D')
1866         {
1867             next if $progress{$fileset} eq 'D'; # We allready have a differential
1868             push @CurrentJobIds,($jobid);
1869         }
1870         elsif ($level eq 'F')
1871         {
1872             push @CurrentJobIds,($jobid);
1873         }
1874
1875         my $status = $refrow->[3] ;
1876         if ($status eq 'T') {              # good end of job
1877             $progress{$fileset} = $level;
1878         }
1879     }
1880
1881     return @CurrentJobIds;
1882 }
1883
1884 sub refresh_screen
1885 {
1886     Gtk2->main_iteration while (Gtk2->events_pending);
1887 }
1888
1889 # TODO : bsr must use only good backup or not (see use_ok_bkp_only)
1890 # This sub creates a BSR from the information in the restore_list
1891 # Returns the BSR as a string
1892 sub create_filelist
1893 {
1894         my $self = shift;
1895         my %mediainfos;
1896         # This query gets all jobid/jobmedia/media combination.
1897         my $query = "
1898 SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile, 
1899        JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
1900        JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex, 
1901        Media.Volumename, Media.MediaType
1902 FROM Job, JobMedia, Media
1903 WHERE Job.JobId = JobMedia.JobId
1904   AND JobMedia.MediaId = Media.MediaId
1905   ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
1906         
1907
1908         my $result = $self->dbh_selectall_arrayref($query);
1909
1910         # We will store everything hashed by jobid.
1911
1912         foreach my $refrow (@$result)
1913         {
1914                 my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
1915                 $firstindex, $lastindex, $startblock, $endblock,
1916                 $volindex, $volumename, $mediatype) = @{$refrow};
1917
1918                 # We just have to deal with the case where starfile != endfile
1919                 # In this case, we concatenate both, for the bsr
1920                 if ($startfile != $endfile) { 
1921                       $startfile = $startfile . '-' . $endfile;
1922                 }
1923
1924                 my @tmparray = 
1925                 ($jobid, $volsessionid, $volsessiontime, $startfile, 
1926                 $firstindex, $lastindex, $startblock .'-'. $endblock,
1927                 $volindex, $volumename, $mediatype);
1928                 
1929                 push @{$mediainfos{$refrow->[0]}},(\@tmparray);
1930         }
1931
1932         
1933         # reminder : restore_list looks like this : 
1934         # ($pid,$fid,$name,$jobid,'file',$curjobids,
1935         #   undef, undef, undef, $dirfileindex);
1936         
1937         # Here, we retrieve every file/dir that could be in the restore
1938         # We do as simple as possible for the SQL engine (no crazy joins,
1939         # no pseudo join (>= FirstIndex ...), etc ...
1940         # We do a SQL union of all the files/dirs specified in the restore_list
1941         my @select_queries;
1942         foreach my $entry (@{$self->{restore_list}->{data}})
1943         {
1944                 if ($entry->[4] eq 'dir')
1945                 {
1946                         my $dirid = $entry->[0];
1947                         my $inclause = $entry->[5]; #curjobids
1948         
1949                         my $query = 
1950 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1951   FROM File, Path, Filename
1952   WHERE Path.PathId = File.PathId
1953   AND File.FilenameId = Filename.FilenameId
1954   AND Path.Path LIKE 
1955         (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path 
1956           WHERE PathId IN ($dirid)
1957     UNION 
1958          SELECT " . $self->dbh_strcat('Path',"'\%'") ." FROM brestore_missing_path          WHERE PathId IN ($dirid)
1959         )
1960   AND File.JobId IN ($inclause) )";
1961                         push @select_queries,($query);
1962                 }
1963                 else
1964                 {
1965                         # It's a file. Great, we allready have most 
1966                         # of what is needed. Simple and efficient query
1967                         my $dir = $entry->[0];
1968                         my $file = $entry->[1];
1969                         
1970                         my $jobid = $entry->[3];
1971                         my $fileindex = $entry->[9];
1972                         my $inclause = $entry->[5]; # curjobids
1973                         my $query = 
1974 "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
1975   FROM File,Path,Filename
1976   WHERE File.PathId = $dir
1977   AND File.PathId = Path.PathId
1978   AND File.FilenameId = $file
1979   AND File.FilenameId = Filename.FilenameId
1980   AND File.JobId = $jobid
1981  )
1982 ";
1983                         push @select_queries,($query);
1984                 }
1985         }
1986         $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
1987
1988         #Now we run the query and parse the result...
1989         # there may be a lot of records, so we better be efficient
1990         # We use the bind column method, working with references...
1991
1992         my $sth = $self->dbh_prepare($query);
1993         $sth->execute;
1994
1995         my ($path,$name,$fileindex,$jobid);
1996         $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
1997         
1998         # The temp place we're going to save all file
1999         # list to before the real list
2000         my @temp_list;
2001
2002         RECORD_LOOP:
2003         while ($sth->fetchrow_arrayref())
2004         {
2005                 # This may look dumb, but we're going to do a join by ourselves,
2006                 # to save memory and avoid sending a complex query to mysql
2007                 my $complete_path = $path . $name;
2008                 my $is_dir = 0;
2009                 
2010                 if ( $name eq '')
2011                 {
2012                         $is_dir = 1;
2013                 }
2014                 
2015                 # Remove trailing slash (normalize file and dir name)
2016                 $complete_path =~ s/\/$//;
2017                 
2018                 # Let's find the ref(s) for the %mediainfo element(s) 
2019                 # containing the data for this file
2020                 # There can be several matches. It is the pseudo join.
2021                 my $med_idx=0;
2022                 my $max_elt=@{$mediainfos{$jobid}}-1;
2023                 MEDIA_LOOP:
2024                 while($med_idx <= $max_elt)
2025                 {
2026                         my $ref = $mediainfos{$jobid}->[$med_idx];
2027                         # First, can we get rid of the first elements of the
2028                         # array ? (if they don't contain valuable records
2029                         # anymore
2030                         if ($fileindex > $ref->[5])
2031                         {
2032                                 # It seems we don't need anymore
2033                                 # this entry in %mediainfo (the input data
2034                                 # is sorted...)
2035                                 # We get rid of it.
2036                                 shift @{$mediainfos{$jobid}};
2037                                 $max_elt--;
2038                                 next MEDIA_LOOP;
2039                         }
2040                         # We will do work on this elt. We can ++
2041                         # $med_idx for next loop
2042                         $med_idx++;
2043
2044                         # %mediainfo row looks like : 
2045                         # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2046                         # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
2047                         # MediaType)
2048                         
2049                         # We are in range. We store and continue looping
2050                         # in the medias
2051                         if ($fileindex >= $ref->[4])
2052                         {
2053                                 my @data = ($complete_path,$is_dir,
2054                                             $fileindex,$ref);
2055                                 push @temp_list,(\@data);
2056                                 next MEDIA_LOOP;
2057                         }
2058                         
2059                         # We are not in range. No point in continuing looping
2060                         # We go to next record.
2061                         next RECORD_LOOP;
2062                 }
2063         }
2064         # Now we have the array.
2065         # We're going to sort it, by 
2066         # path, volsessiontime DESC (get the most recent file...)
2067         # The array rows look like this :
2068         # complete_path,is_dir,fileindex,
2069         # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
2070         #       LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2071         @temp_list = sort {$a->[0] cmp $b->[0]
2072                         || $b->[3]->[2] <=> $a->[3]->[2]
2073                           } @temp_list;
2074
2075         my @restore_list;
2076         my $prev_complete_path='////'; # Sure not to match
2077         my $prev_is_file=1;
2078         my $prev_jobid;
2079
2080         while (my $refrow = shift @temp_list)
2081         {
2082                 # For the sake of readability, we load $refrow 
2083                 # contents in real scalars
2084                 my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
2085                 my $jobid= $refother->[0]; # We don't need the rest...
2086
2087                 # We skip this entry.
2088                 # We allready have a newer one and this 
2089                 # isn't a continuation of the same file
2090                 next if ($complete_path eq $prev_complete_path 
2091                          and $jobid != $prev_jobid);
2092                 
2093                 
2094                 if ($prev_is_file 
2095                     and $complete_path =~ m|^\Q$prev_complete_path\E/|)
2096                 {
2097                         # We would be recursing inside a file.
2098                         # Just what we don't want (dir replaced by file
2099                         # between two backups
2100                         next;
2101                 }
2102                 elsif ($is_dir)
2103                 {
2104                         # It is a directory
2105                         push @restore_list,($refrow);
2106                         
2107                         $prev_complete_path = $complete_path;
2108                         $prev_jobid = $jobid;
2109                         $prev_is_file = 0;
2110                 }
2111                 else
2112                 {
2113                         # It is a file
2114                         push @restore_list,($refrow);
2115                         
2116                         $prev_complete_path = $complete_path;
2117                         $prev_jobid = $jobid;
2118                         $prev_is_file = 1;
2119                 }
2120         }
2121         # We get rid of @temp_list... save memory
2122         @temp_list=();
2123
2124         # Ok everything is in the list. Let's sort it again in another way.
2125         # This time it will be in the bsr file order
2126
2127         # we sort the results by 
2128         # volsessiontime, volsessionid, volindex, fileindex 
2129         # to get all files in right order...
2130         # Reminder : The array rows look like this :
2131         # complete_path,is_dir,fileindex,
2132         # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
2133         #       StartBlock-EndBlock,VolIndex,Volumename,MediaType)
2134
2135         @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2] 
2136                            || $a->[3]->[1] <=> $b->[3]->[1] 
2137                            || $a->[3]->[7] <=> $b->[3]->[7] 
2138                            || $a->[2] <=> $b->[2] } 
2139                                 @restore_list;
2140
2141         # Now that everything is ready, we create the bsr
2142         my $prev_fileindex=-1;
2143         my $prev_volsessionid=-1;
2144         my $prev_volsessiontime=-1;
2145         my $prev_volumename=-1;
2146         my $prev_volfile=-1;
2147         my $prev_mediatype;
2148         my $prev_volblocks;
2149         my $count=0;
2150         my $first_of_current_range=0;
2151         my @fileindex_ranges;
2152         my $bsr='';
2153
2154         foreach my $refrow (@restore_list)
2155         {
2156                 my (undef,undef,$fileindex,$refother)=@{$refrow};
2157                 my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
2158                     $volblocks,undef,$volumename,$mediatype)=@{$refother};
2159                 
2160                 # We can specifiy the number of files in each section of the
2161                 # bsr to speedup restore (bacula can then jump over the
2162                 # end of tape files.
2163                 $count++;
2164                 
2165                 
2166                 if ($prev_volumename eq '-1')
2167                 {
2168                         # We only have to start the new range...
2169                         $first_of_current_range=$fileindex;
2170                 }
2171                 elsif ($prev_volsessionid != $volsessionid 
2172                        or $prev_volsessiontime != $volsessiontime 
2173                        or $prev_volumename ne $volumename 
2174                        or $prev_volfile ne $volfile)
2175                 {
2176                         # We have to create a new section in the bsr...
2177                         # We print the previous one ... 
2178                         # (before that, save the current range ...)
2179                         if ($first_of_current_range != $prev_fileindex)
2180                         {
2181                                 # we are in a range
2182                                 push @fileindex_ranges,
2183                                     ("$first_of_current_range-$prev_fileindex");
2184                         }
2185                         else
2186                         {
2187                                  # We are out of a range,
2188                                  # but there is only one element in the range
2189                                 push @fileindex_ranges,
2190                                     ("$first_of_current_range");
2191                         }
2192                         
2193                         $bsr.=print_bsr_section(\@fileindex_ranges,
2194                                                 $prev_volsessionid,
2195                                                 $prev_volsessiontime,
2196                                                 $prev_volumename,
2197                                                 $prev_volfile,
2198                                                 $prev_mediatype,
2199                                                 $prev_volblocks,
2200                                                 $count-1);
2201                         $count=1;
2202                         # Reset for next loop
2203                         @fileindex_ranges=();
2204                         $first_of_current_range=$fileindex;
2205                 }
2206                 elsif ($fileindex-1 != $prev_fileindex)
2207                 {
2208                         # End of a range of fileindexes
2209                         if ($first_of_current_range != $prev_fileindex)
2210                         {
2211                                 #we are in a range
2212                                 push @fileindex_ranges,
2213                                     ("$first_of_current_range-$prev_fileindex");
2214                         }
2215                         else
2216                         {
2217                                  # We are out of a range,
2218                                  # but there is only one element in the range
2219                                 push @fileindex_ranges,
2220                                     ("$first_of_current_range");
2221                         }
2222                         $first_of_current_range=$fileindex;
2223                 }
2224                 $prev_fileindex=$fileindex;
2225                 $prev_volsessionid = $volsessionid;
2226                 $prev_volsessiontime = $volsessiontime;
2227                 $prev_volumename = $volumename;
2228                 $prev_volfile=$volfile;
2229                 $prev_mediatype=$mediatype;
2230                 $prev_volblocks=$volblocks;
2231
2232         }
2233
2234         # Ok, we're out of the loop. Alas, there's still the last record ...
2235         if ($first_of_current_range != $prev_fileindex)
2236         {
2237                 # we are in a range
2238                 push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
2239                 
2240         }
2241         else
2242         {
2243                 # We are out of a range,
2244                 # but there is only one element in the range
2245                 push @fileindex_ranges,("$first_of_current_range");
2246                 
2247         }
2248         $bsr.=print_bsr_section(\@fileindex_ranges,
2249                                 $prev_volsessionid,
2250                                 $prev_volsessiontime,
2251                                 $prev_volumename,
2252                                 $prev_volfile,
2253                                 $prev_mediatype,
2254                                 $prev_volblocks,
2255                                 $count);
2256         
2257         return $bsr;
2258 }
2259
2260 sub print_bsr_section
2261 {
2262     my ($ref_fileindex_ranges,$volsessionid,
2263         $volsessiontime,$volumename,$volfile,
2264         $mediatype,$volblocks,$count)=@_;
2265     
2266     my $bsr='';
2267     $bsr .= "Volume=\"$volumename\"\n";
2268     $bsr .= "MediaType=\"$mediatype\"\n";
2269     $bsr .= "VolSessionId=$volsessionid\n";
2270     $bsr .= "VolSessionTime=$volsessiontime\n";
2271     $bsr .= "VolFile=$volfile\n";
2272     $bsr .= "VolBlock=$volblocks\n";
2273     
2274     foreach my $range (@{$ref_fileindex_ranges})
2275     {
2276         $bsr .= "FileIndex=$range\n";
2277     }
2278     
2279     $bsr .= "Count=$count\n";
2280     return $bsr;
2281 }
2282
2283 1;
2284
2285 ################################################################
2286
2287 package Bvfs;
2288 use base qw/Bbase/;
2289
2290 sub get_pathid
2291 {
2292     my ($self, $dir) = @_;
2293     my $query = 
2294         "SELECT PathId FROM Path WHERE Path = ?
2295           UNION 
2296          SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2297     my $sth = $self->dbh_prepare($query);
2298     $sth->execute($dir,$dir);
2299     my $result = $sth->fetchall_arrayref();
2300     $sth->finish();
2301     
2302     return join(',', map { $_->[0] } @$result);
2303 }
2304
2305
2306 sub update_cache
2307 {
2308     my ($self) = @_;
2309
2310     my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId";
2311     my $jobs = $self->dbh_selectall_arrayref($query);
2312
2313     $self->update_brestore_table(map { $_->[0] } @$jobs);
2314 }
2315
2316 sub get_root
2317 {
2318     my ($self, $dir) = @_;
2319     return $self->get_pathid('');
2320 }
2321
2322 sub ch_dir
2323 {
2324     my ($self, $pathid) = @_;
2325     $self->{cwd} = $pathid;
2326 }
2327
2328 sub up_dir
2329 {
2330     my ($self) = @_ ;
2331     my $query = 
2332   "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) ";
2333
2334     my $all = $self->dbh_selectall_arrayref($query);
2335     return unless ($all);       # already at root
2336
2337     my $dir = join(',', map { $_->[0] } @$all);
2338     if ($dir) {
2339         $self->{cwd} = $dir;
2340     }
2341 }
2342
2343 sub pwd
2344 {
2345     my ($self) = @_;
2346     return $self->get_path($self->{cwd});
2347 }
2348
2349 sub get_path
2350 {
2351     my ($self, $pathid) = @_;
2352     $self->debug("Call with pathid = $pathid");
2353     my $query = 
2354         "SELECT Path FROM Path WHERE PathId IN (?)
2355           UNION 
2356          SELECT Path FROM brestore_missing_path WHERE PathId IN (?)";
2357     my $sth = $self->dbh_prepare($query);
2358     $sth->execute($pathid,$pathid);
2359     my $result = $sth->fetchrow_arrayref();
2360     $sth->finish();
2361     return $result->[0];    
2362 }
2363
2364 sub set_curjobids
2365 {
2366     my ($self, @jobids) = @_;
2367     $self->{curjobids} = join(',', @jobids);
2368     $self->update_brestore_table(@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     $self->debug(\@jobs);
2731
2732     foreach my $job (sort {$a <=> $b} @jobs)
2733     {
2734         my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
2735         my $retour = $self->dbh_selectrow_arrayref($query);
2736         next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
2737
2738         print STDERR "Inserting path records for JobId $job\n";
2739         $query = "INSERT INTO brestore_pathvisibility (PathId, JobId) 
2740                    (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
2741
2742         $self->dbh_do($query);
2743
2744         # Now we have to do the directory recursion stuff to determine missing visibility
2745         # We try to avoid recursion, to be as fast as possible
2746         # We also only work on not allready hierarchised directories...
2747
2748         print STDERR "Creating missing recursion paths for $job\n";
2749
2750         $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility 
2751                   JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
2752                   LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
2753                   WHERE brestore_pathvisibility.JobId = $job
2754                   AND brestore_pathhierarchy.PathId IS NULL
2755                   ORDER BY Path";
2756
2757         my $sth = $self->dbh_prepare($query);
2758         $sth->execute();
2759         my $pathid; my $path;
2760         $sth->bind_columns(\$pathid,\$path);
2761         
2762         while ($sth->fetch)
2763         {
2764             $self->build_path_hierarchy($path,$pathid);
2765         }
2766         $sth->finish();
2767
2768         # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
2769         # This query gives all parent pathids for a given jobid that aren't stored.
2770         # It has to be called until no record is updated ...
2771         $query = "
2772         INSERT INTO brestore_pathvisibility (PathId, JobId) (
2773         SELECT a.PathId,$job
2774         FROM
2775                 (SELECT DISTINCT h.PPathId AS PathId
2776                 FROM brestore_pathhierarchy AS h
2777                 JOIN  brestore_pathvisibility AS p ON (h.PathId=p.PathId)
2778                 WHERE p.JobId=$job) AS a
2779                 LEFT JOIN
2780                 (SELECT PathId
2781                 FROM brestore_pathvisibility
2782                 WHERE JobId=$job) AS b
2783                 ON (a.PathId = b.PathId)
2784         WHERE b.PathId IS NULL)";
2785
2786         my $rows_affected;
2787         while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
2788         {
2789             print STDERR "Recursively adding $rows_affected records from $job\n";
2790         }
2791         # Job's done
2792         $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
2793         $self->dbh_do($query);
2794     }
2795 }
2796
2797 sub cleanup_brestore_table
2798 {
2799     my ($self) = @_;
2800
2801     my $query = "SELECT JobId from brestore_knownjobid";
2802     my @jobs = @{$self->dbh_selectall_arrayref($query)};
2803
2804     foreach my $jobentry (@jobs)
2805     {
2806         my $job = $jobentry->[0];
2807         $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1";
2808         my $result = $self->dbh_selectall_arrayref($query);
2809         if (scalar(@{$result}))
2810         {
2811             # There are still files for this jobid
2812             print STDERR "$job still exists. Not cleaning...\n";
2813
2814         } else {
2815                 $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job";
2816                 $self->dbh_do($query);
2817                 $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job";
2818                 $self->dbh_do($query);
2819         }
2820     }
2821 }
2822
2823 sub parent_dir
2824 {
2825     my ($path) = @_;
2826     # Root Unix case :
2827     if ($path eq '/')
2828     {
2829         return '';
2830     }
2831     # Root Windows case :
2832     if ($path =~ /^[a-z]+:\/$/i)
2833     {
2834         return '';
2835     }
2836     # Split
2837     my @tmp = split('/',$path);
2838     # We remove the last ...
2839     pop @tmp;
2840     my $tmp = join ('/',@tmp) . '/';
2841     return $tmp;
2842 }
2843
2844 sub build_path_hierarchy
2845 {
2846     my ($self, $path,$pathid)=@_;
2847     # Does the ppathid exist for this ? we use a memory cache...
2848     # In order to avoid the full loop, we consider that if a dir is allready in the
2849     # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
2850     while ($path ne '')
2851     {
2852         if (! $self->{cache_ppathid}->{$pathid})
2853         {
2854             my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
2855             my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2856             $sth2->execute($pathid);
2857             # Do we have a result ?
2858             if (my $refrow = $sth2->fetchrow_arrayref)
2859             {
2860                 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
2861                 $sth2->finish();
2862                 # This dir was in the db ...
2863                 # It means we can leave, the tree has allready been built for
2864                 # this dir
2865                 return 1;
2866             } else {
2867                 $sth2->finish();
2868                 # We have to create the record ...
2869                 # What's the current p_path ?
2870                 my $ppath = parent_dir($path);
2871                 my $ppathid = $self->return_pathid_from_path($ppath);
2872                 $self->{cache_ppathid}->{$pathid}= $ppathid;
2873                 
2874                 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
2875                 $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
2876                 $sth2->execute($pathid,$ppathid);
2877                 $sth2->finish();
2878                 $path = $ppath;
2879                 $pathid = $ppathid;
2880             }
2881         } else {
2882            # It's allready in the cache.
2883            # We can leave, no time to waste here, all the parent dirs have allready
2884            # been done
2885            return 1;
2886         }
2887     }
2888     return 1;
2889 }
2890
2891
2892 sub return_pathid_from_path
2893 {
2894     my ($self, $path) = @_;
2895     my $query = "SELECT PathId FROM Path WHERE Path = ?
2896                  UNION
2897                  SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2898     #print STDERR $query,"\n" if $debug;
2899     my $sth = $self->{conf}->{dbh}->prepare_cached($query);
2900     $sth->execute($path,$path);
2901     my $result =$sth->fetchrow_arrayref();
2902     $sth->finish();
2903     if (defined $result)
2904     {
2905         return $result->[0];
2906
2907     } else {
2908         # A bit dirty : we insert into path AND missing_path, to be sure
2909         # we aren't deleted by a purge. We still need to insert into path to get
2910         # the pathid, because of mysql
2911         $query = "INSERT INTO Path (Path) VALUES (?)";
2912         #print STDERR $query,"\n" if $debug;
2913         $sth = $self->{conf}->{dbh}->prepare_cached($query);
2914         $sth->execute($path);
2915         $sth->finish();
2916         
2917         $query = " INSERT INTO brestore_missing_path (PathId,Path)
2918                    SELECT PathId,Path FROM Path WHERE Path = ?";
2919         #print STDERR $query,"\n" if $debug;
2920         $sth = $self->{conf}->{dbh}->prepare_cached($query);
2921         $sth->execute($path);
2922         $sth->finish();
2923         $query = " DELETE FROM Path WHERE Path = ?";
2924         #print STDERR $query,"\n" if $debug;
2925         $sth = $self->{conf}->{dbh}->prepare_cached($query);
2926         $sth->execute($path);
2927         $sth->finish();
2928         $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?";
2929         #print STDERR $query,"\n" if $debug;
2930         $sth = $self->{conf}->{dbh}->prepare_cached($query);
2931         $sth->execute($path);
2932         $result = $sth->fetchrow_arrayref();
2933         $sth->finish();
2934         return $result->[0];
2935     }
2936 }
2937
2938
2939 sub create_brestore_tables
2940 {
2941     my ($self) = @_;
2942
2943     my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
2944
2945     unless ($self->dbh_do($verif)) {
2946         new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
2947
2948         $self->{error} = "Creating internal brestore tables";
2949         my $req = "
2950     CREATE TABLE brestore_knownjobid
2951     (
2952      JobId int4 NOT NULL,
2953      CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
2954     )";
2955         $self->dbh_do($req);
2956     }
2957     
2958     $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
2959     unless ($self->dbh_do($verif)) {
2960         my $req = "
2961    CREATE TABLE brestore_pathhierarchy
2962    (
2963      PathId int4 NOT NULL,
2964      PPathId int4 NOT NULL,
2965      CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
2966    )";
2967         $self->dbh_do($req);
2968
2969
2970         $req = "CREATE INDEX brestore_pathhierarchy_ppathid 
2971                           ON brestore_pathhierarchy (PPathId)";
2972         $self->dbh_do($req);
2973     }
2974     
2975     $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
2976     unless ($self->dbh_do($verif)) {
2977         my $req = "
2978     CREATE TABLE brestore_pathvisibility
2979     (
2980       PathId int4 NOT NULL,
2981       JobId int4 NOT NULL,
2982       Size int8 DEFAULT 0,
2983       Files int4 DEFAULT 0,
2984       CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
2985     )";
2986         $self->dbh_do($req);
2987
2988         $req = "CREATE INDEX brestore_pathvisibility_jobid
2989                           ON brestore_pathvisibility (JobId)";
2990         $self->dbh_do($req);
2991     }
2992     
2993     $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1";
2994     unless ($self->dbh_do($verif)) {
2995         my $req = "
2996     CREATE TABLE brestore_missing_path
2997     (
2998       PathId int4 NOT NULL,
2999       Path text NOT NULL,
3000       CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId)
3001     )";
3002         $self->dbh_do($req);
3003
3004         $req = "CREATE INDEX brestore_missing_path_path
3005                           ON brestore_missing_path (Path)";
3006         $self->dbh_do($req);
3007     }
3008 }
3009
3010 # Get metadata
3011 {
3012     my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
3013                           'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
3014                           'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
3015                           'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
3016                           'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
3017                           'data_stream' => 15);;
3018     sub array_attrib
3019     {
3020         my ($attrib,$ref_attrib)=@_;
3021         return $ref_attrib->[$attrib_name_id{$attrib}];
3022     }
3023         
3024     sub file_attrib
3025     {   # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
3026
3027         my ($file, $attrib)=@_;
3028         
3029         if (defined $attrib_name_id{$attrib}) {
3030
3031             my @d = split(' ', $file->[3]) ; # TODO : cache this
3032             
3033             return from_base64($d[$attrib_name_id{$attrib}]);
3034
3035         } elsif ($attrib eq 'jobid') {
3036
3037             return $file->[4];
3038
3039         } elsif ($attrib eq 'name') {
3040
3041             return $file->[2];
3042             
3043         } else  {
3044             die "Attribute not known : $attrib.\n";
3045         }
3046     }
3047     
3048     sub lstat_attrib
3049     {
3050         my ($lstat,$attrib)=@_;
3051         if ($lstat and defined $attrib_name_id{$attrib}) 
3052         {
3053             my @d = split(' ', $lstat) ; # TODO : cache this
3054             return from_base64($d[$attrib_name_id{$attrib}]);
3055         }
3056         return 0;
3057     }
3058 }
3059
3060 {
3061     # Base 64 functions, directly from recover.pl.
3062     # Thanks to
3063     # Karl Hakimian <hakimian@aha.com>
3064     # This section is also under GPL v2 or later.
3065     my @base64_digits;
3066     my @base64_map;
3067     my $is_init=0;
3068     sub init_base64
3069     {
3070         @base64_digits = (
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         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3074         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3075         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
3076                           );
3077         @base64_map = (0) x 128;
3078         
3079         for (my $i=0; $i<64; $i++) {
3080             $base64_map[ord($base64_digits[$i])] = $i;
3081         }
3082         $is_init = 1;
3083     }
3084
3085     sub from_base64 {
3086         if(not $is_init)
3087         {
3088             init_base64();
3089         }
3090         my $where = shift;
3091         my $val = 0;
3092         my $i = 0;
3093         my $neg = 0;
3094         
3095         if (substr($where, 0, 1) eq '-') {
3096             $neg = 1;
3097             $where = substr($where, 1);
3098         }
3099         
3100         while ($where ne '') {
3101             $val *= 64;
3102             my $d = substr($where, 0, 1);
3103             $val += $base64_map[ord(substr($where, 0, 1))];
3104             $where = substr($where, 1);
3105         }
3106         
3107         return $val;
3108     }
3109
3110     sub parse_lstat {
3111         my ($lstat)=@_;
3112         my @attribs = split(' ',$lstat);
3113         foreach my $element (@attribs)
3114         {
3115             $element = from_base64($element);
3116         }
3117         return @attribs;
3118     }
3119 }
3120
3121 1;
3122
3123 ################################################################
3124 package BwebConsole;
3125 use LWP::UserAgent;
3126 use HTTP::Request::Common;
3127
3128 sub new
3129 {
3130     my ($class, %arg) = @_;
3131
3132     my $self = bless {
3133         pref => $arg{pref},     # Pref object
3134         timeout => $arg{timeout} || 20,
3135         debug   => $arg{debug} || 0,
3136         'list_job'     => '',
3137         'list_client'  => '',
3138         'list_fileset' => '',
3139         'list_storage' => '',
3140         'run'          => '',
3141     };
3142
3143     return $self;
3144 }
3145
3146 sub prepare
3147 {
3148     my ($self, @what) = @_;
3149     my $ua = LWP::UserAgent->new();
3150     $ua->agent("Brestore/$VERSION");
3151     my $req = POST($self->{pref}->{bconsole},
3152                    Content_Type => 'form-data',
3153                    Content => [ map { (action => $_) } @what ]);
3154     #$req->authorization_basic('eric', 'test');
3155
3156     my $res = $ua->request($req);
3157
3158     if ($res->is_success) {
3159         foreach my $l (split(/\n/, $res->content)) {
3160             my ($k, $c) = split(/=/,$l,2);
3161             $self->{$k} = $c;
3162         }
3163     } else {
3164         $self->{error} = "Can't connect to bweb : " . $res->status_line;
3165         new DlgWarn($self->{error});
3166     }
3167 }
3168
3169 sub run
3170 {
3171     my ($self, %arg) = @_;
3172
3173     my $ua = LWP::UserAgent->new();
3174     $ua->agent("Brestore/$VERSION");
3175     my $req = POST($self->{pref}->{bconsole},
3176                    Content_Type => 'form-data',
3177                    Content => [ job     => $arg{job},
3178                                 client  => $arg{client},
3179                                 storage => $arg{storage} || '',
3180                                 fileset => $arg{fileset} || '',
3181                                 where   => $arg{where},
3182                                 replace => $arg{replace},
3183                                 priority=> $arg{prio}    || '',
3184                                 action  => 'run',
3185                                 timeout => 10,
3186                                 bootstrap => [$arg{bootstrap}],
3187                                 ]);
3188     #$req->authorization_basic('eric', 'test');
3189
3190     my $res = $ua->request($req);
3191
3192     if ($res->is_success) {
3193         foreach my $l (split(/\n/, $res->content)) {
3194             my ($k, $c) = split(/=/,$l,2);
3195             $self->{$k} = $c;
3196         }
3197     } 
3198
3199     if (!$self->{run}) {
3200         new DlgWarn("Can't connect to bweb : " . $res->status_line);
3201     } 
3202
3203     unlink($arg{bootstrap});
3204
3205     return $self->{run};
3206 }
3207
3208 sub list_job
3209 {
3210     my ($self) = @_;
3211     return sort split(/;/, $self->{'list_job'});
3212 }
3213
3214 sub list_fileset
3215 {
3216     my ($self) = @_;
3217     return sort split(/;/, $self->{'list_fileset'});
3218 }
3219
3220 sub list_storage
3221 {
3222     my ($self) = @_;
3223     return sort split(/;/, $self->{'list_storage'});
3224 }
3225 sub list_client
3226 {
3227     my ($self) = @_;
3228     return sort split(/;/, $self->{'list_client'});
3229 }
3230
3231 1;
3232
3233 package main;
3234
3235 use Getopt::Long ;
3236
3237 sub HELP_MESSAGE
3238 {
3239     print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
3240     exit 1;
3241 }
3242
3243 my $file_conf = "$ENV{HOME}/.brestore.conf" ;
3244 my $batch_mod;
3245
3246 GetOptions("conf=s"   => \$file_conf,
3247            "batch"    => \$batch_mod,
3248            "debug"    => \$debug,
3249            "help"     => \&HELP_MESSAGE) ;
3250
3251 my $p = new Pref($file_conf);
3252
3253 if (! -f $file_conf) {
3254     $p->write_config();
3255 }
3256
3257 if ($batch_mod) {
3258     my $vfs = new Bvfs(conf => $p);
3259     if ($p->connect_db()) {
3260         $vfs->update_cache();
3261     }
3262     exit (0);
3263 }
3264
3265 $glade_file = $p->{glade_file};
3266
3267 foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
3268     if (-f "$path/$glade_file") {
3269         $glade_file = "$path/$glade_file" ;
3270         last;
3271     }
3272 }
3273
3274 # gtk have lots of warning on stderr
3275 if ($^O eq 'MSWin32')
3276 {
3277     close(STDERR);
3278     open(STDERR, ">stderr.log");
3279 }
3280
3281 Gtk2->init();
3282
3283 if ( -f $glade_file) {
3284     my $w = new DlgResto($p);
3285
3286 } else {
3287     my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close', 
3288 "Can't find your brestore.glade (glade_file => '$glade_file')
3289 Please, edit your $file_conf to setup it." );
3290  
3291     $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
3292     $widget->run;
3293     exit 1;
3294 }
3295
3296 Gtk2->main; # Start Gtk2 main loop      
3297
3298 # that's it!
3299
3300 exit 0;
3301
3302 __END__
3303 package main;
3304
3305 my $p = new Pref("$ENV{HOME}/.brestore.conf");
3306 $p->{debug} = 1;
3307 $p->connect_db() || print $p->{error};
3308
3309 my $bvfs = new Bvfs(conf => $p);
3310
3311 $bvfs->debug($bvfs->get_root());
3312 $bvfs->ch_dir($bvfs->get_root());
3313 $bvfs->up_dir();
3314 $bvfs->set_curjobids(268,178,282,281,279);
3315 $bvfs->ls_files();
3316 my $dirs = $bvfs->ls_dirs();
3317 $bvfs->ch_dir(123496);
3318 $dirs = $bvfs->ls_dirs();
3319 $bvfs->ls_files();
3320 map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);