mirror of
https://github.com/ItsBlurf/BFpilot.git
synced 2026-10-18 23:06:53 -05:00
Fix launcher install and progress reporting
This commit is contained in:
1 parent
7186e2d66a
commit
30d89ecafa
5 files changed
+86
-27
No files matched your search
@@ -28,6 +28,10 @@ When the launcher is newly installed or updated, the payload also asks the PS5
|
||||
to open that local browser URL once. Later loads keep the tile available without
|
||||
forcing the browser open again.
|
||||
|
||||
Launcher state is stored in `/data/BS5fm`. The PS5 app tile itself still lives
|
||||
under `/user/app/BS5F00001`, which is the console app location used by the app
|
||||
install API.
|
||||
|
||||
The payload also shows a PS5 notification with the external browser URL when
|
||||
the web server is ready. Port `5905` is fixed so this payload can run next to
|
||||
older builds that may still be listening on another port.
|
||||
@@ -59,9 +63,10 @@ At a high level the payload has five parts:
|
||||
notifications, installs the launcher tile if needed, and starts the HTTP
|
||||
server on port `5905`.
|
||||
- `src/app_installer.c` writes the embedded launcher metadata/icon under
|
||||
`/user/app/BS5F00001` and registers the PS5 home-screen tile. The installer
|
||||
resolves the title-directory install function from `libSceAppInstUtil.sprx`
|
||||
at runtime and falls back to the broader app install call when needed.
|
||||
`/user/app/BS5F00001`, stores its update marker under `/data/BS5fm`, and
|
||||
registers the PS5 home-screen tile. The installer resolves the title-directory
|
||||
install function from `libSceAppInstUtil.sprx` at runtime and falls back to
|
||||
the broader app install call when needed.
|
||||
- `src/websrv_lite.c` is a compact HTTP server. It serves the embedded UI,
|
||||
static downloads under `/fs`, and JSON APIs under `/api/fs`.
|
||||
- `src/transfer.c` implements the file-manager API: list, stat, folder size,
|
||||
@@ -114,3 +119,5 @@ port `5905` instead.
|
||||
Recursive folder size, copy, move, and delete can still take time on very large
|
||||
external drives. Progress polling is tolerant of short hiccups, and active jobs
|
||||
continue on the PS5 side even if the browser temporarily loses contact.
|
||||
Copy, move, and delete jobs report PS5-side throughput in `MB/s` plus ETA from
|
||||
the server-side job counters.
|
||||
+18
-5
@@ -727,6 +727,12 @@
|
||||
return s + 's';
|
||||
}
|
||||
|
||||
function speedMBps(bytesPerSecond) {
|
||||
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) return '';
|
||||
const mb = bytesPerSecond / (1024 * 1024);
|
||||
return mb.toFixed(mb >= 10 ? 1 : 2) + ' MB/s';
|
||||
}
|
||||
|
||||
function formatDate(seconds) {
|
||||
if (!Number.isFinite(seconds) || seconds <= 0) return 'Unknown';
|
||||
return new Date(seconds * 1000).toLocaleString();
|
||||
@@ -739,7 +745,10 @@
|
||||
const totalFiles = Number(job.totalFiles || 0);
|
||||
const failedFiles = Number(job.failedFiles || 0);
|
||||
const elapsed = Math.max(0, (Date.now() / 1000) - Number(job.startedAt || 0));
|
||||
const speed = elapsed > 0 && copied > 0 ? copied / elapsed : 0;
|
||||
const reportedSpeed = Number(job.speedBytesPerSec || 0);
|
||||
const speed = reportedSpeed > 0 ? reportedSpeed :
|
||||
(elapsed > 0 && copied > 0 ? copied / elapsed : 0);
|
||||
const reportedEta = Number(job.etaSeconds || 0);
|
||||
const pct = total > 0 ? Math.max(0, Math.min(100, (copied / total) * 100)) : 0;
|
||||
const parts = [];
|
||||
|
||||
@@ -755,8 +764,12 @@
|
||||
}
|
||||
|
||||
if (speed > 0) {
|
||||
parts.push(shortSize(speed) + '/s');
|
||||
if (total > copied) parts.push('ETA ' + shortDuration((total - copied) / speed));
|
||||
parts.push(speedMBps(speed));
|
||||
if (reportedEta > 0) {
|
||||
parts.push('ETA ' + shortDuration(reportedEta));
|
||||
} else if (total > copied) {
|
||||
parts.push('ETA ' + shortDuration((total - copied) / speed));
|
||||
}
|
||||
}
|
||||
|
||||
if (failedFiles > 0) parts.push(failedFiles + ' failed');
|
||||
@@ -1027,7 +1040,7 @@
|
||||
if (!job.busy) return job;
|
||||
} catch (err) {
|
||||
missed++;
|
||||
if (missed >= 120) throw err;
|
||||
if (missed >= 600) throw err;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 700));
|
||||
}
|
||||
@@ -1171,7 +1184,7 @@
|
||||
} catch (err) {
|
||||
missed++;
|
||||
text.textContent = 'Waiting for status...';
|
||||
if (missed >= 120) {
|
||||
if (missed >= 600) {
|
||||
clearInterval(state.jobTimer);
|
||||
state.jobTimer = 0;
|
||||
box.classList.remove('show');
|
||||
|
||||
+25
-7
@@ -18,7 +18,9 @@
|
||||
#define BS5FM_APP_TITLE_ID "BS5F00001"
|
||||
#define BS5FM_APP_ROOT "/user/app"
|
||||
#define BS5FM_APP_PARENT BS5FM_APP_ROOT "/"
|
||||
#define BS5FM_INSTALL_MARKER "bs5filemanager-launcher-v2\n"
|
||||
#define BS5FM_DATA_DIR "/data/BS5fm"
|
||||
#define BS5FM_MARKER_PATH BS5FM_DATA_DIR "/launcher.ok"
|
||||
#define BS5FM_INSTALL_MARKER "bs5filemanager-launcher-v3\n"
|
||||
|
||||
#define INCASSET(name, file) \
|
||||
__asm__(".section .rodata\n" \
|
||||
@@ -37,6 +39,7 @@ INCASSET(bs5fm_icon0_png, "assets-app/icon0.png");
|
||||
|
||||
int sceAppInstUtilInitialize(void);
|
||||
int sceAppInstUtilAppInstallAll(void *);
|
||||
int sceAppInstUtilAppUnInstall(const char *);
|
||||
|
||||
typedef int (*app_install_title_dir_fn)(const char *, const char *, void *);
|
||||
|
||||
@@ -106,27 +109,33 @@ mkdir_if_needed(const char *path) {
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ensure_data_dir(void) {
|
||||
if(mkdir_if_needed("/data") != 0) return -1;
|
||||
return mkdir_if_needed(BS5FM_DATA_DIR);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
bs5fm_install_app_if_needed(void) {
|
||||
char app_dir[256];
|
||||
char sce_sys_dir[256];
|
||||
char param_path[256];
|
||||
char icon_path[256];
|
||||
char marker_path[256];
|
||||
struct stat st;
|
||||
|
||||
snprintf(app_dir, sizeof(app_dir), BS5FM_APP_ROOT "/%s", BS5FM_APP_TITLE_ID);
|
||||
snprintf(sce_sys_dir, sizeof(sce_sys_dir), "%s/sce_sys", app_dir);
|
||||
snprintf(param_path, sizeof(param_path), "%s/param.json", sce_sys_dir);
|
||||
snprintf(icon_path, sizeof(icon_path), "%s/icon0.png", sce_sys_dir);
|
||||
snprintf(marker_path, sizeof(marker_path), "%s/bs5fm.ok", app_dir);
|
||||
|
||||
int needs_install = stat(app_dir, &st) != 0 ||
|
||||
int app_exists = stat(app_dir, &st) == 0;
|
||||
int needs_install = !app_exists ||
|
||||
file_differs(param_path, bs5fm_param_json,
|
||||
bs5fm_param_json_size) ||
|
||||
file_differs(icon_path, bs5fm_icon0_png,
|
||||
bs5fm_icon0_png_size) ||
|
||||
file_differs(marker_path, g_install_marker,
|
||||
file_differs(BS5FM_MARKER_PATH, g_install_marker,
|
||||
sizeof(g_install_marker) - 1);
|
||||
|
||||
if(!needs_install) return 0;
|
||||
@@ -143,6 +152,11 @@ bs5fm_install_app_if_needed(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(app_exists) {
|
||||
int uninstall_err = sceAppInstUtilAppUnInstall(BS5FM_APP_TITLE_ID);
|
||||
printf(" launcher install: refresh old tile 0x%08x\n", uninstall_err);
|
||||
}
|
||||
|
||||
if(mkdir_if_needed(app_dir) != 0 || mkdir_if_needed(sce_sys_dir) != 0) {
|
||||
printf(" launcher install: mkdir failed errno %d\n", errno);
|
||||
return -1;
|
||||
@@ -164,9 +178,13 @@ bs5fm_install_app_if_needed(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(write_file(marker_path, g_install_marker,
|
||||
if(ensure_data_dir() != 0) {
|
||||
printf(" launcher install: warning, failed creating %s errno %d\n",
|
||||
BS5FM_DATA_DIR, errno);
|
||||
} else if(write_file(BS5FM_MARKER_PATH, g_install_marker,
|
||||
sizeof(g_install_marker) - 1) != 0) {
|
||||
printf(" launcher install: warning, failed writing %s\n", marker_path);
|
||||
printf(" launcher install: warning, failed writing %s\n",
|
||||
BS5FM_MARKER_PATH);
|
||||
}
|
||||
|
||||
bs5fm_notify("BS5FileManager app ready",
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
#define BS5FM_WEB_PORT 5905
|
||||
|
||||
int sceNetCtlInit(void);
|
||||
int sceSystemServiceLaunchWebBrowser(const char *uri);
|
||||
int sceSystemServiceLaunchWebBrowser(const char *uri, void *);
|
||||
int sceUserServiceInitialize(void *);
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ static void
|
||||
launch_browser_once(ready_state_t *state) {
|
||||
if(!state->launch_browser || state->browser_launched) return;
|
||||
|
||||
int err = sceSystemServiceLaunchWebBrowser("http://127.0.0.1:5905/");
|
||||
int err = sceSystemServiceLaunchWebBrowser("http://127.0.0.1:5905/", NULL);
|
||||
if(err == 0) {
|
||||
puts(" ps5 browser: opened http://127.0.0.1:5905/");
|
||||
} else {
|
||||
|
||||
+31
-10
@@ -388,7 +388,7 @@ copy_recursive(const char *src, const char *dst) {
|
||||
|
||||
|
||||
static int
|
||||
delete_recursive(const char *path) {
|
||||
delete_recursive(const char *path, int count_progress) {
|
||||
if(job_cancelled()) {
|
||||
errno = ECANCELED;
|
||||
return -1;
|
||||
@@ -405,7 +405,7 @@ delete_recursive(const char *path) {
|
||||
if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue;
|
||||
char child[1024];
|
||||
join_path(child, sizeof(child), path, ent->d_name);
|
||||
if(delete_recursive(child) != 0) {
|
||||
if(delete_recursive(child, count_progress) != 0) {
|
||||
rc = -1;
|
||||
atomic_fetch_add(&g_job.failed_files, 1);
|
||||
if(job_cancelled()) break;
|
||||
@@ -415,16 +415,24 @@ delete_recursive(const char *path) {
|
||||
if(rc == 0) {
|
||||
job_set_current(path);
|
||||
if(rmdir(path) != 0) return -1;
|
||||
if(st.st_size > 0) atomic_fetch_add(&g_job.copied_bytes, (long)st.st_size);
|
||||
atomic_fetch_add(&g_job.done_files, 1);
|
||||
if(count_progress) {
|
||||
if(st.st_size > 0) {
|
||||
atomic_fetch_add(&g_job.copied_bytes, (long)st.st_size);
|
||||
}
|
||||
atomic_fetch_add(&g_job.done_files, 1);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
job_set_current(path);
|
||||
if(unlink(path) != 0) return -1;
|
||||
if(st.st_size > 0) atomic_fetch_add(&g_job.copied_bytes, (long)st.st_size);
|
||||
atomic_fetch_add(&g_job.done_files, 1);
|
||||
if(count_progress) {
|
||||
if(st.st_size > 0) {
|
||||
atomic_fetch_add(&g_job.copied_bytes, (long)st.st_size);
|
||||
}
|
||||
atomic_fetch_add(&g_job.done_files, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -492,7 +500,7 @@ copy_worker(void *arg) {
|
||||
free(a);
|
||||
return NULL;
|
||||
}
|
||||
if(a->is_move && delete_recursive(a->src) != 0) {
|
||||
if(a->is_move && delete_recursive(a->src, 0) != 0) {
|
||||
char err[160];
|
||||
snprintf(err, sizeof(err), "post-move cleanup: %s", strerror(errno));
|
||||
job_end(-1, err);
|
||||
@@ -520,7 +528,7 @@ delete_worker(void *arg) {
|
||||
atomic_store(&g_job.total_files, (int)(items > 0 ? items : 1));
|
||||
atomic_store(&g_job.total_bytes, bytes);
|
||||
|
||||
if(delete_recursive(a->path) != 0) {
|
||||
if(delete_recursive(a->path, 1) != 0) {
|
||||
char err[160];
|
||||
snprintf(err, sizeof(err), "delete: %s", strerror(errno));
|
||||
job_end(-1, err);
|
||||
@@ -882,6 +890,16 @@ status_handler(const http_request_t *req) {
|
||||
ended_at = g_job.ended_at;
|
||||
pthread_mutex_unlock(&g_job.lock);
|
||||
|
||||
time_t now = time(NULL);
|
||||
time_t ref_at = busy ? now : (ended_at ? ended_at : now);
|
||||
long elapsed = started_at > 0 && ref_at > started_at
|
||||
? (long)(ref_at - started_at)
|
||||
: 0;
|
||||
long speed = elapsed > 0 && cb > 0 ? cb / elapsed : 0;
|
||||
long eta = busy && speed > 0 && tb > cb
|
||||
? (tb - cb + speed - 1) / speed
|
||||
: 0;
|
||||
|
||||
json_buf_t b = {0};
|
||||
if(json_appendf(&b,
|
||||
"{\"ok\":true,\"busy\":%s,\"cancelling\":%s,\"verb\":",
|
||||
@@ -894,8 +912,11 @@ status_handler(const http_request_t *req) {
|
||||
json_appendf(&b,
|
||||
",\"totalBytes\":%ld,\"copiedBytes\":%ld,"
|
||||
"\"totalFiles\":%d,\"doneFiles\":%d,\"failedFiles\":%d,"
|
||||
"\"startedAt\":%ld,\"endedAt\":%ld}",
|
||||
tb, cb, tf, df, ff, (long)started_at, (long)ended_at) != 0) {
|
||||
"\"startedAt\":%ld,\"endedAt\":%ld,"
|
||||
"\"elapsedSeconds\":%ld,\"speedBytesPerSec\":%ld,"
|
||||
"\"etaSeconds\":%ld}",
|
||||
tb, cb, tf, df, ff, (long)started_at, (long)ended_at,
|
||||
elapsed, speed, eta) != 0) {
|
||||
free(b.data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user