mirror of
https://github.com/LisherSong/ps5-web-file-manager.git
synced 2026-10-06 18:00:28 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f460178ac8 |
No files matched your search
+2
-1
@@ -1,4 +1,5 @@
|
||||
gen/
|
||||
web-file-mgr.elf
|
||||
*.elf
|
||||
web-file-mgr-linux
|
||||
*.o
|
||||
*.d
|
||||
@@ -1,43 +1,79 @@
|
||||
ifdef PS5_PAYLOAD_SDK
|
||||
ifneq ($(filter-out linux linux-deps clean,$(MAKECMDGOALS)),)
|
||||
ifdef PS5_PAYLOAD_SDK
|
||||
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
|
||||
else
|
||||
else
|
||||
$(error PS5_PAYLOAD_SDK is undefined)
|
||||
endif
|
||||
endif
|
||||
ifeq ($(MAKECMDGOALS),)
|
||||
ifdef PS5_PAYLOAD_SDK
|
||||
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
|
||||
else
|
||||
$(error PS5_PAYLOAD_SDK is undefined)
|
||||
endif
|
||||
endif
|
||||
|
||||
VERSION_TAG := v0.1
|
||||
VERSION_TAG := v0.2
|
||||
TITLE_ID := FMGR88888
|
||||
PYTHON ?= python3
|
||||
STRIP ?= $(PS5_PAYLOAD_SDK)/bin/prospero-strip
|
||||
PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
|
||||
HOST_CC ?= cc
|
||||
HOST_STRIP ?= strip
|
||||
HOST_PKG_CONFIG ?= pkg-config
|
||||
|
||||
BIN := web-file-mgr.elf
|
||||
SRCS := src/main.c src/websrv.c src/filemgr.c src/asset.c src/mime.c
|
||||
SRCS += src/app_installer.c src/notify.c
|
||||
ASSETS := $(wildcard assets/*)
|
||||
GEN_SRCS := $(patsubst assets/%,gen/%, $(ASSETS:=.c))
|
||||
BIN := web-file-mgr.elf
|
||||
LEGACY_BIN := web-file-mgr-legacy.elf
|
||||
LINUX_BIN := web-file-mgr-linux
|
||||
COMMON_SRCS := src/main.c src/websrv.c src/filemgr.c src/asset.c src/mime.c src/notify.c
|
||||
PS5_SRCS := $(COMMON_SRCS) src/app_installer.c
|
||||
LINUX_SRCS := $(COMMON_SRCS)
|
||||
ASSETS := $(wildcard assets/*)
|
||||
GEN_SRCS := $(patsubst assets/%,gen/%, $(ASSETS:=.c))
|
||||
|
||||
CFLAGS := -Oz -fno-asynchronous-unwind-tables -fno-unwind-tables -Wall -Werror -ffunction-sections -fdata-sections -Isrc -DVERSION_TAG=\"$(VERSION_TAG)\" -DTITLE_ID=\"$(TITLE_ID)\"
|
||||
CFLAGS += `$(PKG_CONFIG) libmicrohttpd --cflags`
|
||||
LDFLAGS := -Wl,--gc-sections
|
||||
LEGACY_CFLAGS := $(filter-out -ffunction-sections -fdata-sections,$(CFLAGS))
|
||||
LEGACY_LDFLAGS :=
|
||||
LDADD := `$(PKG_CONFIG) libmicrohttpd --libs`
|
||||
LDADD += -lSceIpmi -lSceAppInstUtil
|
||||
LINUX_CFLAGS := -O2 -Wall -Werror -Isrc -DVERSION_TAG=\"$(VERSION_TAG)\" -DTITLE_ID=\"$(TITLE_ID)\"
|
||||
LINUX_CFLAGS += `$(HOST_PKG_CONFIG) libmicrohttpd --cflags`
|
||||
LINUX_LDADD := `$(HOST_PKG_CONFIG) libmicrohttpd --libs` -pthread
|
||||
|
||||
.PHONY: all deps clean
|
||||
.PHONY: all legacy linux deps linux-deps clean
|
||||
|
||||
all: deps $(BIN)
|
||||
|
||||
legacy: deps $(LEGACY_BIN)
|
||||
|
||||
linux: linux-deps $(LINUX_BIN)
|
||||
|
||||
deps:
|
||||
@$(PKG_CONFIG) --exists libmicrohttpd || ./install-libmicrohttpd.sh
|
||||
|
||||
linux-deps:
|
||||
@$(HOST_PKG_CONFIG) --exists libmicrohttpd || \
|
||||
(echo "libmicrohttpd development package is required for make linux" >&2; exit 1)
|
||||
|
||||
gen:
|
||||
mkdir gen
|
||||
|
||||
clean:
|
||||
rm -rf $(BIN) gen
|
||||
rm -rf $(BIN) $(LEGACY_BIN) $(LINUX_BIN) gen
|
||||
|
||||
gen/%.c: assets/% gen-asset-module.py gen
|
||||
$(PYTHON) gen-asset-module.py --path $* $< > $@
|
||||
|
||||
$(BIN): $(SRCS) $(GEN_SRCS)
|
||||
$(BIN): $(PS5_SRCS) $(GEN_SRCS)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDADD)
|
||||
$(STRIP) $@
|
||||
|
||||
$(LEGACY_BIN): $(PS5_SRCS) $(GEN_SRCS)
|
||||
$(CC) $(LEGACY_CFLAGS) $(LEGACY_LDFLAGS) -o $@ $^ $(LDADD)
|
||||
$(STRIP) $@
|
||||
|
||||
$(LINUX_BIN): $(LINUX_SRCS) $(GEN_SRCS)
|
||||
$(HOST_CC) $(LINUX_CFLAGS) -o $@ $^ $(LINUX_LDADD)
|
||||
$(HOST_STRIP) $@
|
||||
@@ -5,7 +5,7 @@ safely copying game dump folders from USB storage to internal storage.
|
||||
|
||||
## Brief
|
||||
|
||||
PS5 web file manager payload. It runs an HTTP UI starting at port `8888`, installs a home screen launcher on startup when needed, and provides file operations from the PS5 browser. If `8888` is already in use, the payload tries the next port until one is available; the startup notification shows the actual listen port.
|
||||
PS5 web file manager payload. It runs an HTTP UI starting at port `8888`, installs a home screen launcher in Media catagory on startup when needed, and provides file operations from the PS5 browser. If `8888` is already in use, the payload tries the next port until one is available; the startup notification shows the actual listen port.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -69,7 +69,7 @@ After the payload starts, the PS5 notification shows the app name, version, and
|
||||
http://${PS5_IP_ADDRESS}:8888/
|
||||
```
|
||||
|
||||
On first startup the payload installs a `PS5 Web File Manager` web shortcut on the PS5 home screen when needed. If the payload had to use a fallback port such as `8889`, use the port shown in the startup notification.
|
||||
On first startup the payload installs a `PS5 Web File Manager` web shortcut in the Media category when needed. If the payload had to use a fallback port such as `8889`, use the port shown in the startup notification.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
+2
-1
@@ -60,7 +60,8 @@ window.WFM_LANG = {
|
||||
tasksPollFailed: "Failed to read task status: {error}",
|
||||
renamePrompt: "New name",
|
||||
mkdirPrompt: "Folder name",
|
||||
deleteConfirm: "Delete {name}?\n\nFolders will be deleted recursively.",
|
||||
deleteConfirm: "Delete {name}?",
|
||||
deleteConfirmRecursive: "Delete {name}?\n\nFolders will be deleted recursively.",
|
||||
activeTask: "A task is running",
|
||||
pasteTitle: "{label} {name} to {path}",
|
||||
pasteToCurrent: "to current folder",
|
||||
|
||||
+2
-1
@@ -60,7 +60,8 @@ window.WFM_LANG = {
|
||||
tasksPollFailed: "任务状态读取失败: {error}",
|
||||
renamePrompt: "新名称",
|
||||
mkdirPrompt: "目录名",
|
||||
deleteConfirm: "确认删除 {name}?\n\n目录会递归删除。",
|
||||
deleteConfirm: "确认删除 {name}?",
|
||||
deleteConfirmRecursive: "确认删除 {name}?\n\n目录会递归删除。",
|
||||
activeTask: "有任务正在执行",
|
||||
pasteTitle: "{label} {name} 到 {path}",
|
||||
pasteToCurrent: "到当前目录",
|
||||
|
||||
+79
-35
@@ -1,5 +1,4 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: #111316;
|
||||
color: #edf0f2;
|
||||
@@ -15,21 +14,25 @@
|
||||
|
||||
.init-loading {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 20000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #111316;
|
||||
}
|
||||
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loading-dots span {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: 0 6px;
|
||||
border-radius: 50%;
|
||||
background: #8fa1b3;
|
||||
animation: loading-pulse 1s ease-in-out infinite;
|
||||
@@ -68,14 +71,13 @@ body {
|
||||
.shell {
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto minmax(0, 1fr) auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 12px 30px;
|
||||
background: #1b2026;
|
||||
border-bottom: 1px solid #2d343d;
|
||||
@@ -96,7 +98,6 @@ body {
|
||||
.space-info {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 14px;
|
||||
flex: 0 1 auto;
|
||||
max-width: 58vw;
|
||||
color: #c9d3dc;
|
||||
@@ -107,6 +108,7 @@ body {
|
||||
}
|
||||
|
||||
.space-item {
|
||||
margin-left: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -128,6 +130,7 @@ body {
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
flex: 0 0 24px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.power-button {
|
||||
@@ -162,7 +165,6 @@ body {
|
||||
.tool-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -178,6 +180,16 @@ body {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.tool-left > *,
|
||||
.tool-right > * {
|
||||
margin: 0 12px 0 0;
|
||||
}
|
||||
|
||||
.tool-right > * {
|
||||
margin-right: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.tool-right {
|
||||
justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
@@ -226,19 +238,23 @@ button:disabled,
|
||||
}
|
||||
|
||||
.paste-action {
|
||||
max-width: min(480px, 100%);
|
||||
gap: 8px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.paste-action > * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.paste-name {
|
||||
display: inline-block;
|
||||
max-width: min(180px, 18vw);
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -336,6 +352,7 @@ tr.selected {
|
||||
input[type="checkbox"] {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -362,20 +379,27 @@ input[type="checkbox"] {
|
||||
background: transparent;
|
||||
color: #edf0f2;
|
||||
justify-content: flex-start;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.row-action .icon {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.name-text {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.empty,
|
||||
.empty {
|
||||
color: #aab4be;
|
||||
padding: 20px 90px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #aab4be;
|
||||
padding: 10px 25px;
|
||||
@@ -388,7 +412,6 @@ input[type="checkbox"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.version-text {
|
||||
@@ -398,10 +421,14 @@ input[type="checkbox"] {
|
||||
|
||||
.task-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 10000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px;
|
||||
background: rgba(4, 6, 8, .82);
|
||||
}
|
||||
@@ -411,7 +438,8 @@ input[type="checkbox"] {
|
||||
}
|
||||
|
||||
.task-panel {
|
||||
width: min(980px, calc(100vw - 96px));
|
||||
width: 980px;
|
||||
max-width: calc(100vw - 96px);
|
||||
border: 1px solid #46515f;
|
||||
border-radius: 10px;
|
||||
background: #15191e;
|
||||
@@ -420,19 +448,24 @@ input[type="checkbox"] {
|
||||
}
|
||||
|
||||
.tasks {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task > * + * {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.task .danger {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.task-head {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
@@ -441,7 +474,7 @@ input[type="checkbox"] {
|
||||
}
|
||||
|
||||
.task-amount {
|
||||
min-width: max-content;
|
||||
min-width: 160px;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -456,10 +489,13 @@ input[type="checkbox"] {
|
||||
|
||||
.task-name {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.task-name > * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.task-name-op,
|
||||
.task-name-state {
|
||||
flex: 0 0 auto;
|
||||
@@ -467,27 +503,31 @@ input[type="checkbox"] {
|
||||
|
||||
.task-name-subject {
|
||||
min-width: 0;
|
||||
max-width: min(56vw, 560px);
|
||||
max-width: 560px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-meta {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 18px;
|
||||
display: flex;
|
||||
color: #c9d3dc;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.task-meta div {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin-right: 18px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-meta div:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.task-meta div:nth-child(2) {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -517,9 +557,13 @@ input[type="checkbox"] {
|
||||
|
||||
.progress-text {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #edf0f2;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
|
||||
+60
-20
@@ -12,7 +12,7 @@ let hoverPaused = false;
|
||||
let hoverResumeTimer = 0;
|
||||
let L = {};
|
||||
|
||||
const APP_VERSION = "v0.1";
|
||||
const APP_VERSION = "v0.2";
|
||||
const LAST_PATH_KEY = "ps5-web-file-mgr:last-path";
|
||||
|
||||
const filesEl = document.getElementById("files");
|
||||
@@ -318,7 +318,7 @@ function renderTaskName(el, task) {
|
||||
const state = document.createElement("span");
|
||||
state.className = "task-name-state";
|
||||
state.textContent = stateLabel(task.state);
|
||||
el.append(op, subject, state);
|
||||
appendChildren(el, op, subject, state);
|
||||
}
|
||||
|
||||
function renderClipboard() {
|
||||
@@ -387,6 +387,43 @@ function cssEscape(value) {
|
||||
return String(value).replace(/["\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function appendChildren(parent) {
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
parent.appendChild(arguments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function setClass(el, name, enabled) {
|
||||
if (enabled) el.classList.add(name);
|
||||
else el.classList.remove(name);
|
||||
}
|
||||
|
||||
function bindPress(el, fn) {
|
||||
let handled = false;
|
||||
const run = event => {
|
||||
if (handled) {
|
||||
if (event && event.preventDefault) event.preventDefault();
|
||||
return;
|
||||
}
|
||||
handled = true;
|
||||
if (event && event.preventDefault) event.preventDefault();
|
||||
fn();
|
||||
setTimeout(() => {
|
||||
handled = false;
|
||||
}, 350);
|
||||
};
|
||||
el.addEventListener("mousedown", run);
|
||||
el.addEventListener("click", run);
|
||||
}
|
||||
|
||||
function findRowFromTarget(target) {
|
||||
while (target && target !== contentEl) {
|
||||
if (target.tagName === "TR" && target.getAttribute("data-path") !== null) return target;
|
||||
target = target.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function togglePath(path, checked) {
|
||||
if (checked) selected.add(path);
|
||||
else selected.delete(path);
|
||||
@@ -410,8 +447,8 @@ function render() {
|
||||
const isParent = item.type === "parent";
|
||||
const tr = document.createElement("tr");
|
||||
tr.dataset.path = item.path;
|
||||
tr.classList.toggle("selected", !isParent && selected.has(item.path));
|
||||
tr.classList.toggle("focused", focusedPath === item.path);
|
||||
setClass(tr, "selected", !isParent && selected.has(item.path));
|
||||
setClass(tr, "focused", focusedPath === item.path);
|
||||
|
||||
const selectTd = document.createElement("td");
|
||||
selectTd.className = "select-cell";
|
||||
@@ -448,10 +485,11 @@ function render() {
|
||||
if (isParent || item.type === "d") load(item.path);
|
||||
else togglePath(item.path, !selected.has(item.path));
|
||||
});
|
||||
nameBtn.append(icon, nameText);
|
||||
appendChildren(nameBtn, icon, nameText);
|
||||
nameTd.appendChild(nameBtn);
|
||||
|
||||
tr.append(
|
||||
appendChildren(
|
||||
tr,
|
||||
selectTd,
|
||||
nameTd,
|
||||
classCell("type-col", isParent ? t("parent") : item.type === "d" ? t("dir") : t("file")),
|
||||
@@ -618,7 +656,7 @@ function renderPendingOverlay(text) {
|
||||
const amount = document.createElement("div");
|
||||
amount.className = "task-amount";
|
||||
amount.textContent = t("pleaseWait");
|
||||
head.append(name, amount);
|
||||
appendChildren(head, name, amount);
|
||||
|
||||
const current = document.createElement("div");
|
||||
current.className = "task-current";
|
||||
@@ -629,12 +667,12 @@ function renderPendingOverlay(text) {
|
||||
const progressText = document.createElement("div");
|
||||
progressText.className = "progress-text";
|
||||
progressText.textContent = t("checking");
|
||||
progress.append(progressText);
|
||||
progress.appendChild(progressText);
|
||||
|
||||
const cancel = document.createElement("button");
|
||||
cancel.className = "danger";
|
||||
cancel.textContent = t("cancel");
|
||||
cancel.addEventListener("click", () => {
|
||||
bindPress(cancel, () => {
|
||||
if (pendingAbortController) pendingAbortController.abort();
|
||||
pendingAbortController = null;
|
||||
pendingOverlayText = "";
|
||||
@@ -643,7 +681,7 @@ function renderPendingOverlay(text) {
|
||||
setBusy(false);
|
||||
});
|
||||
|
||||
div.append(head, current, progress, cancel);
|
||||
appendChildren(div, head, current, progress, cancel);
|
||||
tasksEl.appendChild(div);
|
||||
}
|
||||
|
||||
@@ -737,7 +775,7 @@ function renderTasks(tasks) {
|
||||
const name = document.createElement("div");
|
||||
name.className = "task-name";
|
||||
renderTaskName(name, task);
|
||||
head.append(name);
|
||||
head.appendChild(name);
|
||||
|
||||
const current = document.createElement("div");
|
||||
current.className = "task-current";
|
||||
@@ -751,7 +789,7 @@ function renderTasks(tasks) {
|
||||
progressItem.textContent = t("progressLabel") + ": " + formatSize(done) + " / " + formatSize(total);
|
||||
const etaItem = document.createElement("div");
|
||||
etaItem.textContent = t("etaLabel") + ": " + averageEta(task, done, total);
|
||||
meta.append(speedItem, progressItem, etaItem);
|
||||
appendChildren(meta, speedItem, progressItem, etaItem);
|
||||
|
||||
const progress = document.createElement("div");
|
||||
progress.className = "progress";
|
||||
@@ -761,13 +799,13 @@ function renderTasks(tasks) {
|
||||
const progressText = document.createElement("div");
|
||||
progressText.className = "progress-text";
|
||||
progressText.textContent = isPreparing ? t("preparing") : isFinishing ? t("finishing") : total ? pct + "%" : stateLabel(task.state);
|
||||
progress.append(bar, progressText);
|
||||
appendChildren(progress, bar, progressText);
|
||||
|
||||
const cancel = document.createElement("button");
|
||||
cancel.className = "danger";
|
||||
cancel.textContent = task.cancel_requested ? t("canceling") : t("cancel");
|
||||
cancel.disabled = task.cancel_requested;
|
||||
cancel.addEventListener("click", () => {
|
||||
bindPress(cancel, () => {
|
||||
if (confirm(t("cancelTaskConfirm", { label: opLabel(task.op) }))) {
|
||||
requestTaskCancel(task.id);
|
||||
api("/api/cancel", { id: task.id }).then(pollTasks).catch(err => {
|
||||
@@ -777,9 +815,9 @@ function renderTasks(tasks) {
|
||||
}
|
||||
});
|
||||
|
||||
if (isDelete) div.append(head, current, cancel);
|
||||
else if (total && !isFinishing) div.append(head, current, meta, progress, cancel);
|
||||
else div.append(head, current, progress, cancel);
|
||||
if (isDelete) appendChildren(div, head, current, cancel);
|
||||
else if (total && !isFinishing) appendChildren(div, head, current, meta, progress, cancel);
|
||||
else appendChildren(div, head, current, progress, cancel);
|
||||
tasksEl.appendChild(div);
|
||||
}
|
||||
|
||||
@@ -839,7 +877,9 @@ function actionDelete() {
|
||||
const items = selectedEntries();
|
||||
const paths = items.map(item => item.path);
|
||||
const target = itemTitle(items);
|
||||
if (!confirm(t("deleteConfirm", { name: target }))) return;
|
||||
const confirmKey = items.some(item => item.type === "d") ?
|
||||
"deleteConfirmRecursive" : "deleteConfirm";
|
||||
if (!confirm(t(confirmKey, { name: target }))) return;
|
||||
runAction(t("delete"), () => api("/api/delete", { paths: paths.join("\n") }));
|
||||
}
|
||||
|
||||
@@ -873,8 +913,8 @@ selectAllEl.addEventListener("change", () => {
|
||||
render();
|
||||
});
|
||||
contentEl.addEventListener("mousemove", event => {
|
||||
const row = event.target.closest("tr[data-path]");
|
||||
hoverPath(row ? row.dataset.path : null);
|
||||
const row = findRowFromTarget(event.target);
|
||||
hoverPath(row ? row.getAttribute("data-path") : null);
|
||||
});
|
||||
contentEl.addEventListener("mouseleave", () => hoverPath(null));
|
||||
contentEl.addEventListener("scroll", () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"applicationCategoryType": 65536,
|
||||
"titleId": "FMGR88888",
|
||||
"deeplinkUri": "http://127.0.0.1:8888/",
|
||||
"localizedParameters": {
|
||||
|
||||
+17
-1
@@ -9,10 +9,14 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/vfs.h>
|
||||
#else
|
||||
#include <sys/mount.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -580,6 +584,7 @@ ignore_chmod_error(int err) {
|
||||
return err == ENOTSUP || err == EPERM || err == EINVAL || err == EROFS;
|
||||
}
|
||||
|
||||
#ifndef __linux__
|
||||
static int
|
||||
fs_type_has_unix_modes(const char *type) {
|
||||
return strcmp(type, "exfat") &&
|
||||
@@ -588,25 +593,36 @@ fs_type_has_unix_modes(const char *type) {
|
||||
strcmp(type, "fat") &&
|
||||
strcmp(type, "vfat");
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
path_has_unix_modes(const char *path) {
|
||||
#ifdef __linux__
|
||||
(void)path;
|
||||
return 1;
|
||||
#else
|
||||
struct statfs fs;
|
||||
|
||||
if(statfs(path, &fs)) {
|
||||
return 1;
|
||||
}
|
||||
return fs_type_has_unix_modes(fs.f_fstypename);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
fd_has_unix_modes(int fd) {
|
||||
#ifdef __linux__
|
||||
(void)fd;
|
||||
return 1;
|
||||
#else
|
||||
struct statfs fs;
|
||||
|
||||
if(fstatfs(fd, &fs)) {
|
||||
return 1;
|
||||
}
|
||||
return fs_type_has_unix_modes(fs.f_fstypename);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
|
||||
+31
-13
@@ -28,12 +28,12 @@ port_available(unsigned short port) {
|
||||
|
||||
if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
perror("socket");
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) {
|
||||
perror("setsockopt");
|
||||
close(fd);
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
@@ -50,7 +50,11 @@ find_available_port(unsigned short start) {
|
||||
unsigned int port;
|
||||
|
||||
for(port = start; port <= 65535; port++) {
|
||||
if(port_available((unsigned short)port)) {
|
||||
int available = port_available((unsigned short)port);
|
||||
if(available < 0) {
|
||||
return 0;
|
||||
}
|
||||
if(available) {
|
||||
return (unsigned short)port;
|
||||
}
|
||||
}
|
||||
@@ -99,7 +103,10 @@ find_pid(const char *name) {
|
||||
int
|
||||
main(int argc, char **argv) {
|
||||
unsigned short port;
|
||||
#ifdef __SCE__
|
||||
unsigned short notified_port = 0;
|
||||
pid_t pid;
|
||||
#endif
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
@@ -114,26 +121,37 @@ main(int argc, char **argv) {
|
||||
sleep(1);
|
||||
}
|
||||
#else
|
||||
(void)pid;
|
||||
#endif
|
||||
|
||||
port = find_available_port(DEFAULT_PORT);
|
||||
if(!port) {
|
||||
fprintf(stderr, "no available port from %u\n", DEFAULT_PORT);
|
||||
return 1;
|
||||
}
|
||||
|
||||
puts(PROCESS_NAME);
|
||||
printf("version: %s\n", VERSION_TAG);
|
||||
printf("listening on port %u\n", port);
|
||||
|
||||
#ifdef __SCE__
|
||||
app_install_if_needed();
|
||||
notify_user("Web File Manager\nVersion: %s\nPort: %u", VERSION_TAG, port);
|
||||
#endif
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
return websrv_listen(port) ? 1 : 0;
|
||||
while(1) {
|
||||
port = find_available_port(DEFAULT_PORT);
|
||||
if(!port) {
|
||||
fprintf(stderr, "no available port from %u\n", DEFAULT_PORT);
|
||||
sleep(3);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("listening on port %u\n", port);
|
||||
#ifdef __SCE__
|
||||
if(notified_port != port) {
|
||||
notify_user("Web File Manager\nVersion: %s\nPort: %u", VERSION_TAG, port);
|
||||
notified_port = port;
|
||||
}
|
||||
#endif
|
||||
|
||||
websrv_listen(port);
|
||||
sleep(3);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user