]> git.sur5r.net Git - bacula/bacula/blob - gui/bweb/cgi/bresto.pl
ebl Make drag&drop working between tree, file list, file versions and
[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 ls_files
125 {
126     my ($self) = @_;
127
128     return undef unless ($self->{curjobids});
129
130     my $inclause   = $self->{curjobids};
131     my $inlistpath = $self->{cwdid};
132
133     my $query = 
134 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
135  FROM
136         (SELECT Filename.Name, max(File.FileId) as id
137          FROM File, Filename
138          WHERE File.FilenameId = Filename.FilenameId
139            AND Filename.Name != ''
140            AND File.PathId IN ($inlistpath)
141            AND File.JobId IN ($inclause)
142          GROUP BY Filename.Name
143          ORDER BY Filename.Name) AS listfiles,
144 File
145 WHERE File.FileId = listfiles.id";
146         
147     $self->debug($query);
148     my $result = $self->dbh_selectall_arrayref($query);
149     $self->debug($result);
150         
151     return $result;
152 }
153
154
155 # return ($dirid,$dir_basename,$lstat,$jobid)
156 sub ls_dirs
157 {
158     my ($self) = @_;
159
160     return undef unless ($self->{curjobids});
161
162     my $pathid = $self->{cwdid};
163     my $jobclause = $self->{curjobids};
164
165     # Let's retrieve the list of the visible dirs in this dir ...
166     # First, I need the empty filenameid to locate efficiently the dirs in the file table
167     my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
168     my $sth = $self->dbh_prepare($query);
169     $sth->execute();
170     my $result = $sth->fetchrow_arrayref();
171     $sth->finish();
172     my $dir_filenameid = $result->[0];
173      
174     # Then we get all the dir entries from File ...
175     $query = "
176 SELECT PathId, Path, JobId, Lstat FROM (
177     
178     SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
179            listfile1.JobId, listfile1.Lstat
180     FROM (
181         SELECT DISTINCT brestore_pathhierarchy1.PathId
182         FROM brestore_pathhierarchy AS brestore_pathhierarchy1
183         JOIN Path AS Path2
184             ON (brestore_pathhierarchy1.PathId = Path2.PathId)
185         JOIN brestore_pathvisibility AS brestore_pathvisibility1
186             ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
187         WHERE brestore_pathhierarchy1.PPathId = $pathid
188         AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
189     JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
190     LEFT JOIN (
191         SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
192         WHERE File1.FilenameId = $dir_filenameid
193         AND File1.JobId IN ($jobclause)) AS listfile1
194         ON (listpath1.PathId = listfile1.PathId)
195      ) AS A ORDER BY 2,3 DESC
196 ";
197     $self->debug($query);
198     $sth=$self->dbh_prepare($query);
199     $sth->execute();
200     $result = $sth->fetchall_arrayref();
201     my @return_list;
202     my $prev_dir='';
203     foreach my $refrow (@{$result})
204     {
205         my $dirid = $refrow->[0];
206         my $dir = $refrow->[1];
207         my $lstat = $refrow->[3];
208         my $jobid = $refrow->[2] || 0;
209         next if ($dirid eq $prev_dir);
210         # We have to clean up this dirname ... we only want it's 'basename'
211         my $return_value;
212         if ($dir ne '/')
213         {
214             my @temp = split ('/',$dir);
215             $return_value = pop @temp;
216         }
217         else
218         {
219             $return_value = '/';
220         }
221         my @return_array = ($dirid,$return_value,$lstat,$jobid);
222         push @return_list,(\@return_array);
223         $prev_dir = $dirid;
224     }
225     $self->debug(\@return_list);
226     return \@return_list;    
227 }
228
229 # TODO : we want be able to restore files from a bad ended backup
230 # we have JobStatus IN ('T', 'A', 'E') and we must 
231
232 # Data acces subs from here. Interaction with SGBD and caching
233
234 # This sub retrieves the list of jobs corresponding to the jobs selected in the
235 # GUI and stores them in @CurrentJobIds.
236 # date must be quoted
237 sub set_job_ids_for_date
238 {
239     my ($self, $client, $date)=@_;
240
241     if (!$client or !$date) {
242         return ();
243     }
244         
245     # The algorithm : for a client, we get all the backups for each
246     # fileset, in reverse order Then, for each fileset, we store the 'good'
247     # incrementals and differentials until we have found a full so it goes
248     # like this : store all incrementals until we have found a differential
249     # or a full, then find the full #
250
251     my $query = "SELECT JobId, FileSet, Level, JobStatus
252                 FROM Job, Client, FileSet
253                 WHERE Job.ClientId = Client.ClientId
254                 AND FileSet.FileSetId = Job.FileSetId
255                 AND EndTime <= $date
256                 AND Client.Name = '$client'
257                 AND Type IN ('B')
258                 AND JobStatus IN ('T')
259                 ORDER BY FileSet, JobTDate DESC";
260     
261     my @CurrentJobIds;
262     my $result = $self->dbh_selectall_arrayref($query);
263     my %progress;
264     foreach my $refrow (@$result)
265     {
266         my $jobid = $refrow->[0];
267         my $fileset = $refrow->[1];
268         my $level = $refrow->[2];
269                 
270         defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
271                 
272         next if $progress{$fileset} eq 'F'; # It's over for this fileset...
273                 
274         if ($level eq 'I')
275         {
276             next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
277             push @CurrentJobIds,($jobid);
278         }
279         elsif ($level eq 'D')
280         {
281             next if $progress{$fileset} eq 'D'; # We allready have a differential
282             push @CurrentJobIds,($jobid);
283         }
284         elsif ($level eq 'F')
285         {
286             push @CurrentJobIds,($jobid);
287         }
288
289         my $status = $refrow->[3] ;
290         if ($status eq 'T') {              # good end of job
291             $progress{$fileset} = $level;
292         }
293     }
294
295     return @CurrentJobIds;
296 }
297
298
299 # Returns list of versions of a file that could be restored
300 # returns an array of 
301 # (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
302 # there will be only one jobid in the array of jobids...
303 sub get_all_file_versions
304 {
305     my ($self,$pathid,$fileid,$client,$see_all)=@_;
306     
307     defined $see_all or $see_all=0;
308     
309     my @versions;
310     my $query;
311     $query = 
312 "SELECT File.JobId, File.FileId, File.Lstat, 
313         File.Md5, Media.VolumeName, Media.InChanger
314  FROM File, Job, Client, JobMedia, Media
315  WHERE File.FilenameId = $fileid
316    AND File.PathId=$pathid
317    AND File.JobId = Job.JobId
318    AND Job.ClientId = Client.ClientId
319    AND Job.JobId = JobMedia.JobId
320    AND File.FileIndex >= JobMedia.FirstIndex
321    AND File.FileIndex <= JobMedia.LastIndex
322    AND JobMedia.MediaId = Media.MediaId
323    AND Client.Name = '$client'";
324
325     $self->debug($query);
326         
327     my $result = $self->dbh_selectall_arrayref($query);
328         
329     foreach my $refrow (@$result)
330     {
331         my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
332         my @attribs = parse_lstat($lstat);
333         my $mtime = array_attrib('st_mtime',\@attribs);
334         my $size = array_attrib('st_size',\@attribs);
335                 
336         my @list = ($pathid,$fileid,$jobid,
337                     $fid, $mtime, $size, $inchanger,
338                     $md5, $volname);
339         push @versions, (\@list);
340     }
341         
342     # We have the list of all versions of this file.
343     # We'll sort it by mtime desc, size, md5, inchanger desc
344     # the rest of the algorithm will be simpler
345     # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
346     @versions = sort { $b->[4] <=> $a->[4] 
347                     || $a->[5] <=> $b->[5] 
348                     || $a->[7] cmp $a->[7] 
349                     || $b->[6] <=> $a->[6]} @versions;
350
351         
352     my @good_versions;
353     my %allready_seen_by_mtime;
354     my %allready_seen_by_md5;
355     # Now we should create a new array with only the interesting records
356     foreach my $ref (@versions)
357     {   
358         if ($ref->[7])
359         {
360             # The file has a md5. We compare his md5 to other known md5...
361             # We take size into account. It may happen that 2 files
362             # have the same md5sum and are different. size is a supplementary
363             # criterion
364             
365             # If we allready have a (better) version
366             next if ( (not $see_all) 
367                       and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}); 
368
369             # we never met this one before...
370             $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
371         }
372         # Even if it has a md5, we should also work with mtimes
373         # We allready have a (better) version
374         next if ( (not $see_all)
375                   and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]}); 
376         $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
377         
378         # We reached there. The file hasn't been seen.
379         push @good_versions,($ref);
380     }
381         
382     # To be nice with the user, we re-sort good_versions by
383     # inchanger desc, mtime desc
384     @good_versions = sort { $b->[4] <=> $a->[4] 
385                          || $b->[2] <=> $a->[2]} @good_versions;
386         
387     return \@good_versions;
388 }
389 {
390     my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
391                           'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
392                           'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
393                           'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
394                           'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
395                           'data_stream' => 15);;
396     sub array_attrib
397     {
398         my ($attrib,$ref_attrib)=@_;
399         return $ref_attrib->[$attrib_name_id{$attrib}];
400     }
401         
402     sub file_attrib
403     {   # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
404
405         my ($file, $attrib)=@_;
406         
407         if (defined $attrib_name_id{$attrib}) {
408
409             my @d = split(' ', $file->[3]) ; # TODO : cache this
410             
411             return from_base64($d[$attrib_name_id{$attrib}]);
412
413         } elsif ($attrib eq 'jobid') {
414
415             return $file->[4];
416
417         } elsif ($attrib eq 'name') {
418
419             return $file->[2];
420             
421         } else  {
422             die "Attribute not known : $attrib.\n";
423         }
424     }
425     
426     sub lstat_attrib
427     {
428         my ($lstat,$attrib)=@_;
429         if ($lstat and defined $attrib_name_id{$attrib}) 
430         {
431             my @d = split(' ', $lstat) ; # TODO : cache this
432             return from_base64($d[$attrib_name_id{$attrib}]);
433         }
434         return 0;
435     }
436 }
437
438 {
439     # Base 64 functions, directly from recover.pl.
440     # Thanks to
441     # Karl Hakimian <hakimian@aha.com>
442     # This section is also under GPL v2 or later.
443     my @base64_digits;
444     my @base64_map;
445     my $is_init=0;
446     sub init_base64
447     {
448         @base64_digits = (
449         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
450         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
451         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
452         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
453         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
454                           );
455         @base64_map = (0) x 128;
456         
457         for (my $i=0; $i<64; $i++) {
458             $base64_map[ord($base64_digits[$i])] = $i;
459         }
460         $is_init = 1;
461     }
462
463     sub from_base64 {
464         if(not $is_init)
465         {
466             init_base64();
467         }
468         my $where = shift;
469         my $val = 0;
470         my $i = 0;
471         my $neg = 0;
472         
473         if (substr($where, 0, 1) eq '-') {
474             $neg = 1;
475             $where = substr($where, 1);
476         }
477         
478         while ($where ne '') {
479             $val *= 64;
480             my $d = substr($where, 0, 1);
481             $val += $base64_map[ord(substr($where, 0, 1))];
482             $where = substr($where, 1);
483         }
484         
485         return $val;
486     }
487
488     sub parse_lstat {
489         my ($lstat)=@_;
490         my @attribs = split(' ',$lstat);
491         foreach my $element (@attribs)
492         {
493             $element = from_base64($element);
494         }
495         return @attribs;
496     }
497 }
498
499
500 ################################################################
501
502
503 package main;
504 use strict;
505 use Bweb;
506
507 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
508 $conf->load();
509
510 my $bvfs = new Bvfs(info => $conf);
511 $bvfs->connect_db();
512
513 my $action = CGI::param('action') || '';
514
515 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
516                            'limit', 'offset', 'client');
517
518 my @jobid = grep { /^\d+$/ } CGI::param('jobid');
519
520 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
521     @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});    
522 print join(",", $args->{qdate}, $args->{client}, @jobid), "\n";
523
524 }
525
526 $bvfs->set_curjobids(@jobid);
527 $bvfs->set_limits($args->{limit}, $args->{offset});
528
529 print CGI::header('application/x-javascript');
530
531 if ($action eq 'list_client') {
532
533   my $q = 'SELECT Name FROM Client';
534   my $ret = $bvfs->dbh_selectall_arrayref($q);
535
536   print "[";
537   print join(',', map { "['$_->[0]']" } @$ret);
538   print "]\n";
539   exit 0;
540
541 } elsif ($action eq 'list_job') {
542
543     my $query = "
544  SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
545   FROM Job,Client,FileSet
546   WHERE Job.ClientId=Client.ClientId
547   AND Client.Name = '$args->{client}'
548   AND Job.Type = 'B'
549   AND JobStatus IN ('f', 'T')
550   AND Job.FileSetId = FileSet.FileSetId
551   ORDER BY EndTime desc";
552     my $result = $bvfs->dbh_selectall_arrayref($query);
553
554     print "[";
555
556     print join(',', map {
557       "[$_->[4], '$_->[0]', '$_->[0] $_->[1] $_->[2] ($_->[3])']"
558       } @$result);
559
560     print "]\n";
561 }
562
563 my $pathid = CGI::param('node') || '';
564 my $path = CGI::param('path');
565
566 if ($pathid =~ /^(\d+)$/) {
567     $pathid = $1;
568 } elsif ($path) {
569     $pathid = $bvfs->get_pathid($path);
570 } else {
571     $pathid = $bvfs->get_root();
572 }
573
574 $bvfs->ch_dir($pathid);
575
576 if ($action eq 'list_files') {
577     print "[";
578     my $files = $bvfs->ls_files();
579 #       [ 1, 2, 3, "Bill",  10, '2007-01-01 00:00:00'], 
580 #   File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId 
581
582     print join(',',
583                map { "[$_->[1], $_->[0], $pathid, \"$_->[2]\", 10, \"2007-01-01 00:00:00\"]" }
584                @$files);
585     print "]\n";
586
587 } elsif ($action eq 'list_dirs') {
588
589     print "[";
590     my $dirs = $bvfs->ls_dirs();
591         # return ($dirid,$dir_basename,$lstat,$jobid)
592
593     print join(',', 
594                map { "{ 'id': '$_->[0]', 'text': '$_->[1]', 'cls':'folder'}" }
595                @$dirs);
596
597     print "]\n";
598
599 } elsif ($action eq 'list_versions') {
600
601     print "[";
602     #   0       1       2        3   4       5      6           7      8
603     #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
604     my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, 1);
605     print join(',', 
606                map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5], $_->[4] ]" }
607                @$files);
608     print "]\n";
609
610 }
611
612
613