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