mirror of
https://github.com/LisherSong/ps5-web-file-manager.git
synced 2026-10-06 21:00:26 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a61ac54e1f | ||
|
|
6a110f95ae | ||
|
|
b463c3f86b |
No files matched your search
@@ -13,7 +13,7 @@ ifeq ($(MAKECMDGOALS),)
|
||||
endif
|
||||
endif
|
||||
|
||||
VERSION_TAG := v1.0
|
||||
VERSION_TAG := v1.2
|
||||
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/*))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -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>
|
||||
|
||||
+10
-1
@@ -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
@@ -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: "请求方式无效",
|
||||
|
||||
+13
-2
@@ -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 {
|
||||
background: #433521;
|
||||
}
|
||||
|
||||
.paste-count {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
+95
-10
@@ -18,6 +18,7 @@ let directoryLoadingStartedAt = 0;
|
||||
let taskOverlayTimer = 0;
|
||||
let lastCompletionId = null;
|
||||
let taskPollFailedAlertShown = false;
|
||||
let localActionBusy = false;
|
||||
let textEditorPath = null;
|
||||
let textEditorVersion = null;
|
||||
let textEditorOriginal = "";
|
||||
@@ -26,7 +27,7 @@ let downloadFrame = null;
|
||||
let uploadXhr = null;
|
||||
let L = {};
|
||||
|
||||
const APP_VERSION = "v1.0";
|
||||
const APP_VERSION = "v1.2";
|
||||
const LAST_PATH_KEY = "ps5-web-file-mgr:last-path";
|
||||
const SORT_KEY = "ps5-web-file-mgr:list-sort";
|
||||
const LOADING_DISPLAY_DELAY = 250;
|
||||
@@ -54,6 +55,7 @@ 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");
|
||||
@@ -559,6 +561,7 @@ function pathIsSameOrChild(parent, child) {
|
||||
}
|
||||
|
||||
function typeLabel(type) {
|
||||
if (type === "parent") return t("parent");
|
||||
return type === "d" ? t("dir") : t("file");
|
||||
}
|
||||
|
||||
@@ -571,6 +574,54 @@ 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 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 +660,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 +759,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 +802,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 +866,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;
|
||||
@@ -826,6 +901,7 @@ function updateButtons() {
|
||||
selectAllEl.disabled = locked;
|
||||
selectAllEl.checked = entries.length > 0 && items.length === entries.length;
|
||||
selectAllEl.indeterminate = items.length > 0 && items.length < entries.length;
|
||||
renderInstallPkgButton(items, locked);
|
||||
renderClipboard();
|
||||
}
|
||||
|
||||
@@ -1006,6 +1082,7 @@ function render() {
|
||||
const iconImg = document.createElement("img");
|
||||
iconImg.className = "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 +1094,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))
|
||||
@@ -1164,10 +1241,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 +1411,7 @@ function renderTasks(tasks) {
|
||||
DOWNLOAD_OVERLAY_DISPLAY_DELAY : LOADING_DISPLAY_DELAY);
|
||||
else {
|
||||
hideTaskOverlay();
|
||||
setBusy(false);
|
||||
if (!localActionBusy) setBusy(false);
|
||||
}
|
||||
|
||||
if (!hasActive) return;
|
||||
@@ -1436,8 +1513,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 +1538,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 +1770,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);
|
||||
@@ -1755,10 +1836,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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+123
-89
@@ -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};
|
||||
@@ -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") :
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "pkg_installer.h"
|
||||
|
||||
#ifndef __linux__
|
||||
|
||||
#include <pthread.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) {
|
||||
pkg_metadata_t metadata = {
|
||||
.uri = path,
|
||||
.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;
|
||||
|
||||
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
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user