deleteVersion(); } elseif ($show === 'updatesources') { $this->updateSources(); } elseif ($show === 'setdefault') { $this->setDefault(); } Util::redirect('?do=minilinux'); } User::assertPermission('view'); Dashboard::addSubmenu('?do=minilinux', Dictionary::translate('menu-versions')); Dashboard::addSubmenu('?do=minilinux&show=sources', Dictionary::translate('menu-sources')); } protected function doRender() { $show = Request::get('show', 'list', 'string'); if ($show === 'list') { // List branches and versions $branches = Database::queryAll('SELECT sourceid, branchid, title, color, description FROM minilinux_branch ORDER BY title ASC'); $versions = MiniLinux::queryAllVersionsByBranch(); $usage = MiniLinux::getBootMenuUsage(); $sourceList = []; // Group by branch for detailed listing, add usage info foreach ($branches as &$branch) { // Little hack: We abuse the title for ordering, so if the second char is a space, assume the first one // is just for sort order and remove it. if ($branch['title'][1] === ' ') { $branch['title'] = substr($branch['title'], 2); } $bid = 'div-' . str_replace('/', '-', $branch['branchid']); if (!isset($sourceList[$branch['sourceid']])) { $sourceList[$branch['sourceid']] = ['sourceid' => $branch['sourceid'], 'list' => []]; } $sourceList[$branch['sourceid']]['list'][] = [ 'title' => $branch['title'], 'color' => $branch['color'], 'bid' => $bid ]; $branch['bid'] = $bid; if (isset($versions[$branch['branchid']])) { $branch['versionlist'] = $this->renderVersionList($versions[$branch['branchid']], $usage); } } unset($branch); $sourceList = array_values($sourceList); } elseif ($show === 'sources') { // List sources $res = Database::simpleQuery('SELECT sourceid, title, url, lastupdate, pubkey FROM minilinux_source ORDER BY title, sourceid'); $sourceViewData = ['list' => [], 'show_refresh' => true]; $tooOld = strtotime('-7 days'); $showRefresh = strtotime('-5 minutes'); foreach ($res as $row) { $row['lastupdate_s'] = Util::prettyTime($row['lastupdate']); if ($row['lastupdate'] != 0 && $row['lastupdate'] < $tooOld) { $row['update_class'] = 'text-danger'; } if ($row['lastupdate'] > $showRefresh) { $sourceViewData['show_refresh'] = false; } $sourceViewData['list'][] = $row; } } // Output Render::addTemplate('page-minilinux', [ 'default' => Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT), 'sources' => $sourceList ?? null, ]); // Warning if (!MiniLinux::updateCurrentBootSetting()) { Message::addError('default-not-installed', Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT)); } if (isset($branches)) { Render::addTemplate('branches', ['branches' => $branches]); } elseif (isset($sourceViewData)) { Render::addTemplate('sources', $sourceViewData); } else { Message::addError('main.invalid-action', $show); } } protected function doAjax() { User::load(); $show = Request::post('show', false, 'string'); if ($show === 'version') { $this->ajaxVersionDetails(); } elseif ($show === 'download') { $this->ajaxDownload(); } } private function renderVersionList(array $versions, array $usage): string { $def = Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT); //$eff = Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT_EFFECTIVE); foreach ($versions as &$version) { $version['dateline_s'] = Util::prettyTime($version['dateline']); $version['orphan'] = ($version['orphan'] > 0 && $version['installed'] == MiniLinux::INSTALL_MISSING) || ($version['orphan'] > 1); $version['downloading'] = $version['taskid'] && Taskmanager::isRunning(Taskmanager::status($version['taskid'])); if ($version['installed'] != MiniLinux::INSTALL_MISSING && $version['versionid'] !== $def) { $version['showsetdefault'] = true; } if ($version['versionid'] === $def) { $version['isdefault'] = true; if (!$version['installed'] != MiniLinux::INSTALL_OK) { $version['default_class'] = 'bg-danger'; } } if (isset($usage[$version['versionid']])) { $version['usage'] = $usage[$version['versionid']]; } $version['versionid_dash'] = str_replace('/', '-', $version['versionid']); } return Render::parse('versionlist', ['versions' => array_values($versions)]); } private function ajaxVersionDetails() { 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, description, taskid, data, installed 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'] === null; if (is_array($data['files'])) { $valid = true; $sort = []; foreach ($data['files'] as &$file) { if (empty($file['name'])) { $sort[] = 'zzz' . implode(',', $file); continue; } $sort[] = $file['name']; $s = $this->getFileState($versionid, $file, $verify); if ($s !== self::FILE_OK) { $valid = false; } 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'] !== null) { $file['fileid'] = MiniLinux::fileToId($versionid, $file['name']); } } unset($file); array_multisort($sort, SORT_ASC, $data['files']); if (!$valid) { $data['verify_button'] = false; if ($ver['installed'] != MiniLinux::INSTALL_MISSING) { MiniLinux::setInstalledState($versionid, MiniLinux::INSTALL_BROKEN); } } elseif ($ver['installed'] != MiniLinux::INSTALL_OK && $verify) { MiniLinux::setInstalledState($versionid, MiniLinux::INSTALL_OK); } } if ($data['dltask'] !== null || $ver['installed'] != MiniLinux::INSTALL_MISSING) { MiniLinux::checkStage4($data, $data['s4_errors']); } $data['changelog'] = Util::markup($ver['description'] ?? ''); echo Render::parse('filelist', $data); } const FILE_OK = 0; const FILE_MISSING = 1; const FILE_SIZE_MISMATCH = 2; const FILE_CHECKSUM_BAD = 3; const FILE_NOT_READABLE = 4; private function getFileState(string $versionid, array $file, bool $verify): int { $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 (!is_readable($path)) return self::FILE_NOT_READABLE; if ($verify) { foreach (['sha512', 'sha384', 'sha256', 'sha224', 'sha1', 'md5'] as $algo) { if (isset($file[$algo])) { $calced = hash_file($algo, $path); if ($calced === false) continue; // Algo not supported? if ($calced !== $file['md5']) return self::FILE_CHECKSUM_BAD; } } } return self::FILE_OK; } private function fileStateToString($state) { switch ($state) { case self::FILE_CHECKSUM_BAD: return Dictionary::translate('file-checksum-bad'); case self::FILE_SIZE_MISMATCH: return Dictionary::translate('file-size-mismatch'); case self::FILE_MISSING: return Dictionary::translate('file-missing'); case self::FILE_NOT_READABLE: return Dictionary::translate('file-not-readable'); case self::FILE_OK: return Dictionary::translate('file-ok'); } 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 === null) { Message::addError('no-such-version', $version); Message::renderList(); } else { $this->ajaxVersionDetails(); } } 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)) { MiniLinux::setInstalledState($version['versionid'], MiniLinux::INSTALL_BROKEN); Message::addError('delete-error', $versionid, $task['data']['error']); } else { MiniLinux::setInstalledState($version['versionid'], MiniLinux::INSTALL_MISSING); Message::addSuccess('version-deleted', $versionid); } } } private function updateSources() { User::assertPermission('view'); // As it doesn't really change anything, accept view permission $ret = MiniLinux::updateList(); if ($ret > 0) { for ($i = 0; $i < 6; ++$i) { sleep(1); if (!Trigger::checkCallbacks()) break; } } } private function setDefault() { User::assertPermission('update'); $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; } MiniLinux::setDefaultVersion($version['versionid']); } }