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};
457 # Let's retrieve the list of the visible dirs in this dir ...
458 # First, I need the empty filenameid to locate efficiently
459 # the dirs in the file table
460 my $dir_filenameid = $self->get_dir_filenameid();
462 # Then we get all the dir entries from File ...
464 SELECT PathId, Path, JobId, Lstat FROM (
466 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
467 listfile1.JobId, listfile1.Lstat
469 SELECT DISTINCT brestore_pathhierarchy1.PathId
470 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
472 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
473 JOIN brestore_pathvisibility AS brestore_pathvisibility1
474 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
475 WHERE brestore_pathhierarchy1.PPathId = $pathid
476 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
477 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
479 LEFT JOIN ( -- get attributes if any
480 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
481 WHERE File1.FilenameId = $dir_filenameid
482 AND File1.JobId IN ($jobclause)) AS listfile1
483 ON (listpath1.PathId = listfile1.PathId)
484 ) AS A ORDER BY 2,3 DESC LIMIT $self->{limit} OFFSET $self->{offset}
486 # print STDERR $query;
487 my $sth=$self->dbh_prepare($query);
489 my $result = $sth->fetchall_arrayref();
492 foreach my $refrow (@{$result})
494 my $dirid = $refrow->[0];
495 my $dir = $refrow->[1];
496 my $lstat = $refrow->[3];
497 my $jobid = $refrow->[2] || 0;
498 next if ($dirid eq $prev_dir);
499 # We have to clean up this dirname ... we only want it's 'basename'
503 my @temp = split ('/',$dir);
504 $return_value = pop @temp;
510 my @return_array = ($dirid,$return_value,$lstat,$jobid);
511 push @return_list,(\@return_array);
514 $self->debug(\@return_list);
515 return \@return_list;
518 # TODO : we want be able to restore files from a bad ended backup
519 # we have JobStatus IN ('T', 'A', 'E') and we must
521 # Data acces subs from here. Interaction with SGBD and caching
523 # This sub retrieves the list of jobs corresponding to the jobs selected in the
524 # GUI and stores them in @CurrentJobIds.
525 # date must be quoted
526 sub set_job_ids_for_date
528 my ($self, $client, $date)=@_;
530 if (!$client or !$date) {
533 my $filter = $self->get_client_filter();
534 # The algorithm : for a client, we get all the backups for each
535 # fileset, in reverse order Then, for each fileset, we store the 'good'
536 # incrementals and differentials until we have found a full so it goes
537 # like this : store all incrementals until we have found a differential
538 # or a full, then find the full
540 SELECT JobId, FileSet, Level, JobStatus
542 JOIN FileSet USING (FileSetId)
543 JOIN Client USING (ClientId) $filter
544 WHERE EndTime <= $date
545 AND Client.Name = '$client'
547 AND JobStatus IN ('T')
548 ORDER BY FileSet, JobTDate DESC";
551 my $result = $self->dbh_selectall_arrayref($query);
553 foreach my $refrow (@$result)
555 my $jobid = $refrow->[0];
556 my $fileset = $refrow->[1];
557 my $level = $refrow->[2];
559 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
561 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
565 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
566 push @CurrentJobIds,($jobid);
568 elsif ($level eq 'D')
570 next if $progress{$fileset} eq 'D'; # We allready have a differential
571 push @CurrentJobIds,($jobid);
573 elsif ($level eq 'F')
575 push @CurrentJobIds,($jobid);
578 my $status = $refrow->[3] ;
579 if ($status eq 'T') { # good end of job
580 $progress{$fileset} = $level;
584 return @CurrentJobIds;
587 sub dbh_selectrow_arrayref
589 my ($self, $query) = @_;
590 $self->debug($query, up => 1);
591 return $self->{dbh}->selectrow_arrayref($query);
594 # Returns list of versions of a file that could be restored
595 # returns an array of
596 # (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
597 # there will be only one jobid in the array of jobids...
598 sub get_all_file_versions
600 my ($self,$pathid,$fileid,$client,$see_all,$see_copies)=@_;
602 defined $see_all or $see_all=0;
603 my $backup_type=" AND Job.Type = 'B' ";
605 $backup_type=" AND Job.Type IN ('C', 'B') ";
611 "SELECT File.JobId, File.FileId, File.Lstat,
612 File.Md5, Media.VolumeName, Media.InChanger
613 FROM File, Job, Client, JobMedia, Media
614 WHERE File.FilenameId = $fileid
615 AND File.PathId=$pathid
616 AND File.JobId = Job.JobId
617 AND Job.ClientId = Client.ClientId
618 AND Job.JobId = JobMedia.JobId
619 AND File.FileIndex >= JobMedia.FirstIndex
620 AND File.FileIndex <= JobMedia.LastIndex
621 AND JobMedia.MediaId = Media.MediaId
622 AND Client.Name = '$client'
626 $self->debug($query);
627 my $result = $self->dbh_selectall_arrayref($query);
629 foreach my $refrow (@$result)
631 my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
632 my @attribs = parse_lstat($lstat);
633 my $mtime = array_attrib('st_mtime',\@attribs);
634 my $size = array_attrib('st_size',\@attribs);
636 my @list = ($pathid,$fileid,$jobid,
637 $fid, $mtime, $size, $inchanger,
639 push @versions, (\@list);
642 # We have the list of all versions of this file.
643 # We'll sort it by mtime desc, size, md5, inchanger desc, FileId
644 # the rest of the algorithm will be simpler
645 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
646 @versions = sort { $b->[4] <=> $a->[4]
647 || $a->[5] <=> $b->[5]
648 || $a->[7] cmp $a->[7]
649 || $b->[6] <=> $a->[6]} @versions;
652 my %allready_seen_by_mtime;
653 my %allready_seen_by_md5;
654 # Now we should create a new array with only the interesting records
655 foreach my $ref (@versions)
659 # The file has a md5. We compare his md5 to other known md5...
660 # We take size into account. It may happen that 2 files
661 # have the same md5sum and are different. size is a supplementary
664 # If we allready have a (better) version
665 next if ( (not $see_all)
666 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
668 # we never met this one before...
669 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
671 # Even if it has a md5, we should also work with mtimes
672 # We allready have a (better) version
673 next if ( (not $see_all)
674 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
675 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
677 # We reached there. The file hasn't been seen.
678 push @good_versions,($ref);
681 # To be nice with the user, we re-sort good_versions by
682 # inchanger desc, mtime desc
683 @good_versions = sort { $b->[4] <=> $a->[4]
684 || $b->[2] <=> $a->[2]} @good_versions;
686 return \@good_versions;
689 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
690 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
691 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
692 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
693 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
694 'data_stream' => 15);;
697 my ($attrib,$ref_attrib)=@_;
698 return $ref_attrib->[$attrib_name_id{$attrib}];
702 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
704 my ($file, $attrib)=@_;
706 if (defined $attrib_name_id{$attrib}) {
708 my @d = split(' ', $file->[3]) ; # TODO : cache this
710 return from_base64($d[$attrib_name_id{$attrib}]);
712 } elsif ($attrib eq 'jobid') {
716 } elsif ($attrib eq 'name') {
721 die "Attribute not known : $attrib.\n";
727 my ($lstat,$attrib)=@_;
728 if ($lstat and defined $attrib_name_id{$attrib})
730 my @d = split(' ', $lstat) ; # TODO : cache this
731 return from_base64($d[$attrib_name_id{$attrib}]);
738 # Base 64 functions, directly from recover.pl.
740 # Karl Hakimian <hakimian@aha.com>
741 # This section is also under GPL v2 or later.
748 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
749 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
750 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
751 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
752 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
754 @base64_map = (0) x 128;
756 for (my $i=0; $i<64; $i++) {
757 $base64_map[ord($base64_digits[$i])] = $i;
772 if (substr($where, 0, 1) eq '-') {
774 $where = substr($where, 1);
777 while ($where ne '') {
779 my $d = substr($where, 0, 1);
780 $val += $base64_map[ord(substr($where, 0, 1))];
781 $where = substr($where, 1);
789 my @attribs = split(' ',$lstat);
790 foreach my $element (@attribs)
792 $element = from_base64($element);
798 # get jobids that the current user can view (ACL)
801 my ($self, @jobid) = @_;
802 my $filter = $self->get_client_filter();
804 my $jobids = $self->dbh_join(@jobid);
807 FROM Job JOIN Client USING (ClientId) $filter
808 WHERE Jobid IN ($jobids)";
809 my $res = $self->dbh_selectall_arrayref($q);
810 @jobid = map { $_->[0] } @$res;
815 ################################################################
820 use POSIX qw/strftime/;
823 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
826 my $bvfs = new Bvfs(info => $conf);
829 my $action = CGI::param('action') || '';
831 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
832 'limit', 'offset', 'client', 'qpattern');
834 if ($action eq 'batch') {
835 $bvfs->update_cache();
839 # All these functions are returning JSON compatible data
840 # for javascript parsing
842 if ($action eq 'list_client') { # list all client [ ['c1'],['c2']..]
843 print CGI::header('application/x-javascript');
845 my $filter = $bvfs->get_client_filter();
846 my $q = "SELECT Name FROM Client $filter";
847 my $ret = $bvfs->dbh_selectall_arrayref($q);
850 print join(',', map { "['$_->[0]']" } @$ret);
854 } elsif ($action eq 'list_job') {
855 # list jobs for a client [[jobid,endtime,'desc'],..]
857 print CGI::header('application/x-javascript');
859 my $filter = $bvfs->get_client_filter();
861 SELECT Job.JobId,Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus
862 FROM Job JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) $filter
863 WHERE Client.Name = '$args->{client}'
865 AND JobStatus IN ('f', 'T')
866 ORDER BY EndTime desc";
867 my $result = $bvfs->dbh_selectall_arrayref($query);
871 print join(',', map {
872 "[$_->[0], '$_->[1]', '$_->[1] $_->[2] $_->[3] ($_->[4]) $_->[0]']"
877 } elsif ($action eq 'list_storage') { # TODO: use .storage here
878 print CGI::header('application/x-javascript');
880 my $q="SELECT Name FROM Storage";
881 my $lst = $bvfs->dbh_selectall_arrayref($q);
883 print join(',', map { "[ '$_->[0]' ]" } @$lst);
888 sub fill_table_for_restore
892 # in "force" mode, we need the FileId to compute media list
893 my $FileId = CGI::param('force')?",FileId":"";
895 my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
896 # can get dirid=("10,11", 10, 11)
897 my @dirid = grep { /^\d+$/ } map { split(/,/) } CGI::param('dirid') ;
898 my $inclause = join(',', @jobid);
904 "(SELECT JobId, FileIndex, FilenameId, PathId $FileId
905 FROM File WHERE FileId IN ($fileid))";
908 # using this is not good because the sql engine doesn't know
909 # what LIKE will use. It will be better to get Path% in perl
910 # but it doesn't work with accents... :(
911 foreach my $dirid (@dirid) {
913 (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId $FileId
914 FROM Path JOIN File USING (PathId)
916 (SELECT ". $bvfs->dbh_strcat('Path',"'\%'") ." FROM Path
917 WHERE PathId = $dirid
919 AND File.JobId IN ($inclause))";
922 return unless scalar(@union);
924 my $u = join(" UNION ", @union);
926 $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp AS $u");
927 # TODO: remove FilenameId et PathId
929 # now we have to choose the file with the max(jobid)
930 # for each file of btemp
931 if ($bvfs->dbh_is_mysql()) {
932 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
933 SELECT max(JobId) as JobId, FileIndex $FileId
935 GROUP BY PathId, FilenameId
938 } else { # postgresql have distinct with more than one criteria
939 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
940 SELECT JobId, FileIndex $FileId
942 SELECT DISTINCT ON (PathId, FilenameId) JobId, FileIndex $FileId
944 ORDER BY PathId, FilenameId, JobId DESC
953 sub get_media_list_with_dir
957 SELECT DISTINCT VolumeName, Enabled, InChanger
959 ( -- Get all media from this job
960 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
961 VolumeName, Enabled, Inchanger
962 FROM JobMedia JOIN Media USING (MediaId)
963 WHERE JobId IN (SELECT DISTINCT JobId FROM $table)
964 GROUP BY VolumeName,Enabled,InChanger
966 WHERE $table.FileIndex >= allmedia.FirstIndex
967 AND $table.FileIndex <= allmedia.LastIndex
969 my $lst = $bvfs->dbh_selectall_arrayref($q);
975 my ($jobid, $fileid) = @_;
977 SELECT DISTINCT VolumeName, Enabled, InChanger
979 ( -- Get all media from this job
980 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
981 VolumeName, Enabled, Inchanger
982 FROM JobMedia JOIN Media USING (MediaId)
983 WHERE JobId IN ($jobid)
984 GROUP BY VolumeName,Enabled,InChanger
986 WHERE File.FileId IN ($fileid)
987 AND File.FileIndex >= allmedia.FirstIndex
988 AND File.FileIndex <= allmedia.LastIndex
990 my $lst = $bvfs->dbh_selectall_arrayref($q);
994 # get jobid param and apply user filter
995 my @jobid = $bvfs->get_jobids(grep { /^\d+(,\d+)*$/ } CGI::param('jobid'));
997 # get jobid from date arg
998 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
999 @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});
1002 $bvfs->set_curjobids(@jobid);
1003 $bvfs->set_limits($args->{offset}, $args->{limit});
1005 if (!scalar(@jobid)) {
1009 if (CGI::param('init')) { # used when choosing a job
1010 $bvfs->update_brestore_table(@jobid);
1013 my $pathid = CGI::param('node') || CGI::param('pathid') || '';
1014 my $path = CGI::param('path');
1016 if ($pathid =~ /^(\d+)$/) {
1019 $pathid = $bvfs->get_pathid($path);
1021 $pathid = $bvfs->get_root();
1023 $bvfs->ch_dir($pathid);
1025 #print STDERR "pathid=$pathid\n";
1027 # permit to use a regex filter
1028 if ($args->{qpattern}) {
1029 $bvfs->set_pattern($args->{qpattern});
1032 if ($action eq 'restore') {
1034 # TODO: pouvoir choisir le replace et le jobname
1035 my $arg = $bvfs->get_form(qw/client storage regexwhere where/);
1037 if (!$arg->{client}) {
1038 print "ERROR: missing client\n";
1042 my $table = fill_table_for_restore(@jobid);
1047 my $bconsole = $bvfs->get_bconsole();
1048 # TODO: pouvoir choisir le replace et le jobname
1049 my $jobid = $bconsole->run(client => $arg->{client},
1050 storage => $arg->{storage},
1051 where => $arg->{where},
1052 regexwhere=> $arg->{regexwhere},
1056 $bvfs->dbh_do("DROP TABLE $table");
1059 print CGI::header('text/html');
1060 $bvfs->display_begin();
1061 $bvfs->error("Can't start your job:<br/>" . $bconsole->before());
1062 $bvfs->display_end();
1066 print CGI::redirect("bweb.pl?action=dsp_cur_job;jobid=$jobid") ;
1080 print CGI::header('application/x-javascript');
1083 if ($action eq 'list_files_dirs') {
1084 # fileid, filenameid, pathid, jobid, name, size, mtime
1085 my $jids = join(",", @jobid);
1087 my $files = $bvfs->ls_special_dirs();
1088 # return ($dirid,$dir_basename,$lstat,$jobid)
1091 map { my @p=Bvfs::parse_lstat($_->[3]);
1097 "'" . escape_quote($_->[1]) . "'", # name
1098 "'" . $p[7] . "'", # size
1099 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1103 print "," if (@$files);
1105 $files = $bvfs->ls_dirs();
1106 # return ($dirid,$dir_basename,$lstat,$jobid)
1108 map { my @p=Bvfs::parse_lstat($_->[3]);
1114 "'" . escape_quote($_->[1]) . "'", # name
1115 "'" . $p[7] . "'", # size
1116 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1120 print "," if (@$files);
1122 $files = $bvfs->ls_files();
1124 map { my @p=Bvfs::parse_lstat($_->[3]);
1130 "'" . escape_quote($_->[2]) . "'",
1132 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1137 } elsif ($action eq 'list_files') {
1138 print "[[0,0,0,0,'.',4096,'1970-01-01 00:00:00'],";
1139 my $files = $bvfs->ls_files();
1140 # [ 1, 2, 3, "Bill", 10, '2007-01-01 00:00:00'],
1141 # File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
1144 map { my @p=Bvfs::parse_lstat($_->[3]);
1150 "'" . escape_quote($_->[2]) . "'",
1152 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1157 } elsif ($action eq 'list_dirs') {
1160 my $dirs = $bvfs->ls_dirs();
1161 # return ($dirid,$dir_basename,$lstat,$jobid)
1164 map { "{ 'jobid': '$bvfs->{curjobids}', 'id': '$_->[0]'," .
1165 "'text': '" . escape_quote($_->[1]) . "', 'cls':'folder'}" }
1169 } elsif ($action eq 'list_versions') {
1171 my $vafv = CGI::param('vafv') || 'false'; # view all file versions
1172 $vafv = ($vafv eq 'false')?0:1;
1174 my $vcopies = CGI::param('vcopies') || 'false'; # view copies file versions
1175 $vcopies = ($vcopies eq 'false')?0:1;
1179 #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
1180 my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, $vafv, $vcopies);
1182 map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5],'" . strftime('%Y-%m-%d %H:%m:%S', localtime($_->[4])) . "']" }
1186 # this action is used when the restore box appear, we can display
1187 # the media list that will be needed for restore
1188 } elsif ($action eq 'get_media') {
1189 my ($jobid, $fileid, $table);
1192 # in this mode, we compute the result to get all needed media
1193 # print STDERR "force=", CGI::param('force'), "\n";
1194 if (CGI::param('force')) {
1195 $table = fill_table_for_restore(@jobid);
1199 # mysql is very slow without this index...
1200 if ($bvfs->dbh_is_mysql()) {
1201 $bvfs->dbh_do("CREATE INDEX idx_$table ON $table (JobId)");
1203 $lst = get_media_list_with_dir($table);
1205 $jobid = join(',', @jobid);
1206 $fileid = join(',', grep { /^\d+(,\d+)*$/ } CGI::param('fileid'));
1207 $lst = get_media_list($jobid, $fileid);
1212 print join(',', map { "['$_->[0]',$_->[1],$_->[2]]" } @$lst);
1217 #$bvfs->dbh_do("DROP TABLE $table");
1224 CREATE VIEW files AS
1225 SELECT path || name AS name,pathid,filenameid,fileid,jobid
1226 FROM File JOIN FileName USING (FilenameId) JOIN Path USING (PathId);
1228 SELECT 'drop table ' || tablename || ';'
1229 FROM pg_tables WHERE tablename ~ '^b[0-9]';