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 | |
|---|---|---|---|
|
|
48d669b848 | ||
|
|
dafbd8dcbf | ||
|
|
939257eda0 | ||
|
|
3efaf24385 | ||
|
|
5302e971f9 | ||
|
|
582d154cdf |
No files matched your search
@@ -13,7 +13,7 @@ ifeq ($(MAKECMDGOALS),)
|
||||
endif
|
||||
endif
|
||||
|
||||
VERSION_TAG := v1.2
|
||||
VERSION_TAG := v1.4
|
||||
TITLE_ID := FMGR88888
|
||||
PYTHON ?= python3
|
||||
STRIP ?= $(PS5_PAYLOAD_SDK)/bin/prospero-strip
|
||||
|
||||
@@ -121,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 |
+8
-1
@@ -72,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>
|
||||
|
||||
+102
-8
@@ -65,13 +65,13 @@ body {
|
||||
}
|
||||
|
||||
.top-left {
|
||||
flex: 1 1 420px;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
flex: 1 1 360px;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
margin-left: auto;
|
||||
justify-content: flex-end;
|
||||
@@ -88,6 +88,7 @@ body {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -110,6 +111,7 @@ body {
|
||||
}
|
||||
|
||||
.space-item {
|
||||
flex: 0 0 auto;
|
||||
margin-left: 14px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
@@ -297,8 +299,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 +321,7 @@ button:disabled,
|
||||
background: #285943;
|
||||
}
|
||||
|
||||
button.primary:hover {
|
||||
button.primary:hover:not(:disabled) {
|
||||
background: #347358;
|
||||
}
|
||||
|
||||
@@ -354,7 +356,7 @@ button.primary:hover {
|
||||
color: #f8dfae;
|
||||
}
|
||||
|
||||
button.install-action:hover {
|
||||
button.install-action:hover:not(:disabled) {
|
||||
background: #433521;
|
||||
}
|
||||
|
||||
@@ -440,6 +442,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;
|
||||
@@ -527,6 +603,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;
|
||||
@@ -658,6 +741,7 @@ input[type="checkbox"] {
|
||||
|
||||
.image-preview-stage {
|
||||
width: 100%;
|
||||
height: calc(100vh - 250px);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
@@ -672,8 +756,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;
|
||||
@@ -986,12 +1069,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;
|
||||
}
|
||||
|
||||
+149
-18
@@ -19,6 +19,8 @@ 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 = "";
|
||||
@@ -27,7 +29,7 @@ let downloadFrame = null;
|
||||
let uploadXhr = null;
|
||||
let L = {};
|
||||
|
||||
const APP_VERSION = "v1.2";
|
||||
const APP_VERSION = "v1.4";
|
||||
const LAST_PATH_KEY = "ps5-web-file-mgr:last-path";
|
||||
const SORT_KEY = "ps5-web-file-mgr:list-sort";
|
||||
const LOADING_DISPLAY_DELAY = 250;
|
||||
@@ -50,6 +52,7 @@ 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");
|
||||
@@ -133,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;
|
||||
}
|
||||
@@ -311,6 +316,23 @@ function displayPath(path) {
|
||||
return decodeFsText(path);
|
||||
}
|
||||
|
||||
function renderAddressPath() {
|
||||
const path = displayPath(cwd);
|
||||
pathEl.title = path;
|
||||
pathEl.textContent = path;
|
||||
if (pathEl.scrollWidth <= pathEl.clientWidth) return;
|
||||
|
||||
let low = 0;
|
||||
let high = path.length;
|
||||
while (low < high) {
|
||||
const count = Math.ceil((low + high) / 2);
|
||||
pathEl.textContent = "..." + path.slice(-count);
|
||||
if (pathEl.scrollWidth <= pathEl.clientWidth) low = count;
|
||||
else high = count - 1;
|
||||
}
|
||||
pathEl.textContent = "..." + path.slice(-low);
|
||||
}
|
||||
|
||||
async function fetchText(path, params) {
|
||||
const response = await request(path, params, { method: "GET" });
|
||||
return {
|
||||
@@ -493,6 +515,54 @@ function readSavedPath() {
|
||||
}
|
||||
}
|
||||
|
||||
function historyPath() {
|
||||
const hash = window.location.hash || "";
|
||||
if (hash.length <= 1) return "";
|
||||
try {
|
||||
const path = decodeURIComponent(hash.slice(1));
|
||||
return path.charAt(0) === "/" ? path : "";
|
||||
} catch (err) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function writeHistoryPath(path, replace) {
|
||||
if (!window.history || !history.pushState) return;
|
||||
const hash = "#" + encodeURIComponent(path || "/");
|
||||
if (window.location.hash === hash) return;
|
||||
const state = { path: path || "/" };
|
||||
if (replace && history.replaceState) history.replaceState(state, "", hash);
|
||||
else history.pushState(state, "", hash);
|
||||
}
|
||||
|
||||
function pathHistoryChain(path) {
|
||||
const clean = String(path || "/").replace(/\/+$/, "") || "/";
|
||||
const parts = clean.split("/");
|
||||
const chain = ["/"];
|
||||
let current = "";
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
if (!parts[i]) continue;
|
||||
current += "/" + parts[i];
|
||||
chain.push(current);
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
function seedHistoryPath(path) {
|
||||
if (!window.history || !history.pushState || !history.replaceState) return;
|
||||
const chain = pathHistoryChain(path);
|
||||
for (let i = 0; i < chain.length; i++) {
|
||||
writeHistoryPath(chain[i], i === 0);
|
||||
}
|
||||
}
|
||||
|
||||
function historyBlocked() {
|
||||
return Boolean(busy || loadingPath || pendingAbortController ||
|
||||
pendingOverlayText || taskOverlayTimer || !overlayEl.hidden ||
|
||||
!contentLoadingEl.hidden || contentEl.classList.contains("loading") ||
|
||||
!textEditorOverlayEl.hidden || !imagePreviewOverlayEl.hidden);
|
||||
}
|
||||
|
||||
function savePath(path) {
|
||||
try {
|
||||
localStorage.setItem(LAST_PATH_KEY, path || "/");
|
||||
@@ -525,6 +595,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");
|
||||
@@ -541,11 +612,31 @@ async function refreshSpaces() {
|
||||
span.title = item.path + " " + span.textContent;
|
||||
spaceInfoEl.appendChild(span);
|
||||
}
|
||||
renderAddressPath();
|
||||
} catch (err) {
|
||||
spaceInfoEl.textContent = "";
|
||||
renderAddressPath();
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -578,6 +669,13 @@ 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";
|
||||
@@ -899,6 +997,7 @@ 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);
|
||||
@@ -1040,14 +1139,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";
|
||||
@@ -1080,7 +1172,7 @@ 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" :
|
||||
@@ -1112,6 +1204,19 @@ function parentPath(path) {
|
||||
return parts.join("/") || "/";
|
||||
}
|
||||
|
||||
function actionParentDirectory(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
if (busy || loadingPath || cwd === "/") return;
|
||||
if (window.history && history.back && historyPath() === cwd) {
|
||||
history.back();
|
||||
} else {
|
||||
load(parentPath(cwd), undefined, false, true, "push");
|
||||
}
|
||||
}
|
||||
|
||||
function cell(text) {
|
||||
const td = document.createElement("td");
|
||||
td.textContent = text;
|
||||
@@ -1125,7 +1230,7 @@ function classCell(className, text) {
|
||||
return td;
|
||||
}
|
||||
|
||||
async function load(path, scrollTop, force, alertOnError) {
|
||||
async function load(path, scrollTop, force, alertOnError, historyMode) {
|
||||
const shouldScrollTop = scrollTop !== false;
|
||||
if ((busy && !force) || loadingPath) return;
|
||||
loadingPath = path;
|
||||
@@ -1138,8 +1243,9 @@ async function load(path, scrollTop, force, alertOnError) {
|
||||
}
|
||||
if (!contentLoadingEl.hidden) await nextPaint();
|
||||
cwd = data.path;
|
||||
pathEl.textContent = displayPath(cwd);
|
||||
renderAddressPath();
|
||||
savePath(cwd);
|
||||
if (historyMode) writeHistoryPath(cwd, historyMode === "replace");
|
||||
refreshSpaces();
|
||||
entries = data.entries.sort(compareEntries);
|
||||
selected.clear();
|
||||
@@ -1153,11 +1259,17 @@ async function load(path, scrollTop, force, alertOnError) {
|
||||
if (alertOnError) alert(err.message);
|
||||
return false;
|
||||
} finally {
|
||||
hideContentLoading();
|
||||
loadingPath = null;
|
||||
hideContentLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAndReveal(path, scrollTop, force, alertOnError, historyMode, revealPath) {
|
||||
const ok = await load(path, scrollTop, force, alertOnError, historyMode);
|
||||
if (ok && revealPath) revealPathInList(revealPath);
|
||||
return ok;
|
||||
}
|
||||
|
||||
async function runAction(label, fn, options) {
|
||||
try {
|
||||
taskRefreshPath = cwd;
|
||||
@@ -1781,16 +1893,31 @@ 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);
|
||||
imagePreviewCloseBtn.addEventListener("click", closeImagePreview);
|
||||
window.addEventListener("resize", renderAddressPath);
|
||||
window.addEventListener("popstate", event => {
|
||||
if (historyBlocked()) {
|
||||
writeHistoryPath(cwd, false);
|
||||
setStatus(t("activeTask"));
|
||||
return;
|
||||
}
|
||||
const path = event.state && event.state.path ? event.state.path : historyPath();
|
||||
if (path && path !== cwd) {
|
||||
const revealPath = parentPath(cwd) === path ? cwd : "";
|
||||
loadAndReveal(path, false, false, true, null, revealPath);
|
||||
}
|
||||
});
|
||||
document.addEventListener("click", event => {
|
||||
if (!uploadMenuEl || uploadMenuEl.contains(event.target)) return;
|
||||
uploadMenuEl.classList.remove("open");
|
||||
});
|
||||
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"));
|
||||
@@ -1838,8 +1965,8 @@ filesEl.addEventListener("click", event => {
|
||||
if (item.type === "parent" || item.type === "d") {
|
||||
const revealPath = item.type === "parent" ? cwd : "";
|
||||
setTimeout(async () => {
|
||||
if (await load(item.path, item.type === "parent" ? false : undefined, false, true)) {
|
||||
/* if (revealPath) revealPathInList(revealPath); */
|
||||
if (await load(item.path, item.type === "parent" ? false : undefined, false, true, "push")) {
|
||||
if (revealPath) revealPathInList(revealPath);
|
||||
} else await load(cwd, false, true);
|
||||
}, 0);
|
||||
}
|
||||
@@ -1882,9 +2009,9 @@ async function init() {
|
||||
applyStaticText();
|
||||
readSavedSort();
|
||||
updateSortHeaders();
|
||||
const savedPath = readSavedPath();
|
||||
const savedPath = historyPath() || readSavedPath();
|
||||
cwd = savedPath;
|
||||
pathEl.textContent = displayPath(cwd);
|
||||
renderAddressPath();
|
||||
taskRefreshPath = savedPath;
|
||||
refreshSpaces();
|
||||
await pollTasks();
|
||||
@@ -1892,7 +2019,11 @@ async function init() {
|
||||
setStatus(t("activeTask"));
|
||||
return;
|
||||
}
|
||||
if (!(await load(savedPath || "/")) && savedPath !== "/") await load("/");
|
||||
if (await load(savedPath || "/", undefined, false, false)) {
|
||||
seedHistoryPath(cwd);
|
||||
} else if (savedPath !== "/" && await load("/", undefined, false, false)) {
|
||||
seedHistoryPath(cwd);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
+6
-1
@@ -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;
|
||||
}
|
||||
+3
-3
@@ -1868,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;
|
||||
}
|
||||
|
||||
@@ -1879,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);
|
||||
|
||||
@@ -150,6 +150,9 @@ main(int argc, char **argv) {
|
||||
#endif
|
||||
|
||||
websrv_listen(port);
|
||||
if(websrv_stop_requested()) {
|
||||
break;
|
||||
}
|
||||
sleep(3);
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -2,7 +2,10 @@
|
||||
|
||||
#ifndef __linux__
|
||||
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct pkg_metadata {
|
||||
const char *uri;
|
||||
@@ -57,8 +60,10 @@ pkg_installer_initialize(void) {
|
||||
|
||||
int
|
||||
pkg_installer_install(const char *path) {
|
||||
char install_path[PATH_MAX + sizeof("/user")];
|
||||
const char *uri = path;
|
||||
pkg_metadata_t metadata = {
|
||||
.uri = path,
|
||||
.uri = NULL,
|
||||
.ex_uri = "",
|
||||
.playgo_scenario_id = "",
|
||||
.content_id = "",
|
||||
@@ -69,6 +74,13 @@ pkg_installer_install(const char *path) {
|
||||
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) {
|
||||
|
||||
+21
-2
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user