Compare commits

..
4 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
12 changed files with 203 additions and 27 deletions

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ ifeq ($(MAKECMDGOALS),)
endif
endif
VERSION_TAG := v1.2
VERSION_TAG := v1.3
TITLE_ID := FMGR88888
PYTHON ?= python3
STRIP ?= $(PS5_PAYLOAD_SDK)/bin/prospero-strip
+1 -1
View File
@@ -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
View File
@@ -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>
+98 -6
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;
}
@@ -354,7 +354,7 @@ button.primary:hover {
color: #f8dfae;
}
button.install-action:hover {
button.install-action:hover:not(:disabled) {
background: #433521;
}
@@ -440,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;
@@ -527,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;
@@ -658,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;
@@ -672,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;
@@ -986,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;
}
+47 -11
View File
@@ -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.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;
@@ -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;
}
@@ -525,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");
@@ -546,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));
}
@@ -578,6 +602,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 +930,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 +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";
@@ -1080,7 +1105,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 +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;
@@ -1153,8 +1187,8 @@ async function load(path, scrollTop, force, alertOnError) {
if (alertOnError) alert(err.message);
return false;
} finally {
hideContentLoading();
loadingPath = null;
hideContentLoading();
}
}
@@ -1781,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);
@@ -1791,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"));
+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;
}
+3 -3
View File
@@ -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);
+3
View File
@@ -150,6 +150,9 @@ main(int argc, char **argv) {
#endif
websrv_listen(port);
if(websrv_stop_requested()) {
break;
}
sleep(3);
}
+13 -1
View File
@@ -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
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);