From: Marcin Haba Date: Wed, 28 Oct 2020 05:37:15 +0000 (+0100) Subject: baculum: Rework job file list API endpoint X-Git-Tag: Release-9.6.7~39 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bfc97445c0097218125ebba722d7eecee3a2468b;p=thirdparty%2Fbacula.git baculum: Rework job file list API endpoint Changes: - Use SQL query to get detailed file list - Add details parameter to job file list API endpoint --- diff --git a/gui/baculum/protected/API/Class/BLStat.php b/gui/baculum/protected/API/Class/BLStat.php index 5845ba581..482e0f046 100644 --- a/gui/baculum/protected/API/Class/BLStat.php +++ b/gui/baculum/protected/API/Class/BLStat.php @@ -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; + } } ?> diff --git a/gui/baculum/protected/API/Class/JobManager.php b/gui/baculum/protected/API/Class/JobManager.php index 1ecf60a32..636ef7bc7 100644 --- a/gui/baculum/protected/API/Class/JobManager.php +++ b/gui/baculum/protected/API/Class/JobManager.php @@ -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; + } } ?> diff --git a/gui/baculum/protected/API/Pages/API/JobListFiles.php b/gui/baculum/protected/API/Pages/API/JobListFiles.php index 3ad1d0021..01ee7e96f 100644 --- a/gui/baculum/protected/API/Pages/API/JobListFiles.php +++ b/gui/baculum/protected/API/Pages/API/JobListFiles.php @@ -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; + } } ?> diff --git a/gui/baculum/protected/API/openapi_baculum.json b/gui/baculum/protected/API/openapi_baculum.json index 35acd1b4b..071c72ed7 100644 --- a/gui/baculum/protected/API/openapi_baculum.json +++ b/gui/baculum/protected/API/openapi_baculum.json @@ -1084,6 +1084,15 @@ "schema": { "type": "string" } + }, + { + "name": "details", + "in": "query", + "description": "Show more details (including LStat value)", + "required": false, + "schema": { + "type": "boolean" + } } ] }