4 die "bresto is not enabled" if (not $bresto_enable);
8 Bweb - A Bacula web interface
9 Bacula® - The Network Backup Solution
11 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
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.
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.
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.
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
32 Bacula® is a registered trademark of Kern Sibbald.
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.
51 return $self->get_pathid('');
54 # change the current directory
57 my ($self, $pathid) = @_;
58 $self->{cwdid} = $pathid;
67 FROM brestore_pathhierarchy
68 WHERE PathId IN ($self->{cwdid}) ";
70 my $all = $self->dbh_selectall_arrayref($query);
71 return unless ($all); # already at root
73 my $dir = join(',', map { $_->[0] } @$all);
79 # return the current PWD
83 return $self->get_path($self->{cwdid});
86 # get the Path from a PathId
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();
99 # we are working with these jobids
102 my ($self, @jobids) = @_;
103 $self->{curjobids} = join(',', @jobids);
104 # $self->update_brestore_table(@jobids);
107 # get the PathId from a Path
110 my ($self, $dir) = @_;
112 "SELECT PathId FROM Path WHERE Path = ?";
113 my $sth = $self->dbh_prepare($query);
115 my $result = $sth->fetchrow_arrayref();
123 my ($self, $offset, $limit) = @_;
124 $self->{limit} = $limit || 100;
125 $self->{offset} = $offset || 0;
130 my ($self, $pattern) = @_;
131 $self->{pattern} = $pattern;
134 # fill brestore_xxx tables for speedup
139 $self->{dbh}->begin_work();
141 # getting all Jobs to "cache"
143 SELECT JobId from Job
144 WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid)
145 AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A')
147 my $jobs = $self->dbh_selectall_arrayref($query);
149 $self->update_brestore_table(map { $_->[0] } @$jobs);
151 $self->{dbh}->commit();
152 $self->{dbh}->begin_work(); # we can break here
154 print STDERR "Cleaning path visibility\n";
156 my $nb = $self->dbh_do("
157 DELETE FROM brestore_pathvisibility
159 (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
161 print STDERR "$nb rows affected\n";
162 print STDERR "Cleaning known jobid\n";
164 $nb = $self->dbh_do("
165 DELETE FROM brestore_knownjobid
167 (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
169 print STDERR "$nb rows affected\n";
171 $self->{dbh}->commit();
174 sub update_brestore_table
176 my ($self, @jobs) = @_;
178 $self->debug(\@jobs);
180 foreach my $job (sort {$a <=> $b} @jobs)
182 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
183 my $retour = $self->dbh_selectrow_arrayref($query);
184 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
186 print STDERR "Inserting path records for JobId $job\n";
187 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
188 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
190 $self->dbh_do($query);
192 # Now we have to do the directory recursion stuff to determine missing visibility
193 # We try to avoid recursion, to be as fast as possible
194 # We also only work on not allready hierarchised directories...
196 print STDERR "Creating missing recursion paths for $job\n";
199 SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
200 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
201 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
202 WHERE brestore_pathvisibility.JobId = $job
203 AND brestore_pathhierarchy.PathId IS NULL
206 my $sth = $self->dbh_prepare($query);
208 my $pathid; my $path;
209 $sth->bind_columns(\$pathid,\$path);
213 $self->build_path_hierarchy($path,$pathid);
217 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
218 # This query gives all parent pathids for a given jobid that aren't stored.
219 # It has to be called until no record is updated ...
221 INSERT INTO brestore_pathvisibility (PathId, JobId) (
224 SELECT DISTINCT h.PPathId AS PathId
225 FROM brestore_pathhierarchy AS h
226 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
227 WHERE p.JobId=$job) AS a LEFT JOIN
229 FROM brestore_pathvisibility
230 WHERE JobId=$job) AS b ON (a.PathId = b.PathId)
231 WHERE b.PathId IS NULL)";
234 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
236 print STDERR "Recursively adding $rows_affected records from $job\n";
239 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
240 $self->dbh_do($query);
244 # compute the parent directory
253 # Root Windows case :
254 if ($path =~ /^[a-z]+:\/$/i)
259 my @tmp = split('/',$path);
260 # We remove the last ...
262 my $tmp = join ('/',@tmp) . '/';
266 sub build_path_hierarchy
268 my ($self, $path,$pathid)=@_;
269 # Does the ppathid exist for this ? we use a memory cache...
270 # In order to avoid the full loop, we consider that if a dir is allready in the
271 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
274 if (! $self->{cache_ppathid}->{$pathid})
276 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
277 my $sth2 = $self->{dbh}->prepare_cached($query);
278 $sth2->execute($pathid);
279 # Do we have a result ?
280 if (my $refrow = $sth2->fetchrow_arrayref)
282 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
284 # This dir was in the db ...
285 # It means we can leave, the tree has allready been built for
290 # We have to create the record ...
291 # What's the current p_path ?
292 my $ppath = parent_dir($path);
293 my $ppathid = $self->return_pathid_from_path($ppath);
294 $self->{cache_ppathid}->{$pathid}= $ppathid;
296 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
297 $sth2 = $self->{dbh}->prepare_cached($query);
298 $sth2->execute($pathid,$ppathid);
304 # It's allready in the cache.
305 # We can leave, no time to waste here, all the parent dirs have allready
313 sub return_pathid_from_path
315 my ($self, $path) = @_;
316 my $query = "SELECT PathId FROM Path WHERE Path = ?";
318 #print STDERR $query,"\n" if $debug;
319 my $sth = $self->{dbh}->prepare_cached($query);
320 $sth->execute($path);
321 my $result =$sth->fetchrow_arrayref();
328 # A bit dirty : we insert into path, and we have to be sure
329 # we aren't deleted by a purge. We still need to insert into path to get
330 # the pathid, because of mysql
331 $query = "INSERT INTO Path (Path) VALUES (?)";
332 #print STDERR $query,"\n" if $debug;
333 $sth = $self->{dbh}->prepare_cached($query);
334 $sth->execute($path);
337 $query = "SELECT PathId FROM Path WHERE Path = ?";
338 #print STDERR $query,"\n" if $debug;
339 $sth = $self->{dbh}->prepare_cached($query);
340 $sth->execute($path);
341 $result = $sth->fetchrow_arrayref();
347 # list all files in a directory, accross curjobids
352 return undef unless ($self->{curjobids});
354 my $inclause = $self->{curjobids};
355 my $inpath = $self->{cwdid};
357 if ($self->{pattern}) {
358 $filter = " AND Filename.Name $self->{sql}->{MATCH} $self->{pattern} ";
362 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
364 SELECT Filename.Name, max(File.FileId) as id
366 WHERE File.FilenameId = Filename.FilenameId
367 AND Filename.Name != ''
368 AND File.PathId = $inpath
369 AND File.JobId IN ($inclause)
371 GROUP BY Filename.Name
372 ORDER BY Filename.Name LIMIT $self->{limit} OFFSET $self->{offset}
374 WHERE File.FileId = listfiles.id";
376 # print STDERR $query;
377 $self->debug($query);
378 my $result = $self->dbh_selectall_arrayref($query);
379 $self->debug($result);
387 return undef unless ($self->{curjobids});
389 my $pathid = $self->{cwdid};
390 my $jobclause = $self->{curjobids};
391 my $dir_filenameid = $self->get_dir_filenameid();
394 "((SELECT PPathId AS PathId, '..' AS Path
395 FROM brestore_pathhierarchy
396 WHERE PathId = $pathid)
398 (SELECT $pathid AS PathId, '.' AS Path))";
401 SELECT tmp.PathId, tmp.Path, LStat, JobId
402 FROM $sq1 AS tmp LEFT JOIN ( -- get attributes if any
403 SELECT File1.PathId, File1.JobId, File1.LStat FROM File AS File1
404 WHERE File1.FilenameId = $dir_filenameid
405 AND File1.JobId IN ($jobclause)) AS listfile1
406 ON (tmp.PathId = listfile1.PathId)
407 ORDER BY tmp.Path, JobId DESC
410 my $result = $self->dbh_selectall_arrayref($sq2);
414 foreach my $refrow (@{$result})
416 my $dirid = $refrow->[0];
417 my $dir = $refrow->[1];
418 my $lstat = $refrow->[3];
419 my $jobid = $refrow->[2] || 0;
420 next if ($dirid eq $prev_dir);
421 my @return_array = ($dirid,$dir,$lstat,$jobid);
422 push @return_list,(\@return_array);
426 return \@return_list;
429 # Let's retrieve the list of the visible dirs in this dir ...
430 # First, I need the empty filenameid to locate efficiently
431 # the dirs in the file table
432 sub get_dir_filenameid
435 if ($self->{dir_filenameid}) {
436 return $self->{dir_filenameid};
438 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
439 my $sth = $self->dbh_prepare($query);
441 my $result = $sth->fetchrow_arrayref();
443 return $self->{dir_filenameid} = $result->[0];
446 # list all directories in a directory, accross curjobids
447 # return ($dirid,$dir_basename,$lstat,$jobid)
452 return undef unless ($self->{curjobids});
454 my $pathid = $self->{cwdid};
455 my $jobclause = $self->{curjobids};
458 if ($self->{pattern}) {
459 $filter = " AND Path2.Path $self->{sql}->{MATCH} $self->{pattern} ";
462 # Let's retrieve the list of the visible dirs in this dir ...
463 # First, I need the empty filenameid to locate efficiently
464 # the dirs in the file table
465 my $dir_filenameid = $self->get_dir_filenameid();
467 # Then we get all the dir entries from File ...
469 SELECT PathId, Path, JobId, LStat FROM (
471 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
472 listfile1.JobId, listfile1.LStat
474 SELECT DISTINCT brestore_pathhierarchy1.PathId
475 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
477 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
478 JOIN brestore_pathvisibility AS brestore_pathvisibility1
479 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
480 WHERE brestore_pathhierarchy1.PPathId = $pathid
481 AND brestore_pathvisibility1.jobid IN ($jobclause)
484 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
486 LEFT JOIN ( -- get attributes if any
487 SELECT File1.PathId, File1.JobId, File1.LStat FROM File AS File1
488 WHERE File1.FilenameId = $dir_filenameid
489 AND File1.JobId IN ($jobclause)) AS listfile1
490 ON (listpath1.PathId = listfile1.PathId)
491 ) AS A ORDER BY 2,3 DESC LIMIT $self->{limit} OFFSET $self->{offset}
493 # print STDERR $query;
494 my $sth=$self->dbh_prepare($query);
496 my $result = $sth->fetchall_arrayref();
499 foreach my $refrow (@{$result})
501 my $dirid = $refrow->[0];
502 my $dir = $refrow->[1];
503 my $lstat = $refrow->[3];
504 my $jobid = $refrow->[2] || 0;
505 next if ($dirid eq $prev_dir);
506 # We have to clean up this dirname ... we only want it's 'basename'
510 my @temp = split ('/',$dir);
511 $return_value = pop @temp;
517 my @return_array = ($dirid,$return_value,$lstat,$jobid);
518 push @return_list,(\@return_array);
521 $self->debug(\@return_list);
522 return \@return_list;
525 # TODO : we want be able to restore files from a bad ended backup
526 # we have JobStatus IN ('T', 'A', 'E') and we must
528 # Data acces subs from here. Interaction with SGBD and caching
530 # This sub retrieves the list of jobs corresponding to the jobs selected in the
531 # GUI and stores them in @CurrentJobIds.
532 # date must be quoted
533 sub set_job_ids_for_date
535 my ($self, $client, $date)=@_;
537 if (!$client or !$date) {
540 my $filter = $self->get_client_filter();
541 # The algorithm : for a client, we get all the backups for each
542 # fileset, in reverse order Then, for each fileset, we store the 'good'
543 # incrementals and differentials until we have found a full so it goes
544 # like this : store all incrementals until we have found a differential
545 # or a full, then find the full
547 SELECT JobId, FileSet, Level, JobStatus
549 JOIN FileSet USING (FileSetId)
550 JOIN Client USING (ClientId) $filter
551 WHERE EndTime <= $date
552 AND Client.Name = '$client'
554 AND JobStatus IN ('T')
555 ORDER BY FileSet, JobTDate DESC";
558 my $result = $self->dbh_selectall_arrayref($query);
560 foreach my $refrow (@$result)
562 my $jobid = $refrow->[0];
563 my $fileset = $refrow->[1];
564 my $level = $refrow->[2];
566 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
568 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
572 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
573 push @CurrentJobIds,($jobid);
575 elsif ($level eq 'D')
577 next if $progress{$fileset} eq 'D'; # We allready have a differential
578 push @CurrentJobIds,($jobid);
580 elsif ($level eq 'F')
582 push @CurrentJobIds,($jobid);
585 my $status = $refrow->[3] ;
586 if ($status eq 'T') { # good end of job
587 $progress{$fileset} = $level;
591 return @CurrentJobIds;
594 sub dbh_selectrow_arrayref
596 my ($self, $query) = @_;
597 $self->debug($query, up => 1);
598 return $self->{dbh}->selectrow_arrayref($query);
601 # Returns list of versions of a file that could be restored
602 # returns an array of
603 # (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
604 # there will be only one jobid in the array of jobids...
605 sub get_all_file_versions
607 my ($self,$pathid,$fileid,$client,$see_all,$see_copies)=@_;
609 defined $see_all or $see_all=0;
610 my $backup_type=" AND Job.Type = 'B' ";
612 $backup_type=" AND Job.Type IN ('C', 'B') ";
618 "SELECT File.JobId, File.FileId, File.LStat,
619 File.Md5, Media.VolumeName, Media.InChanger
620 FROM File, Job, Client, JobMedia, Media
621 WHERE File.FilenameId = $fileid
622 AND File.PathId=$pathid
623 AND File.JobId = Job.JobId
624 AND Job.ClientId = Client.ClientId
625 AND Job.JobId = JobMedia.JobId
626 AND File.FileIndex >= JobMedia.FirstIndex
627 AND File.FileIndex <= JobMedia.LastIndex
628 AND JobMedia.MediaId = Media.MediaId
629 AND Client.Name = '$client'
633 $self->debug($query);
634 my $result = $self->dbh_selectall_arrayref($query);
636 foreach my $refrow (@$result)
638 my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
639 my @attribs = parse_lstat($lstat);
640 my $mtime = array_attrib('st_mtime',\@attribs);
641 my $size = array_attrib('st_size',\@attribs);
643 my @list = ($pathid,$fileid,$jobid,
644 $fid, $mtime, $size, $inchanger,
646 push @versions, (\@list);
649 # We have the list of all versions of this file.
650 # We'll sort it by mtime desc, size, md5, inchanger desc, FileId
651 # the rest of the algorithm will be simpler
652 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
653 @versions = sort { $b->[4] <=> $a->[4]
654 || $a->[5] <=> $b->[5]
655 || $a->[7] cmp $a->[7]
656 || $b->[6] <=> $a->[6]} @versions;
659 my %allready_seen_by_mtime;
660 my %allready_seen_by_md5;
661 # Now we should create a new array with only the interesting records
662 foreach my $ref (@versions)
666 # The file has a md5. We compare his md5 to other known md5...
667 # We take size into account. It may happen that 2 files
668 # have the same md5sum and are different. size is a supplementary
671 # If we allready have a (better) version
672 next if ( (not $see_all)
673 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
675 # we never met this one before...
676 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
678 # Even if it has a md5, we should also work with mtimes
679 # We allready have a (better) version
680 next if ( (not $see_all)
681 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
682 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
684 # We reached there. The file hasn't been seen.
685 push @good_versions,($ref);
688 # To be nice with the user, we re-sort good_versions by
689 # inchanger desc, mtime desc
690 @good_versions = sort { $b->[4] <=> $a->[4]
691 || $b->[2] <=> $a->[2]} @good_versions;
693 return \@good_versions;
696 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
697 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
698 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
699 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
700 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
701 'data_stream' => 15);;
704 my ($attrib,$ref_attrib)=@_;
705 return $ref_attrib->[$attrib_name_id{$attrib}];
709 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
711 my ($file, $attrib)=@_;
713 if (defined $attrib_name_id{$attrib}) {
715 my @d = split(' ', $file->[3]) ; # TODO : cache this
717 return from_base64($d[$attrib_name_id{$attrib}]);
719 } elsif ($attrib eq 'jobid') {
723 } elsif ($attrib eq 'name') {
728 die "Attribute not known : $attrib.\n";
734 my ($lstat,$attrib)=@_;
735 if ($lstat and defined $attrib_name_id{$attrib})
737 my @d = split(' ', $lstat) ; # TODO : cache this
738 return from_base64($d[$attrib_name_id{$attrib}]);
745 # Base 64 functions, directly from recover.pl.
747 # Karl Hakimian <hakimian@aha.com>
748 # This section is also under GPL v2 or later.
755 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
756 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
757 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
758 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
759 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
761 @base64_map = (0) x 128;
763 for (my $i=0; $i<64; $i++) {
764 $base64_map[ord($base64_digits[$i])] = $i;
779 if (substr($where, 0, 1) eq '-') {
781 $where = substr($where, 1);
784 while ($where ne '') {
786 my $d = substr($where, 0, 1);
787 $val += $base64_map[ord(substr($where, 0, 1))];
788 $where = substr($where, 1);
796 my @attribs = split(' ',$lstat);
797 foreach my $element (@attribs)
799 $element = from_base64($element);
805 # get jobids that the current user can view (ACL)
808 my ($self, @jobid) = @_;
809 my $filter = $self->get_client_filter();
811 my $jobids = $self->dbh_join(@jobid);
814 FROM Job JOIN Client USING (ClientId) $filter
815 WHERE Jobid IN ($jobids)";
816 my $res = $self->dbh_selectall_arrayref($q);
817 @jobid = map { $_->[0] } @$res;
822 ################################################################
827 use POSIX qw/strftime/;
830 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
833 my $bvfs = new Bvfs(info => $conf);
836 my $action = CGI::param('action') || '';
838 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
839 'limit', 'offset', 'client', 'qpattern');
841 if ($action eq 'batch') {
842 $bvfs->update_cache();
846 # All these functions are returning JSON compatible data
847 # for javascript parsing
849 if ($action eq 'list_client') { # list all client [ ['c1'],['c2']..]
850 print CGI::header('application/x-javascript');
852 my $filter = $bvfs->get_client_filter();
853 my $q = "SELECT Name FROM Client $filter";
854 my $ret = $bvfs->dbh_selectall_arrayref($q);
857 print join(',', map { "['$_->[0]']" } @$ret);
861 } elsif ($action eq 'list_job') {
862 # list jobs for a client [[jobid,endtime,'desc'],..]
864 print CGI::header('application/x-javascript');
866 my $filter = $bvfs->get_client_filter();
868 SELECT Job.JobId,Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus
869 FROM Job JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) $filter
870 WHERE Client.Name = '$args->{client}'
872 AND JobStatus IN ('f', 'T')
873 ORDER BY EndTime desc";
874 my $result = $bvfs->dbh_selectall_arrayref($query);
878 print join(',', map {
879 "[$_->[0], '$_->[1]', '$_->[1] $_->[2] $_->[3] ($_->[4]) $_->[0]']"
884 } elsif ($action eq 'list_storage') { # TODO: use .storage here
885 print CGI::header('application/x-javascript');
887 my $q="SELECT Name FROM Storage";
888 my $lst = $bvfs->dbh_selectall_arrayref($q);
890 print join(',', map { "[ '$_->[0]' ]" } @$lst);
895 sub fill_table_for_restore
899 # in "force" mode, we need the FileId to compute media list
900 my $FileId = CGI::param('force')?",FileId":"";
902 my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
903 # can get dirid=("10,11", 10, 11)
904 my @dirid = grep { /^\d+$/ } map { split(/,/) } CGI::param('dirid') ;
905 my $inclause = join(',', @jobid);
911 "(SELECT JobId, FileIndex, FilenameId, PathId $FileId
912 FROM File WHERE FileId IN ($fileid))";
915 foreach my $dirid (@dirid) {
916 my $p = $bvfs->get_path($dirid);
917 if ($p =~ m!^[a-z0-9/\-_\s,.;:*={}()\[\]\!?]+$!i) {
919 (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId $FileId
920 FROM Path JOIN File USING (PathId)
921 WHERE Path.Path LIKE '$p%'
922 AND File.JobId IN ($inclause))";
925 # using this is not good because the sql engine doesn't know
926 # what LIKE will use. It will be better to get Path% in perl
927 # but it doesn't work with accents... :(
930 (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId $FileId
931 FROM Path JOIN File USING (PathId)
933 (SELECT ". $bvfs->dbh_strcat('Path',"'\%'") ." FROM Path
934 WHERE PathId = $dirid
936 AND File.JobId IN ($inclause))";
940 return unless scalar(@union);
942 my $u = join(" UNION ", @union);
944 $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp AS $u");
945 # TODO: remove FilenameId et PathId
947 # now we have to choose the file with the max(jobid)
948 # for each file of btemp
949 if ($bvfs->dbh_is_mysql()) {
950 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
951 SELECT max(JobId) as JobId, FileIndex $FileId
953 GROUP BY PathId, FilenameId
956 } else { # postgresql have distinct with more than one criteria
957 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
958 SELECT JobId, FileIndex $FileId
960 SELECT DISTINCT ON (PathId, FilenameId) JobId, FileIndex $FileId
962 ORDER BY PathId, FilenameId, JobId DESC
971 sub get_media_list_with_dir
975 SELECT DISTINCT VolumeName, Enabled, InChanger
977 ( -- Get all media from this job
978 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
979 VolumeName, Enabled, Inchanger
980 FROM JobMedia JOIN Media USING (MediaId)
981 WHERE JobId IN (SELECT DISTINCT JobId FROM $table)
982 GROUP BY VolumeName,Enabled,InChanger
984 WHERE $table.FileIndex >= allmedia.FirstIndex
985 AND $table.FileIndex <= allmedia.LastIndex
987 my $lst = $bvfs->dbh_selectall_arrayref($q);
993 my ($jobid, $fileid) = @_;
995 SELECT DISTINCT VolumeName, Enabled, InChanger
997 ( -- Get all media from this job
998 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
999 VolumeName, Enabled, Inchanger
1000 FROM JobMedia JOIN Media USING (MediaId)
1001 WHERE JobId IN ($jobid)
1002 GROUP BY VolumeName,Enabled,InChanger
1004 WHERE File.FileId IN ($fileid)
1005 AND File.FileIndex >= allmedia.FirstIndex
1006 AND File.FileIndex <= allmedia.LastIndex
1008 my $lst = $bvfs->dbh_selectall_arrayref($q);
1012 # get jobid param and apply user filter
1013 my @jobid = $bvfs->get_jobids(grep { /^\d+(,\d+)*$/ } CGI::param('jobid'));
1015 # get jobid from date arg
1016 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
1017 @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});
1020 $bvfs->set_curjobids(@jobid);
1021 $bvfs->set_limits($args->{offset}, $args->{limit});
1023 if (!scalar(@jobid)) {
1027 if (CGI::param('init')) { # used when choosing a job
1028 $bvfs->update_brestore_table(@jobid);
1031 my $pathid = CGI::param('node') || CGI::param('pathid') || '';
1032 my $path = CGI::param('path');
1034 if ($pathid =~ /^(\d+)$/) {
1037 $pathid = $bvfs->get_pathid($path);
1039 $pathid = $bvfs->get_root();
1041 $bvfs->ch_dir($pathid);
1043 #print STDERR "pathid=$pathid\n";
1045 # permit to use a regex filter
1046 if ($args->{qpattern}) {
1047 $bvfs->set_pattern($args->{qpattern});
1050 if ($action eq 'restore') {
1052 # TODO: pouvoir choisir le replace et le jobname
1053 my $arg = $bvfs->get_form(qw/client storage regexwhere where/);
1055 if (!$arg->{client}) {
1056 print "ERROR: missing client\n";
1060 my $table = fill_table_for_restore(@jobid);
1065 my $bconsole = $bvfs->get_bconsole();
1066 # TODO: pouvoir choisir le replace et le jobname
1067 my $jobid = $bconsole->run(client => $arg->{client},
1068 storage => $arg->{storage},
1069 where => $arg->{where},
1070 regexwhere=> $arg->{regexwhere},
1074 $bvfs->dbh_do("DROP TABLE $table");
1077 print CGI::header('text/html');
1078 $bvfs->display_begin();
1079 $bvfs->error("Can't start your job:<br/>" . $bconsole->before());
1080 $bvfs->display_end();
1084 print CGI::redirect("bweb.pl?action=dsp_cur_job;jobid=$jobid") ;
1098 print CGI::header('application/x-javascript');
1101 if ($action eq 'list_files_dirs') {
1102 # fileid, filenameid, pathid, jobid, name, size, mtime
1103 my $jids = join(",", @jobid);
1105 my $files = $bvfs->ls_special_dirs();
1106 # return ($dirid,$dir_basename,$lstat,$jobid)
1109 map { my @p=Bvfs::parse_lstat($_->[3]);
1115 "'" . escape_quote($_->[1]) . "'", # name
1116 "'" . $p[7] . "'", # size
1117 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11]||0)) . "'") .
1120 print "," if (@$files);
1122 $files = $bvfs->ls_dirs();
1123 # return ($dirid,$dir_basename,$lstat,$jobid)
1125 map { my @p=Bvfs::parse_lstat($_->[3]);
1131 "'" . escape_quote($_->[1]) . "'", # name
1132 "'" . $p[7] . "'", # size
1133 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11]||0)) . "'") .
1137 print "," if (@$files);
1139 $files = $bvfs->ls_files();
1141 map { my @p=Bvfs::parse_lstat($_->[3]);
1147 "'" . escape_quote($_->[2]) . "'",
1149 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1154 } elsif ($action eq 'list_files') {
1155 print "[[0,0,0,0,'.',4096,'1970-01-01 00:00:00'],";
1156 my $files = $bvfs->ls_files();
1157 # [ 1, 2, 3, "Bill", 10, '2007-01-01 00:00:00'],
1158 # File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
1161 map { my @p=Bvfs::parse_lstat($_->[3]);
1167 "'" . escape_quote($_->[2]) . "'",
1169 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1174 } elsif ($action eq 'list_dirs') {
1177 my $dirs = $bvfs->ls_dirs();
1178 # return ($dirid,$dir_basename,$lstat,$jobid)
1181 map { "{ 'jobid': '$bvfs->{curjobids}', 'id': '$_->[0]'," .
1182 "'text': '" . escape_quote($_->[1]) . "', 'cls':'folder'}" }
1186 } elsif ($action eq 'list_versions') {
1188 my $vafv = CGI::param('vafv') || 'false'; # view all file versions
1189 $vafv = ($vafv eq 'false')?0:1;
1191 my $vcopies = CGI::param('vcopies') || 'false'; # view copies file versions
1192 $vcopies = ($vcopies eq 'false')?0:1;
1196 #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
1197 my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, $vafv, $vcopies);
1199 map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5],'" . strftime('%Y-%m-%d %H:%m:%S', localtime($_->[4])) . "']" }
1203 # this action is used when the restore box appear, we can display
1204 # the media list that will be needed for restore
1205 } elsif ($action eq 'get_media') {
1206 my ($jobid, $fileid, $table);
1209 # in this mode, we compute the result to get all needed media
1210 # print STDERR "force=", CGI::param('force'), "\n";
1211 if (CGI::param('force')) {
1212 $table = fill_table_for_restore(@jobid);
1216 # mysql is very slow without this index...
1217 if ($bvfs->dbh_is_mysql()) {
1218 $bvfs->dbh_do("CREATE INDEX idx_$table ON $table (JobId)");
1220 $lst = get_media_list_with_dir($table);
1222 $jobid = join(',', @jobid);
1223 $fileid = join(',', grep { /^\d+(,\d+)*$/ } CGI::param('fileid'));
1224 $lst = get_media_list($jobid, $fileid);
1229 print join(',', map { "['$_->[0]',$_->[1],$_->[2]]" } @$lst);
1234 $bvfs->dbh_do("DROP TABLE $table");
1241 CREATE VIEW files AS
1242 SELECT path || name AS name,pathid,filenameid,fileid,jobid
1243 FROM File JOIN FileName USING (FilenameId) JOIN Path USING (PathId);
1245 SELECT 'drop table ' || tablename || ';'
1246 FROM pg_tables WHERE tablename ~ '^b[0-9]';