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