mirror of
https://github.com/LisherSong/ps5-web-file-manager.git
synced 2026-10-06 06:00:23 +02:00
fix(7z): keep the spinner alive on small archives; surface password errors with the archive path
UI regression: a 7z with fewer than 4096 entries spun a "scanning archive"
label and never replaced it until extraction had been running long enough
for the byte-level callback to fire, on a slow PS5 that meant "looks hung".
Lower the per-entry barrier in scan_entries to 256 entries, force a final
report after the scan so entries_total / bytes_total always reach the UI,
and force a report at the start of precheck_folders so the throttled
"between scan end and first extraction byte" window is not silent either.
Error regression: when the password was wrong the engine returned
ZIPX_ERR_PASSWORD with an empty detail field; extract_set_error copied it
into task->error_arg verbatim, so the i18n template's {arg} expanded to ""
and the user saw "Wrong password: ". Carry the original archive path into
the error detail so the toast reads "Wrong password: myfile.7z".
Languages: drop the stale "only ZIP/RAR" line from err_extract_unsupported
(7z has been supported since v1.9), and add the new err_extract_password
key in both en/zh.
Tests: lock down both regressions.
* progress recorder: bytes_done monotonic, total > 0, final byte count
reaches >= 90% of the declared total.
* wrong password: r.detail contains the archive basename.
7z 28 + ZIP 108 + RAR 27 = 163 checks, 0 failures.
This commit is contained in:
1 parent
27eec8c399
commit
112f8a6b72
4 files changed
+87
-5
No files matched your search
+2
-1
@@ -180,7 +180,8 @@ window.WFM_LANG = {
|
||||
err_destination_must_be_directory: "Destination must be a folder for multiple items",
|
||||
err_extract_open_failed: "Cannot open archive: {arg}",
|
||||
err_extract_corrupt: "Archive is corrupt or incomplete: {arg}",
|
||||
err_extract_unsupported: "Unsupported archive (only unencrypted plain ZIP and single-volume RAR are supported): {arg}",
|
||||
err_extract_unsupported: "Unsupported archive (only plain ZIP, single-volume RAR, and 7z are supported): {arg}",
|
||||
err_extract_password: "Wrong password or the archive is not encrypted with the one supplied: {arg}",
|
||||
err_extract_unsafe_name: "Archive contains an unsafe path: {arg}",
|
||||
err_extract_special_entry: "Archive contains an unsupported special file: {arg}",
|
||||
err_extract_duplicate: "Archive contains duplicate entries: {arg}",
|
||||
|
||||
+2
-1
@@ -180,7 +180,8 @@ window.WFM_LANG = {
|
||||
err_destination_must_be_directory: "多个项目的目标必须是目录",
|
||||
err_extract_open_failed: "无法打开压缩包: {arg}",
|
||||
err_extract_corrupt: "压缩包损坏或不完整: {arg}",
|
||||
err_extract_unsupported: "不支持的压缩包(仅支持未加密的普通 ZIP 与单卷 RAR): {arg}",
|
||||
err_extract_unsupported: "不支持的压缩包(仅支持未加密的普通 ZIP、单卷 RAR,以及 7z):{arg}",
|
||||
err_extract_password: "密码错误,或压缩包未使用所提供的密码加密: {arg}",
|
||||
err_extract_unsafe_name: "压缩包包含不安全的路径: {arg}",
|
||||
err_extract_special_entry: "压缩包包含不支持的特殊文件: {arg}",
|
||||
err_extract_duplicate: "压缩包包含重复条目: {arg}",
|
||||
|
||||
+18
-3
@@ -76,6 +76,7 @@ typedef struct {
|
||||
void *userdata;
|
||||
zipx_result_t *result;
|
||||
const char *password;
|
||||
const char *sevenz_path; /* original archive path, surfaced in errors */
|
||||
uint64_t entries_total;
|
||||
uint64_t entries_done;
|
||||
uint64_t bytes_total;
|
||||
@@ -858,7 +859,9 @@ scan_entries(const CSzArEx *db, szx_ctx_t *c) {
|
||||
(unsigned long long)c->limits.max_entries);
|
||||
break;
|
||||
}
|
||||
if(c->entries_total % 4096 == 0) {
|
||||
/* Once per ~256 entries is plenty for a header walk that only needs to
|
||||
keep the spinner alive; szx_report() also rate-limits at 200 ms. */
|
||||
if(c->entries_total % 256 == 0) {
|
||||
if(szx_canceled(c)) {
|
||||
ret = szx_fail(c, ZIPX_ERR_CANCELED, name, NULL);
|
||||
break;
|
||||
@@ -867,6 +870,11 @@ scan_entries(const CSzArEx *db, szx_ctx_t *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* For archives below the per-N-entry report threshold the scan never fires a
|
||||
throttled callback; push the final totals out so the UI sees total/entries
|
||||
populated before extraction begins. */
|
||||
szx_report(c, ZIPX_PHASE_SCAN, NULL, 1);
|
||||
|
||||
szx_nameset_free(&set);
|
||||
return ret;
|
||||
}
|
||||
@@ -904,6 +912,10 @@ static int
|
||||
precheck_folders(const CSzArEx *db, szx_ctx_t *c) {
|
||||
UInt32 folder;
|
||||
|
||||
/* The decode stack for every folder has to round-trip here before the first
|
||||
byte is written, so a multi-thousand-folder archive does not look stalled
|
||||
between the entry scan ending and extraction starting. */
|
||||
szx_report(c, ZIPX_PHASE_SCAN, "validating folders", 1);
|
||||
for(folder = 0; folder < db->db.NumFolders; folder++) {
|
||||
uint64_t pack_positions[SZ_CHAIN_MAX_STREAMS + 1];
|
||||
UInt32 pack_count = 0;
|
||||
@@ -1197,9 +1209,11 @@ szx_sink_write(void *ctx, const void *data, size_t size) {
|
||||
static int
|
||||
szx_decode_error(szx_ctx_t *c, UInt32 folder, const sz_chain_err_t *derr,
|
||||
const char *desc, int encrypted) {
|
||||
const char *detail = encrypted ? c->sevenz_path : NULL;
|
||||
|
||||
switch(derr->status) {
|
||||
case SZ_CHAIN_ERR_PASSWORD:
|
||||
return szx_fail(c, ZIPX_ERR_PASSWORD, NULL, "folder %u (%s): %s",
|
||||
return szx_fail(c, ZIPX_ERR_PASSWORD, detail, "folder %u (%s): %s",
|
||||
(unsigned)folder, desc, derr->message);
|
||||
case SZ_CHAIN_ERR_METHOD:
|
||||
case SZ_CHAIN_ERR_LAYOUT:
|
||||
@@ -1216,7 +1230,7 @@ szx_decode_error(szx_ctx_t *c, UInt32 folder, const sz_chain_err_t *derr,
|
||||
(unsigned)folder, desc, derr->message);
|
||||
case SZ_CHAIN_ERR_DATA:
|
||||
if(encrypted) {
|
||||
return szx_fail(c, ZIPX_ERR_PASSWORD, NULL,
|
||||
return szx_fail(c, ZIPX_ERR_PASSWORD, detail,
|
||||
"folder %u (%s): %s (the password is wrong, or the "
|
||||
"archive is damaged)",
|
||||
(unsigned)folder, desc, derr->message);
|
||||
@@ -1585,6 +1599,7 @@ sevenz_extract(const char *sevenz_path, const char *dst_dir,
|
||||
c->progress = progress;
|
||||
c->userdata = userdata;
|
||||
c->password = password;
|
||||
c->sevenz_path = sevenz_path;
|
||||
|
||||
snprintf(dst_copy, sizeof(dst_copy), "%s", dst_dir);
|
||||
{
|
||||
|
||||
@@ -116,6 +116,41 @@ policy_case(const char *label, const char *archive, const char *dst,
|
||||
check(st == want, buf);
|
||||
}
|
||||
|
||||
/* The progress callback the dispatch driver installs fires the UI loop; it has
|
||||
to be called multiple times, with monotonic bytes_done and a non-zero
|
||||
bytes_total, or the front-end ends up with no spinner. These checks pin
|
||||
that down so a refactor that drops the callback returns the test set to
|
||||
red instead of "no progress shown" on the device. */
|
||||
typedef struct {
|
||||
int calls;
|
||||
unsigned long long prev_done;
|
||||
unsigned long long max_bytes_total;
|
||||
unsigned long long max_bytes_done;
|
||||
unsigned long long max_entries_total;
|
||||
unsigned long long max_entries_done;
|
||||
} progress_recorder_t;
|
||||
|
||||
static void
|
||||
recorder_progress(void *userdata, const zipx_progress_t *p) {
|
||||
progress_recorder_t *r = userdata;
|
||||
|
||||
r->calls++;
|
||||
if(p->bytes_total > r->max_bytes_total) r->max_bytes_total = p->bytes_total;
|
||||
if(p->bytes_done > r->max_bytes_done) r->max_bytes_done = p->bytes_done;
|
||||
if((unsigned long long)p->entries_total > r->max_entries_total) {
|
||||
r->max_entries_total = (unsigned long long)p->entries_total;
|
||||
}
|
||||
if((unsigned long long)p->entries_done > r->max_entries_done) {
|
||||
r->max_entries_done = (unsigned long long)p->entries_done;
|
||||
}
|
||||
if(p->bytes_done < r->prev_done) {
|
||||
printf(" FAIL progress: bytes_done went backwards (%llu -> %llu)\n",
|
||||
r->prev_done, p->bytes_done);
|
||||
g_failures++;
|
||||
}
|
||||
r->prev_done = p->bytes_done;
|
||||
}
|
||||
|
||||
static int
|
||||
run_cases(const char *fx, const char *work) {
|
||||
char arc[PATH_MAX_LOCAL];
|
||||
@@ -136,6 +171,16 @@ run_cases(const char *fx, const char *work) {
|
||||
expect_fail("aes with the wrong password", arc, dst, "NotThePassword",
|
||||
ZIPX_CONFLICT_FAIL, zipx_default_limits(), NULL,
|
||||
ZIPX_ERR_PASSWORD, "7zAES");
|
||||
{
|
||||
char b2[256];
|
||||
zipx_result_t r;
|
||||
(void)sevenz_extract(arc, dst, ZIPX_CONFLICT_FAIL,
|
||||
zipx_default_limits(), NULL, NULL, NULL,
|
||||
"NotThePassword", &r);
|
||||
snprintf(b2, sizeof(b2),
|
||||
"wrong password: detail names the archive (got '%s')", r.detail);
|
||||
check(strstr(r.detail, "aes.7z") != NULL, b2);
|
||||
}
|
||||
check(!has_staging_leftover(work), "no staging tree survives a wrong password");
|
||||
|
||||
snprintf(dst, sizeof(dst), "%s/pw-ok", work);
|
||||
@@ -195,6 +240,26 @@ run_cases(const char *fx, const char *work) {
|
||||
ZIPX_CONFLICT_FAIL, &tight, NULL, ZIPX_ERR_LIMIT_ENTRIES,
|
||||
"more than 1 entries");
|
||||
|
||||
/* --- progress callback ---------------------------------------------- */
|
||||
{
|
||||
progress_recorder_t rec = {0};
|
||||
zipx_result_t r;
|
||||
|
||||
snprintf(dst, sizeof(dst), "%s/progress", work);
|
||||
mkdir(dst, 0777);
|
||||
(void)sevenz_extract(arc, dst, ZIPX_CONFLICT_FAIL,
|
||||
zipx_default_limits(), NULL, recorder_progress,
|
||||
&rec, NULL, &r);
|
||||
check(rec.calls >= 2, "progress callback fires multiple times");
|
||||
check(rec.max_bytes_total > 0, "progress reports a non-zero bytes_total");
|
||||
check(rec.max_bytes_done > 0,
|
||||
"progress reports a non-zero bytes_done during extraction");
|
||||
check(rec.max_entries_total >= 1,
|
||||
"progress reports a non-zero entries_total");
|
||||
check(rec.max_bytes_done >= rec.max_bytes_total * 9 / 10,
|
||||
"progress reaches within 90% of the declared total");
|
||||
}
|
||||
|
||||
printf(" cases: %d checks, %d failures\n", g_checks, g_failures);
|
||||
return g_failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user