]> git.sur5r.net Git - bacula/bacula/blobdiff - gui/bweb/cgi/bresto.pl
bweb: Update some GPL2 notice to AGPL
[bacula/bacula] / gui / bweb / cgi / bresto.pl
index 6a77af6c630ef39574ce10e67f01e938e838a1e1..df92a485990138cbdde7a556c8d5d8ea709c660f 100755 (executable)
@@ -1,5 +1,5 @@
 #!/usr/bin/perl -w
-
+use strict;
 my $bresto_enable = 1;
 die "bresto is not enabled" if (not $bresto_enable);
 
@@ -13,25 +13,24 @@ die "bresto is not enabled" if (not $bresto_enable);
    The main author of Bweb is Eric Bollengier.
    The main author of Bacula is Kern Sibbald, with contributions from
    many others, a complete list can be found in the file AUTHORS.
-
    This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
-   License as published by the Free Software Foundation plus additions
-   that are listed in the file LICENSE.
+   modify it under the terms of version three of the GNU Affero General Public
+   License as published by the Free Software Foundation and included
+   in the file LICENSE.
 
    This program is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-   General Public License for more details.
+   Affero General Public License for more details.
 
-   You should have received a copy of the GNU General Public License
+   You should have received a copy of the GNU Affero General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
 
-   Bacula® is a registered trademark of John Walker.
+   Bacula® is a registered trademark of Kern Sibbald.
    The licensor of Bacula is the Free Software Foundation Europe
-   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
+   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
    Switzerland, email:ftf@fsfeurope.org.
 
 =head1 VERSION
@@ -51,12 +50,14 @@ sub get_root
     return $self->get_pathid('');
 }
 
+# change the current directory
 sub ch_dir
 {
     my ($self, $pathid) = @_;
     $self->{cwdid} = $pathid;
 }
 
+# do a cd ..
 sub up_dir
 {
     my ($self) = @_ ;
@@ -74,19 +75,19 @@ sub up_dir
     }
 }
 
+# return the current PWD
 sub pwd
 {
     my ($self) = @_;
     return $self->get_path($self->{cwdid});
 }
 
+# get the Path from a PathId
 sub get_path
 {
     my ($self, $pathid) = @_;
     $self->debug("Call with pathid = $pathid");
-    my $query =
-       "SELECT Path FROM Path WHERE PathId IN (?)";
-
+    my $query =        "SELECT Path FROM Path WHERE PathId = ?";
     my $sth = $self->dbh_prepare($query);
     $sth->execute($pathid);
     my $result = $sth->fetchrow_arrayref();
@@ -94,6 +95,7 @@ sub get_path
     return $result->[0];
 }
 
+# we are working with these jobids
 sub set_curjobids
 {
     my ($self, @jobids) = @_;
@@ -101,6 +103,7 @@ sub set_curjobids
 #    $self->update_brestore_table(@jobids);
 }
 
+# get the PathId from a Path
 sub get_pathid
 {
     my ($self, $dir) = @_;
@@ -108,10 +111,10 @@ sub get_pathid
        "SELECT PathId FROM Path WHERE Path = ?";
     my $sth = $self->dbh_prepare($query);
     $sth->execute($dir);
-    my $result = $sth->fetchall_arrayref();
+    my $result = $sth->fetchrow_arrayref();
     $sth->finish();
 
-    return join(',', map { $_->[0] } @$result);
+    return $result->[0];
 }
 
 sub set_limits
@@ -121,19 +124,32 @@ sub set_limits
     $self->{offset} = $offset || 0;
 }
 
+sub set_pattern
+{
+    my ($self, $pattern) = @_;
+    $self->{pattern} = $pattern;
+}
+
+# fill brestore_xxx tables for speedup
 sub update_cache
 {
     my ($self) = @_;
 
     $self->{dbh}->begin_work();
 
+    # getting all Jobs to "cache"
     my $query = "
   SELECT JobId from Job
-   WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) AND JobStatus IN ('T', 'f', 'A') ORDER BY JobId";
+   WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) 
+     AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') 
+   ORDER BY JobId";
     my $jobs = $self->dbh_selectall_arrayref($query);
 
     $self->update_brestore_table(map { $_->[0] } @$jobs);
 
+    $self->{dbh}->commit();
+    $self->{dbh}->begin_work();        # we can break here
+
     print STDERR "Cleaning path visibility\n";
 
     my $nb = $self->dbh_do("
@@ -224,6 +240,7 @@ INSERT INTO brestore_pathvisibility (PathId, JobId) (
     }
 }
 
+# compute the parent directory
 sub parent_dir
 {
     my ($path) = @_;
@@ -326,6 +343,7 @@ sub return_pathid_from_path
     }
 }
 
+# list all files in a directory, accross curjobids
 sub ls_files
 {
     my ($self) = @_;
@@ -333,7 +351,11 @@ sub ls_files
     return undef unless ($self->{curjobids});
 
     my $inclause   = $self->{curjobids};
-    my $inlistpath = $self->{cwdid};
+    my $inpath = $self->{cwdid};
+    my $filter = '';
+    if ($self->{pattern}) {
+        $filter = " AND Filename.Name $self->{sql}->{MATCH} $self->{pattern} ";
+    }
 
     my $query =
 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
@@ -342,12 +364,15 @@ sub ls_files
         FROM File, Filename
        WHERE File.FilenameId = Filename.FilenameId
           AND Filename.Name != ''
-          AND File.PathId IN ($inlistpath)
+          AND File.PathId = $inpath
           AND File.JobId IN ($inclause)
+          $filter
         GROUP BY Filename.Name
-        ORDER BY Filename.Name) AS listfiles
+        ORDER BY Filename.Name LIMIT $self->{limit} OFFSET $self->{offset}
+     ) AS listfiles
 WHERE File.FileId = listfiles.id";
 
+#    print STDERR $query;
     $self->debug($query);
     my $result = $self->dbh_selectall_arrayref($query);
     $self->debug($result);
@@ -355,53 +380,119 @@ WHERE File.FileId = listfiles.id";
     return $result;
 }
 
-
-# return ($dirid,$dir_basename,$lstat,$jobid)
-sub ls_dirs
+sub ls_special_dirs
 {
     my ($self) = @_;
-
     return undef unless ($self->{curjobids});
 
     my $pathid = $self->{cwdid};
     my $jobclause = $self->{curjobids};
+    my $dir_filenameid = $self->get_dir_filenameid();
+
+    my $sq1 =  
+"((SELECT PPathId AS PathId, '..' AS Path
+    FROM  brestore_pathhierarchy 
+   WHERE  PathId = $pathid)
+UNION
+ (SELECT $pathid AS PathId, '.' AS Path))";
+
+    my $sq2 = "
+SELECT tmp.PathId, tmp.Path, LStat, JobId 
+  FROM $sq1 AS tmp  LEFT JOIN ( -- get attributes if any
+       SELECT File1.PathId, File1.JobId, File1.LStat FROM File AS File1
+       WHERE File1.FilenameId = $dir_filenameid
+       AND File1.JobId IN ($jobclause)) AS listfile1
+  ON (tmp.PathId = listfile1.PathId)
+  ORDER BY tmp.Path, JobId DESC
+";
 
-    # Let's retrieve the list of the visible dirs in this dir ...
-    # First, I need the empty filenameid to locate efficiently the dirs in the file table
+    my $result = $self->dbh_selectall_arrayref($sq2);
+
+    my @return_list;
+    my $prev_dir='';
+    foreach my $refrow (@{$result})
+    {
+       my $dirid = $refrow->[0];
+        my $dir = $refrow->[1];
+        my $lstat = $refrow->[3];
+        my $jobid = $refrow->[2] || 0;
+        next if ($dirid eq $prev_dir);
+        my @return_array = ($dirid,$dir,$lstat,$jobid);
+        push @return_list,(\@return_array);
+        $prev_dir = $dirid;
+    }
+    return \@return_list;
+}
+
+# Let's retrieve the list of the visible dirs in this dir ...
+# First, I need the empty filenameid to locate efficiently
+# the dirs in the file table
+sub get_dir_filenameid
+{
+    my ($self) = @_;
+    if ($self->{dir_filenameid}) {
+        return $self->{dir_filenameid};
+    }
     my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
     my $sth = $self->dbh_prepare($query);
     $sth->execute();
     my $result = $sth->fetchrow_arrayref();
     $sth->finish();
-    my $dir_filenameid = $result->[0];
+    return $self->{dir_filenameid} = $result->[0];
+}
+
+# list all directories in a directory, accross curjobids
+# return ($dirid,$dir_basename,$lstat,$jobid)
+sub ls_dirs
+{
+    my ($self) = @_;
+
+    return undef unless ($self->{curjobids});
+
+    my $pathid = $self->{cwdid};
+    my $jobclause = $self->{curjobids};
+    my $filter ='';
+
+    if ($self->{pattern}) {
+        $filter = " AND Path2.Path $self->{sql}->{MATCH} $self->{pattern} ";
+    }
+
+    # Let's retrieve the list of the visible dirs in this dir ...
+    # First, I need the empty filenameid to locate efficiently
+    # the dirs in the file table
+    my $dir_filenameid = $self->get_dir_filenameid();
 
     # Then we get all the dir entries from File ...
-    $query = "
-SELECT PathId, Path, JobId, Lstat FROM (
+    my $query = "
+SELECT PathId, Path, JobId, LStat FROM (
 
     SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
-           listfile1.JobId, listfile1.Lstat
+           listfile1.JobId, listfile1.LStat
     FROM (
-        SELECT DISTINCT brestore_pathhierarchy1.PathId
-        FROM brestore_pathhierarchy AS brestore_pathhierarchy1
-        JOIN Path AS Path2
-            ON (brestore_pathhierarchy1.PathId = Path2.PathId)
-        JOIN brestore_pathvisibility AS brestore_pathvisibility1
-            ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
-        WHERE brestore_pathhierarchy1.PPathId = $pathid
-        AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
-    JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
-    LEFT JOIN (
-        SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
-        WHERE File1.FilenameId = $dir_filenameid
-        AND File1.JobId IN ($jobclause)) AS listfile1
-        ON (listpath1.PathId = listfile1.PathId)
-     ) AS A ORDER BY 2,3 DESC
+       SELECT DISTINCT brestore_pathhierarchy1.PathId
+       FROM brestore_pathhierarchy AS brestore_pathhierarchy1
+       JOIN Path AS Path2
+           ON (brestore_pathhierarchy1.PathId = Path2.PathId)
+       JOIN brestore_pathvisibility AS brestore_pathvisibility1
+           ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
+       WHERE brestore_pathhierarchy1.PPathId = $pathid
+       AND brestore_pathvisibility1.jobid IN ($jobclause)
+           $filter
+     ) AS listpath1
+   JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
+
+   LEFT JOIN ( -- get attributes if any
+       SELECT File1.PathId, File1.JobId, File1.LStat FROM File AS File1
+       WHERE File1.FilenameId = $dir_filenameid
+       AND File1.JobId IN ($jobclause)) AS listfile1
+       ON (listpath1.PathId = listfile1.PathId)
+     ) AS A ORDER BY 2,3 DESC LIMIT $self->{limit} OFFSET $self->{offset} 
 ";
-    $self->debug($query);
-    $sth=$self->dbh_prepare($query);
+#    print STDERR $query;
+    my $sth=$self->dbh_prepare($query);
     $sth->execute();
-    $result = $sth->fetchall_arrayref();
+    my $result = $sth->fetchall_arrayref();
     my @return_list;
     my $prev_dir='';
     foreach my $refrow (@{$result})
@@ -512,14 +603,18 @@ sub dbh_selectrow_arrayref
 # there will be only one jobid in the array of jobids...
 sub get_all_file_versions
 {
-    my ($self,$pathid,$fileid,$client,$see_all)=@_;
+    my ($self,$pathid,$fileid,$client,$see_all,$see_copies)=@_;
 
     defined $see_all or $see_all=0;
+    my $backup_type=" AND Job.Type = 'B' ";
+    if ($see_copies) {
+        $backup_type=" AND Job.Type IN ('C', 'B') ";
+    }
 
     my @versions;
     my $query;
     $query =
-"SELECT File.JobId, File.FileId, File.Lstat,
+"SELECT File.JobId, File.FileId, File.LStat,
         File.Md5, Media.VolumeName, Media.InChanger
  FROM File, Job, Client, JobMedia, Media
  WHERE File.FilenameId = $fileid
@@ -530,7 +625,9 @@ sub get_all_file_versions
    AND File.FileIndex >= JobMedia.FirstIndex
    AND File.FileIndex <= JobMedia.LastIndex
    AND JobMedia.MediaId = Media.MediaId
-   AND Client.Name = '$client'";
+   AND Client.Name = '$client'
+   $backup_type
+";
 
     $self->debug($query);
     my $result = $self->dbh_selectall_arrayref($query);
@@ -549,7 +646,7 @@ sub get_all_file_versions
     }
 
     # We have the list of all versions of this file.
-    # We'll sort it by mtime desc, size, md5, inchanger desc
+    # We'll sort it by mtime desc, size, md5, inchanger desc, FileId
     # the rest of the algorithm will be simpler
     # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
     @versions = sort { $b->[4] <=> $a->[4]
@@ -577,7 +674,7 @@ sub get_all_file_versions
            # we never met this one before...
            $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
        }
-       # Even if it has a md5, we should also work with mtimes
+       # Even if it has a md5, we should also work with mtimes
         # We allready have a (better) version
        next if ( (not $see_all)
                  and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
@@ -704,6 +801,7 @@ sub get_all_file_versions
     }
 }
 
+# get jobids that the current user can view (ACL)
 sub get_jobids
 {
   my ($self, @jobid) = @_;
@@ -725,6 +823,7 @@ SELECT JobId
 
 package main;
 use strict;
+use POSIX qw/strftime/;
 use Bweb;
 
 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
@@ -736,31 +835,36 @@ $bvfs->connect_db();
 my $action = CGI::param('action') || '';
 
 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
-                          'limit', 'offset', 'client');
+                          'limit', 'offset', 'client', 'qpattern');
 
-if (CGI::param('batch')) {
+if ($action eq 'batch') {
     $bvfs->update_cache();
     exit 0;
 }
 
-if ($action eq 'list_client') {
-    print CGI::header('application/x-javascript');
+# All these functions are returning JSON compatible data
+# for javascript parsing
 
-  my $filter = $bvfs->get_client_filter();
-  my $q = "SELECT Name FROM Client $filter";
-  my $ret = $bvfs->dbh_selectall_arrayref($q);
+if ($action eq 'list_client') {        # list all client [ ['c1'],['c2']..]
+    print CGI::header('application/x-javascript');
 
-  print "[";
-  print join(',', map { "['$_->[0]']" } @$ret);
-  print "]\n";
-  exit 0;
+    my $filter = $bvfs->get_client_filter();
+    my $q = "SELECT Name FROM Client $filter";
+    my $ret = $bvfs->dbh_selectall_arrayref($q);
 
+    print "[";
+    print join(',', map { "['$_->[0]']" } @$ret);
+    print "]\n";
+    exit 0;
+    
 } elsif ($action eq 'list_job') {
-    print CGI::header('application/x-javascript');
+    # list jobs for a client [[jobid,endtime,'desc'],..]
 
+    print CGI::header('application/x-javascript');
+    
     my $filter = $bvfs->get_client_filter();
     my $query = "
- SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
+ SELECT Job.JobId,Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus
   FROM Job JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) $filter
  WHERE Client.Name = '$args->{client}'
    AND Job.Type = 'B'
@@ -771,12 +875,12 @@ if ($action eq 'list_client') {
     print "[";
 
     print join(',', map {
-      "[$_->[4], '$_->[0]', '$_->[0] $_->[1] $_->[2] ($_->[3]) $_->[4]']"
+      "[$_->[0], '$_->[1]', '$_->[1] $_->[2] $_->[3] ($_->[4]) $_->[0]']"
       } @$result);
 
     print "]\n";
     exit 0;
-} elsif ($action eq 'list_storage') { # TODO: use .storage hier
+} elsif ($action eq 'list_storage') { # TODO: use .storage here
     print CGI::header('application/x-javascript');
 
     my $q="SELECT Name FROM Storage";
@@ -787,22 +891,129 @@ if ($action eq 'list_client') {
     exit 0;
 }
 
+sub fill_table_for_restore
+{
+    my (@jobid) = @_;
+
+    # in "force" mode, we need the FileId to compute media list
+    my $FileId = CGI::param('force')?",FileId":"";
+
+    my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
+    # can get dirid=("10,11", 10, 11)
+    my @dirid = grep { /^\d+$/ } map { split(/,/) } CGI::param('dirid') ;
+    my $inclause = join(',', @jobid);
+
+    my @union;
+
+    if ($fileid) {
+      push @union,
+      "(SELECT JobId, FileIndex, FilenameId, PathId $FileId
+          FROM File WHERE FileId IN ($fileid))";
+    }
+
+    foreach my $dirid (@dirid) {
+        my $p = $bvfs->get_path($dirid);
+        $p =~ s/([%_\\])/\\$1/g;  # Escape % and _ for LIKE search
+        $p = $bvfs->dbh_quote($p);
+        push @union, "
+  (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId $FileId
+    FROM Path JOIN File USING (PathId)
+   WHERE Path.Path LIKE " . $bvfs->dbh_strcat($p, "'%'") . "
+     AND File.JobId IN ($inclause))";
+    }
+
+    return unless scalar(@union);
+
+    my $u = join(" UNION ", @union);
+
+    $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp AS $u");
+    # TODO: remove FilenameId et PathId
+
+    # now we have to choose the file with the max(jobid)
+    # for each file of btemp
+    if ($bvfs->dbh_is_mysql()) {
+       $bvfs->dbh_do("CREATE TABLE b2$$ AS (
+SELECT max(JobId) as JobId, FileIndex $FileId
+  FROM btemp
+ GROUP BY PathId, FilenameId
+ HAVING FileIndex > 0
+)");
+   } else { # postgresql have distinct with more than one criteria
+        $bvfs->dbh_do("CREATE TABLE b2$$ AS (
+SELECT JobId, FileIndex $FileId
+FROM (
+ SELECT DISTINCT ON (PathId, FilenameId) JobId, FileIndex $FileId
+   FROM btemp
+  ORDER BY PathId, FilenameId, JobId DESC
+ ) AS T
+ WHERE FileIndex > 0
+)");
+    }
+
+    return "b2$$";
+}
+
+sub get_media_list_with_dir
+{
+    my ($table) = @_;
+    my $q="
+ SELECT DISTINCT VolumeName, Enabled, InChanger
+   FROM $table,
+    ( -- Get all media from this job
+      SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
+             VolumeName, Enabled, Inchanger
+        FROM JobMedia JOIN Media USING (MediaId)
+       WHERE JobId IN (SELECT DISTINCT JobId FROM $table)
+       GROUP BY VolumeName,Enabled,InChanger
+    ) AS allmedia
+  WHERE $table.FileIndex >= allmedia.FirstIndex
+    AND $table.FileIndex <= allmedia.LastIndex
+";
+    my $lst = $bvfs->dbh_selectall_arrayref($q);
+    return $lst;
+}
+
+sub get_media_list
+{
+    my ($jobid, $fileid) = @_;
+    my $q="
+ SELECT DISTINCT VolumeName, Enabled, InChanger
+   FROM File,
+    ( -- Get all media from this job
+      SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
+             VolumeName, Enabled, Inchanger
+        FROM JobMedia JOIN Media USING (MediaId)
+       WHERE JobId IN ($jobid)
+       GROUP BY VolumeName,Enabled,InChanger
+    ) AS allmedia
+  WHERE File.FileId IN ($fileid)
+    AND File.FileIndex >= allmedia.FirstIndex
+    AND File.FileIndex <= allmedia.LastIndex
+";
+    my $lst = $bvfs->dbh_selectall_arrayref($q);
+    return $lst;
+}
+
+# get jobid param and apply user filter
 my @jobid = $bvfs->get_jobids(grep { /^\d+(,\d+)*$/ } CGI::param('jobid'));
+
+# get jobid from date arg
 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
     @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});
 }
+
 $bvfs->set_curjobids(@jobid);
-$bvfs->set_limits($args->{limit}, $args->{offset});
+$bvfs->set_limits($args->{offset}, $args->{limit});
 
 if (!scalar(@jobid)) {
     exit 0;
 }
 
-if (CGI::param('init')) {
+if (CGI::param('init')) { # used when choosing a job
     $bvfs->update_brestore_table(@jobid);
 }
 
-my $pathid = CGI::param('node') || '';
+my $pathid = CGI::param('node') || CGI::param('pathid') || '';
 my $path = CGI::param('path');
 
 if ($pathid =~ /^(\d+)$/) {
@@ -812,9 +1023,15 @@ if ($pathid =~ /^(\d+)$/) {
 } else {
     $pathid = $bvfs->get_root();
 }
-
 $bvfs->ch_dir($pathid);
 
+#print STDERR "pathid=$pathid\n";
+
+# permit to use a regex filter
+if ($args->{qpattern}) {
+    $bvfs->set_pattern($args->{qpattern});
+}
+
 if ($action eq 'restore') {
 
     # TODO: pouvoir choisir le replace et le jobname
@@ -825,48 +1042,11 @@ if ($action eq 'restore') {
        exit 1;
     }
 
-    my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
-    my @dirid = grep { /^\d+$/ } CGI::param('dirid');
-    my $inclause = join(',', @jobid);
-
-    my @union;
-
-    if ($fileid) {
-      push @union,
-      "(SELECT JobId, FileIndex, FilenameId, PathId FROM File WHERE FileId IN ($fileid))";
+    my $table = fill_table_for_restore(@jobid);
+    if (!$table) {
+        exit 1;
     }
 
-    # using this is not good because the sql engine doesn't know
-    # what LIKE will use. It will be better to get Path% in perl
-    # but it doesn't work with accents... :(
-    foreach my $dirid (@dirid) {
-      push @union, "
-  (SELECT File.JobId, File.FileIndex, File.FilenameId, File.PathId
-    FROM Path JOIN File USING (PathId)
-   WHERE Path.Path LIKE
-        (SELECT ". $bvfs->dbh_strcat('Path',"'\%'") ." FROM Path
-          WHERE PathId = $dirid
-        )
-     AND File.JobId IN ($inclause))";
-    }
-
-    return unless scalar(@union);
-
-    my $u = join(" UNION ", @union);
-
-    $bvfs->dbh_do("CREATE TEMPORARY TABLE btemp AS ($u)");
-    # TODO: remove FilenameId et PathId
-    $bvfs->dbh_do("CREATE TABLE b2$$ AS (
-SELECT btemp.JobId, btemp.FileIndex, btemp.FilenameId, btemp.PathId
-  FROM btemp,
-       (SELECT max(JobId) as JobId, PathId, FilenameId
-          FROM btemp
-      GROUP BY PathId, FilenameId
-      ORDER BY JobId DESC) AS a
-  WHERE a.JobId = btemp.JobId
-    AND a.PathId= btemp.PathId
-    AND a.FilenameId = btemp.FilenameId
-)");
     my $bconsole = $bvfs->get_bconsole();
     # TODO: pouvoir choisir le replace et le jobname
     my $jobid = $bconsole->run(client    => $arg->{client},
@@ -874,37 +1054,131 @@ SELECT btemp.JobId, btemp.FileIndex, btemp.FilenameId, btemp.PathId
                               where     => $arg->{where},
                               regexwhere=> $arg->{regexwhere},
                               restore   => 1,
-                              file      => "?b2$$");
+                              file      => "?$table");
     
-    $bvfs->dbh_do("DROP TABLE b2$$");
+    $bvfs->dbh_do("DROP TABLE $table");
+
+    if (!$jobid) {
+       print CGI::header('text/html');
+       $bvfs->display_begin();
+       $bvfs->error("Can't start your job:<br/>" . $bconsole->before());
+       $bvfs->display_end();
+       exit 0;
+    }
     sleep(2);
     print CGI::redirect("bweb.pl?action=dsp_cur_job;jobid=$jobid") ;
     exit 0;
 }
+sub escape_quote
+{
+    my ($str) = @_;
+    my %esc = (
+        "\n" => '\n',
+        "\r" => '\r',
+        "\t" => '\t',
+        "\f" => '\f',
+        "\b" => '\b',
+        "\"" => '\"',
+        "\\" => '\\\\',
+        "\'" => '\\\'',
+    );
+
+    if (!$str) {
+        return '';
+    }
+
+    $str =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g;
+    $str =~ s/\//\\\//g;
+    $str =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg;
+    return $str;
+}
 
 print CGI::header('application/x-javascript');
 
-if ($action eq 'list_files') {
-    print "[";
+
+if ($action eq 'list_files_dirs') {
+# fileid, filenameid, pathid, jobid, name, size, mtime
+    my $jids = join(",", @jobid);
+
+    my $files = $bvfs->ls_special_dirs();
+    # return ($dirid,$dir_basename,$lstat,$jobid)
+    print "[\n";
+    print join(',',
+              map { my @p=Bvfs::parse_lstat($_->[3]); 
+                    '[' . join(',', 
+                               0, # fileid
+                               0, # filenameid
+                               $_->[0], # pathid
+                               "'$jids'", # jobid
+                                '"' . escape_quote($_->[1]) . '"', # name
+                               "'" . $p[7] . "'",                 # size
+                               "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11]||0)) .  "'") .
+                   ']'; 
+              } @$files);
+    print "," if (@$files);
+
+    $files = $bvfs->ls_dirs();
+    # return ($dirid,$dir_basename,$lstat,$jobid)
+    print join(',',
+              map { my @p=Bvfs::parse_lstat($_->[3]); 
+                    '[' . join(',', 
+                               0, # fileid
+                               0, # filenameid
+                               $_->[0], # pathid
+                               "'$jids'", # jobid
+                               '"' . escape_quote($_->[1]) . '"', # name
+                               "'" . $p[7] . "'",                 # size
+                               "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11]||0)) .  "'") .
+                   ']'; 
+              } @$files);
+
+    print "," if (@$files);
+    $files = $bvfs->ls_files();
+    print join(',',
+              map { my @p=Bvfs::parse_lstat($_->[3]); 
+                    '[' . join(',', 
+                               $_->[1],
+                               $_->[0],
+                               $pathid,
+                               $_->[4],
+                                '"' . escape_quote($_->[2]) . '"', # name
+                               "'" . $p[7] . "'",
+                               "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) .  "'") .
+                   ']'; 
+              } @$files);
+    print "]\n";
+
+} elsif ($action eq 'list_files') {
+    print "[[0,0,0,0,'.',4096,'1970-01-01 00:00:00'],";
     my $files = $bvfs->ls_files();
 #      [ 1, 2, 3, "Bill",  10, '2007-01-01 00:00:00'],
 #   File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
 
     print join(',',
-              map { "[$_->[1], $_->[0], $pathid, $_->[4], \"$_->[2]\", 10, \"2007-01-01 00:00:00\"]" }
-              @$files);
+              map { my @p=Bvfs::parse_lstat($_->[3]); 
+                    '[' . join(',', 
+                               $_->[1],
+                               $_->[0],
+                               $pathid,
+                               $_->[4],
+                                '"' . escape_quote($_->[2]) . '"', # name
+                               "'" . $p[7] . "'",
+                               "'" . strftime('%Y-%m-%d %H:%m:%S', localtime($p[11])) .  "'") .
+                   ']'; 
+              } @$files);
     print "]\n";
 
 } elsif ($action eq 'list_dirs') {
 
     print "[";
     my $dirs = $bvfs->ls_dirs();
-       # return ($dirid,$dir_basename,$lstat,$jobid)
+    # return ($dirid,$dir_basename,$lstat,$jobid)
 
     print join(',',
-              map { "{ 'jobid': '$bvfs->{curjobids}', 'id': '$_->[0]', 'text': '$_->[1]', 'cls':'folder'}" }
+              map { "{ 'jobid': '$bvfs->{curjobids}', 'id': '$_->[0]'," . 
+                       "'text': '" . escape_quote($_->[1]) . "', 'cls':'folder'}" }
               @$dirs);
-
     print "]\n";
 
 } elsif ($action eq 'list_versions') {
@@ -912,39 +1186,51 @@ if ($action eq 'list_files') {
     my $vafv = CGI::param('vafv') || 'false'; # view all file versions
     $vafv = ($vafv eq 'false')?0:1;
 
+    my $vcopies = CGI::param('vcopies') || 'false'; # view copies file versions
+    $vcopies = ($vcopies eq 'false')?0:1;
+
     print "[";
     #   0       1       2        3   4       5      6           7      8
     #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
-    my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, $vafv);
+    my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, $vafv, $vcopies);
     print join(',',
-              map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5], $_->[4] ]" }
+              map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5],'" . strftime('%Y-%m-%d %H:%m:%S', localtime($_->[4])) . "']" }
               @$files);
     print "]\n";
 
+# this action is used when the restore box appear, we can display
+# the media list that will be needed for restore
 } elsif ($action eq 'get_media') {
+    my ($jobid, $fileid, $table);
+    my $lst;
+
+    # in this mode, we compute the result to get all needed media
+#    print STDERR "force=", CGI::param('force'), "\n";
+    if (CGI::param('force')) {
+        $table = fill_table_for_restore(@jobid);
+        if (!$table) {
+            exit 1;
+        }
+        # mysql is very slow without this index...
+        if ($bvfs->dbh_is_mysql()) {
+            $bvfs->dbh_do("CREATE INDEX idx_$table ON $table (JobId)");
+        }
+        $lst = get_media_list_with_dir($table);
+    } else {
+        $jobid = join(',', @jobid);
+        $fileid = join(',', grep { /^\d+(,\d+)*$/ } CGI::param('fileid'));
+        $lst = get_media_list($jobid, $fileid);
+    }        
+    
+    if ($lst) {
+        print "[";
+        print join(',', map { "['$_->[0]',$_->[1],$_->[2]]" } @$lst);
+        print "]\n";
+    }
 
-    my $jobid = join(',', @jobid);
-    my $fileid = join(',', grep { /^\d+$/ } CGI::param('fileid'));
-
-    # TODO: use client filter
-    my $q="
- SELECT DISTINCT VolumeName, InChanger
-   FROM File,
-    ( -- Get all media from this job
-      SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex,
-             VolumeName, Inchanger
-        FROM JobMedia JOIN Media USING (MediaId)
-       WHERE JobId IN ($jobid)
-       GROUP BY VolumeName, InChanger
-    ) AS allmedia
-  WHERE File.FileId IN ($fileid)
-    AND File.FileIndex >= allmedia.FirstIndex
-    AND File.FileIndex <= allmedia.LastIndex
-";
-    my $lst = $bvfs->dbh_selectall_arrayref($q);
-    print "[";
-    print join(',', map { "[ '$_->[0]', $_->[1]]" } @$lst);
-    print "]\n";
+    if ($table) {
+        $bvfs->dbh_do("DROP TABLE $table");
+    }
 
 }