]> git.sur5r.net Git - bacula/bacula/blob - gui/bweb/cgi/bresto.pl
ebl Add brestore_xxx tables to sql scripts
[bacula/bacula] / gui / bweb / cgi / bresto.pl
1 #!/usr/bin/perl -w
2
3 my $bresto_enable = 1;
4 die "bresto is not enabled" if (not $bresto_enable);
5
6 =head1 LICENSE
7
8    Bweb - A Bacula web interface
9    Bacula® - The Network Backup Solution
10
11    Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
12
13    The main author of Bweb is Eric Bollengier.
14    The main author of Bacula is Kern Sibbald, with contributions from
15    many others, a complete list can be found in the file AUTHORS.
16
17    This program is Free Software; you can redistribute it and/or
18    modify it under the terms of version two of the GNU General Public
19    License as published by the Free Software Foundation plus additions
20    that are listed in the file LICENSE.
21
22    This program is distributed in the hope that it will be useful, but
23    WITHOUT ANY WARRANTY; without even the implied warranty of
24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25    General Public License for more details.
26
27    You should have received a copy of the GNU General Public License
28    along with this program; if not, write to the Free Software
29    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30    02110-1301, USA.
31
32    Bacula® is a registered trademark of John Walker.
33    The licensor of Bacula is the Free Software Foundation Europe
34    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
35    Switzerland, email:ftf@fsfeurope.org.
36
37 =head1 VERSION
38
39     $Id$
40
41 =cut
42
43 use Bweb;
44
45 package Bvfs;
46 use base qw/Bweb/;
47
48 sub get_root
49 {
50     my ($self) = @_;
51     return $self->get_pathid('');
52 }
53
54 sub ch_dir
55 {
56     my ($self, $pathid) = @_;
57     $self->{cwdid} = $pathid;
58 }
59
60 sub up_dir
61 {
62     my ($self) = @_ ;
63     my $query = "
64   SELECT PPathId
65     FROM brestore_pathhierarchy
66    WHERE PathId IN ($self->{cwdid}) ";
67
68     my $all = $self->dbh_selectall_arrayref($query);
69     return unless ($all);       # already at root
70
71     my $dir = join(',', map { $_->[0] } @$all);
72     if ($dir) {
73         $self->ch_dir($dir);
74     }
75 }
76
77 sub pwd
78 {
79     my ($self) = @_;
80     return $self->get_path($self->{cwdid});
81 }
82
83 sub get_path
84 {
85     my ($self, $pathid) = @_;
86     $self->debug("Call with pathid = $pathid");
87     my $query =
88         "SELECT Path FROM Path WHERE PathId IN (?)";
89
90     my $sth = $self->dbh_prepare($query);
91     $sth->execute($pathid);
92     my $result = $sth->fetchrow_arrayref();
93     $sth->finish();
94     return $result->[0];
95 }
96
97 sub set_curjobids
98 {
99     my ($self, @jobids) = @_;
100     $self->{curjobids} = join(',', @jobids);
101 #    $self->update_brestore_table(@jobids);
102 }
103
104 sub get_pathid
105 {
106     my ($self, $dir) = @_;
107     my $query =
108         "SELECT PathId FROM Path WHERE Path = ?";
109     my $sth = $self->dbh_prepare($query);
110     $sth->execute($dir);
111     my $result = $sth->fetchall_arrayref();
112     $sth->finish();
113
114     return join(',', map { $_->[0] } @$result);
115 }
116
117 sub set_limits
118 {
119     my ($self, $offset, $limit) = @_;
120     $self->{limit}  = $limit  || 100;
121     $self->{offset} = $offset || 0;
122 }
123
124 sub update_cache
125 {
126     my ($self) = @_;
127
128     $self->{dbh}->begin_work();
129
130     my $query = "
131   SELECT JobId from Job
132    WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) AND JobStatus IN ('T', 'f', 'A') ORDER BY JobId";
133     my $jobs = $self->dbh_selectall_arrayref($query);
134
135     $self->update_brestore_table(map { $_->[0] } @$jobs);
136
137     print STDERR "Cleaning path visibility\n";
138
139     my $nb = $self->dbh_do("
140   DELETE FROM brestore_pathvisibility
141       WHERE NOT EXISTS
142    (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
143
144     print STDERR "$nb rows affected\n";
145     print STDERR "Cleaning known jobid\n";
146
147     $nb = $self->dbh_do("
148   DELETE FROM brestore_knownjobid
149       WHERE NOT EXISTS
150    (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
151
152     print STDERR "$nb rows affected\n";
153
154     $self->{dbh}->commit();
155 }
156
157 sub update_brestore_table
158 {
159     my ($self, @jobs) = @_;
160
161     $self->debug(\@jobs);
162
163     foreach my $job (sort {$a <=> $b} @jobs)
164     {
165         my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
166         my $retour = $self->dbh_selectrow_arrayref($query);
167         next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
168
169         print STDERR "Inserting path records for JobId $job\n";
170         $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
171                    (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
172
173         $self->dbh_do($query);
174
175         # Now we have to do the directory recursion stuff to determine missing visibility
176         # We try to avoid recursion, to be as fast as possible
177         # We also only work on not allready hierarchised directories...
178
179         print STDERR "Creating missing recursion paths for $job\n";
180
181         $query = "
182 SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
183   JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
184        LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
185  WHERE brestore_pathvisibility.JobId = $job
186    AND brestore_pathhierarchy.PathId IS NULL
187  ORDER BY Path";
188
189         my $sth = $self->dbh_prepare($query);
190         $sth->execute();
191         my $pathid; my $path;
192         $sth->bind_columns(\$pathid,\$path);
193
194         while ($sth->fetch)
195         {
196             $self->build_path_hierarchy($path,$pathid);
197         }
198         $sth->finish();
199
200         # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
201         # This query gives all parent pathids for a given jobid that aren't stored.
202         # It has to be called until no record is updated ...
203         $query = "
204 INSERT INTO brestore_pathvisibility (PathId, JobId) (
205  SELECT a.PathId,$job
206    FROM (
207      SELECT DISTINCT h.PPathId AS PathId
208        FROM brestore_pathhierarchy AS h
209        JOIN  brestore_pathvisibility AS p ON (h.PathId=p.PathId)
210       WHERE p.JobId=$job) AS a LEFT JOIN
211        (SELECT PathId
212           FROM brestore_pathvisibility
213          WHERE JobId=$job) AS b ON (a.PathId = b.PathId)
214   WHERE b.PathId IS NULL)";
215
216         my $rows_affected;
217         while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
218         {
219             print STDERR "Recursively adding $rows_affected records from $job\n";
220         }
221         # Job's done
222         $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
223         $self->dbh_do($query);
224     }
225 }
226
227 sub parent_dir
228 {
229     my ($path) = @_;
230     # Root Unix case :
231     if ($path eq '/')
232     {
233         return '';
234     }
235     # Root Windows case :
236     if ($path =~ /^[a-z]+:\/$/i)
237     {
238         return '';
239     }
240     # Split
241     my @tmp = split('/',$path);
242     # We remove the last ...
243     pop @tmp;
244     my $tmp = join ('/',@tmp) . '/';
245     return $tmp;
246 }
247
248 sub build_path_hierarchy
249 {
250     my ($self, $path,$pathid)=@_;
251     # Does the ppathid exist for this ? we use a memory cache...
252     # In order to avoid the full loop, we consider that if a dir is allready in the
253     # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
254     while ($path ne '')
255     {
256         if (! $self->{cache_ppathid}->{$pathid})
257         {
258             my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
259             my $sth2 = $self->{dbh}->prepare_cached($query);
260             $sth2->execute($pathid);
261             # Do we have a result ?
262             if (my $refrow = $sth2->fetchrow_arrayref)
263             {
264                 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
265                 $sth2->finish();
266                 # This dir was in the db ...
267                 # It means we can leave, the tree has allready been built for
268                 # this dir
269                 return 1;
270             } else {
271                 $sth2->finish();
272                 # We have to create the record ...
273                 # What's the current p_path ?
274                 my $ppath = parent_dir($path);
275                 my $ppathid = $self->return_pathid_from_path($ppath);
276                 $self->{cache_ppathid}->{$pathid}= $ppathid;
277
278                 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
279                 $sth2 = $self->{dbh}->prepare_cached($query);
280                 $sth2->execute($pathid,$ppathid);
281                 $sth2->finish();
282                 $path = $ppath;
283                 $pathid = $ppathid;
284             }
285         } else {
286            # It's allready in the cache.
287            # We can leave, no time to waste here, all the parent dirs have allready
288            # been done
289            return 1;
290         }
291     }
292     return 1;
293 }
294
295 sub return_pathid_from_path
296 {
297     my ($self, $path) = @_;
298     my $query = "SELECT PathId FROM Path WHERE Path = ?";
299
300     #print STDERR $query,"\n" if $debug;
301     my $sth = $self->{dbh}->prepare_cached($query);
302     $sth->execute($path);
303     my $result =$sth->fetchrow_arrayref();
304     $sth->finish();
305     if (defined $result)
306     {
307         return $result->[0];
308
309     } else {
310         # A bit dirty : we insert into path, and we have to be sure
311         # we aren't deleted by a purge. We still need to insert into path to get
312         # the pathid, because of mysql
313         $query = "INSERT INTO Path (Path) VALUES (?)";
314         #print STDERR $query,"\n" if $debug;
315         $sth = $self->{dbh}->prepare_cached($query);
316         $sth->execute($path);
317         $sth->finish();
318
319         $query = "SELECT PathId FROM Path WHERE Path = ?";
320         #print STDERR $query,"\n" if $debug;
321         $sth = $self->{dbh}->prepare_cached($query);
322         $sth->execute($path);
323         $result = $sth->fetchrow_arrayref();
324         $sth->finish();
325         return $result->[0];
326     }
327 }
328
329 sub ls_files
330 {
331     my ($self) = @_;
332
333     return undef unless ($self->{curjobids});
334
335     my $inclause   = $self->{curjobids};
336     my $inlistpath = $self->{cwdid};
337
338     my $query =
339 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
340  FROM File, (
341        SELECT Filename.Name, max(File.FileId) as id
342          FROM File, Filename
343         WHERE File.FilenameId = Filename.FilenameId
344           AND Filename.Name != ''
345           AND File.PathId IN ($inlistpath)
346           AND File.JobId IN ($inclause)
347         GROUP BY Filename.Name
348         ORDER BY Filename.Name) AS listfiles
349 WHERE File.FileId = listfiles.id";
350
351     $self->debug($query);
352     my $result = $self->dbh_selectall_arrayref($query);
353     $self->debug($result);
354
355     return $result;
356 }
357
358
359 # return ($dirid,$dir_basename,$lstat,$jobid)
360 sub ls_dirs
361 {
362     my ($self) = @_;
363
364     return undef unless ($self->{curjobids});
365
366     my $pathid = $self->{cwdid};
367     my $jobclause = $self->{curjobids};
368
369     # Let's retrieve the list of the visible dirs in this dir ...
370     # First, I need the empty filenameid to locate efficiently the dirs in the file table
371     my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
372     my $sth = $self->dbh_prepare($query);
373     $sth->execute();
374     my $result = $sth->fetchrow_arrayref();
375     $sth->finish();
376     my $dir_filenameid = $result->[0];
377
378     # Then we get all the dir entries from File ...
379     $query = "
380 SELECT PathId, Path, JobId, Lstat FROM (
381
382     SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
383            listfile1.JobId, listfile1.Lstat
384     FROM (
385         SELECT DISTINCT brestore_pathhierarchy1.PathId
386         FROM brestore_pathhierarchy AS brestore_pathhierarchy1
387         JOIN Path AS Path2
388             ON (brestore_pathhierarchy1.PathId = Path2.PathId)
389         JOIN brestore_pathvisibility AS brestore_pathvisibility1
390             ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
391         WHERE brestore_pathhierarchy1.PPathId = $pathid
392         AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
393     JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
394     LEFT JOIN (
395         SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
396         WHERE File1.FilenameId = $dir_filenameid
397         AND File1.JobId IN ($jobclause)) AS listfile1
398         ON (listpath1.PathId = listfile1.PathId)
399      ) AS A ORDER BY 2,3 DESC
400 ";
401     $self->debug($query);
402     $sth=$self->dbh_prepare($query);
403     $sth->execute();
404     $result = $sth->fetchall_arrayref();
405     my @return_list;
406     my $prev_dir='';
407     foreach my $refrow (@{$result})
408     {
409         my $dirid = $refrow->[0];
410         my $dir = $refrow->[1];
411         my $lstat = $refrow->[3];
412         my $jobid = $refrow->[2] || 0;
413         next if ($dirid eq $prev_dir);
414         # We have to clean up this dirname ... we only want it's 'basename'
415         my $return_value;
416         if ($dir ne '/')
417         {
418             my @temp = split ('/',$dir);
419             $return_value = pop @temp;
420         }
421         else
422         {
423             $return_value = '/';
424         }
425         my @return_array = ($dirid,$return_value,$lstat,$jobid);
426         push @return_list,(\@return_array);
427         $prev_dir = $dirid;
428     }
429     $self->debug(\@return_list);
430     return \@return_list;
431 }
432
433 # TODO : we want be able to restore files from a bad ended backup
434 # we have JobStatus IN ('T', 'A', 'E') and we must
435
436 # Data acces subs from here. Interaction with SGBD and caching
437
438 # This sub retrieves the list of jobs corresponding to the jobs selected in the
439 # GUI and stores them in @CurrentJobIds.
440 # date must be quoted
441 sub set_job_ids_for_date
442 {
443     my ($self, $client, $date)=@_;
444
445     if (!$client or !$date) {
446         return ();
447     }
448     my $filter = $self->get_client_filter();
449     # The algorithm : for a client, we get all the backups for each
450     # fileset, in reverse order Then, for each fileset, we store the 'good'
451     # incrementals and differentials until we have found a full so it goes
452     # like this : store all incrementals until we have found a differential
453     # or a full, then find the full
454     my $query = "
455 SELECT JobId, FileSet, Level, JobStatus
456   FROM Job 
457        JOIN FileSet USING (FileSetId)
458        JOIN Client USING (ClientId) $filter
459  WHERE EndTime <= $date
460    AND Client.Name = '$client'
461    AND Type IN ('B')
462    AND JobStatus IN ('T')
463  ORDER BY FileSet, JobTDate DESC";
464
465     my @CurrentJobIds;
466     my $result = $self->dbh_selectall_arrayref($query);
467     my %progress;
468     foreach my $refrow (@$result)
469     {
470         my $jobid = $refrow->[0];
471         my $fileset = $refrow->[1];
472         my $level = $refrow->[2];
473
474         defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
475
476         next if $progress{$fileset} eq 'F'; # It's over for this fileset...
477
478         if ($level eq 'I')
479         {
480             next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
481             push @CurrentJobIds,($jobid);
482         }
483         elsif ($level eq 'D')
484         {
485             next if $progress{$fileset} eq 'D'; # We allready have a differential
486             push @CurrentJobIds,($jobid);
487         }
488         elsif ($level eq 'F')
489         {
490             push @CurrentJobIds,($jobid);
491         }
492
493         my $status = $refrow->[3] ;
494         if ($status eq 'T') {              # good end of job
495             $progress{$fileset} = $level;
496         }
497     }
498
499     return @CurrentJobIds;
500 }
501
502 sub dbh_selectrow_arrayref
503 {
504     my ($self, $query) = @_;
505     $self->debug($query, up => 1);
506     return $self->{dbh}->selectrow_arrayref($query);
507 }
508
509 # Returns list of versions of a file that could be restored
510 # returns an array of
511 # (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
512 # there will be only one jobid in the array of jobids...
513 sub get_all_file_versions
514 {
515     my ($self,$pathid,$fileid,$client,$see_all)=@_;
516
517     defined $see_all or $see_all=0;
518
519     my @versions;
520     my $query;
521     $query =
522 "SELECT File.JobId, File.FileId, File.Lstat,
523         File.Md5, Media.VolumeName, Media.InChanger
524  FROM File, Job, Client, JobMedia, Media
525  WHERE File.FilenameId = $fileid
526    AND File.PathId=$pathid
527    AND File.JobId = Job.JobId
528    AND Job.ClientId = Client.ClientId
529    AND Job.JobId = JobMedia.JobId
530    AND File.FileIndex >= JobMedia.FirstIndex
531    AND File.FileIndex <= JobMedia.LastIndex
532    AND JobMedia.MediaId = Media.MediaId
533    AND Client.Name = '$client'";
534
535     $self->debug($query);
536     my $result = $self->dbh_selectall_arrayref($query);
537
538     foreach my $refrow (@$result)
539     {
540         my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
541         my @attribs = parse_lstat($lstat);
542         my $mtime = array_attrib('st_mtime',\@attribs);
543         my $size = array_attrib('st_size',\@attribs);
544
545         my @list = ($pathid,$fileid,$jobid,
546                     $fid, $mtime, $size, $inchanger,
547                     $md5, $volname);
548         push @versions, (\@list);
549     }
550
551     # We have the list of all versions of this file.
552     # We'll sort it by mtime desc, size, md5, inchanger desc
553     # the rest of the algorithm will be simpler
554     # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
555     @versions = sort { $b->[4] <=> $a->[4]
556                     || $a->[5] <=> $b->[5]
557                     || $a->[7] cmp $a->[7]
558                     || $b->[6] <=> $a->[6]} @versions;
559
560     my @good_versions;
561     my %allready_seen_by_mtime;
562     my %allready_seen_by_md5;
563     # Now we should create a new array with only the interesting records
564     foreach my $ref (@versions)
565     {
566         if ($ref->[7])
567         {
568             # The file has a md5. We compare his md5 to other known md5...
569             # We take size into account. It may happen that 2 files
570             # have the same md5sum and are different. size is a supplementary
571             # criterion
572
573             # If we allready have a (better) version
574             next if ( (not $see_all)
575                       and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
576
577             # we never met this one before...
578             $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
579         }
580         # Even if it has a md5, we should also work with mtimes
581         # We allready have a (better) version
582         next if ( (not $see_all)
583                   and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
584         $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
585
586         # We reached there. The file hasn't been seen.
587         push @good_versions,($ref);
588     }
589
590     # To be nice with the user, we re-sort good_versions by
591     # inchanger desc, mtime desc
592     @good_versions = sort { $b->[4] <=> $a->[4]
593                          || $b->[2] <=> $a->[2]} @good_versions;
594
595     return \@good_versions;
596 }
597 {
598     my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
599                           'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
600                           'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
601                           'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
602                           'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
603                           'data_stream' => 15);;
604     sub array_attrib
605     {
606         my ($attrib,$ref_attrib)=@_;
607         return $ref_attrib->[$attrib_name_id{$attrib}];
608     }
609
610     sub file_attrib
611     {   # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
612
613         my ($file, $attrib)=@_;
614
615         if (defined $attrib_name_id{$attrib}) {
616
617             my @d = split(' ', $file->[3]) ; # TODO : cache this
618
619             return from_base64($d[$attrib_name_id{$attrib}]);
620
621         } elsif ($attrib eq 'jobid') {
622
623             return $file->[4];
624
625         } elsif ($attrib eq 'name') {
626
627             return $file->[2];
628
629         } else  {
630             die "Attribute not known : $attrib.\n";
631         }
632     }
633
634     sub lstat_attrib
635     {
636         my ($lstat,$attrib)=@_;
637         if ($lstat and defined $attrib_name_id{$attrib})
638         {
639             my @d = split(' ', $lstat) ; # TODO : cache this
640             return from_base64($d[$attrib_name_id{$attrib}]);
641         }
642         return 0;
643     }
644 }
645
646 {
647     # Base 64 functions, directly from recover.pl.
648     # Thanks to
649     # Karl Hakimian <hakimian@aha.com>
650     # This section is also under GPL v2 or later.
651     my @base64_digits;
652     my @base64_map;
653     my $is_init=0;
654     sub init_base64
655     {
656         @base64_digits = (
657         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
658         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
659         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
660         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
661         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
662                           );
663         @base64_map = (0) x 128;
664
665         for (my $i=0; $i<64; $i++) {
666             $base64_map[ord($base64_digits[$i])] = $i;
667         }
668         $is_init = 1;
669     }
670
671     sub from_base64 {
672         if(not $is_init)
673         {
674             init_base64();
675         }
676         my $where = shift;
677         my $val = 0;
678         my $i = 0;
679         my $neg = 0;
680
681         if (substr($where, 0, 1) eq '-') {
682             $neg = 1;
683             $where = substr($where, 1);
684         }
685
686         while ($where ne '') {
687             $val *= 64;
688             my $d = substr($where, 0, 1);
689             $val += $base64_map[ord(substr($where, 0, 1))];
690             $where = substr($where, 1);
691         }
692
693         return $val;
694     }
695
696     sub parse_lstat {
697         my ($lstat)=@_;
698         my @attribs = split(' ',$lstat);
699         foreach my $element (@attribs)
700         {
701             $element = from_base64($element);
702         }
703         return @attribs;
704     }
705 }
706
707 sub get_jobids
708 {
709   my ($self, @jobid) = @_;
710   my $filter = $self->get_client_filter();
711   if ($filter) {
712     my $jobids = $self->dbh_join(@jobid);
713     my $q="
714 SELECT JobId 
715   FROM Job JOIN Client USING (ClientId) $filter 
716  WHERE Jobid IN ($jobids)";
717     my $res = $self->dbh_selectall_arrayref($q);
718     @jobid = map { $_->[0] } @$res;
719   }
720   return @jobid;
721 }
722
723 ################################################################
724
725
726 package main;
727 use strict;
728 use Bweb;
729
730 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
731 $conf->load();
732
733 my $bvfs = new Bvfs(info => $conf);
734 $bvfs->connect_db();
735
736 my $action = CGI::param('action') || '';
737
738 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
739                            'limit', 'offset', 'client');
740
741 if (CGI::param('batch')) {
742     $bvfs->update_cache();
743     exit 0;
744 }
745
746 if ($action eq 'list_client') {
747     print CGI::header('application/x-javascript');
748
749   my $filter = $bvfs->get_client_filter();
750   my $q = "SELECT Name FROM Client $filter";
751   my $ret = $bvfs->dbh_selectall_arrayref($q);
752
753   print "[";
754   print join(',', map { "['$_->[0]']" } @$ret);
755   print "]\n";
756   exit 0;
757
758 } elsif ($action eq 'list_job') {
759     print CGI::header('application/x-javascript');
760
761     my $filter = $bvfs->get_client_filter();
762     my $query = "
763  SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
764   FROM Job JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) $filter
765  WHERE Client.Name = '$args->{client}'
766    AND Job.Type = 'B'
767    AND JobStatus IN ('f', 'T')
768  ORDER BY EndTime desc";
769     my $result = $bvfs->dbh_selectall_arrayref($query);
770
771     print "[";
772
773     print join(',', map {
774       "[$_->[4], '$_->[0]', '$_->[0] $_->[1] $_->[2] ($_->[3]) $_->[4]']"
775       } @$result);
776
777     print "]\n";
778     exit 0;
779 } elsif ($action eq 'list_storage') { # TODO: use .storage hier
780     print CGI::header('application/x-javascript');
781
782     my $q="SELECT Name FROM Storage";
783     my $lst = $bvfs->dbh_selectall_arrayref($q);
784     print "[";
785     print join(',', map { "[ '$_->[0]' ]" } @$lst);
786     print "]\n";
787     exit 0;
788 }
789
790 my @jobid = $bvfs->get_jobids(grep { /^\d+(,\d+)*$/ } CGI::param('jobid'));
791 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
792     @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});
793 }
794 $bvfs->set_curjobids(@jobid);
795 $bvfs->set_limits($args->{limit}, $args->{offset});
796
797 if (!scalar(@jobid)) {
798     exit 0;
799 }
800
801 if (CGI::param('init')) {
802     $bvfs->update_brestore_table(@jobid);
803 }
804
805 my $pathid = CGI::param('node') || '';
806 my $path = CGI::param('path');
807
808 if ($pathid =~ /^(\d+)$/) {
809     $pathid = $1;
810 } elsif ($path) {
811     $pathid = $bvfs->get_pathid($path);
812 } else {
813     $pathid = $bvfs->get_root();
814 }
815
816 $bvfs->ch_dir($pathid);
817
818 if ($action eq 'restore') {
819
820     # TODO: pouvoir choisir le replace et le jobname
821     my $arg = $bvfs->get_form(qw/client storage regexwhere where/);
822
823     if (!$arg->{client}) {
824         print "ERROR: missing client\n";
825         exit 1;
826     }
827
828     my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
829     my @dirid = grep { /^\d+$/ } CGI::param('dirid');
830     my $inclause = join(',', @jobid);
831
832     my @union;
833
834     if ($fileid) {
835       push @union,
836       "(SELECT JobId, FileIndex, FilenameId, PathId FROM File WHERE FileId IN ($fileid))";
837     }
838
839     # using this is not good because the sql engine doesn't know
840     # what LIKE will use. It will be better to get Path% in perl
841     # but it doesn't work with accents... :(
842     foreach my $dirid (@dirid) {
843       push @union, "
844   (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId
845     FROM Path JOIN File USING (PathId)
846    WHERE Path.Path LIKE
847         (SELECT ". $bvfs->dbh_strcat('Path',"'\%'") ." FROM Path
848           WHERE PathId = $dirid
849         )
850      AND File.JobId IN ($inclause))";
851     }
852
853     return unless scalar(@union);
854
855     my $u = join(" UNION ", @union);
856
857     $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp AS ($u)");
858     # TODO: remove FilenameId et PathId
859     $bvfs->dbh_do("CREATE TABLE b2$$ AS (
860 SELECT btemp.JobId, btemp.FileIndex, btemp.FilenameId, btemp.PathId
861   FROM btemp,
862        (SELECT max(JobId) as JobId, PathId, FilenameId
863           FROM btemp
864       GROUP BY PathId, FilenameId
865       ORDER BY JobId DESC) AS a
866   WHERE a.JobId = btemp.JobId
867     AND a.PathId= btemp.PathId
868     AND a.FilenameId = btemp.FilenameId
869 )");
870     my $bconsole = $bvfs->get_bconsole();
871     # TODO: pouvoir choisir le replace et le jobname
872     my $jobid = $bconsole->run(client    => $arg->{client},
873                                storage   => $arg->{storage},
874                                where     => $arg->{where},
875                                regexwhere=> $arg->{regexwhere},
876                                restore   => 1,
877                                file      => "?b2$$");
878     
879     $bvfs->dbh_do("DROP TABLE b2$$");
880     sleep(2);
881     print CGI::redirect("bweb.pl?action=dsp_cur_job;jobid=$jobid") ;
882     exit 0;
883 }
884
885 print CGI::header('application/x-javascript');
886
887 if ($action eq 'list_files') {
888     print "[";
889     my $files = $bvfs->ls_files();
890 #       [ 1, 2, 3, "Bill",  10, '2007-01-01 00:00:00'],
891 #   File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
892
893     print join(',',
894                map { "[$_->[1], $_->[0], $pathid, $_->[4], \"$_->[2]\", 10, \"2007-01-01 00:00:00\"]" }
895                @$files);
896     print "]\n";
897
898 } elsif ($action eq 'list_dirs') {
899
900     print "[";
901     my $dirs = $bvfs->ls_dirs();
902         # return ($dirid,$dir_basename,$lstat,$jobid)
903
904     print join(',',
905                map { "{ 'jobid': '$bvfs->{curjobids}', 'id': '$_->[0]', 'text': '$_->[1]', 'cls':'folder'}" }
906                @$dirs);
907
908     print "]\n";
909
910 } elsif ($action eq 'list_versions') {
911
912     my $vafv = CGI::param('vafv') || 'false'; # view all file versions
913     $vafv = ($vafv eq 'false')?0:1;
914
915     print "[";
916     #   0       1       2        3   4       5      6           7      8
917     #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
918     my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, $vafv);
919     print join(',',
920                map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5], $_->[4] ]" }
921                @$files);
922     print "]\n";
923
924 } elsif ($action eq 'get_media') {
925
926     my $jobid = join(',', @jobid);
927     my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
928
929     # TODO: use client filter
930     my $q="
931  SELECT DISTINCT VolumeName, InChanger
932    FROM File,
933     ( -- Get all media from this job
934       SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
935              VolumeName, Inchanger
936         FROM JobMedia JOIN Media USING (MediaId)
937        WHERE JobId IN ($jobid)
938        GROUP BY VolumeName, InChanger
939     ) AS allmedia
940   WHERE File.FileId IN ($fileid)
941     AND File.FileIndex >= allmedia.FirstIndex
942     AND File.FileIndex <= allmedia.LastIndex
943 ";
944     my $lst = $bvfs->dbh_selectall_arrayref($q);
945     print "[";
946     print join(',', map { "[ '$_->[0]', $_->[1]]" } @$lst);
947     print "]\n";
948
949 }
950
951 __END__
952
953 CREATE VIEW files AS
954  SELECT path || name AS name,pathid,filenameid,fileid,jobid
955    FROM File JOIN FileName USING (FilenameId) JOIN Path USING (PathId);
956
957 SELECT 'drop table ' || tablename || ';'
958     FROM pg_tables WHERE tablename ~ '^b[0-9]';