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.
16 This program is Free Software; you can redistribute it and/or
17 modify it under the terms of version three of the GNU Affero General Public
18 License as published by the Free Software Foundation and included
21 This program is distributed in the hope that it will be useful, but
22 WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 Affero General Public License for more details.
26 You should have received a copy of the GNU Affero General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
31 Bacula® is a registered trademark of Kern Sibbald.
32 The licensor of Bacula is the Free Software Foundation Europe
33 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
34 Switzerland, email:ftf@fsfeurope.org.
50 return $self->get_pathid('');
53 # change the current directory
56 my ($self, $pathid) = @_;
57 $self->{cwdid} = $pathid;
66 FROM brestore_pathhierarchy
67 WHERE PathId IN ($self->{cwdid}) ";
69 my $all = $self->dbh_selectall_arrayref($query);
70 return unless ($all); # already at root
72 my $dir = join(',', map { $_->[0] } @$all);
78 # return the current PWD
82 return $self->get_path($self->{cwdid});
85 # get the Path from a PathId
88 my ($self, $pathid) = @_;
89 $self->debug("Call with pathid = $pathid");
90 my $query = "SELECT Path FROM Path WHERE PathId = ?";
91 my $sth = $self->dbh_prepare($query);
92 $sth->execute($pathid);
93 my $result = $sth->fetchrow_arrayref();
98 # we are working with these jobids
101 my ($self, @jobids) = @_;
102 $self->{curjobids} = join(',', @jobids);
103 # $self->update_brestore_table(@jobids);
106 # get the PathId from a Path
109 my ($self, $dir) = @_;
111 "SELECT PathId FROM Path WHERE Path = ?";
112 my $sth = $self->dbh_prepare($query);
114 my $result = $sth->fetchrow_arrayref();
122 my ($self, $offset, $limit) = @_;
123 $self->{limit} = $limit || 100;
124 $self->{offset} = $offset || 0;
129 my ($self, $pattern) = @_;
130 $self->{pattern} = $pattern;
133 # fill brestore_xxx tables for speedup
138 $self->{dbh}->begin_work();
140 # getting all Jobs to "cache"
142 SELECT JobId from Job
143 WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid)
144 AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A')
146 my $jobs = $self->dbh_selectall_arrayref($query);
148 $self->update_brestore_table(map { $_->[0] } @$jobs);
150 $self->{dbh}->commit();
151 $self->{dbh}->begin_work(); # we can break here
153 print STDERR "Cleaning path visibility\n";
155 my $nb = $self->dbh_do("
156 DELETE FROM brestore_pathvisibility
158 (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
160 print STDERR "$nb rows affected\n";
161 print STDERR "Cleaning known jobid\n";
163 $nb = $self->dbh_do("
164 DELETE FROM brestore_knownjobid
166 (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
168 print STDERR "$nb rows affected\n";
170 $self->{dbh}->commit();
173 sub update_brestore_table
175 my ($self, @jobs) = @_;
177 $self->debug(\@jobs);
179 foreach my $job (sort {$a <=> $b} @jobs)
181 my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
182 my $retour = $self->dbh_selectrow_arrayref($query);
183 next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
185 print STDERR "Inserting path records for JobId $job\n";
186 $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
187 (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
189 $self->dbh_do($query);
191 # Now we have to do the directory recursion stuff to determine missing visibility
192 # We try to avoid recursion, to be as fast as possible
193 # We also only work on not allready hierarchised directories...
195 print STDERR "Creating missing recursion paths for $job\n";
198 SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
199 JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
200 LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
201 WHERE brestore_pathvisibility.JobId = $job
202 AND brestore_pathhierarchy.PathId IS NULL
205 my $sth = $self->dbh_prepare($query);
207 my $pathid; my $path;
208 $sth->bind_columns(\$pathid,\$path);
212 $self->build_path_hierarchy($path,$pathid);
216 # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
217 # This query gives all parent pathids for a given jobid that aren't stored.
218 # It has to be called until no record is updated ...
220 INSERT INTO brestore_pathvisibility (PathId, JobId) (
223 SELECT DISTINCT h.PPathId AS PathId
224 FROM brestore_pathhierarchy AS h
225 JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
226 WHERE p.JobId=$job) AS a LEFT JOIN
228 FROM brestore_pathvisibility
229 WHERE JobId=$job) AS b ON (a.PathId = b.PathId)
230 WHERE b.PathId IS NULL)";
233 while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
235 print STDERR "Recursively adding $rows_affected records from $job\n";
238 $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
239 $self->dbh_do($query);
243 # compute the parent directory
252 # Root Windows case :
253 if ($path =~ /^[a-z]+:\/$/i)
258 my @tmp = split('/',$path);
259 # We remove the last ...
261 my $tmp = join ('/',@tmp) . '/';
265 sub build_path_hierarchy
267 my ($self, $path,$pathid)=@_;
268 # Does the ppathid exist for this ? we use a memory cache...
269 # In order to avoid the full loop, we consider that if a dir is allready in the
270 # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
273 if (! $self->{cache_ppathid}->{$pathid})
275 my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
276 my $sth2 = $self->{dbh}->prepare_cached($query);
277 $sth2->execute($pathid);
278 # Do we have a result ?
279 if (my $refrow = $sth2->fetchrow_arrayref)
281 $self->{cache_ppathid}->{$pathid}=$refrow->[0];
283 # This dir was in the db ...
284 # It means we can leave, the tree has allready been built for
289 # We have to create the record ...
290 # What's the current p_path ?
291 my $ppath = parent_dir($path);
292 my $ppathid = $self->return_pathid_from_path($ppath);
293 $self->{cache_ppathid}->{$pathid}= $ppathid;
295 $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
296 $sth2 = $self->{dbh}->prepare_cached($query);
297 $sth2->execute($pathid,$ppathid);
303 # It's allready in the cache.
304 # We can leave, no time to waste here, all the parent dirs have allready
312 sub return_pathid_from_path
314 my ($self, $path) = @_;
315 my $query = "SELECT PathId FROM Path WHERE Path = ?";
317 #print STDERR $query,"\n" if $debug;
318 my $sth = $self->{dbh}->prepare_cached($query);
319 $sth->execute($path);
320 my $result =$sth->fetchrow_arrayref();
327 # A bit dirty : we insert into path, and we have to be sure
328 # we aren't deleted by a purge. We still need to insert into path to get
329 # the pathid, because of mysql
330 $query = "INSERT INTO Path (Path) VALUES (?)";
331 #print STDERR $query,"\n" if $debug;
332 $sth = $self->{dbh}->prepare_cached($query);
333 $sth->execute($path);
336 $query = "SELECT PathId FROM Path WHERE Path = ?";
337 #print STDERR $query,"\n" if $debug;
338 $sth = $self->{dbh}->prepare_cached($query);
339 $sth->execute($path);
340 $result = $sth->fetchrow_arrayref();
346 # list all files in a directory, accross curjobids
351 return undef unless ($self->{curjobids});
353 my $inclause = $self->{curjobids};
354 my $inpath = $self->{cwdid};
356 if ($self->{pattern}) {
357 $filter = " AND Filename.Name $self->{sql}->{MATCH} $self->{pattern} ";
361 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
363 SELECT Filename.Name, max(File.FileId) as id
365 WHERE File.FilenameId = Filename.FilenameId
366 AND Filename.Name != ''
367 AND File.PathId = $inpath
368 AND File.JobId IN ($inclause)
370 GROUP BY Filename.Name
371 ORDER BY Filename.Name LIMIT $self->{limit} OFFSET $self->{offset}
373 WHERE File.FileId = listfiles.id";
375 # print STDERR $query;
376 $self->debug($query);
377 my $result = $self->dbh_selectall_arrayref($query);
378 $self->debug($result);
386 return undef unless ($self->{curjobids});
388 my $pathid = $self->{cwdid};
389 my $jobclause = $self->{curjobids};
390 my $dir_filenameid = $self->get_dir_filenameid();
393 "((SELECT PPathId AS PathId, '..' AS Path
394 FROM brestore_pathhierarchy
395 WHERE PathId = $pathid)
397 (SELECT $pathid AS PathId, '.' AS Path))";
400 SELECT tmp.PathId, tmp.Path, LStat, JobId
401 FROM $sq1 AS tmp LEFT JOIN ( -- get attributes if any
402 SELECT File1.PathId, File1.JobId, File1.LStat FROM File AS File1
403 WHERE File1.FilenameId = $dir_filenameid
404 AND File1.JobId IN ($jobclause)) AS listfile1
405 ON (tmp.PathId = listfile1.PathId)
406 ORDER BY tmp.Path, JobId DESC
409 my $result = $self->dbh_selectall_arrayref($sq2);
413 foreach my $refrow (@{$result})
415 my $dirid = $refrow->[0];
416 my $dir = $refrow->[1];
417 my $lstat = $refrow->[3];
418 my $jobid = $refrow->[2] || 0;
419 next if ($dirid eq $prev_dir);
420 my @return_array = ($dirid,$dir,$lstat,$jobid);
421 push @return_list,(\@return_array);
425 return \@return_list;
428 # Let's retrieve the list of the visible dirs in this dir ...
429 # First, I need the empty filenameid to locate efficiently
430 # the dirs in the file table
431 sub get_dir_filenameid
434 if ($self->{dir_filenameid}) {
435 return $self->{dir_filenameid};
437 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
438 my $sth = $self->dbh_prepare($query);
440 my $result = $sth->fetchrow_arrayref();
442 return $self->{dir_filenameid} = $result->[0];
445 # list all directories in a directory, accross curjobids
446 # return ($dirid,$dir_basename,$lstat,$jobid)
451 return undef unless ($self->{curjobids});
453 my $pathid = $self->{cwdid};
454 my $jobclause = $self->{curjobids};
457 if ($self->{pattern}) {
458 $filter = " AND Path2.Path $self->{sql}->{MATCH} $self->{pattern} ";
461 # Let's retrieve the list of the visible dirs in this dir ...
462 # First, I need the empty filenameid to locate efficiently
463 # the dirs in the file table
464 my $dir_filenameid = $self->get_dir_filenameid();
466 # Then we get all the dir entries from File ...
468 SELECT PathId, Path, JobId, LStat FROM (
470 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
471 listfile1.JobId, listfile1.LStat
473 SELECT DISTINCT brestore_pathhierarchy1.PathId
474 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
476 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
477 JOIN brestore_pathvisibility AS brestore_pathvisibility1
478 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
479 WHERE brestore_pathhierarchy1.PPathId = $pathid
480 AND brestore_pathvisibility1.jobid IN ($jobclause)
483 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
485 LEFT JOIN ( -- get attributes if any
486 SELECT File1.PathId, File1.JobId, File1.LStat FROM File AS File1
487 WHERE File1.FilenameId = $dir_filenameid
488 AND File1.JobId IN ($jobclause)) AS listfile1
489 ON (listpath1.PathId = listfile1.PathId)
490 ) AS A ORDER BY 2,3 DESC LIMIT $self->{limit} OFFSET $self->{offset}
492 # print STDERR $query;
493 my $sth=$self->dbh_prepare($query);
495 my $result = $sth->fetchall_arrayref();
498 foreach my $refrow (@{$result})
500 my $dirid = $refrow->[0];
501 my $dir = $refrow->[1];
502 my $lstat = $refrow->[3];
503 my $jobid = $refrow->[2] || 0;
504 next if ($dirid eq $prev_dir);
505 # We have to clean up this dirname ... we only want it's 'basename'
509 my @temp = split ('/',$dir);
510 $return_value = pop @temp;
516 my @return_array = ($dirid,$return_value,$lstat,$jobid);
517 push @return_list,(\@return_array);
520 $self->debug(\@return_list);
521 return \@return_list;
524 # TODO : we want be able to restore files from a bad ended backup
525 # we have JobStatus IN ('T', 'A', 'E') and we must
527 # Data acces subs from here. Interaction with SGBD and caching
529 # This sub retrieves the list of jobs corresponding to the jobs selected in the
530 # GUI and stores them in @CurrentJobIds.
531 # date must be quoted
532 sub set_job_ids_for_date
534 my ($self, $client, $date)=@_;
536 if (!$client or !$date) {
539 my $filter = $self->get_client_filter();
540 # The algorithm : for a client, we get all the backups for each
541 # fileset, in reverse order Then, for each fileset, we store the 'good'
542 # incrementals and differentials until we have found a full so it goes
543 # like this : store all incrementals until we have found a differential
544 # or a full, then find the full
546 SELECT JobId, FileSet, Level, JobStatus
548 JOIN FileSet USING (FileSetId)
549 JOIN Client USING (ClientId) $filter
550 WHERE EndTime <= $date
551 AND Client.Name = '$client'
553 AND JobStatus IN ('T')
554 ORDER BY FileSet, JobTDate DESC";
557 my $result = $self->dbh_selectall_arrayref($query);
559 foreach my $refrow (@$result)
561 my $jobid = $refrow->[0];
562 my $fileset = $refrow->[1];
563 my $level = $refrow->[2];
565 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
567 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
571 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
572 push @CurrentJobIds,($jobid);
574 elsif ($level eq 'D')
576 next if $progress{$fileset} eq 'D'; # We allready have a differential
577 push @CurrentJobIds,($jobid);
579 elsif ($level eq 'F')
581 push @CurrentJobIds,($jobid);
584 my $status = $refrow->[3] ;
585 if ($status eq 'T') { # good end of job
586 $progress{$fileset} = $level;
590 return @CurrentJobIds;
593 sub dbh_selectrow_arrayref
595 my ($self, $query) = @_;
596 $self->debug($query, up => 1);
597 return $self->{dbh}->selectrow_arrayref($query);
600 # Returns list of versions of a file that could be restored
601 # returns an array of
602 # (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
603 # there will be only one jobid in the array of jobids...
604 sub get_all_file_versions
606 my ($self,$pathid,$fileid,$client,$see_all,$see_copies)=@_;
608 defined $see_all or $see_all=0;
609 my $backup_type=" AND Job.Type = 'B' ";
611 $backup_type=" AND Job.Type IN ('C', 'B') ";
617 "SELECT File.JobId, File.FileId, File.LStat,
618 File.Md5, Media.VolumeName, Media.InChanger
619 FROM File, Job, Client, JobMedia, Media
620 WHERE File.FilenameId = $fileid
621 AND File.PathId=$pathid
622 AND File.JobId = Job.JobId
623 AND Job.ClientId = Client.ClientId
624 AND Job.JobId = JobMedia.JobId
625 AND File.FileIndex >= JobMedia.FirstIndex
626 AND File.FileIndex <= JobMedia.LastIndex
627 AND JobMedia.MediaId = Media.MediaId
628 AND Client.Name = '$client'
632 $self->debug($query);
633 my $result = $self->dbh_selectall_arrayref($query);
635 foreach my $refrow (@$result)
637 my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
638 my @attribs = parse_lstat($lstat);
639 my $mtime = array_attrib('st_mtime',\@attribs);
640 my $size = array_attrib('st_size',\@attribs);
642 my @list = ($pathid,$fileid,$jobid,
643 $fid, $mtime, $size, $inchanger,
645 push @versions, (\@list);
648 # We have the list of all versions of this file.
649 # We'll sort it by mtime desc, size, md5, inchanger desc, FileId
650 # the rest of the algorithm will be simpler
651 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
652 @versions = sort { $b->[4] <=> $a->[4]
653 || $a->[5] <=> $b->[5]
654 || $a->[7] cmp $a->[7]
655 || $b->[6] <=> $a->[6]} @versions;
658 my %allready_seen_by_mtime;
659 my %allready_seen_by_md5;
660 # Now we should create a new array with only the interesting records
661 foreach my $ref (@versions)
665 # The file has a md5. We compare his md5 to other known md5...
666 # We take size into account. It may happen that 2 files
667 # have the same md5sum and are different. size is a supplementary
670 # If we allready have a (better) version
671 next if ( (not $see_all)
672 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
674 # we never met this one before...
675 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
677 # Even if it has a md5, we should also work with mtimes
678 # We allready have a (better) version
679 next if ( (not $see_all)
680 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
681 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
683 # We reached there. The file hasn't been seen.
684 push @good_versions,($ref);
687 # To be nice with the user, we re-sort good_versions by
688 # inchanger desc, mtime desc
689 @good_versions = sort { $b->[4] <=> $a->[4]
690 || $b->[2] <=> $a->[2]} @good_versions;
692 return \@good_versions;
695 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
696 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
697 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
698 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
699 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
700 'data_stream' => 15);;
703 my ($attrib,$ref_attrib)=@_;
704 return $ref_attrib->[$attrib_name_id{$attrib}];
708 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
710 my ($file, $attrib)=@_;
712 if (defined $attrib_name_id{$attrib}) {
714 my @d = split(' ', $file->[3]) ; # TODO : cache this
716 return from_base64($d[$attrib_name_id{$attrib}]);
718 } elsif ($attrib eq 'jobid') {
722 } elsif ($attrib eq 'name') {
727 die "Attribute not known : $attrib.\n";
733 my ($lstat,$attrib)=@_;
734 if ($lstat and defined $attrib_name_id{$attrib})
736 my @d = split(' ', $lstat) ; # TODO : cache this
737 return from_base64($d[$attrib_name_id{$attrib}]);
744 # Base 64 functions, directly from recover.pl.
746 # Karl Hakimian <hakimian@aha.com>
747 # This section is also under GPL v2 or later.
754 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
755 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
756 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
757 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
758 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
760 @base64_map = (0) x 128;
762 for (my $i=0; $i<64; $i++) {
763 $base64_map[ord($base64_digits[$i])] = $i;
778 if (substr($where, 0, 1) eq '-') {
780 $where = substr($where, 1);
783 while ($where ne '') {
785 my $d = substr($where, 0, 1);
786 $val += $base64_map[ord(substr($where, 0, 1))];
787 $where = substr($where, 1);
795 my @attribs = split(' ',$lstat);
796 foreach my $element (@attribs)
798 $element = from_base64($element);
804 # get jobids that the current user can view (ACL)
807 my ($self, @jobid) = @_;
808 my $filter = $self->get_client_filter();
810 my $jobids = $self->dbh_join(@jobid);
813 FROM Job JOIN Client USING (ClientId) $filter
814 WHERE Jobid IN ($jobids)";
815 my $res = $self->dbh_selectall_arrayref($q);
816 @jobid = map { $_->[0] } @$res;
821 ################################################################
826 use POSIX qw/strftime/;
829 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
832 my $bvfs = new Bvfs(info => $conf);
835 my $action = CGI::param('action') || '';
837 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
838 'limit', 'offset', 'client', 'qpattern');
840 if ($action eq 'batch') {
841 $bvfs->update_cache();
845 # All these functions are returning JSON compatible data
846 # for javascript parsing
848 if ($action eq 'list_client') { # list all client [ ['c1'],['c2']..]
849 print CGI::header('application/x-javascript');
851 my $filter = $bvfs->get_client_filter();
852 my $q = "SELECT Name FROM Client $filter";
853 my $ret = $bvfs->dbh_selectall_arrayref($q);
856 print join(',', map { "['$_->[0]']" } @$ret);
860 } elsif ($action eq 'list_job') {
861 # list jobs for a client [[jobid,endtime,'desc'],..]
863 print CGI::header('application/x-javascript');
865 my $filter = $bvfs->get_client_filter();
867 SELECT Job.JobId,Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus
868 FROM Job JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) $filter
869 WHERE Client.Name = '$args->{client}'
871 AND JobStatus IN ('f', 'T')
872 ORDER BY EndTime desc";
873 my $result = $bvfs->dbh_selectall_arrayref($query);
877 print join(',', map {
878 "[$_->[0], '$_->[1]', '$_->[1] $_->[2] $_->[3] ($_->[4]) $_->[0]']"
883 } elsif ($action eq 'list_storage') { # TODO: use .storage here
884 print CGI::header('application/x-javascript');
886 my $q="SELECT Name FROM Storage";
887 my $lst = $bvfs->dbh_selectall_arrayref($q);
889 print join(',', map { "[ '$_->[0]' ]" } @$lst);
894 sub fill_table_for_restore
898 # in "force" mode, we need the FileId to compute media list
899 my $FileId = CGI::param('force')?",FileId":"";
901 my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
902 # can get dirid=("10,11", 10, 11)
903 my @dirid = grep { /^\d+$/ } map { split(/,/) } CGI::param('dirid') ;
904 my $inclause = join(',', @jobid);
910 "(SELECT JobId, FileIndex, FilenameId, PathId $FileId
911 FROM File WHERE FileId IN ($fileid))";
914 foreach my $dirid (@dirid) {
915 my $p = $bvfs->get_path($dirid);
916 $p =~ s/([%_\\])/\\$1/g; # Escape % and _ for LIKE search
917 $p = $bvfs->dbh_quote($p);
919 (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId $FileId
920 FROM Path JOIN File USING (PathId)
921 WHERE Path.Path LIKE " . $bvfs->dbh_strcat($p, "'%'") . "
922 AND File.JobId IN ($inclause))";
925 return unless scalar(@union);
927 my $u = join(" UNION ", @union);
929 $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp AS $u");
930 # TODO: remove FilenameId et PathId
932 # now we have to choose the file with the max(jobid)
933 # for each file of btemp
934 if ($bvfs->dbh_is_mysql()) {
935 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
936 SELECT max(JobId) as JobId, FileIndex $FileId
938 GROUP BY PathId, FilenameId
941 } else { # postgresql have distinct with more than one criteria
942 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
943 SELECT JobId, FileIndex $FileId
945 SELECT DISTINCT ON (PathId, FilenameId) JobId, FileIndex $FileId
947 ORDER BY PathId, FilenameId, JobId DESC
956 sub get_media_list_with_dir
960 SELECT DISTINCT VolumeName, Enabled, InChanger
962 ( -- Get all media from this job
963 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
964 VolumeName, Enabled, Inchanger
965 FROM JobMedia JOIN Media USING (MediaId)
966 WHERE JobId IN (SELECT DISTINCT JobId FROM $table)
967 GROUP BY VolumeName,Enabled,InChanger
969 WHERE $table.FileIndex >= allmedia.FirstIndex
970 AND $table.FileIndex <= allmedia.LastIndex
972 my $lst = $bvfs->dbh_selectall_arrayref($q);
978 my ($jobid, $fileid) = @_;
980 SELECT DISTINCT VolumeName, Enabled, InChanger
982 ( -- Get all media from this job
983 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
984 VolumeName, Enabled, Inchanger
985 FROM JobMedia JOIN Media USING (MediaId)
986 WHERE JobId IN ($jobid)
987 GROUP BY VolumeName,Enabled,InChanger
989 WHERE File.FileId IN ($fileid)
990 AND File.FileIndex >= allmedia.FirstIndex
991 AND File.FileIndex <= allmedia.LastIndex
993 my $lst = $bvfs->dbh_selectall_arrayref($q);
997 # get jobid param and apply user filter
998 my @jobid = $bvfs->get_jobids(grep { /^\d+(,\d+)*$/ } CGI::param('jobid'));
1000 # get jobid from date arg
1001 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
1002 @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});
1005 $bvfs->set_curjobids(@jobid);
1006 $bvfs->set_limits($args->{offset}, $args->{limit});
1008 if (!scalar(@jobid)) {
1012 if (CGI::param('init')) { # used when choosing a job
1013 $bvfs->update_brestore_table(@jobid);
1016 my $pathid = CGI::param('node') || CGI::param('pathid') || '';
1017 my $path = CGI::param('path');
1019 if ($pathid =~ /^(\d+)$/) {
1022 $pathid = $bvfs->get_pathid($path);
1024 $pathid = $bvfs->get_root();
1026 $bvfs->ch_dir($pathid);
1028 #print STDERR "pathid=$pathid\n";
1030 # permit to use a regex filter
1031 if ($args->{qpattern}) {
1032 $bvfs->set_pattern($args->{qpattern});
1035 if ($action eq 'restore') {
1037 # TODO: pouvoir choisir le replace et le jobname
1038 my $arg = $bvfs->get_form(qw/client storage regexwhere where/);
1040 if (!$arg->{client}) {
1041 print "ERROR: missing client\n";
1045 my $table = fill_table_for_restore(@jobid);
1050 my $bconsole = $bvfs->get_bconsole();
1051 # TODO: pouvoir choisir le replace et le jobname
1052 my $jobid = $bconsole->run(client => $arg->{client},
1053 storage => $arg->{storage},
1054 where => $arg->{where},
1055 regexwhere=> $arg->{regexwhere},
1059 $bvfs->dbh_do("DROP TABLE $table");
1062 print CGI::header('text/html');
1063 $bvfs->display_begin();
1064 $bvfs->error("Can't start your job:<br/>" . $bconsole->before());
1065 $bvfs->display_end();
1069 print CGI::redirect("bweb.pl?action=dsp_cur_job;jobid=$jobid") ;
1090 $str =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g;
1091 $str =~ s/\//\\\//g;
1092 $str =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg;
1096 print CGI::header('application/x-javascript');
1099 if ($action eq 'list_files_dirs') {
1100 # fileid, filenameid, pathid, jobid, name, size, mtime
1101 my $jids = join(",", @jobid);
1103 my $files = $bvfs->ls_special_dirs();
1104 # return ($dirid,$dir_basename,$lstat,$jobid)
1107 map { my @p=Bvfs::parse_lstat($_->[3]);
1113 '"' . escape_quote($_->[1]) . '"', # name
1114 "'" . $p[7] . "'", # size
1115 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11]||0)) . "'") .
1118 print "," if (@$files);
1120 $files = $bvfs->ls_dirs();
1121 # return ($dirid,$dir_basename,$lstat,$jobid)
1123 map { my @p=Bvfs::parse_lstat($_->[3]);
1129 '"' . escape_quote($_->[1]) . '"', # name
1130 "'" . $p[7] . "'", # size
1131 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11]||0)) . "'") .
1135 print "," if (@$files);
1137 $files = $bvfs->ls_files();
1139 map { my @p=Bvfs::parse_lstat($_->[3]);
1145 '"' . escape_quote($_->[2]) . '"', # name
1147 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1152 } elsif ($action eq 'list_files') {
1153 print "[[0,0,0,0,'.',4096,'1970-01-01 00:00:00'],";
1154 my $files = $bvfs->ls_files();
1155 # [ 1, 2, 3, "Bill", 10, '2007-01-01 00:00:00'],
1156 # File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
1159 map { my @p=Bvfs::parse_lstat($_->[3]);
1165 '"' . escape_quote($_->[2]) . '"', # name
1167 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1172 } elsif ($action eq 'list_dirs') {
1175 my $dirs = $bvfs->ls_dirs();
1176 # return ($dirid,$dir_basename,$lstat,$jobid)
1179 map { "{ 'jobid': '$bvfs->{curjobids}', 'id': '$_->[0]'," .
1180 "'text': '" . escape_quote($_->[1]) . "', 'cls':'folder'}" }
1184 } elsif ($action eq 'list_versions') {
1186 my $vafv = CGI::param('vafv') || 'false'; # view all file versions
1187 $vafv = ($vafv eq 'false')?0:1;
1189 my $vcopies = CGI::param('vcopies') || 'false'; # view copies file versions
1190 $vcopies = ($vcopies eq 'false')?0:1;
1194 #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
1195 my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, $vafv, $vcopies);
1197 map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5],'" . strftime('%Y-%m-%d %H:%m:%S', localtime($_->[4])) . "']" }
1201 # this action is used when the restore box appear, we can display
1202 # the media list that will be needed for restore
1203 } elsif ($action eq 'get_media') {
1204 my ($jobid, $fileid, $table);
1207 # in this mode, we compute the result to get all needed media
1208 # print STDERR "force=", CGI::param('force'), "\n";
1209 if (CGI::param('force')) {
1210 $table = fill_table_for_restore(@jobid);
1214 # mysql is very slow without this index...
1215 if ($bvfs->dbh_is_mysql()) {
1216 $bvfs->dbh_do("CREATE INDEX idx_$table ON $table (JobId)");
1218 $lst = get_media_list_with_dir($table);
1220 $jobid = join(',', @jobid);
1221 $fileid = join(',', grep { /^\d+(,\d+)*$/ } CGI::param('fileid'));
1222 $lst = get_media_list($jobid, $fileid);
1227 print join(',', map { "['$_->[0]',$_->[1],$_->[2]]" } @$lst);
1232 $bvfs->dbh_do("DROP TABLE $table");
1239 CREATE VIEW files AS
1240 SELECT path || name AS name,pathid,filenameid,fileid,jobid
1241 FROM File JOIN FileName USING (FilenameId) JOIN Path USING (PathId);
1243 SELECT 'drop table ' || tablename || ';'
1244 FROM pg_tables WHERE tablename ~ '^b[0-9]';