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 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.
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";
377 $self->debug($query);
378 my $result = $self->dbh_selectall_arrayref($query);
379 $self->debug($result);
384 # list all directories in a directory, accross curjobids
385 # return ($dirid,$dir_basename,$lstat,$jobid)
390 return undef unless ($self->{curjobids});
392 my $pathid = $self->{cwdid};
393 my $jobclause = $self->{curjobids};
395 # Let's retrieve the list of the visible dirs in this dir ...
396 # First, I need the empty filenameid to locate efficiently
397 # the dirs in the file table
398 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
399 my $sth = $self->dbh_prepare($query);
401 my $result = $sth->fetchrow_arrayref();
403 my $dir_filenameid = $result->[0];
405 # Then we get all the dir entries from File ...
407 SELECT PathId, Path, JobId, Lstat FROM (
409 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
410 listfile1.JobId, listfile1.Lstat
412 SELECT DISTINCT brestore_pathhierarchy1.PathId
413 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
415 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
416 JOIN brestore_pathvisibility AS brestore_pathvisibility1
417 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
418 WHERE brestore_pathhierarchy1.PPathId = $pathid
419 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
420 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
422 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
423 WHERE File1.FilenameId = $dir_filenameid
424 AND File1.JobId IN ($jobclause)) AS listfile1
425 ON (listpath1.PathId = listfile1.PathId)
426 ) AS A ORDER BY 2,3 DESC LIMIT $self->{limit} OFFSET $self->{offset}
428 $self->debug($query);
430 $sth=$self->dbh_prepare($query);
432 $result = $sth->fetchall_arrayref();
435 foreach my $refrow (@{$result})
437 my $dirid = $refrow->[0];
438 my $dir = $refrow->[1];
439 my $lstat = $refrow->[3];
440 my $jobid = $refrow->[2] || 0;
441 next if ($dirid eq $prev_dir);
442 # We have to clean up this dirname ... we only want it's 'basename'
446 my @temp = split ('/',$dir);
447 $return_value = pop @temp;
453 my @return_array = ($dirid,$return_value,$lstat,$jobid);
454 push @return_list,(\@return_array);
457 $self->debug(\@return_list);
458 return \@return_list;
461 # TODO : we want be able to restore files from a bad ended backup
462 # we have JobStatus IN ('T', 'A', 'E') and we must
464 # Data acces subs from here. Interaction with SGBD and caching
466 # This sub retrieves the list of jobs corresponding to the jobs selected in the
467 # GUI and stores them in @CurrentJobIds.
468 # date must be quoted
469 sub set_job_ids_for_date
471 my ($self, $client, $date)=@_;
473 if (!$client or !$date) {
476 my $filter = $self->get_client_filter();
477 # The algorithm : for a client, we get all the backups for each
478 # fileset, in reverse order Then, for each fileset, we store the 'good'
479 # incrementals and differentials until we have found a full so it goes
480 # like this : store all incrementals until we have found a differential
481 # or a full, then find the full
483 SELECT JobId, FileSet, Level, JobStatus
485 JOIN FileSet USING (FileSetId)
486 JOIN Client USING (ClientId) $filter
487 WHERE EndTime <= $date
488 AND Client.Name = '$client'
490 AND JobStatus IN ('T')
491 ORDER BY FileSet, JobTDate DESC";
494 my $result = $self->dbh_selectall_arrayref($query);
496 foreach my $refrow (@$result)
498 my $jobid = $refrow->[0];
499 my $fileset = $refrow->[1];
500 my $level = $refrow->[2];
502 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
504 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
508 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
509 push @CurrentJobIds,($jobid);
511 elsif ($level eq 'D')
513 next if $progress{$fileset} eq 'D'; # We allready have a differential
514 push @CurrentJobIds,($jobid);
516 elsif ($level eq 'F')
518 push @CurrentJobIds,($jobid);
521 my $status = $refrow->[3] ;
522 if ($status eq 'T') { # good end of job
523 $progress{$fileset} = $level;
527 return @CurrentJobIds;
530 sub dbh_selectrow_arrayref
532 my ($self, $query) = @_;
533 $self->debug($query, up => 1);
534 return $self->{dbh}->selectrow_arrayref($query);
537 # Returns list of versions of a file that could be restored
538 # returns an array of
539 # (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
540 # there will be only one jobid in the array of jobids...
541 sub get_all_file_versions
543 my ($self,$pathid,$fileid,$client,$see_all,$see_copies)=@_;
545 defined $see_all or $see_all=0;
546 my $backup_type=" AND Job.Type = 'B' ";
548 $backup_type=" AND Job.Type IN ('C', 'B') ";
554 "SELECT File.JobId, File.FileId, File.Lstat,
555 File.Md5, Media.VolumeName, Media.InChanger
556 FROM File, Job, Client, JobMedia, Media
557 WHERE File.FilenameId = $fileid
558 AND File.PathId=$pathid
559 AND File.JobId = Job.JobId
560 AND Job.ClientId = Client.ClientId
561 AND Job.JobId = JobMedia.JobId
562 AND File.FileIndex >= JobMedia.FirstIndex
563 AND File.FileIndex <= JobMedia.LastIndex
564 AND JobMedia.MediaId = Media.MediaId
565 AND Client.Name = '$client'
569 $self->debug($query);
570 my $result = $self->dbh_selectall_arrayref($query);
572 foreach my $refrow (@$result)
574 my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
575 my @attribs = parse_lstat($lstat);
576 my $mtime = array_attrib('st_mtime',\@attribs);
577 my $size = array_attrib('st_size',\@attribs);
579 my @list = ($pathid,$fileid,$jobid,
580 $fid, $mtime, $size, $inchanger,
582 push @versions, (\@list);
585 # We have the list of all versions of this file.
586 # We'll sort it by mtime desc, size, md5, inchanger desc, FileId
587 # the rest of the algorithm will be simpler
588 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
589 @versions = sort { $b->[4] <=> $a->[4]
590 || $a->[5] <=> $b->[5]
591 || $a->[7] cmp $a->[7]
592 || $b->[6] <=> $a->[6]} @versions;
595 my %allready_seen_by_mtime;
596 my %allready_seen_by_md5;
597 # Now we should create a new array with only the interesting records
598 foreach my $ref (@versions)
602 # The file has a md5. We compare his md5 to other known md5...
603 # We take size into account. It may happen that 2 files
604 # have the same md5sum and are different. size is a supplementary
607 # If we allready have a (better) version
608 next if ( (not $see_all)
609 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
611 # we never met this one before...
612 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
614 # Even if it has a md5, we should also work with mtimes
615 # We allready have a (better) version
616 next if ( (not $see_all)
617 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
618 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
620 # We reached there. The file hasn't been seen.
621 push @good_versions,($ref);
624 # To be nice with the user, we re-sort good_versions by
625 # inchanger desc, mtime desc
626 @good_versions = sort { $b->[4] <=> $a->[4]
627 || $b->[2] <=> $a->[2]} @good_versions;
629 return \@good_versions;
632 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
633 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
634 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
635 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
636 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
637 'data_stream' => 15);;
640 my ($attrib,$ref_attrib)=@_;
641 return $ref_attrib->[$attrib_name_id{$attrib}];
645 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
647 my ($file, $attrib)=@_;
649 if (defined $attrib_name_id{$attrib}) {
651 my @d = split(' ', $file->[3]) ; # TODO : cache this
653 return from_base64($d[$attrib_name_id{$attrib}]);
655 } elsif ($attrib eq 'jobid') {
659 } elsif ($attrib eq 'name') {
664 die "Attribute not known : $attrib.\n";
670 my ($lstat,$attrib)=@_;
671 if ($lstat and defined $attrib_name_id{$attrib})
673 my @d = split(' ', $lstat) ; # TODO : cache this
674 return from_base64($d[$attrib_name_id{$attrib}]);
681 # Base 64 functions, directly from recover.pl.
683 # Karl Hakimian <hakimian@aha.com>
684 # This section is also under GPL v2 or later.
691 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
692 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
693 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
694 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
695 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
697 @base64_map = (0) x 128;
699 for (my $i=0; $i<64; $i++) {
700 $base64_map[ord($base64_digits[$i])] = $i;
715 if (substr($where, 0, 1) eq '-') {
717 $where = substr($where, 1);
720 while ($where ne '') {
722 my $d = substr($where, 0, 1);
723 $val += $base64_map[ord(substr($where, 0, 1))];
724 $where = substr($where, 1);
732 my @attribs = split(' ',$lstat);
733 foreach my $element (@attribs)
735 $element = from_base64($element);
741 # get jobids that the current user can view (ACL)
744 my ($self, @jobid) = @_;
745 my $filter = $self->get_client_filter();
747 my $jobids = $self->dbh_join(@jobid);
750 FROM Job JOIN Client USING (ClientId) $filter
751 WHERE Jobid IN ($jobids)";
752 my $res = $self->dbh_selectall_arrayref($q);
753 @jobid = map { $_->[0] } @$res;
758 ################################################################
763 use POSIX qw/strftime/;
766 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
769 my $bvfs = new Bvfs(info => $conf);
772 my $action = CGI::param('action') || '';
774 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
775 'limit', 'offset', 'client', 'qpattern');
777 if ($action eq 'batch') {
778 $bvfs->update_cache();
782 # All these functions are returning JSON compatible data
783 # for javascript parsing
785 if ($action eq 'list_client') { # list all client [ ['c1'],['c2']..]
786 print CGI::header('application/x-javascript');
788 my $filter = $bvfs->get_client_filter();
789 my $q = "SELECT Name FROM Client $filter";
790 my $ret = $bvfs->dbh_selectall_arrayref($q);
793 print join(',', map { "['$_->[0]']" } @$ret);
797 } elsif ($action eq 'list_job') {
798 # list jobs for a client [[jobid,endtime,'desc'],..]
800 print CGI::header('application/x-javascript');
802 my $filter = $bvfs->get_client_filter();
804 SELECT Job.JobId,Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus
805 FROM Job JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) $filter
806 WHERE Client.Name = '$args->{client}'
808 AND JobStatus IN ('f', 'T')
809 ORDER BY EndTime desc";
810 my $result = $bvfs->dbh_selectall_arrayref($query);
814 print join(',', map {
815 "[$_->[0], '$_->[1]', '$_->[1] $_->[2] $_->[3] ($_->[4]) $_->[0]']"
820 } elsif ($action eq 'list_storage') { # TODO: use .storage here
821 print CGI::header('application/x-javascript');
823 my $q="SELECT Name FROM Storage";
824 my $lst = $bvfs->dbh_selectall_arrayref($q);
826 print join(',', map { "[ '$_->[0]' ]" } @$lst);
831 sub fill_table_for_restore
835 # in "force" mode, we need the FileId to compute media list
836 my $FileId = CGI::param('force')?",FileId":"";
838 my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
839 my @dirid = grep { /^\d+$/ } CGI::param('dirid');
840 my $inclause = join(',', @jobid);
846 "(SELECT JobId, FileIndex, FilenameId, PathId $FileId
847 FROM File WHERE FileId IN ($fileid))";
850 # using this is not good because the sql engine doesn't know
851 # what LIKE will use. It will be better to get Path% in perl
852 # but it doesn't work with accents... :(
853 foreach my $dirid (@dirid) {
855 (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId $FileId
856 FROM Path JOIN File USING (PathId)
858 (SELECT ". $bvfs->dbh_strcat('Path',"'\%'") ." FROM Path
859 WHERE PathId = $dirid
861 AND File.JobId IN ($inclause))";
864 return unless scalar(@union);
866 my $u = join(" UNION ", @union);
868 $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp AS $u");
869 # TODO: remove FilenameId et PathId
871 # now we have to choose the file with the max(jobid)
872 # for each file of btemp
873 if ($bvfs->dbh_is_mysql()) {
874 $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp2 AS (
875 SELECT max(JobId) as JobId, PathId, FilenameId, FileIndex
877 GROUP BY PathId, FilenameId
880 $bvfs->dbh_do("CREATE INDEX btemp2_idx ON btemp2 " .
881 "(JobId, PathId, FilenameId)");
883 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
884 SELECT btemp.JobId, btemp.FileIndex $FileId
886 WHERE btemp2.JobId = btemp.JobId
887 AND btemp2.PathId= btemp.PathId
888 AND btemp2.FilenameId = btemp.FilenameId
890 } else { # postgresql have distinct with more than one criteria...
891 $bvfs->dbh_do("CREATE TABLE b2$$ AS (
892 SELECT JobId, FileIndex $FileId
894 SELECT DISTINCT ON (PathId, FilenameId) JobId, FileIndex $FileId
896 ORDER BY PathId, FilenameId, JobId DESC
905 sub get_media_list_with_dir
909 SELECT DISTINCT VolumeName, Enabled, InChanger
911 ( -- Get all media from this job
912 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
913 VolumeName, Enabled, Inchanger
914 FROM JobMedia JOIN Media USING (MediaId)
915 WHERE JobId IN (SELECT DISTINCT JobId FROM $table)
916 GROUP BY VolumeName,Enabled,InChanger
918 WHERE $table.FileIndex >= allmedia.FirstIndex
919 AND $table.FileIndex <= allmedia.LastIndex
921 my $lst = $bvfs->dbh_selectall_arrayref($q);
927 my ($jobid, $fileid) = @_;
929 SELECT DISTINCT VolumeName, Enabled, InChanger
931 ( -- Get all media from this job
932 SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
933 VolumeName, Enabled, Inchanger
934 FROM JobMedia JOIN Media USING (MediaId)
935 WHERE JobId IN ($jobid)
936 GROUP BY VolumeName,Enabled,InChanger
938 WHERE File.FileId IN ($fileid)
939 AND File.FileIndex >= allmedia.FirstIndex
940 AND File.FileIndex <= allmedia.LastIndex
942 my $lst = $bvfs->dbh_selectall_arrayref($q);
946 # get jobid param and apply user filter
947 my @jobid = $bvfs->get_jobids(grep { /^\d+(,\d+)*$/ } CGI::param('jobid'));
949 # get jobid from date arg
950 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
951 @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});
954 $bvfs->set_curjobids(@jobid);
955 $bvfs->set_limits($args->{offset}, $args->{limit});
957 if (!scalar(@jobid)) {
961 if (CGI::param('init')) { # used when choosing a job
962 $bvfs->update_brestore_table(@jobid);
965 my $pathid = CGI::param('node') || '';
966 my $path = CGI::param('path');
968 if ($pathid =~ /^(\d+)$/) {
971 $pathid = $bvfs->get_pathid($path);
973 $pathid = $bvfs->get_root();
975 $bvfs->ch_dir($pathid);
977 # permit to use a regex filter
978 if ($args->{qpattern}) {
979 $bvfs->set_pattern($args->{qpattern});
982 if ($action eq 'restore') {
984 # TODO: pouvoir choisir le replace et le jobname
985 my $arg = $bvfs->get_form(qw/client storage regexwhere where/);
987 if (!$arg->{client}) {
988 print "ERROR: missing client\n";
992 my $table = fill_table_for_restore(@jobid);
997 my $bconsole = $bvfs->get_bconsole();
998 # TODO: pouvoir choisir le replace et le jobname
999 my $jobid = $bconsole->run(client => $arg->{client},
1000 storage => $arg->{storage},
1001 where => $arg->{where},
1002 regexwhere=> $arg->{regexwhere},
1006 $bvfs->dbh_do("DROP TABLE $table");
1009 print CGI::header('text/html');
1010 $bvfs->display_begin();
1011 $bvfs->error("Can't start your job:<br/>" . $bconsole->before());
1012 $bvfs->display_end();
1016 print CGI::redirect("bweb.pl?action=dsp_cur_job;jobid=$jobid") ;
1027 print CGI::header('application/x-javascript');
1029 if ($action eq 'list_files') {
1030 print "[[0,0,0,0,'.',4096,'1970-01-01 00:00:00'],";
1031 my $files = $bvfs->ls_files();
1032 # [ 1, 2, 3, "Bill", 10, '2007-01-01 00:00:00'],
1033 # File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
1036 map { my @p=Bvfs::parse_lstat($_->[3]);
1042 "'" . escape_quote($_->[2]) . "'",
1044 "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) . "'") .
1049 } elsif ($action eq 'list_dirs') {
1052 my $dirs = $bvfs->ls_dirs();
1053 # return ($dirid,$dir_basename,$lstat,$jobid)
1056 map { "{ 'jobid': '$bvfs->{curjobids}', 'id': '$_->[0]'," .
1057 "'text': '" . escape_quote($_->[1]) . "', 'cls':'folder'}" }
1061 } elsif ($action eq 'list_versions') {
1063 my $vafv = CGI::param('vafv') || 'false'; # view all file versions
1064 $vafv = ($vafv eq 'false')?0:1;
1066 my $vcopies = CGI::param('vcopies') || 'false'; # view copies file versions
1067 $vcopies = ($vcopies eq 'false')?0:1;
1071 #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
1072 my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, $vafv, $vcopies);
1074 map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5],'" . strftime('%Y-%m-%d %H:%m:%S', localtime($_->[4])) . "']" }
1078 # this action is used when the restore box appear, we can display
1079 # the media list that will be needed for restore
1080 } elsif ($action eq 'get_media') {
1081 my ($jobid, $fileid, $table);
1084 # in this mode, we compute the result to get all needed media
1085 if (CGI::param('force')) {
1086 $table = fill_table_for_restore(@jobid);
1090 # mysql is very slow without this index...
1091 # if ($bvfs->dbh_is_mysql()) {
1092 # $bvfs->dbh_do("CREATE INDEX idx_$table ON $table (FileId)");
1094 $lst = get_media_list_with_dir($table);
1096 $jobid = join(',', @jobid);
1097 $fileid = join(',', grep { /^\d+(,\d+)*$/ } CGI::param('fileid'));
1098 $lst = get_media_list($jobid, $fileid);
1103 print join(',', map { "['$_->[0]',$_->[1],$_->[2]]" } @$lst);
1108 $bvfs->dbh_do("DROP TABLE $table");
1115 CREATE VIEW files AS
1116 SELECT path || name AS name,pathid,filenameid,fileid,jobid
1117 FROM File JOIN FileName USING (FilenameId) JOIN Path USING (PathId);
1119 SELECT 'drop table ' || tablename || ';'
1120 FROM pg_tables WHERE tablename ~ '^b[0-9]';