Compare commits

..
7 Commits
Author SHA1 Message Date
owendswang 939257eda0 Fixed PKG install from internal storage.
Adjust back button display effects
2026-08-09 12:38:23 +08:00
owendswang 3efaf24385 Highlight specific directories' icon.
Moved the back button to the header of the name column.
2026-08-08 17:47:54 +08:00
owendswang 5302e971f9 Enhance credits with PKG install function reference
Updated the credits section to include PKG install function in the websrv reference.
2026-08-06 21:18:15 +08:00
owendswang 582d154cdf Changed the way to end the daemon process 2026-08-06 14:51:43 +08:00
owendswang a61ac54e1f Added PKG install function 2026-08-06 14:33:02 +08:00
owendswang 6a110f95ae v1.1 2026-08-03 23:35:41 +08:00
owendswang b463c3f86b Small optimization of directory deletion 2026-08-01 00:11:46 +08:00
20 changed files with 564 additions and 146 deletions

No files matched your search

+2 -2
View File
@@ -13,7 +13,7 @@ ifeq ($(MAKECMDGOALS),)
endif
endif
VERSION_TAG := v1.0
VERSION_TAG := v1.3
TITLE_ID := FMGR88888
PYTHON ?= python3
STRIP ?= $(PS5_PAYLOAD_SDK)/bin/prospero-strip
@@ -24,7 +24,7 @@ HOST_PKG_CONFIG ?= pkg-config
BIN := web-file-mgr.elf
LINUX_BIN := web-file-mgr-linux
COMMON_SRCS := src/main.c src/websrv.c src/filemgr.c src/file_response.c src/task.c src/upload.c src/download.c src/text.c src/list.c src/space.c src/fs_util.c src/json_util.c src/path_util.c src/asset.c src/mime.c src/notify.c
COMMON_SRCS := src/main.c src/websrv.c src/filemgr.c src/file_response.c src/task.c src/upload.c src/download.c src/text.c src/list.c src/space.c src/fs_util.c src/json_util.c src/path_util.c src/asset.c src/mime.c src/notify.c src/pkg_installer.c
PS5_SRCS := $(COMMON_SRCS) src/app_installer.c
LINUX_SRCS := $(COMMON_SRCS)
BASE_ASSETS := $(filter-out %.dds,$(wildcard assets/*))
+2 -1
View File
@@ -38,6 +38,7 @@ PS5 web file manager payload. It runs an HTTP UI starting at port `8888`, instal
- Home screen launcher icon and browser favicon use the same embedded `icon0.png` data in the PS5 build to avoid storing the icon twice in the ELF.
- Create/edit text files ending with `.txt`, `.json`, `.xml`, `.ini`, `.cfg`, `.conf`, `.md`, `.log`, `.lua`, `.js`, `.css`, `.html`, `.htm`, `.c`, `.h`, `.cpp`, `.hpp`, `.sh`, `.csv`, `.yaml`, `.yml`, `.shn`.
- Preview image files ending with `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.webp`.
- Install PKG files ends with `.pkg`.
## Build
@@ -120,7 +121,7 @@ On first startup the payload installs a `PS5 Web File Manager` web shortcut in t
This project was built with reference to these projects:
- **[ps5-payload-dev/websrv](https://github.com/ps5-payload-dev/websrv):** HTTP server structure, static asset embedding ideas, and PS5 browser/websrv behavior. License: GPLv3+.
- **[ps5-payload-dev/websrv](https://github.com/ps5-payload-dev/websrv):** HTTP server structure, static asset embedding ideas, PS5 browser/websrv behavior and PKG install function. License: GPLv3+.
- **[ps5-payload-dev/ftpsrv](https://github.com/ps5-payload-dev/ftpsrv):** PS5 payload conventions, home screen launcher/install flow reference, process handling style and startup installation reference. License: GPLv3+.
- **[itsPLK/ps5-payload-manager](https://github.com/itsPLK/ps5-payload-manager):** Payload building behavior. License: GPLv3.
- **[libmicrohttpd](https://ftp.gnu.org/gnu/libmicrohttpd/):** Used as the embedded HTTP server library. It is licensed by GNU under the LGPL; this payload links it as the SDK-provided static library.
Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

+9 -1
View File
@@ -43,6 +43,7 @@
<button id="renameBtn" data-i18n="rename"></button>
<button id="downloadBtn" class="remote-only" data-i18n="download"></button>
<button id="deleteBtn" class="danger" data-i18n="delete"></button>
<button id="installPkgBtn" class="install-action" data-i18n="install" hidden></button>
<button id="pasteBtn" class="paste-action" hidden>
<span id="pasteVerb" data-i18n="paste"></span>
<span id="pasteName" class="paste-name"></span><span id="pasteCount" class="paste-count"></span>
@@ -71,7 +72,14 @@
<thead>
<tr>
<th class="select-col"><label class="select-hit"><input id="selectAll" type="checkbox"></label></th>
<th class="name-col sortable" data-sort="name" data-i18n="name"></th>
<th class="name-col sortable" data-sort="name">
<div class="name-head">
<button id="parentBtn" class="parent-nav-button" type="button" aria-label="Parent directory" disabled>
<img src="/icon-back.png" alt="">
</button>
<span class="name-heading" data-i18n="name"></span>
</div>
</th>
<th class="type-col sortable" data-sort="type" data-i18n="type"></th>
<th class="size-col sortable" data-sort="size" data-i18n="size"></th>
<th class="time-col sortable" data-sort="mtime" data-i18n="mtime"></th>
+10 -1
View File
@@ -19,7 +19,13 @@ window.WFM_LANG = {
refresh: "Refresh",
mkdir: "New Folder",
newText: "New Text",
install: "Install",
installPackage: "Install package",
pkgInstalling: "Starting package installation: {name}...",
pkgInstallStarted: "Package installation started: {name}",
file: "File",
textFile: "Text",
image: "Image",
dir: "Folder",
parent: "Parent",
name: "Name",
@@ -87,7 +93,7 @@ window.WFM_LANG = {
newTextPrompt: "Text file name",
creatingText: "Creating text file...",
createTextFailed: "Failed to create text file: {error}",
unsavedSaveConfirm: "The text has unsaved changes. Save before closing?\n\nCancel discards the changes and closes.",
unsavedSaveConfirm: "The text has unsaved changes. Close without saving?\n\nCancel keeps the editor open.",
loadingText: "Loading text...",
savingText: "Saving...",
textSaved: "Text saved",
@@ -118,6 +124,9 @@ window.WFM_LANG = {
err_source_destination_same: "Source and destination are the same",
err_destination_inside_source: "Cannot copy or move a folder inside itself",
err_invalid_path: "Invalid path",
err_pkg_type_invalid: "Only .pkg files can be installed",
err_pkg_install_failed: "Package installation failed ({arg})",
err_pkg_install_unsupported: "Package installation is only available on PS5",
err_file_not_found: "File not found",
err_file_already_exists: "A file with the same name already exists",
err_invalid_method: "Invalid request method",
+10 -1
View File
@@ -19,7 +19,13 @@ window.WFM_LANG = {
refresh: "刷新",
mkdir: "新建目录",
newText: "新建文本",
install: "安装",
installPackage: "安装 PKG",
pkgInstalling: "正在提交安装:{name}...",
pkgInstallStarted: "已开始安装:{name}",
file: "文件",
textFile: "文本",
image: "图片",
dir: "目录",
parent: "上级目录",
name: "名称",
@@ -87,7 +93,7 @@ window.WFM_LANG = {
newTextPrompt: "文本文件名",
creatingText: "正在新建文本...",
createTextFailed: "新建文本失败: {error}",
unsavedSaveConfirm: "文本有未保存的修改,是否保存后关闭?\n\n取消将放弃修改并关闭。",
unsavedSaveConfirm: "文本有未保存的修改,是否不保存并关闭?\n\n取消将留在编辑器中。",
loadingText: "正在读取文本...",
savingText: "正在保存...",
textSaved: "文本已保存",
@@ -118,6 +124,9 @@ window.WFM_LANG = {
err_source_destination_same: "源和目标相同",
err_destination_inside_source: "不能复制或移动目录到它自己的内部",
err_invalid_path: "路径无效",
err_pkg_type_invalid: "只能安装 .pkg 文件",
err_pkg_install_failed: "PKG 安装失败({arg})",
err_pkg_install_unsupported: "PKG 安装仅支持 PS5 环境",
err_file_not_found: "文件不存在",
err_file_already_exists: "同名文件已存在",
err_invalid_method: "请求方式无效",
+110 -7
View File
@@ -297,8 +297,8 @@ button:focus,
outline-offset: 2px;
}
button:not(.row-action):hover,
.button:hover {
button:not(.row-action):hover:not(:disabled),
.button:hover:not(:disabled) {
background: #303945;
}
@@ -319,7 +319,7 @@ button:disabled,
background: #285943;
}
button.primary:hover {
button.primary:hover:not(:disabled) {
background: #347358;
}
@@ -329,7 +329,7 @@ button.primary:hover {
}
.paste-action {
max-width: 480px;
max-width: 360px;
}
.paste-action > * {
@@ -341,12 +341,23 @@ button.primary:hover {
display: inline-block;
flex: 1 1 auto;
min-width: 0;
max-width: 180px;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.install-action {
min-width: 88px;
border-color: #8c6425;
background: #352b1c;
color: #f8dfae;
}
button.install-action:hover:not(:disabled) {
background: #433521;
}
.paste-count {
white-space: nowrap;
}
@@ -429,6 +440,80 @@ th.sorted-desc::after {
content: "v";
}
.name-head {
height: 78px;
min-height: 78px;
display: flex;
align-items: center;
min-width: 0;
}
th.name-col {
padding: 0;
}
th.name-col.sortable::after {
content: none;
}
.name-heading {
min-width: 100px;
padding-left: 18px;
overflow: hidden;
text-overflow: ellipsis;
}
.name-heading::after {
content: "";
display: inline-block;
width: 12px;
margin-left: 8px;
color: #aeb8c2;
text-align: center;
}
th.name-col.sorted-asc .name-heading::after {
content: "^";
}
th.name-col.sorted-desc .name-heading::after {
content: "v";
}
.parent-nav-button {
width: 78px;
min-width: 78px;
height: 78px;
min-height: 78px;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
position: relative;
}
.parent-nav-button img {
width: 32px;
height: 32px;
display: block;
}
.parent-nav-button:hover:not(:disabled) {
background: #222932;
}
.parent-nav-button:not(:disabled):focus {
outline-offset: -5px;
}
.parent-nav-button:disabled img {
opacity: .42;
}
.parent-nav-button:disabled {
pointer-events: auto;
}
.select-col {
width: 74px;
padding: 0;
@@ -516,6 +601,13 @@ input[type="checkbox"] {
display: block;
}
.special-folder-icon {
-webkit-filter: hue-rotate(65deg);
filter: hue-rotate(65deg);
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
.row-action {
width: 100%;
height: 78px;
@@ -647,6 +739,7 @@ input[type="checkbox"] {
.image-preview-stage {
width: 100%;
height: calc(100vh - 250px);
min-width: 0;
min-height: 0;
flex: 1 1 auto;
@@ -661,8 +754,7 @@ input[type="checkbox"] {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
object-fit: contain;
max-height: calc(100vh - 250px);
pointer-events: none;
-webkit-user-drag: none;
user-select: none;
@@ -975,12 +1067,23 @@ input[type="checkbox"] {
tr,
thead tr,
.name-head,
.parent-nav-button,
.select-hit,
.row-action {
height: 56px;
min-height: 56px;
}
.parent-nav-button {
width: 56px;
min-width: 56px;
}
.name-heading {
padding-left: 12px;
}
.row-action {
padding: 10px 12px;
}
+141 -20
View File
@@ -18,6 +18,9 @@ let directoryLoadingStartedAt = 0;
let taskOverlayTimer = 0;
let lastCompletionId = null;
let taskPollFailedAlertShown = false;
let localActionBusy = false;
let specialStoragePaths = {};
let hasSpecialMntStorage = false;
let textEditorPath = null;
let textEditorVersion = null;
let textEditorOriginal = "";
@@ -26,7 +29,7 @@ let downloadFrame = null;
let uploadXhr = null;
let L = {};
const APP_VERSION = "v1.0";
const APP_VERSION = "v1.3";
const LAST_PATH_KEY = "ps5-web-file-mgr:last-path";
const SORT_KEY = "ps5-web-file-mgr:list-sort";
const LOADING_DISPLAY_DELAY = 250;
@@ -49,11 +52,13 @@ const versionEl = document.getElementById("versionText");
const tasksEl = document.getElementById("tasks");
const overlayEl = document.getElementById("taskOverlay");
const selectAllEl = document.getElementById("selectAll");
const parentBtn = document.getElementById("parentBtn");
const pasteBtn = document.getElementById("pasteBtn");
const pasteVerbEl = document.getElementById("pasteVerb");
const pasteNameEl = document.getElementById("pasteName");
const pasteCountEl = document.getElementById("pasteCount");
const pasteTargetTextEl = document.getElementById("pasteTargetText");
const installPkgBtn = document.getElementById("installPkgBtn");
const clearClipboardBtn = document.getElementById("clearClipboardBtn");
const downloadBtn = document.getElementById("downloadBtn");
const uploadMenuEl = document.getElementById("uploadMenu");
@@ -131,6 +136,8 @@ function applyStaticText() {
}
exitBtn.title = t("exit");
exitBtn.setAttribute("aria-label", t("exit"));
parentBtn.title = t("parent");
parentBtn.setAttribute("aria-label", t("parent"));
versionEl.textContent = APP_VERSION;
if (initLoadingEl) initLoadingEl.hidden = true;
}
@@ -523,6 +530,7 @@ function saveSort() {
async function refreshSpaces() {
try {
const data = await api("/api/space", { path: cwd });
updateSpecialStoragePaths(data.spaces || []);
spaceInfoEl.innerHTML = "";
if ((data.spaces || []).length) {
const prefix = document.createElement("span");
@@ -544,6 +552,24 @@ async function refreshSpaces() {
}
}
function updateSpecialStoragePaths(spaces) {
const next = {};
let hasMnt = false;
for (let i = 0; i < spaces.length; i++) {
const path = spaces[i].path || "";
if (/^\/mnt\/(?:usb|ext)[0-9]+$/.test(path)) {
next[path] = true;
hasMnt = true;
}
}
const changed = hasMnt !== hasSpecialMntStorage ||
Object.keys(next).join("\n") !== Object.keys(specialStoragePaths).join("\n");
specialStoragePaths = next;
hasSpecialMntStorage = hasMnt;
if (changed && entries.length) render();
}
function selectedEntries() {
return entries.filter(item => selected.has(item.path));
}
@@ -559,6 +585,7 @@ function pathIsSameOrChild(parent, child) {
}
function typeLabel(type) {
if (type === "parent") return t("parent");
return type === "d" ? t("dir") : t("file");
}
@@ -571,6 +598,61 @@ function isPreviewableImage(item) {
return item.type === "-" && /\.(png|jpe?g|gif|bmp|webp)$/i.test(item.name);
}
function isPkgPackage(item) {
return item.type === "-" && /\.pkg$/i.test(item.name);
}
function isSpecialDirectory(item) {
if (item.type !== "d") return false;
if (item.path === "/data") return true;
if (item.path === "/mnt") return hasSpecialMntStorage;
return Boolean(specialStoragePaths[item.path]);
}
function itemTypeLabel(item) {
if (item.type === "parent" || item.type === "d") return typeLabel(item.type);
if (isPkgPackage(item)) return "PKG";
if (isPreviewableImage(item)) return t("image");
if (isEditableText(item)) return t("textFile");
return t("file");
}
async function installPkg(item) {
if (busy || loadingPath) return;
try {
localActionBusy = true;
setBusy(true);
setStatus(t("pkgInstalling", { name: displayName(item) }));
await api("/api/install-pkg", { path: item.path });
setStatus(t("pkgInstallStarted", { name: displayName(item) }));
} catch (err) {
showActionFailed(t("installPackage"), err.message);
} finally {
localActionBusy = false;
setBusy(false);
}
}
async function actionInstallSelectedPkgs() {
if (busy || loadingPath) return;
const items = selectedEntries().filter(isPkgPackage);
if (!items.length) return;
try {
localActionBusy = true;
setBusy(true);
for (let i = 0; i < items.length; i++) {
setStatus(t("pkgInstalling", { name: displayName(items[i]) }));
await api("/api/install-pkg", { path: items[i].path });
}
setStatus(t("pkgInstallStarted", { name: itemTitle(items) }));
} catch (err) {
showActionFailed(t("installPackage"), err.message);
} finally {
localActionBusy = false;
setBusy(false);
}
}
function openImagePreview(item) {
if (busy) return;
imagePreviewNameEl.textContent = displayName(item);
@@ -609,7 +691,7 @@ function closeTextEditor() {
function requestCloseTextEditor() {
if (textEditorBusy) return;
if (textEditorEl.value !== textEditorOriginal &&
confirm(t("unsavedSaveConfirm"))) return saveTextEditor();
!confirm(t("unsavedSaveConfirm"))) return;
return closeTextEditor();
}
@@ -708,6 +790,17 @@ function resetScrollTop() {
if (window.requestAnimationFrame) requestAnimationFrame(apply);
}
function revealPathInList(path) {
const row = filesEl.querySelector("tr[data-path=\"" + cssEscape(path) + "\"]");
if (!row) return;
const top = row.offsetTop;
const maxScroll = contentEl.scrollHeight - contentEl.clientHeight;
if (maxScroll <= 0) return;
const target = top - Math.floor((contentEl.clientHeight - row.offsetHeight) / 2);
contentEl.scrollTop = Math.max(0, Math.min(maxScroll, target));
}
function clipboardTitle() {
if (!clipboard || clipboard.items.length === 0) return "";
return itemTitle(clipboard.items);
@@ -740,7 +833,7 @@ function compareEntries(a, b) {
let result = 0;
if (sortKey === "name") result = compareText(displayName(a), displayName(b));
else if (sortKey === "type") result = compareText(typeLabel(a.type), typeLabel(b.type));
else if (sortKey === "type") result = compareText(itemTypeLabel(a), itemTypeLabel(b));
else if (sortKey === "size") result = (a.type === "d" ? 0 : Number(a.size || 0)) -
(b.type === "d" ? 0 : Number(b.size || 0));
else if (sortKey === "mtime") result = Number(a.mtime || 0) - Number(b.mtime || 0);
@@ -804,6 +897,19 @@ function renderClipboard() {
clearClipboardBtn.disabled = locked;
}
function renderInstallPkgButton(items, locked) {
const pkgs = items.filter(isPkgPackage);
installPkgBtn.hidden = pkgs.length === 0;
if (!pkgs.length) {
installPkgBtn.title = "";
installPkgBtn.disabled = true;
return;
}
const title = itemTitle(pkgs);
installPkgBtn.title = t("installPackage") + ": " + title;
installPkgBtn.disabled = locked;
}
function singleSelected() {
const items = selectedEntries();
return items.length === 1 ? items[0] : null;
@@ -824,8 +930,10 @@ function updateButtons() {
document.getElementById("mkdirBtn").disabled = locked;
newTextBtn.disabled = locked;
selectAllEl.disabled = locked;
parentBtn.disabled = cwd === "/";
selectAllEl.checked = entries.length > 0 && items.length === entries.length;
selectAllEl.indeterminate = items.length > 0 && items.length < entries.length;
renderInstallPkgButton(items, locked);
renderClipboard();
}
@@ -964,14 +1072,7 @@ function render() {
emptyEl.hidden = entries.length !== 0;
const fragment = document.createDocumentFragment();
const rows = cwd === "/" ? entries : [{
name: "..",
path: parentPath(cwd),
type: "parent",
mode: 0,
size: 0,
mtime: 0
}].concat(entries);
const rows = entries;
for (const item of rows) {
const isParent = item.type === "parent";
@@ -1004,8 +1105,9 @@ function render() {
nameText.textContent = displayName(item);
nameBtn.title = displayPath(item.path);
const iconImg = document.createElement("img");
iconImg.className = "icon";
iconImg.className = "icon" + (isSpecialDirectory(item) ? " special-folder-icon" : "");
iconImg.src = isParent ? "/icon-up.png" : item.type === "d" ? "/icon-folder.png" :
isPkgPackage(item) ? "/icon-pkg.png" :
isPreviewableImage(item) ? "/icon-image.png" :
isEditableText(item) ? "/icon-file.png" : "/icon-generic.png";
iconImg.alt = "";
@@ -1017,7 +1119,7 @@ function render() {
tr,
selectTd,
nameTd,
classCell("type-col", isParent ? t("parent") : item.type === "d" ? t("dir") : t("file")),
classCell("type-col", itemTypeLabel(item)),
classCell("size-col", formatSize(item.size, item.type)),
classCell("time-col", formatTime(item.mtime)),
classCell("mode-col", isParent ? "" : "0" + Number(item.mode).toString(8))
@@ -1035,6 +1137,15 @@ function parentPath(path) {
return parts.join("/") || "/";
}
function actionParentDirectory(event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
if (busy || loadingPath || cwd === "/") return;
load(parentPath(cwd), undefined, false, true);
}
function cell(text) {
const td = document.createElement("td");
td.textContent = text;
@@ -1076,8 +1187,8 @@ async function load(path, scrollTop, force, alertOnError) {
if (alertOnError) alert(err.message);
return false;
} finally {
hideContentLoading();
loadingPath = null;
hideContentLoading();
}
}
@@ -1164,10 +1275,10 @@ function validatePasteTarget() {
return {
ok: false,
message: t("removeConflictFirst", {
existingType: typeLabel(existing.type),
existingType: itemTypeLabel(existing),
name: item.name,
label: clipboard.op === "move" ? t("move") : t("copy"),
sourceType: typeLabel(item.type)
sourceType: itemTypeLabel(item)
})
};
}
@@ -1334,7 +1445,7 @@ function renderTasks(tasks) {
DOWNLOAD_OVERLAY_DISPLAY_DELAY : LOADING_DISPLAY_DELAY);
else {
hideTaskOverlay();
setBusy(false);
if (!localActionBusy) setBusy(false);
}
if (!hasActive) return;
@@ -1436,8 +1547,11 @@ async function pollTasks() {
}));
}
const wasBusy = busy;
const wasTaskBusy = Boolean(trackedTask || pendingOverlayText ||
!overlayEl.hidden || taskOverlayTimer);
renderTasks(tasks);
const becameIdle = wasBusy && !busy;
const taskBecameIdle = wasTaskBusy && !busy;
let shouldRefresh = false;
let sawTrackedTask = false;
for (const task of tasks) {
@@ -1458,9 +1572,9 @@ async function pollTasks() {
shouldRefresh = true;
clearTrackedTask();
}
if (becameIdle && !tasks.length) shouldRefresh = true;
if (taskBecameIdle && !tasks.length) shouldRefresh = true;
if (shouldRefresh) {
if (becameIdle) startContentLoadingTimer();
if (taskBecameIdle) startContentLoadingTimer();
clearSelection(false);
await load(taskRefreshPath || cwd, false);
await refreshSpaces();
@@ -1690,6 +1804,7 @@ document.getElementById("mkdirBtn").addEventListener("click", () => {
document.getElementById("copyBtn").addEventListener("click", actionCopy);
document.getElementById("moveBtn").addEventListener("click", actionMove);
pasteBtn.addEventListener("click", actionPaste);
installPkgBtn.addEventListener("click", actionInstallSelectedPkgs);
clearClipboardBtn.addEventListener("click", clearClipboard);
document.getElementById("renameBtn").addEventListener("click", actionRename);
downloadBtn.addEventListener("click", actionDownload);
@@ -1700,6 +1815,7 @@ uploadFolderBtn.addEventListener("click", actionUploadFolder);
uploadFilesEl.addEventListener("change", () => uploadFiles(uploadFilesEl.files));
uploadFolderEl.addEventListener("change", () => uploadFiles(uploadFolderEl.files));
exitBtn.addEventListener("click", actionExit);
parentBtn.addEventListener("click", actionParentDirectory);
textEditorCloseBtn.addEventListener("click", requestCloseTextEditor);
textEditorSaveBtn.addEventListener("click", saveTextEditor);
newTextBtn.addEventListener("click", actionNewText);
@@ -1710,6 +1826,7 @@ document.addEventListener("click", event => {
});
document.querySelector("thead").addEventListener("click", event => {
if (busy || loadingPath) return;
if (parentBtn.contains(event.target)) return;
let target = event.target;
while (target && target.tagName !== "TH") target = target.parentNode;
if (target && target.getAttribute("data-sort")) setSort(target.getAttribute("data-sort"));
@@ -1755,10 +1872,14 @@ filesEl.addEventListener("click", event => {
};
focusPath(item.path);
if (item.type === "parent" || item.type === "d") {
const revealPath = item.type === "parent" ? cwd : "";
setTimeout(async () => {
if (!(await load(item.path, undefined, false, true))) await load(cwd, false, true);
if (await load(item.path, item.type === "parent" ? false : undefined, false, true)) {
/* if (revealPath) revealPathInList(revealPath); */
} else await load(cwd, false, true);
}, 0);
}
else if (isPkgPackage(item)) installPkg(item);
else if (isPreviewableImage(item)) openImagePreview(item);
else if (isEditableText(item)) openTextEditor(item);
else togglePath(item.path, !selected.has(item.path));
+2 -2
View File
@@ -9,6 +9,7 @@
#include <ps5/kernel.h>
#include "asset.h"
#include "pkg_installer.h"
#define INCASSET(name, file) \
__asm__(".section .rodata\n" \
@@ -28,7 +29,6 @@
INCASSET(param_json, "assets/param.json");
INCASSET(icon0_png, "assets/icon0.png");
int sceAppInstUtilInitialize(void);
int sceAppInstUtilAppInstallAll(void *);
static int
@@ -111,7 +111,7 @@ app_install_if_needed(void) {
printf("Installing launcher app %s\n", title_id);
if((err = sceAppInstUtilInitialize())) {
if((err = pkg_installer_initialize())) {
printf("sceAppInstUtilInitialize: error 0x%08X\n", err);
return -1;
}
+2 -2
View File
@@ -401,8 +401,8 @@ prepare_download_task(file_task_t *task) {
task_update(task, TASK_RUNNING, "preparing", 0, NULL);
for(i = 0; i < task->src_count; i++) {
if(count_task_path_bytes(task, task->srcs[i], task->srcs[i], NULL,
&total, NULL, &file_count, &dir_count)) {
if(count_task_path_bytes(task, task->srcs[i], task->srcs[i],
&total, &file_count, &dir_count)) {
return -1;
}
}
+6 -1
View File
@@ -2,11 +2,13 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "filemgr_internal.h"
#include "mime.h"
#include "path_util.h"
#include "websrv.h"
static ssize_t
@@ -31,7 +33,7 @@ file_close(void *cls) {
enum MHD_Result
filemgr_fs_request(struct MHD_Connection *conn) {
const char *path = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "path");
char *path = fs_path_value(query_value(conn, "path"));
struct MHD_Response *resp;
enum MHD_Result ret = MHD_NO;
struct stat st;
@@ -43,6 +45,7 @@ filemgr_fs_request(struct MHD_Connection *conn) {
if(!path || stat(path, &st) || !S_ISREG(st.st_mode) ||
!(file = fopen(path, "rb"))) {
free(path);
return send_json_error(conn, MHD_HTTP_NOT_FOUND, "file not found");
}
@@ -55,9 +58,11 @@ filemgr_fs_request(struct MHD_Connection *conn) {
}
ret = websrv_queue_response(conn, MHD_HTTP_OK, resp);
MHD_destroy_response(resp);
free(path);
return ret;
}
fclose(file);
free(path);
return MHD_NO;
}
+126 -92
View File
@@ -18,6 +18,7 @@
#include "filemgr_internal.h"
#include "json_util.h"
#include "path_util.h"
#include "pkg_installer.h"
#include "websrv.h"
#define COPY_BUFFER_SIZE (8 * 1024 * 1024)
@@ -40,6 +41,7 @@ typedef struct task_completion {
} task_completion_t;
static int ensure_copy_dir(const char *path);
static int open_copy_temp(const char *dst, char *temp, size_t temp_size);
#if FILEMGR_PIPELINE_COPY
typedef struct copy_pipeline_slot {
char *data;
@@ -255,15 +257,13 @@ static int task_target_path(file_task_t *task, const char *src,
char *out, size_t size);
static int count_path_bytes_sync(file_task_t *task, const char *path,
const char *display, const char *target,
const char *display,
unsigned long long *total,
unsigned long long *reclaimable,
size_t *file_count, size_t *dir_count);
static int
count_dir_bytes_sync(file_task_t *task, const char *path, const char *display,
const char *target, unsigned long long *total,
unsigned long long *reclaimable, size_t *file_count,
unsigned long long *total, size_t *file_count,
size_t *dir_count) {
DIR *dir = opendir(path);
struct dirent *entry;
@@ -275,7 +275,6 @@ count_dir_bytes_sync(file_task_t *task, const char *path, const char *display,
while((entry = readdir(dir))) {
char child[PATH_MAX];
char display_child[PATH_MAX];
char target_child[PATH_MAX];
if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
continue;
}
@@ -285,11 +284,8 @@ count_dir_bytes_sync(file_task_t *task, const char *path, const char *display,
if(path_join(child, sizeof(child), path, entry->d_name) ||
(display && path_join(display_child, sizeof(display_child), display,
entry->d_name)) ||
(target && path_join(target_child, sizeof(target_child), target,
entry->d_name)) ||
count_path_bytes_sync(task, child, display ? display_child : NULL,
target ? target_child : NULL, total, reclaimable,
file_count, dir_count)) {
total, file_count, dir_count)) {
goto done;
}
}
@@ -301,12 +297,9 @@ done:
static int
count_path_bytes_sync(file_task_t *task, const char *path, const char *display,
const char *target, unsigned long long *total,
unsigned long long *reclaimable, size_t *file_count,
unsigned long long *total, size_t *file_count,
size_t *dir_count) {
struct stat st;
struct stat target_st;
int target_exists = 0;
if(task && task_cancel_requested(task)) {
return -1;
@@ -317,25 +310,13 @@ count_path_bytes_sync(file_task_t *task, const char *path, const char *display,
if(lstat(path, &st)) {
return -1;
}
if(target) {
if(!lstat(target, &target_st)) {
target_exists = 1;
} else if(errno != ENOENT) {
return -1;
}
}
if(S_ISDIR(st.st_mode)) {
if(dir_count) (*dir_count)++;
return count_dir_bytes_sync(task, path, display,
target_exists && S_ISDIR(target_st.st_mode) ?
target : NULL,
total, reclaimable, file_count, dir_count);
return count_dir_bytes_sync(task, path, display, total, file_count,
dir_count);
}
if(S_ISREG(st.st_mode)) {
*total += (unsigned long long)st.st_size;
if(reclaimable && target_exists && S_ISREG(target_st.st_mode)) {
*reclaimable += (unsigned long long)target_st.st_size;
}
if(file_count) (*file_count)++;
}
return 0;
@@ -345,9 +326,7 @@ count_path_bytes_sync(file_task_t *task, const char *path, const char *display,
typedef struct count_job {
char path[PATH_MAX];
char display[PATH_MAX];
char target[PATH_MAX];
int has_display;
int has_target;
struct count_job *next;
} count_job_t;
@@ -367,18 +346,15 @@ typedef struct count_queue {
int error_number;
int worker_count;
unsigned long long total;
unsigned long long reclaimable;
size_t file_count;
size_t dir_count;
} count_queue_t;
static void
count_queue_add(count_queue_t *queue, unsigned long long total,
unsigned long long reclaimable, size_t file_count,
count_queue_add(count_queue_t *queue, unsigned long long total, size_t file_count,
size_t dir_count) {
pthread_mutex_lock(&queue->lock);
queue->total += total;
queue->reclaimable += reclaimable;
queue->file_count += file_count;
queue->dir_count += dir_count;
pthread_mutex_unlock(&queue->lock);
@@ -391,7 +367,6 @@ count_queue_worker(void *arg) {
for(;;) {
count_job_t *job;
unsigned long long total = 0;
unsigned long long reclaimable = 0;
size_t file_count = 0;
size_t dir_count = 0;
int ret;
@@ -416,10 +391,9 @@ count_queue_worker(void *arg) {
ret = count_path_bytes_sync(queue->task, job->path,
job->has_display ? job->display : NULL,
job->has_target ? job->target : NULL,
&total, &reclaimable, &file_count, &dir_count);
&total, &file_count, &dir_count);
if(!ret) {
count_queue_add(queue, total, reclaimable, file_count, dir_count);
count_queue_add(queue, total, file_count, dir_count);
}
pthread_mutex_lock(&queue->lock);
@@ -481,8 +455,7 @@ count_queue_init(count_queue_t *queue, file_task_t *task) {
}
static int
count_queue_enqueue(count_queue_t *queue, const char *path, const char *display,
const char *target) {
count_queue_enqueue(count_queue_t *queue, const char *path, const char *display) {
count_job_t *job;
if(!(job = calloc(1, sizeof(*job)))) {
@@ -493,10 +466,6 @@ count_queue_enqueue(count_queue_t *queue, const char *path, const char *display,
snprintf(job->display, sizeof(job->display), "%s", display);
job->has_display = 1;
}
if(target) {
snprintf(job->target, sizeof(job->target), "%s", target);
job->has_target = 1;
}
pthread_mutex_lock(&queue->lock);
while(!queue->stopping && queue->queued >= FILE_TASK_QUEUE_LIMIT) {
@@ -565,14 +534,11 @@ count_queue_finish(count_queue_t *queue, int abort_pending) {
static int
count_path_bytes(file_task_t *task, const char *path, const char *display,
const char *target, unsigned long long *total,
unsigned long long *reclaimable, size_t *file_count,
unsigned long long *total, size_t *file_count,
size_t *dir_count) {
DIR *dir;
struct dirent *entry;
struct stat st;
struct stat target_st;
int target_exists = 0;
int ret = -1;
int queue_finished = 0;
count_queue_t queue;
@@ -584,19 +550,12 @@ count_path_bytes(file_task_t *task, const char *path, const char *display,
return -1;
}
if(!S_ISDIR(st.st_mode)) {
return count_path_bytes_sync(task, path, display, target, total,
reclaimable, file_count, dir_count);
return count_path_bytes_sync(task, path, display, total, file_count,
dir_count);
}
if(task) {
task_update(task, TASK_RUNNING, display ? display : path, 0, NULL);
}
if(target) {
if(!lstat(target, &target_st)) {
target_exists = S_ISDIR(target_st.st_mode);
} else if(errno != ENOENT) {
return -1;
}
}
if(dir_count) (*dir_count)++;
if(count_queue_init(&queue, task)) {
return -1;
@@ -609,7 +568,6 @@ count_path_bytes(file_task_t *task, const char *path, const char *display,
while((entry = readdir(dir))) {
char child[PATH_MAX];
char display_child[PATH_MAX];
char target_child[PATH_MAX];
if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
continue;
@@ -621,10 +579,7 @@ count_path_bytes(file_task_t *task, const char *path, const char *display,
if(path_join(child, sizeof(child), path, entry->d_name) ||
(display && path_join(display_child, sizeof(display_child), display,
entry->d_name)) ||
(target_exists && path_join(target_child, sizeof(target_child), target,
entry->d_name)) ||
count_queue_enqueue(&queue, child, display ? display_child : NULL,
target_exists ? target_child : NULL)) {
count_queue_enqueue(&queue, child, display ? display_child : NULL)) {
goto done;
}
}
@@ -633,9 +588,6 @@ count_path_bytes(file_task_t *task, const char *path, const char *display,
queue_finished = 1;
if(!ret) {
*total += queue.total;
if(reclaimable) {
*reclaimable += queue.reclaimable;
}
if(file_count) {
*file_count += queue.file_count;
}
@@ -656,16 +608,44 @@ done:
int
count_task_path_bytes(file_task_t *task, const char *path, const char *display,
const char *target, unsigned long long *total,
unsigned long long *reclaimable, size_t *file_count,
unsigned long long *total, size_t *file_count,
size_t *dir_count) {
return count_path_bytes(task, path, display, target, total, reclaimable,
file_count, dir_count);
return count_path_bytes(task, path, display, total, file_count, dir_count);
}
static int
open_copy_temp(const char *dst, char *temp, size_t temp_size) {
char parent[PATH_MAX];
char name[96];
int attempt;
if(path_dirname(dst, parent, sizeof(parent))) {
return -1;
}
for(attempt = 0; attempt < 32; attempt++) {
int fd;
snprintf(name, sizeof(name), ".wfm-copy-%ld-%lld-%d.tmp",
(long)getpid(), (long long)time(NULL), attempt);
if(path_join(temp, temp_size, parent, name)) {
return -1;
}
fd = open(temp, O_WRONLY | O_CREAT | O_EXCL, 0600);
if(fd >= 0) {
return fd;
}
if(errno != EEXIST) {
return -1;
}
}
errno = EEXIST;
return -1;
}
static int
copy_file_buffered(file_task_t *task, const char *src, const char *dst) {
char *buf = NULL;
char temp[PATH_MAX] = {0};
int in = -1;
int out = -1;
int ret = -1;
@@ -674,12 +654,13 @@ copy_file_buffered(file_task_t *task, const char *src, const char *dst) {
task_update(task, TASK_RUNNING, dst, 0, NULL);
if(task_cancel_requested(task)) {
errno = ECANCELED;
return -1;
}
if((in = open(src, O_RDONLY)) < 0) {
goto done;
}
if((out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0777)) < 0) {
if((out = open_copy_temp(dst, temp, sizeof(temp))) < 0) {
goto done;
}
if(!(buf = malloc(COPY_BUFFER_SIZE))) {
@@ -691,6 +672,7 @@ copy_file_buffered(file_task_t *task, const char *src, const char *dst) {
ssize_t left = n;
char *p = buf;
if(task_cancel_requested(task)) {
errno = ECANCELED;
goto done;
}
while(left > 0) {
@@ -709,6 +691,10 @@ copy_file_buffered(file_task_t *task, const char *src, const char *dst) {
if(fchmod_0777(out)) {
goto done;
}
if(task_cancel_requested(task)) {
errno = ECANCELED;
goto done;
}
ret = 0;
done:
@@ -717,7 +703,16 @@ done:
if(out >= 0) {
if(close(out)) ret = -1;
}
if(ret) unlink(dst);
if(!ret && task_cancel_requested(task)) {
errno = ECANCELED;
ret = -1;
}
if(!ret && rename(temp, dst)) {
ret = -1;
}
if(ret && temp[0]) {
unlink(temp);
}
return ret;
}
@@ -774,6 +769,7 @@ static int
copy_file_pipeline(file_task_t *task, const char *src, const char *dst) {
copy_pipeline_t p;
pthread_t reader;
char temp[PATH_MAX] = {0};
int out = -1;
int ret = -1;
int reader_started = 0;
@@ -794,7 +790,7 @@ copy_file_pipeline(file_task_t *task, const char *src, const char *dst) {
if((p.in = open(src, O_RDONLY)) < 0) {
goto done;
}
if((out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0777)) < 0) {
if((out = open_copy_temp(dst, temp, sizeof(temp))) < 0) {
goto done;
}
if(pthread_mutex_init(&p.lock, NULL)) {
@@ -873,6 +869,10 @@ copy_file_pipeline(file_task_t *task, const char *src, const char *dst) {
if(fchmod_0777(out)) {
goto done;
}
if(task_cancel_requested(task)) {
errno = ECANCELED;
goto done;
}
ret = 0;
done:
@@ -895,7 +895,16 @@ done:
if(out >= 0) {
if(close(out)) ret = -1;
}
if(ret) unlink(dst);
if(!ret && task_cancel_requested(task)) {
errno = ECANCELED;
ret = -1;
}
if(!ret && rename(temp, dst)) {
ret = -1;
}
if(ret && temp[0]) {
unlink(temp);
}
return ret;
}
#endif
@@ -1398,9 +1407,6 @@ remove_path(file_task_t *task, const char *path) {
closedir(dir);
return rmdir(path);
}
if(S_ISREG(st.st_mode)) {
task_update(task, TASK_RUNNING, path, (unsigned long long)st.st_size, NULL);
}
return unlink(path);
}
@@ -1560,7 +1566,6 @@ task_worker(void *arg) {
file_task_t *task = arg;
unsigned long long total = 0;
unsigned long long required = 0;
unsigned long long reclaimable = 0;
int ret = -1;
task_update(task, TASK_RUNNING, "preparing", 0, NULL);
@@ -1594,7 +1599,6 @@ task_worker(void *arg) {
unsigned long long before = total;
int needs_space = task->op == TASK_COPY;
char target[PATH_MAX];
const char *compare_target = NULL;
if(task->op == TASK_COPY || task->op == TASK_MOVE) {
if(task_target_path(task, task->srcs[i], target, sizeof(target))) {
@@ -1612,17 +1616,7 @@ task_worker(void *arg) {
continue;
}
}
if(needs_space) {
struct stat target_st;
if(!lstat(target, &target_st)) {
compare_target = target;
} else if(errno != ENOENT) {
task_update(task, TASK_FAILED, target, 0, strerror(errno));
return NULL;
}
}
if(count_path_bytes(task, task->srcs[i], task->srcs[i], compare_target,
&total, &reclaimable,
if(count_path_bytes(task, task->srcs[i], task->srcs[i], &total,
&task->file_count, &task->dir_count)) {
if(errno == ECANCELED || task_cancel_requested(task)) {
task_update(task, TASK_CANCELED, task->srcs[i], 0, "canceled");
@@ -1639,7 +1633,6 @@ task_worker(void *arg) {
}
}
task_set_total(task, total);
required = reclaimable >= required ? 0 : required - reclaimable;
if(task->op == TASK_COPY || task->op == TASK_MOVE) {
char error[128] = {0};
@@ -1875,10 +1868,10 @@ api_cancel(struct MHD_Connection *conn) {
}
static void *
exit_later(void *arg) {
stop_websrv_later(void *arg) {
(void)arg;
usleep(250000);
exit(0);
websrv_stop();
return NULL;
}
@@ -1886,7 +1879,7 @@ static enum MHD_Result
api_exit(struct MHD_Connection *conn) {
pthread_t thread;
if(!pthread_create(&thread, NULL, exit_later, NULL)) {
if(!pthread_create(&thread, NULL, stop_websrv_later, NULL)) {
pthread_detach(thread);
}
return send_json_ok(conn);
@@ -2016,6 +2009,42 @@ api_mkdir(struct MHD_Connection *conn) {
: send_json_ok(conn);
}
static enum MHD_Result
api_install_pkg(struct MHD_Connection *conn) {
char *path = fs_path_value(query_value(conn, "path"));
const char *extension;
struct stat st;
char error_code[16];
int result;
if(!path || !(extension = strrchr(path, '.')) ||
strcasecmp(extension, ".pkg")) {
free(path);
return send_json_error_detail(conn, MHD_HTTP_BAD_REQUEST,
"file is not a PKG package",
"pkg_type_invalid", NULL);
}
if(stat(path, &st) || !S_ISREG(st.st_mode)) {
free(path);
return send_json_error(conn, MHD_HTTP_NOT_FOUND, "file not found");
}
result = pkg_installer_install(path);
free(path);
if(result == PKG_INSTALL_UNSUPPORTED) {
return send_json_error_detail(conn, MHD_HTTP_NOT_IMPLEMENTED,
"package installation is only available on PS5",
"pkg_install_unsupported", NULL);
}
if(result) {
snprintf(error_code, sizeof(error_code), "0x%08X", (unsigned int)result);
return send_json_error_detail(conn, MHD_HTTP_INTERNAL_SERVER_ERROR,
"package installation failed",
"pkg_install_failed", error_code);
}
return send_json_ok(conn);
}
enum MHD_Result
filemgr_api_request(struct MHD_Connection *conn, const char *url,
const char *method, const char *body, size_t body_size) {
@@ -2037,6 +2066,11 @@ filemgr_api_request(struct MHD_Connection *conn, const char *url,
if(!strcmp(url, "/api/upload/finish")) return api_upload_finish(conn);
if(!strcmp(url, "/api/rename")) return api_rename(conn);
if(!strcmp(url, "/api/mkdir")) return api_mkdir(conn);
if(!strcmp(url, "/api/install-pkg")) {
return strcmp(method, MHD_HTTP_METHOD_POST) ?
send_json_error(conn, MHD_HTTP_METHOD_NOT_ALLOWED, "invalid method") :
api_install_pkg(conn);
}
if(!strcmp(url, "/api/text")) {
return strcmp(method, MHD_HTTP_METHOD_GET) ?
send_json_error(conn, MHD_HTTP_METHOD_NOT_ALLOWED, "invalid method") :
+1 -3
View File
@@ -103,9 +103,7 @@ int check_target_space(const char *target, unsigned long long required,
char *arg, size_t arg_size);
int target_available_space(const char *target, unsigned long long *available);
int count_task_path_bytes(file_task_t *task, const char *path,
const char *display, const char *target,
unsigned long long *total,
unsigned long long *reclaimable,
const char *display, unsigned long long *total,
size_t *file_count, size_t *dir_count);
enum MHD_Result api_upload_prepare(struct MHD_Connection *conn,
+3
View File
@@ -150,6 +150,9 @@ main(int argc, char **argv) {
#endif
websrv_listen(port);
if(websrv_stop_requested()) {
break;
}
sleep(3);
}
+108
View File
@@ -0,0 +1,108 @@
#include "pkg_installer.h"
#ifndef __linux__
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
typedef struct pkg_metadata {
const char *uri;
const char *ex_uri;
const char *playgo_scenario_id;
const char *content_id;
const char *content_name;
const char *icon_url;
} pkg_metadata_t;
typedef struct pkg_info {
char content_id[48];
int type;
int platform;
} pkg_info_t;
typedef struct playgo_info {
char lang[8][30];
char scenario_ids[3][64];
char content_ids[64];
long unknown[810];
} playgo_info_t;
int sceAppInstUtilInitialize(void);
int sceAppInstUtilInstallByPackage(const pkg_metadata_t *, pkg_info_t *,
playgo_info_t *);
static pthread_mutex_t installer_lock = PTHREAD_MUTEX_INITIALIZER;
static int installer_initialized;
static int
initialize_locked(void) {
int result;
if(!installer_initialized) {
result = sceAppInstUtilInitialize();
if(result) return result;
installer_initialized = 1;
}
return 0;
}
int
pkg_installer_initialize(void) {
int result;
pthread_mutex_lock(&installer_lock);
result = initialize_locked();
pthread_mutex_unlock(&installer_lock);
return result;
}
int
pkg_installer_install(const char *path) {
char install_path[PATH_MAX + sizeof("/user")];
const char *uri = path;
pkg_metadata_t metadata = {
.uri = NULL,
.ex_uri = "",
.playgo_scenario_id = "",
.content_id = "",
.content_name = "",
.icon_url = ""
};
pkg_info_t pkg_info = {0};
playgo_info_t playgo_info = {0};
int result;
if(!path) return -1;
if(!strncmp(path, "/data/", 6)) {
snprintf(install_path, sizeof(install_path), "/user%s", path);
uri = install_path;
}
metadata.uri = uri;
pthread_mutex_lock(&installer_lock);
result = initialize_locked();
if(result) {
pthread_mutex_unlock(&installer_lock);
return result;
}
result = sceAppInstUtilInstallByPackage(&metadata, &pkg_info, &playgo_info);
pthread_mutex_unlock(&installer_lock);
return result;
}
#else
int
pkg_installer_initialize(void) {
return PKG_INSTALL_UNSUPPORTED;
}
int
pkg_installer_install(const char *path) {
(void)path;
return PKG_INSTALL_UNSUPPORTED;
}
#endif
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#define PKG_INSTALL_UNSUPPORTED 0x7fffffff
int pkg_installer_initialize(void);
int pkg_installer_install(const char *path);
+3 -11
View File
@@ -74,8 +74,7 @@ upload_task_from_conn(struct MHD_Connection *conn) {
static int
check_upload_manifest_space(const char *base, const char *rels,
const char *sizes, int overwrite,
unsigned long long fallback_total,
const char *sizes, unsigned long long fallback_total,
char *error, size_t error_size,
char *code, size_t code_size,
char *arg, size_t arg_size) {
@@ -107,9 +106,7 @@ check_upload_manifest_space(const char *base, const char *rels,
size_text = strtok_r(sizes_copy, "\n", &size_save);
while(rel || size_text) {
char target[PATH_MAX];
struct stat st;
unsigned long long size;
unsigned long long reclaim = 0;
if(!rel || !size_text || path_join_relative(target, sizeof(target), base, rel)) {
snprintf(error, error_size, "invalid path");
@@ -130,10 +127,7 @@ check_upload_manifest_space(const char *base, const char *rels,
goto done;
}
if(overwrite && !lstat(target, &st) && S_ISREG(st.st_mode) && st.st_size > 0) {
reclaim = (unsigned long long)st.st_size;
}
available = available - size + reclaim;
available -= size;
rel = strtok_r(NULL, "\n", &rel_save);
size_text = strtok_r(NULL, "\n", &size_save);
@@ -188,9 +182,7 @@ api_upload_prepare(struct MHD_Connection *conn, const char *body,
}
pthread_mutex_unlock(&g_tasks_lock);
if(check_upload_manifest_space(path, rels, sizes,
overwrite && !strcmp(overwrite, "1"),
total, error, sizeof(error),
if(check_upload_manifest_space(path, rels, sizes, total, error, sizeof(error),
code, sizeof(code), arg, sizeof(arg))) {
free_task(task);
free(path); free(src); free(total_text); free(count_text);
+21 -2
View File
@@ -15,6 +15,9 @@
#define REQUEST_BODY_MAX (4 * 1024 * 1024)
static volatile sig_atomic_t g_stop_requested;
static int g_listen_fd = -1;
typedef struct request_context {
char *body;
size_t size;
@@ -51,6 +54,19 @@ websrv_queue_response(struct MHD_Connection *conn, unsigned int status,
return MHD_queue_response(conn, status, resp);
}
void
websrv_stop(void) {
g_stop_requested = 1;
if(g_listen_fd >= 0) {
shutdown(g_listen_fd, SHUT_RDWR);
}
}
int
websrv_stop_requested(void) {
return g_stop_requested;
}
static enum MHD_Result
websrv_on_request(void *cls, struct MHD_Connection *conn, const char *url,
const char *method, const char *version,
@@ -190,6 +206,8 @@ websrv_listen(unsigned short port) {
close(srvfd);
return -1;
}
g_stop_requested = 0;
g_listen_fd = srvfd;
if(!(httpd = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_ITC |
MHD_USE_NO_LISTEN_SOCKET | MHD_USE_DEBUG |
@@ -202,10 +220,10 @@ websrv_listen(unsigned short port) {
return -1;
}
while(1) {
while(!g_stop_requested) {
addr_len = sizeof(client_addr);
if((connfd = accept(srvfd, (struct sockaddr *)&client_addr, &addr_len)) < 0) {
perror("accept");
if(!g_stop_requested) perror("accept");
break;
}
if(MHD_add_connection(httpd, connfd, (struct sockaddr *)&client_addr,
@@ -217,5 +235,6 @@ websrv_listen(unsigned short port) {
}
MHD_stop_daemon(httpd);
g_listen_fd = -1;
return close(srvfd);
}
+2
View File
@@ -23,3 +23,5 @@ enum MHD_Result websrv_queue_response(struct MHD_Connection *conn,
struct MHD_Response *resp);
int websrv_listen(unsigned short port);
void websrv_stop(void);
int websrv_stop_requested(void);