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