]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Rework job file list API endpoint
authorMarcin Haba <marcin.haba@bacula.pl>
Wed, 28 Oct 2020 05:37:15 +0000 (06:37 +0100)
committerMarcin Haba <marcin.haba@bacula.pl>
Wed, 28 Oct 2020 05:37:15 +0000 (06:37 +0100)
Changes:
 - Use SQL query to get detailed file list
 - Add details parameter to job file list API endpoint

gui/baculum/protected/API/Class/BLStat.php
gui/baculum/protected/API/Class/JobManager.php
gui/baculum/protected/API/Pages/API/JobListFiles.php
gui/baculum/protected/API/openapi_baculum.json

index 5845ba581f85b6e706426c40424b9dad096aab9d..482e0f0465c67cd6667b08b21043e8165233cf99 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -104,5 +104,46 @@ class BLStat extends APIModule {
                }
                return $ret;
        }
+
+       /**
+        * Get decoded and human readable LStat values.
+        *
+        * @param string $lstat LStat value to decode
+        * @return array decoded LStat values
+        */
+       public function lstat_human($lstat) {
+               $value = $this->decode($lstat);
+               $value['mode'] = $this->get_human_mode($value['mode']);
+               return $value;
+       }
+
+       /**
+        * Get human readable mode/attributes (ex. drwx-r-xr-x).
+        *
+        * @param integer $dmode mode value in decimal LStat form
+        * @return string mode in human readable form
+        */
+       private function get_human_mode($dmode) {
+               $ts = [
+                       0140000 => 'ssocket',
+                       0120000 => 'llink',
+                       0100000 => '-file',
+                       0060000 => 'bblock',
+                       0040000 => 'ddir',
+                       0020000 => 'cchar',
+                       0010000 => 'pfifo'
+               ];
+
+               $p = $dmode;
+               $t = decoct($dmode & 0170000); // File Encoding Bit
+               $mode = (key_exists(octdec($t), $ts)) ? $ts[octdec($t)]{0} : 'u';
+               $mode .= (($p & 0x0100) ? 'r' : '-') . (($p & 0x0080) ? 'w' : '-');
+               $mode .= (($p & 0x0040) ? (($p & 0x0800) ?'s':'x'):(($p & 0x0800) ? 'S' : '-'));
+               $mode .= (($p & 0x0020) ? 'r':'-').(($p & 0x0010)? 'w' : '-');
+               $mode .= (($p & 0x0008) ? (($p & 0x0400) ?'s':'x'):(($p & 0x0400) ? 'S' : '-'));
+               $mode .= (($p & 0x0004) ? 'r':'-').(($p & 0x0002) ? 'w' : '-');
+               $mode .= (($p & 0x0001) ? (($p & 0x0200) ?'t':'x'):(($p & 0x0200) ? 'T' : '-'));
+               return $mode;
+       }
 }
 ?>
index 1ecf60a324941e9590deb0d96835ce9d019436ba..636ef7bc77050959c1f439a46c295f40a1342980 100644 (file)
@@ -298,5 +298,95 @@ WHERE Client.ClientId='$clientid' $jobs_criteria";
                $sth->execute();
                return $sth->fetchAll(PDO::FETCH_ASSOC);
        }
+
+       /**
+        * Get job file list
+        *
+        * @param integer $jobid job identifier
+        * @param string $type file list type: saved, deleted or all.
+        * @param integer $offset SQL query offset
+        * @param integer $limit SQL query limit
+        * @param string $search search file keyword
+        * @return array jobs job list
+        */
+       public function getJobFiles($jobid, $type, $offset = 0, $limit = 100, $search = null, $fetch_group = false) {
+               $type_crit = '';
+               switch ($type) {
+                       case 'saved': $type_crit = ' AND FileIndex > 0 '; break;
+                       case 'deleted': $type_crit = ' AND FileIndex <= 0 '; break;
+                       case 'all': $type_crit = ''; break;
+                       default: $type_crit = ' AND FileIndex > 0 '; break;
+               }
+
+               $search_crit = '';
+               if (is_string($search)) {
+                       $search_crit = " AND (LOWER(Path.Path) LIKE LOWER('%$search%') OR LOWER(Filename.Name) LIKE LOWER('%$search%')) ";
+               }
+
+               $fname_col = 'Path.Path || Filename.Name';
+               $db_params = $this->getModule('api_config')->getConfig('db');
+               if ($db_params['type'] === Database::MYSQL_TYPE) {
+                       $fname_col = 'CONCAT(Path.Path, Filename.Name)';
+               }
+
+               $offset_sql = '';
+               if ($offset) {
+                       $offset_sql = ' OFFSET ' . $offset;
+               }
+
+               $limit_sql = '';
+               if ($limit) {
+                       $limit_sql = ' LIMIT ' . $limit;
+               }
+
+               $sql = "SELECT $fname_col  AS file, 
+                               F.lstat     AS lstat, 
+                               F.fileindex AS fileindex 
+                        FROM ( 
+                            SELECT PathId     AS pathid, 
+                                   FilenameId AS filenameid, 
+                                   Lstat      AS lstat, 
+                                   FileIndex  AS fileindex 
+                            FROM 
+                                File 
+                            WHERE 
+                                JobId=$jobid 
+                                $type_crit 
+                            UNION ALL 
+                            SELECT PathId         AS pathid, 
+                                   FilenameId     AS filenameid, 
+                                   File.Lstat     AS lstat, 
+                                   File.FileIndex AS fileindex 
+                                FROM BaseFiles 
+                                JOIN File ON (BaseFiles.FileId = File.FileId) 
+                                WHERE 
+                                   BaseFiles.JobId=$jobid 
+                        ) AS F, Filename, Path 
+                        WHERE Filename.FilenameId=F.FilenameId 
+                        AND Path.PathId=F.PathId 
+                        $search_crit 
+                       $offset_sql $limit_sql";
+               $connection = JobRecord::finder()->getDbConnection();
+               $connection->setActive(true);
+               $pdo = $connection->getPdoInstance();
+               $sth = $pdo->prepare($sql);
+               $sth->execute();
+               $result = [];
+               if ($fetch_group) {
+                       $result = $sth->fetchAll(PDO::FETCH_COLUMN);
+               } else {
+                       $result = $sth->fetchAll(PDO::FETCH_ASSOC);
+
+                       // decode LStat value
+                       if (is_array($result)) {
+                               $blstat = $this->getModule('blstat');
+                               $result_len = count($result);
+                               for ($i = 0; $i < $result_len; $i++) {
+                                       $result[$i]['lstat'] = $blstat->lstat_human($result[$i]['lstat']);
+                               }
+                       }
+               }
+               return $result;
+       }
 }
 ?>
index 3ad1d0021b5fa9aaf7c79d13ec169e562d5d91fa..01ee7e96f897977daa0c93c7aa7edecd30f9e684 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -36,6 +36,7 @@ class JobListFiles extends BaculumAPIServer {
                $offset = $this->Request->contains('offset') ? intval($this->Request['offset']) : 0;
                $limit = $this->Request->contains('limit') ? intval($this->Request['limit']) : 0;
                $search = $this->Request->contains('search') && $misc->isValidPath($this->Request['search']) ? $this->Request['search'] : null;
+               $details = $this->Request->contains('details') && $misc->isValidBooleanTrue($this->Request['details']) ? $this->Request['details'] : false;
 
                $result = $this->getModule('bconsole')->bconsoleCommand(
                        $this->director,
@@ -45,41 +46,30 @@ class JobListFiles extends BaculumAPIServer {
                        array_shift($result->output);
                        $job = $this->getModule('job')->getJobById($jobid);
                        if (is_object($job) && in_array($job->name, $result->output)) {
-                               $cmd = array('list', 'files');
-                               if (is_string($type)) {
-                                       /**
-                                        * NOTE: type param has to be used BEFORE jobid=xx, otherwise it doesn't work.
-                                        * This behavior is also described in Bacula source code (src/dird/ua_output.c).
-                                        */
-                                       $cmd[] = 'type="' . $type . '"';
-                               }
-                               $cmd[] = 'jobid="' . $jobid . '"';
-                               $result = $this->getModule('bconsole')->bconsoleCommand(
-                                       $this->director,
-                                       $cmd
-                               );
-                               if ($result->exitcode === 0) {
-                                       $file_list = $this->getModule('list')->parseListFilesOutput($result->output);
-                                       if (is_string($search)) {
-                                               // Find items
-                                               $file_list = $this->getModule('list')->findFileListItems($file_list, $search);
-                                       }
-                                       $total_items = count($file_list);
-                                       if ($offset > 0) {
-                                               if ($limit > 0) {
-                                                       $file_list = array_slice($file_list, $offset, $limit);
-                                               } else {
-                                                       $file_list = array_slice($file_list, $offset);
-                                               }
-                                       } elseif ($limit > 0) {
-                                               $file_list = array_slice($file_list, 0, $limit);
-                                       }
-                                       $this->output = array('items' => $file_list, 'total' => $total_items);
-                                       $this->error = GenericError::ERROR_NO_ERRORS;
+                               if ($details) {
+                                       $result = $this->getDetailedOutput([
+                                               'jobid' => $jobid,
+                                               'type' => $type,
+                                               'offset' => $offset,
+                                               'limit' => $limit,
+                                               'search' => $search
+                                       ]);
                                } else {
-                                       $this->output = $result->output;
-                                       $this->error = $result->exitcode;
+                                       $result = $this->getSimpleOutput([
+                                               'jobid' => $jobid,
+                                               'type' => $type,
+                                               'offset' => $offset,
+                                               'limit' => $limit,
+                                               'search' => $search
+                                       ]);
+                                       //NOTE: Standarize raw and json output in new version API v2
+                                       $result = [
+                                               'items' => $result,
+                                               'totals' => count($result)
+                                       ];
                                }
+                               $this->output = $result;
+                               $this->error = GenericError::ERROR_NO_ERRORS;
                        } else {
                                $this->output = JobError::MSG_ERROR_JOB_DOES_NOT_EXISTS;
                                $this->error = JobError::ERROR_JOB_DOES_NOT_EXISTS;
@@ -89,5 +79,41 @@ class JobListFiles extends BaculumAPIServer {
                        $this->error = $result->exitcode;
                }
        }
+
+       /**
+        * Get simple output with file list and total number of items.
+        *
+        * @params array $params job parameters to get file list
+        * @return array file list
+        */
+       protected function getSimpleOutput($params = []) {
+               $result = $this->getModule('job')->getJobFiles(
+                       $params['jobid'],
+                       $params['type'],
+                       $params['offset'],
+                       $params['limit'],
+                       $params['search'],
+                       true
+               );
+               return $result;
+       }
+
+       /**
+        * Get detailed output with file list.
+        * It also includes LStat value.
+        *
+        * @params array $params job parameters to get file list
+        * @return array file list
+        */
+       protected function getDetailedOutput($params = []) {
+               $result = $this->getModule('job')->getJobFiles(
+                       $params['jobid'],
+                       $params['type'],
+                       $params['offset'],
+                       $params['limit'],
+                       $params['search']
+               );
+               return $result;
+       }
 }
 ?>
index 35acd1b4b563b945cff567243471926ea1283edc..071c72ed7622ae7ed685deec9dca802aed2bb46e 100644 (file)
                                                "schema": {
                                                        "type": "string"
                                                }
+                                       },
+                                       {
+                                               "name": "details",
+                                               "in": "query",
+                                               "description": "Show more details (including LStat value)",
+                                               "required":  false,
+                                               "schema": {
+                                                       "type": "boolean"
+                                               }
                                        }
                                ]
                        }