From f800abeea4f6c68182c51cd4aaea19d7636431c8 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 9 Oct 2019 17:31:19 +0200 Subject: [minilinux] Rewrite for multiple version/sources handling * You can supply multiple sources for updates (URLs) * Sources can provide multiple branches * Each branch can supply multiple versions (eg. updates) TODO: Set global default version TODO: Supply hook to serversetup-ipxe to add specific boot entries TODO: UX polish TODO: phpdoc/polish --- modules-available/minilinux/page.inc.php | 299 +++++++++++++++++++++---------- 1 file changed, 202 insertions(+), 97 deletions(-) (limited to 'modules-available/minilinux/page.inc.php') diff --git a/modules-available/minilinux/page.inc.php b/modules-available/minilinux/page.inc.php index e973ee6e..b54050bf 100644 --- a/modules-available/minilinux/page.inc.php +++ b/modules-available/minilinux/page.inc.php @@ -12,120 +12,225 @@ class Page_MiniLinux extends Page Util::redirect('?do=Main'); } + if (Request::isPost()) { + $show = Request::post('show', false, 'string'); + if ($show === 'delete') { + $this->deleteVersion(); + } elseif ($show === 'updatesources') { + $this->updateSources(); + } + Util::redirect('?do=minilinux'); + } + User::assertPermission('view'); } protected function doRender() { - Render::addTemplate('page-minilinux', array( - 'listurl' => '?do=MiniLinux&async=true&action=list' - )); + Render::addTemplate('page-minilinux'); + // List branches and versions + $branches = Database::queryAll('SELECT sourceid, branchid, title FROM minilinux_branch ORDER BY title ASC'); + $versions = $this->queryAllVersionsByBranch(); + foreach ($branches as &$branch) { + if (isset($versions[$branch['branchid']])) { + $branch['versionlist'] = $this->renderVersionList($versions[$branch['branchid']]); + } + } + Render::addTemplate('branches', ['branches' => $branches]); + // List sources + $res = Database::simpleQuery('SELECT sourceid, title, url, lastupdate, pubkey FROM minilinux_source ORDER BY title, sourceid'); + $data = ['list' => [], 'show_refresh' => true]; + $tooOld = strtotime('-7 days'); + $showRefresh = strtotime('-10 minutes'); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $row['lastupdate_s'] = Util::prettyTime($row['lastupdate']); + if ($row['lastupdate'] != 0 && $row['lastupdate'] < $tooOld) { + $row['update_class'] = 'text-danger'; + } + if ($row['lastupdate'] > $showRefresh) { + $data['show_refresh'] = false; + } + $data['list'][] = $row; + } + Render::addTemplate('sources', $data); } protected function doAjax() { - $data = Property::getVersionCheckInformation(); - if (!is_array($data) || !isset($data['systems'])) { - echo Render::parse('messagebox', array( - 'type' => 'danger', - 'message' => 'Failed to retrieve the list: ' . print_r($data, true) - ), 'main'); - return; + User::load(); + $show = Request::post('show', false, 'string'); + if ($show === 'version') { + $this->ajaxVersion(); + } elseif ($show === 'download') { + $this->ajaxDownload(); } - $action = Request::any('action'); - $selectedVersion = (int)Request::any('version', 0); - switch ($action) { - case 'list': - $count = 0; - foreach ($data['systems'] as &$system) { - // Get latest version, build simple array of all version numbers - $versionNumbers = array(); - $selected = false; - foreach ($system['versions'] as $version) { - if (!is_numeric($version['version']) || $version['version'] < 1) - continue; - if ($selectedVersion === 0 && ($selected === false || $selected['version'] < $version['version'])) - $selected = $version; - elseif ($version['version'] == $selectedVersion) - $selected = $version; - $versionNumbers[(int)$version['version']] = array( - 'version' => $version['version'] - ); + } + + private function queryAllVersionsByBranch() + { + $list = []; + $res = Database::simpleQuery('SELECT branchid, versionid, title, dateline, orphan, taskid + FROM minilinux_version ORDER BY branchid, dateline, versionid'); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $list[$row['branchid']][] = $row; + } + return $list; + } + + private function renderVersionList($versions) + { + foreach ($versions as &$version) { + $version['dateline_s'] = Util::prettyTime($version['dateline']); + $version['orphan'] = ($version['orphan'] > 5); + $version['installed'] = is_dir(CONFIG_HTTP_DIR . '/' . $version['versionid']); + $version['downloading'] = $version['taskid'] && Taskmanager::isRunning(Taskmanager::status($version['taskid'])); + } + return Render::parse('versionlist', ['versions' => $versions]); + } + + private function ajaxVersion() + { + User::assertPermission('view'); + $verify = Request::post('verify', false, 'bool'); + $versionid = Request::post('version', false, 'string'); + if ($versionid === false) { + die('What!'); + } + $ver = Database::queryFirst('SELECT versionid, taskid, data FROM minilinux_version WHERE versionid = :versionid', + ['versionid' => $versionid]); + if ($ver === false) { + die('No such version'); + } + $versionid = $ver['versionid']; // Just to be sure -- should be safe for building a path either way + $data = json_decode($ver['data'], true); + if (!is_array($data)) { + die('Corrupted data'); + } + $data['versionid'] = $versionid; + $data['dltask'] = MiniLinux::validateDownloadTask($versionid, $ver['taskid']); + $data['verify_button'] = !$verify && $data['dltask'] === false; + if (is_array($data['files'])) { + $sort = []; + foreach ($data['files'] as &$file) { + if (empty($file['name'])) { + $sort[] = 'zzz' . implode(',', $file); + continue; } - if ($selected === false) continue; // No versions for this system!? - ksort($versionNumbers); - // Mark latest version as selected - $versionNumbers[(int)$selected['version']]['selected'] = true; - // Add status information to system and its files - foreach ($selected['files'] as &$file) { - $file['uid'] = 'dlid' . $count++; - $local = CONFIG_HTTP_DIR . '/' . $system['id'] . '/' . $file['name']; - if (!file_exists($local) || filesize($local) !== $file['size'] || filemtime($local) < $file['mtime'] - || md5_file($local) !== $file['md5']) { - $file['fileChanged'] = true; - $system['systemChanged'] = true; - } - $taskId = Property::getDownloadTask($file['md5']); - if ($taskId !== false) { - $task = Taskmanager::status($taskId); - if (isset($task['data']['progress'])) { - $file['download'] = Render::parse('download', array( - 'task' => $taskId, - 'name' => $file['name'] - )); - } - } + $sort[] = $file['name']; + $s = $this->getFileState($versionid, $file, $verify); + if ($s !== self::FILE_OK) { + $data['verify_button'] = false; + $data['download_button'] = !$data['dltask']; } - unset($system['versions']); - $system['files'] = $selected['files']; - $system['version'] = $selected['version']; - } - $data['versions'] = array_values($versionNumbers); - Permission::addGlobalTags($data['perms'], null, ['update']); - echo Render::parse('filelist', $data); - return; - case 'download': - User::assertPermission('update'); - $id = Request::post('id'); - $name = Request::post('name'); - if (!$id || !$name || strpos("$id$name", '/') !== false) { - echo "Invalid download request"; - return; - } - $file = false; - $gpg = 'missing'; - foreach ($data['systems'] as &$system) { - if ($system['id'] !== $id) continue; - foreach ($system['versions'] as &$version) { - if ($version['version'] != $selectedVersion) continue; - foreach ($version['files'] as &$f) { - if ($f['name'] !== $name) continue; - $file = $f; - if (!empty($f['gpg'])) $gpg = $f['gpg']; - break; - } + if ($s !== self::FILE_MISSING) { + $data['delete_button'] = true; + } + $file['state'] = $this->fileStateToString($s); + if (isset($file['size'])) { + $file['size_s'] = Util::readableFileSize($file['size']); + } + if (isset($file['mtime'])) { + $file['mtime_s'] = Util::prettyTime($file['mtime']); + } + if ($data['dltask']) { + $file['fileid'] = MiniLinux::fileToId($versionid, $file['name']); } } - if ($file === false) { - echo "Nonexistent system/file: $id / $name"; - return; - } - $task = Taskmanager::submit('DownloadFile', array( - 'url' => CONFIG_REMOTE_ML . '/' . $id . '/' . $selectedVersion . '/' . $name, - 'destination' => CONFIG_HTTP_DIR . '/' . $id . '/' . $name, - 'gpg' => $gpg - )); - if (!isset($task['id'])) { - echo 'Error launching download task: ' . $task['statusCode']; - return; + unset($file); + array_multisort($sort, SORT_ASC, $data['files']); + } + echo Render::parse('filelist', $data); + } + + const FILE_OK = 0; + const FILE_MISSING = 1; + const FILE_SIZE_MISMATCH = 2; + const FILE_CHECKSUM_BAD = 3; + + private function getFileState($versionid, $file, $verify) + { + $path = CONFIG_HTTP_DIR . '/' . $versionid . '/' . $file['name']; + if (!is_file($path)) + return self::FILE_MISSING; + if (isset($file['size']) && filesize($path) != $file['size']) + return self::FILE_SIZE_MISMATCH; + if ($verify) { + // TODO: Others + if (isset($file['md5'])) { + if (md5_file($path) !== $file['md5']) + return self::FILE_CHECKSUM_BAD; } - Property::setDownloadTask($file['md5'], $task['id']); - echo Render::parse('download', array( - 'name' => $name, - 'task' => $task['id'] - )); + } + return self::FILE_OK; + } + + private function fileStateToString($state) + { + switch ($state) { + case self::FILE_CHECKSUM_BAD: + return Dictionary::translate('file-checksum-bad', true); + case self::FILE_SIZE_MISMATCH: + return Dictionary::translate('file-size-mismatch', true); + case self::FILE_MISSING: + return Dictionary::translate('file-missing', true); + case self::FILE_OK: + return Dictionary::translate('file-ok', true); + } + return '???'; + } + + private function ajaxDownload() + { + User::assertPermission('update'); + $version = Request::post('version', false, 'string'); + if ($version === false) { + die('No version'); + } + $task = MiniLinux::downloadVersion($version); + if ($task === false) { + Message::addError('no-such-version', $version); + Message::renderList(); + } else { + $this->ajaxVersion(); + } + } + + private function deleteVersion() + { + User::assertPermission('delete'); + $versionid = Request::post('version', false, 'string'); + if ($versionid === false) { + Message::addError('main.parameter-missing', 'versionid'); + return; + } + $version = Database::queryFirst('SELECT versionid FROM minilinux_version WHERE versionid = :versionid', + ['versionid' => $versionid]); + if ($version === false) { + Message::addError('no-such-version'); return; } + $path = CONFIG_HTTP_DIR . '/' . $version['versionid']; + $task = Taskmanager::submit('DeleteDirectory', [ + 'path' => $path, + 'recursive' => true, + ]); + if ($task !== false) { + $task = Taskmanager::waitComplete($task, 2500); + if (Taskmanager::isFailed($task)) { + Message::addError('delete-error', $versionid, $task['data']['error']); + } else { + Message::addSuccess('version-deleted', $versionid); + } + } + } + + private function updateSources() + { + $ret = MiniLinux::updateList(); + if ($ret > 0) { + sleep(2); + Trigger::checkCallbacks(); + } } } -- cgit v1.2.3-55-g7522