mirror of
https://github.com/phantomptr/ps5upload.git
synced 2026-10-19 02:20:02 -05:00
console page: final hardware-pass fixes
Web threads get the 512 KiB mgmt stack (they run mgmt handlers); a recent
folder that is gone (404) is dropped via a new prefs "forget" op; a non-exFAT
image inside a .ffpfsc reads as unsupported, not damaged, and is logged to
stderr; text find shows its count after a pause in typing; free-name probes
use /api/fs/stat missing_ok (200 {"missing":true}) so no 404 noise. hw_info
comment updated for the page's 10 s HW_TEMPS poll on a web worker.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
This commit is contained in:
1 parent
a507c9b035
commit
712ff87d6a
58 files changed
+177
-20
No files matched your search
@@ -908,7 +908,7 @@ test-payload: payload
|
||||
$(PAYLOAD_DIR)/tests/webui_auth_selftest.c $(PAYLOAD_DIR)/src/webui_auth.c
|
||||
@/tmp/ps5upload-webui-auth-selftest
|
||||
@echo "Running webui server self-test (host build, ~25 s)..."
|
||||
@cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/include \
|
||||
@cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/include -I$(PAYLOAD_DIR)/ava1 \
|
||||
-o /tmp/ps5upload-webui-server-selftest \
|
||||
$(PAYLOAD_DIR)/tests/webui_server_selftest.c $(PAYLOAD_DIR)/src/webui_server.c \
|
||||
$(PAYLOAD_DIR)/src/webui_http.c $(PAYLOAD_DIR)/src/webui_auth.c
|
||||
|
||||
@@ -76,7 +76,7 @@ import { PathBar } from "./PathBar";
|
||||
import { DEFAULT_VIEW, invertShown, loadView, saveView, selectAllShown, statusOf, visibleEntries, LOAD_ALL_CAP, type Status, type ViewPrefs } from "./listing";
|
||||
import { checkFolder, typedPath } from "./pathParts";
|
||||
import { PathPicker } from "./PathPicker";
|
||||
import { isPinned, usePrefs, type FilesPrefs } from "./prefs";
|
||||
import { cleanPath, isPinned, usePrefs, type FilesPrefs } from "./prefs";
|
||||
import { TextPrompt } from "./TextPrompt";
|
||||
import { ViewMenu } from "./ViewMenu";
|
||||
import { useDir, type DirState, type Entry } from "./useDir";
|
||||
@@ -1039,7 +1039,7 @@ export function Files({ ctx }: { ctx: SectionContext }) {
|
||||
const [message, setMessage] = useState<Message | null>(null);
|
||||
const [askBusy, setAskBusy] = useState(false);
|
||||
const [askError, setAskError] = useState<string | null>(null);
|
||||
const { prefs, settled: prefsSettled, pin, unpin, visit } = usePrefs();
|
||||
const { prefs, settled: prefsSettled, pin, unpin, visit, forget } = usePrefs();
|
||||
/** Files was moved somewhere (or opened at a place): the last location is not restored any more. */
|
||||
const moved = useRef(wanted != null);
|
||||
/** The folder restored from the last visit (left for the drive list when it is gone). */
|
||||
@@ -1117,6 +1117,12 @@ export function Files({ ctx }: { ctx: SectionContext }) {
|
||||
go(null);
|
||||
}, [path, dir.error, go]);
|
||||
|
||||
// A recent folder that is gone (deleted, renamed, its drive unplugged) leaves Recent.
|
||||
useEffect(() => {
|
||||
if (path == null || !(dir.error instanceof ApiError) || dir.error.status !== 404) return;
|
||||
if (prefs?.recent.includes(cleanPath(path))) forget(cleanPath(path));
|
||||
}, [path, dir.error, prefs, forget]);
|
||||
|
||||
// A folder that opened is a visit: recent folders and the last location (on the console).
|
||||
useEffect(() => {
|
||||
if (path == null || !dir.loaded || dir.error || visited.current === path) return;
|
||||
|
||||
@@ -17,7 +17,8 @@ export function unpackCauseText(code: string): string | null {
|
||||
if (/no_space/.test(code)) return t("cw_files_unpack_no_space_short");
|
||||
if (/destination_exists/.test(code)) return t("cw_files_err_exists");
|
||||
if (/unpack_busy/.test(code)) return t("cw_files_unpack_busy");
|
||||
if (/corrupt_block|inner_missing|size_mismatch|not_exfat/.test(code)) return t("cw_files_unpack_damaged");
|
||||
if (/not_exfat/.test(code)) return t("cw_files_unpack_not_image");
|
||||
if (/corrupt_block|inner_missing|size_mismatch/.test(code)) return t("cw_files_unpack_damaged");
|
||||
if (/path_not_allowed/.test(code)) return t("cw_files_err_denied");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -291,6 +291,11 @@ describe("decompress", () => {
|
||||
);
|
||||
const failed = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_no_space: needs 9 bytes, 1 free" } }, "g");
|
||||
expect(failed).toEqual({ tone: "error", text: t("cw_files_unpack_failed", { reason: t("cw_files_unpack_no_space_short") }) });
|
||||
// An image of another file system is unsupported, not damaged.
|
||||
const other = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_not_exfat" } }, "g");
|
||||
expect(other?.text).toBe(t("cw_files_unpack_failed", { reason: t("cw_files_unpack_not_image") }));
|
||||
const bad = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_corrupt_block 3" } }, "g");
|
||||
expect(bad?.text).toBe(t("cw_files_unpack_failed", { reason: t("cw_files_unpack_damaged") }));
|
||||
const odd = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_weird" } }, "g");
|
||||
expect(odd?.text).toContain("fs_unpack_weird");
|
||||
expect(unpackMessage({ end: "cancelled", status }, "g")?.text).toBe(t("cw_files_unpack_stopped"));
|
||||
|
||||
@@ -385,6 +385,11 @@ describe("eject and unmount", () => {
|
||||
expect(seen).toEqual([["/b", "/a"]]);
|
||||
mockCall.mockRejectedValueOnce(new ApiError(409, "prefs_full"));
|
||||
await expect(apply("pin", "/c")).rejects.toThrow();
|
||||
// A gone recent folder is forgotten through the same route.
|
||||
mockCall.mockResolvedValueOnce({ favorites: [], recent: ["/a"], last: "/a" } as never);
|
||||
await apply("forget", "/b");
|
||||
expect(mockCall).toHaveBeenLastCalledWith("/api/files/prefs/set", { op: "forget", path: "/b" });
|
||||
expect(seen[seen.length - 1]).toEqual(["/a"]);
|
||||
});
|
||||
|
||||
it("the helper's refusals read as what to do", () => {
|
||||
|
||||
@@ -106,7 +106,10 @@ export function sameEntry(a: Stat | null | undefined, b: Stat | null | undefined
|
||||
/** The entry at `path`, or null when nothing is there (404). */
|
||||
export async function statOrNull(path: string): Promise<Stat | null> {
|
||||
try {
|
||||
return await call<Stat>("/api/fs/stat", { path });
|
||||
// missing_ok: the helper answers 200 {"missing":true} for a path that is not there (no 404
|
||||
// in the browser's console); an older helper ignores it and answers 404, read the same way.
|
||||
const st = await call<Stat | { missing: true }>("/api/fs/stat", { path, missing_ok: true });
|
||||
return "missing" in st && st.missing === true ? null : (st as Stat);
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 404) return null;
|
||||
throw e;
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface FilesPrefs {
|
||||
|
||||
export const EMPTY_PREFS: FilesPrefs = { favorites: [], recent: [], last: null };
|
||||
|
||||
export type PrefsOp = "pin" | "unpin" | "visit";
|
||||
export type PrefsOp = "pin" | "unpin" | "visit" | "forget";
|
||||
|
||||
const strings = (v: unknown): string[] =>
|
||||
Array.isArray(v) ? v.filter((x): x is string => typeof x === "string" && x.startsWith("/")) : [];
|
||||
@@ -73,6 +73,8 @@ export interface PrefsState {
|
||||
pin: (path: string) => Promise<void>;
|
||||
unpin: (path: string) => Promise<void>;
|
||||
visit: (path: string) => void;
|
||||
/** Drops a folder that no longer exists from Recent (fire-and-forget, like a visit). */
|
||||
forget: (path: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,5 +124,12 @@ export function usePrefs(): PrefsState {
|
||||
},
|
||||
[apply],
|
||||
);
|
||||
return { prefs, settled, pin, unpin, visit };
|
||||
const forget = useCallback(
|
||||
(path: string) => {
|
||||
if (!supported.current) return;
|
||||
apply("forget", path).catch(() => undefined);
|
||||
},
|
||||
[apply],
|
||||
);
|
||||
return { prefs, settled, pin, unpin, visit, forget };
|
||||
}
|
||||
@@ -34,6 +34,29 @@ interface Found {
|
||||
|
||||
const NONE: Found = { query: "", matches: [], capped: false };
|
||||
|
||||
/** A pause in typing this long finds what the field holds (the count shows without pressing Next). */
|
||||
export const FIND_TYPED_MS = 350;
|
||||
|
||||
export interface FindState {
|
||||
found: Found;
|
||||
current: number;
|
||||
win: RowWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find `field` as typed: a new query is scanned and its first match from the
|
||||
* window's top becomes current; an empty field clears the find; the query
|
||||
* already found is left as it is (Next / Previous step through it).
|
||||
*/
|
||||
export function findTyped(s: FindState, field: string, rows: Row[]): FindState {
|
||||
if (field === s.found.query) return s;
|
||||
if (!field) return { ...s, found: NONE, current: -1 };
|
||||
const found = { query: field, ...findMatches(rows, field) };
|
||||
const current = firstMatchFrom(found.matches, s.win.start);
|
||||
const win = current >= 0 ? windowAround(found.matches[current].row, s.win, rows.length) : s.win;
|
||||
return { found, current, win };
|
||||
}
|
||||
|
||||
/** One focusable block of rows. Memoized: Next / Previous re-render only the blocks whose current mark changes. */
|
||||
const Block = memo(function Block({
|
||||
rows,
|
||||
@@ -86,7 +109,7 @@ const Block = memo(function Block({
|
||||
* D-pad steps through. Text is rendered as text nodes, never HTML.
|
||||
*/
|
||||
export function TextView({ rows, initialQuery }: { rows: Row[]; /** Tests: start with this found. */ initialQuery?: string }) {
|
||||
const [state, setState] = useState(() => {
|
||||
const [state, setState] = useState<FindState>(() => {
|
||||
const found = initialQuery ? { query: initialQuery, ...findMatches(rows, initialQuery) } : NONE;
|
||||
const current = found.matches.length ? 0 : -1;
|
||||
const win0: RowWindow = { start: 0, end: Math.min(rows.length, ROW_WINDOW) };
|
||||
@@ -128,6 +151,13 @@ export function TextView({ rows, initialQuery }: { rows: Row[]; /** Tests: start
|
||||
if (el && typeof el.scrollIntoView === "function") el.scrollIntoView({ block: "center", inline: "nearest" });
|
||||
}, [current, win]);
|
||||
|
||||
// Typing pauses: find it, so the count shows at once (the PS5 keyboard's Done may not submit).
|
||||
useEffect(() => {
|
||||
if (field === found.query) return;
|
||||
const id = setTimeout(() => setState((s) => findTyped(s, field, rows)), FIND_TYPED_MS);
|
||||
return () => clearTimeout(id);
|
||||
}, [field, found.query, rows]);
|
||||
|
||||
/** Find what the field holds: a new query (scanned once) starts at its first match; the same one steps on. */
|
||||
const go = (dir: 1 | -1) => {
|
||||
setState((s) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { FilesView, sheetActions, type FilesViewProps } from "./Files";
|
||||
import type { DirState, Entry } from "./useDir";
|
||||
import { GameBanner, GameBannerView } from "./viewer/GameBanner";
|
||||
import { HexView } from "./viewer/HexView";
|
||||
import { TextView, findCountText } from "./viewer/TextView";
|
||||
import { TextView, findCountText, findTyped, type FindState } from "./viewer/TextView";
|
||||
import { ROW_WINDOW, toRows } from "./viewer/text";
|
||||
import { GameSummary, textModel, Viewer } from "./Viewer";
|
||||
|
||||
@@ -154,6 +154,20 @@ describe("viewer in Files", () => {
|
||||
expect(findCountText("x", 10000, 0, true)).toContain(t("cw_files_vw_find_capped"));
|
||||
});
|
||||
|
||||
it("typing finds at once: the count shows without Next; Next then steps on", () => {
|
||||
const rows = toRows("alpha\nbeta alpha\ngamma\nalpha");
|
||||
const s0: FindState = { found: { query: "", matches: [], capped: false }, current: -1, win: { start: 0, end: rows.length } };
|
||||
const s1 = findTyped(s0, "alpha", rows);
|
||||
expect(s1.found.matches.length).toBe(3);
|
||||
expect(s1.current).toBe(0);
|
||||
expect(findCountText(s1.found.query, s1.found.matches.length, s1.current, false)).toBe(t("cw_files_vw_find_at", { at: 1, n: "3" }));
|
||||
expect(findTyped(s1, "alpha", rows)).toBe(s1); // already found: unchanged
|
||||
expect(findTyped(s1, "zzz", rows).found.matches).toEqual([]);
|
||||
const cleared = findTyped(s1, "", rows);
|
||||
expect(cleared.found.query).toBe("");
|
||||
expect(cleared.current).toBe(-1);
|
||||
});
|
||||
|
||||
it("a find jump in a 50,000-line file draws a bounded window around the match", () => {
|
||||
const lines = Array.from({ length: 50_000 }, (_, i) => (i === 40_000 ? "the needle is here" : `line ${i + 1}`));
|
||||
const html = renderToStaticMarkup(<TextView rows={toRows(lines.join("\n"))} initialQuery="needle" />);
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "لا توجد مساحة كافية: يحتاج {need} (الصورة مع 1 GiB)، والمتاح {free}.",
|
||||
"cw_files_unpack_no_space_short": "لا توجد مساحة كافية على ذلك المحرك.",
|
||||
"cw_files_unpack_not_container": "هذا ليس ملف .ffpfsc يستطيع الجهاز قراءته.",
|
||||
"cw_files_unpack_not_image": "الصورة الموجودة بداخله ليست صورة exFAT، لذا فهي غير مدعومة.",
|
||||
"cw_files_unpack_stopped": "أُوقف فك الضغط. لم يُحتفظ بشيء.",
|
||||
"cw_files_unpack_title": "فك ضغط «{name}»",
|
||||
"cw_files_unpack_wait_batch": "مهمة أخرى قيد التشغيل. يمكن أن يبدأ فك الضغط بعد انتهائها.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "যথেষ্ট খালি জায়গা নেই: {need} প্রয়োজন (ইমেজ ও 1 GiB), খালি আছে {free}।",
|
||||
"cw_files_unpack_no_space_short": "ওই ড্রাইভে যথেষ্ট খালি জায়গা নেই।",
|
||||
"cw_files_unpack_not_container": "এটি এমন .ffpfsc নয় যা কনসোল পড়তে পারে।",
|
||||
"cw_files_unpack_not_image": "ভেতরের ইমেজটি exFAT ইমেজ নয়, তাই এটি সমর্থিত নয়।",
|
||||
"cw_files_unpack_stopped": "ডিকম্প্রেস থামানো হয়েছে। কিছুই রাখা হয়নি।",
|
||||
"cw_files_unpack_title": "“{name}” ডিকম্প্রেস করুন",
|
||||
"cw_files_unpack_wait_batch": "অন্য একটি কাজ চলছে। সেটি শেষ হলে ডিকম্প্রেস শুরু হতে পারে।",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Nicht genug freier Speicher: Benötigt werden {need} (das Abbild plus 1 GiB), frei sind {free}.",
|
||||
"cw_files_unpack_no_space_short": "Nicht genug freier Speicher auf diesem Laufwerk.",
|
||||
"cw_files_unpack_not_container": "Das ist keine .ffpfsc-Datei, die die Konsole lesen kann.",
|
||||
"cw_files_unpack_not_image": "Das Image darin ist kein exFAT-Image und wird daher nicht unterstützt.",
|
||||
"cw_files_unpack_stopped": "Entpacken gestoppt. Nichts wurde behalten.",
|
||||
"cw_files_unpack_title": "„{name}“ entpacken",
|
||||
"cw_files_unpack_wait_batch": "Eine andere Aufgabe läuft. Entpacken kann starten, wenn sie fertig ist.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Not enough free space: it needs {need} (the image plus 1 GiB), and {free} is free.",
|
||||
"cw_files_unpack_no_space_short": "Not enough free space on that drive.",
|
||||
"cw_files_unpack_not_container": "This is not a .ffpfsc the console can read.",
|
||||
"cw_files_unpack_not_image": "The image inside is not an exFAT image, so it isn't supported.",
|
||||
"cw_files_unpack_stopped": "Decompress stopped. Nothing was kept.",
|
||||
"cw_files_unpack_title": "Decompress “{name}”",
|
||||
"cw_files_unpack_wait_batch": "Another task is running. Decompress can start when it has finished.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "No hay espacio suficiente: necesita {need} (la imagen más 1 GiB) y hay {free} libres.",
|
||||
"cw_files_unpack_no_space_short": "No hay espacio suficiente en esa unidad.",
|
||||
"cw_files_unpack_not_container": "No es un .ffpfsc que la consola pueda leer.",
|
||||
"cw_files_unpack_not_image": "La imagen que contiene no es una imagen exFAT, así que no es compatible.",
|
||||
"cw_files_unpack_stopped": "Descompresión detenida. No se conservó nada.",
|
||||
"cw_files_unpack_title": "Descomprimir «{name}»",
|
||||
"cw_files_unpack_wait_batch": "Hay otra tarea en curso. Descomprimir podrá empezar cuando termine.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "فضای آزاد کافی نیست: {need} لازم است (ایمیج بهعلاوهٔ 1 GiB) و {free} آزاد است.",
|
||||
"cw_files_unpack_no_space_short": "فضای آزاد آن درایو کافی نیست.",
|
||||
"cw_files_unpack_not_container": "این یک .ffpfsc قابل خواندن برای کنسول نیست.",
|
||||
"cw_files_unpack_not_image": "ایمیج داخل آن ایمیج exFAT نیست، پس پشتیبانی نمیشود.",
|
||||
"cw_files_unpack_stopped": "باز کردن متوقف شد. چیزی نگه داشته نشد.",
|
||||
"cw_files_unpack_title": "باز کردن فشردهسازی «{name}»",
|
||||
"cw_files_unpack_wait_batch": "کار دیگری در حال اجراست. پس از پایان آن میتوان شروع کرد.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Pas assez d’espace libre : il faut {need} (l’image plus 1 GiB) et {free} sont libres.",
|
||||
"cw_files_unpack_no_space_short": "Pas assez d’espace libre sur ce disque.",
|
||||
"cw_files_unpack_not_container": "Ce n’est pas un .ffpfsc que la console peut lire.",
|
||||
"cw_files_unpack_not_image": "L’image qu’il contient n’est pas une image exFAT : elle n’est pas prise en charge.",
|
||||
"cw_files_unpack_stopped": "Décompression arrêtée. Rien n’a été conservé.",
|
||||
"cw_files_unpack_title": "Décompresser « {name} »",
|
||||
"cw_files_unpack_wait_batch": "Une autre tâche est en cours. La décompression pourra commencer quand elle sera finie.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "पर्याप्त खाली जगह नहीं: {need} चाहिए (इमेज और 1 GiB), खाली {free} है।",
|
||||
"cw_files_unpack_no_space_short": "उस ड्राइव पर पर्याप्त खाली जगह नहीं है।",
|
||||
"cw_files_unpack_not_container": "यह ऐसी .ffpfsc नहीं है जिसे कंसोल पढ़ सके।",
|
||||
"cw_files_unpack_not_image": "इसके अंदर की इमेज exFAT इमेज नहीं है, इसलिए यह समर्थित नहीं है।",
|
||||
"cw_files_unpack_stopped": "डीकंप्रेस रोका गया। कुछ भी नहीं रखा गया।",
|
||||
"cw_files_unpack_title": "“{name}” डीकंप्रेस करें",
|
||||
"cw_files_unpack_wait_batch": "दूसरा काम चल रहा है। उसके पूरा होने पर डीकंप्रेस शुरू हो सकता है।",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Nincs elég szabad hely: {need} kell (a kép plusz 1 GiB), és {free} szabad.",
|
||||
"cw_files_unpack_no_space_short": "Nincs elég szabad hely azon a meghajtón.",
|
||||
"cw_files_unpack_not_container": "Ez nem olyan .ffpfsc, amelyet a konzol olvasni tud.",
|
||||
"cw_files_unpack_not_image": "A benne lévő image nem exFAT image, ezért nem támogatott.",
|
||||
"cw_files_unpack_stopped": "Kicsomagolás leállítva. Semmi sem maradt meg.",
|
||||
"cw_files_unpack_title": "„{name}” kicsomagolása",
|
||||
"cw_files_unpack_wait_batch": "Egy másik feladat fut. A kicsomagolás akkor indulhat, ha az véget ért.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Ruang kosong tidak cukup: butuh {need} (image plus 1 GiB), tersedia {free}.",
|
||||
"cw_files_unpack_no_space_short": "Ruang kosong di drive itu tidak cukup.",
|
||||
"cw_files_unpack_not_container": "Ini bukan .ffpfsc yang bisa dibaca konsol.",
|
||||
"cw_files_unpack_not_image": "Image di dalamnya bukan image exFAT, jadi tidak didukung.",
|
||||
"cw_files_unpack_stopped": "Ekstrak dihentikan. Tidak ada yang disimpan.",
|
||||
"cw_files_unpack_title": "Ekstrak “{name}”",
|
||||
"cw_files_unpack_wait_batch": "Tugas lain sedang berjalan. Ekstrak bisa mulai setelah selesai.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Spazio libero insufficiente: servono {need} (l’immagine più 1 GiB) e ne sono liberi {free}.",
|
||||
"cw_files_unpack_no_space_short": "Spazio libero insufficiente su quell’unità.",
|
||||
"cw_files_unpack_not_container": "Non è un .ffpfsc che la console può leggere.",
|
||||
"cw_files_unpack_not_image": "L’immagine al suo interno non è un’immagine exFAT, quindi non è supportata.",
|
||||
"cw_files_unpack_stopped": "Decompressione interrotta. Non è stato tenuto nulla.",
|
||||
"cw_files_unpack_title": "Decomprimi «{name}»",
|
||||
"cw_files_unpack_wait_batch": "È in corso un’altra attività. La decompressione potrà iniziare quando sarà finita.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "空き容量が足りません: {need}(イメージ + 1 GiB)が必要で、空きは {free} です。",
|
||||
"cw_files_unpack_no_space_short": "そのドライブの空き容量が足りません。",
|
||||
"cw_files_unpack_not_container": "本体が読み取れる .ffpfsc ではありません。",
|
||||
"cw_files_unpack_not_image": "中のイメージは exFAT イメージではないため、対応していません。",
|
||||
"cw_files_unpack_stopped": "展開を停止しました。何も残っていません。",
|
||||
"cw_files_unpack_title": "「{name}」を展開",
|
||||
"cw_files_unpack_wait_batch": "別のタスクが実行中です。終わると展開を開始できます。",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "공간이 부족합니다: {need}(이미지 + 1 GiB)가 필요한데 {free}만 남아 있습니다.",
|
||||
"cw_files_unpack_no_space_short": "그 드라이브에 공간이 부족합니다.",
|
||||
"cw_files_unpack_not_container": "콘솔이 읽을 수 있는 .ffpfsc가 아닙니다.",
|
||||
"cw_files_unpack_not_image": "안에 있는 이미지가 exFAT 이미지가 아니어서 지원되지 않습니다.",
|
||||
"cw_files_unpack_stopped": "압축 풀기를 중지했습니다. 남은 것은 없습니다.",
|
||||
"cw_files_unpack_title": "“{name}” 압축 풀기",
|
||||
"cw_files_unpack_wait_batch": "다른 작업이 진행 중입니다. 끝나면 압축 풀기를 시작할 수 있습니다.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Za mało wolnego miejsca: potrzeba {need} (obraz plus 1 GiB), wolne: {free}.",
|
||||
"cw_files_unpack_no_space_short": "Za mało wolnego miejsca na tym dysku.",
|
||||
"cw_files_unpack_not_container": "To nie jest plik .ffpfsc, który konsola potrafi odczytać.",
|
||||
"cw_files_unpack_not_image": "Obraz w środku nie jest obrazem exFAT, więc nie jest obsługiwany.",
|
||||
"cw_files_unpack_stopped": "Rozpakowywanie zatrzymane. Nic nie zostało zachowane.",
|
||||
"cw_files_unpack_title": "Rozpakuj „{name}”",
|
||||
"cw_files_unpack_wait_batch": "Trwa inne zadanie. Rozpakowywanie może się zacząć, gdy się skończy.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Espaço livre insuficiente: precisa de {need} (a imagem mais 1 GiB) e há {free} livres.",
|
||||
"cw_files_unpack_no_space_short": "Espaço livre insuficiente nessa unidade.",
|
||||
"cw_files_unpack_not_container": "Não é um .ffpfsc que o console consiga ler.",
|
||||
"cw_files_unpack_not_image": "A imagem dentro dele não é uma imagem exFAT, então não é compatível.",
|
||||
"cw_files_unpack_stopped": "Descompactação parada. Nada foi mantido.",
|
||||
"cw_files_unpack_title": "Descompactar “{name}”",
|
||||
"cw_files_unpack_wait_batch": "Outra tarefa está em andamento. A descompactação pode começar quando ela terminar.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Недостаточно места: нужно {need} (образ плюс 1 GiB), свободно {free}.",
|
||||
"cw_files_unpack_no_space_short": "Недостаточно свободного места на этом диске.",
|
||||
"cw_files_unpack_not_container": "Это не .ffpfsc, который консоль может прочитать.",
|
||||
"cw_files_unpack_not_image": "Образ внутри не является образом exFAT, поэтому не поддерживается.",
|
||||
"cw_files_unpack_stopped": "Распаковка остановлена. Ничего не сохранено.",
|
||||
"cw_files_unpack_title": "Распаковать «{name}»",
|
||||
"cw_files_unpack_wait_batch": "Выполняется другая задача. Распаковка начнётся, когда она закончится.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "พื้นที่ว่างไม่พอ: ต้องใช้ {need} (อิมเมจบวก 1 GiB) ว่างอยู่ {free}",
|
||||
"cw_files_unpack_no_space_short": "พื้นที่ว่างในไดรฟ์นั้นไม่พอ",
|
||||
"cw_files_unpack_not_container": "นี่ไม่ใช่ .ffpfsc ที่เครื่องอ่านได้",
|
||||
"cw_files_unpack_not_image": "อิมเมจด้านในไม่ใช่อิมเมจ exFAT จึงไม่รองรับ",
|
||||
"cw_files_unpack_stopped": "หยุดการแตกไฟล์แล้ว ไม่มีอะไรถูกเก็บไว้",
|
||||
"cw_files_unpack_title": "แตกไฟล์ “{name}”",
|
||||
"cw_files_unpack_wait_batch": "มีงานอื่นกำลังทำงานอยู่ จะแตกไฟล์ได้เมื่องานนั้นเสร็จ",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Yeterli boş alan yok: {need} gerekli (görüntü artı 1 GiB), {free} boş.",
|
||||
"cw_files_unpack_no_space_short": "O sürücüde yeterli boş alan yok.",
|
||||
"cw_files_unpack_not_container": "Bu, konsolun okuyabildiği bir .ffpfsc değil.",
|
||||
"cw_files_unpack_not_image": "İçindeki imaj bir exFAT imajı değil, bu yüzden desteklenmiyor.",
|
||||
"cw_files_unpack_stopped": "Sıkıştırma açma durduruldu. Hiçbir şey tutulmadı.",
|
||||
"cw_files_unpack_title": "“{name}” sıkıştırmasını aç",
|
||||
"cw_files_unpack_wait_batch": "Başka bir görev çalışıyor. Bitince sıkıştırma açma başlayabilir.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "Không đủ dung lượng trống: cần {need} (ảnh đĩa cộng 1 GiB), còn trống {free}.",
|
||||
"cw_files_unpack_no_space_short": "Không đủ dung lượng trống trên ổ đó.",
|
||||
"cw_files_unpack_not_container": "Đây không phải tệp .ffpfsc mà máy đọc được.",
|
||||
"cw_files_unpack_not_image": "Ảnh bên trong không phải ảnh exFAT nên không được hỗ trợ.",
|
||||
"cw_files_unpack_stopped": "Đã dừng giải nén. Không giữ lại gì.",
|
||||
"cw_files_unpack_title": "Giải nén “{name}”",
|
||||
"cw_files_unpack_wait_batch": "Một tác vụ khác đang chạy. Giải nén có thể bắt đầu khi nó xong.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "可用空间不足:需要 {need}(镜像加 1 GiB),可用 {free}。",
|
||||
"cw_files_unpack_no_space_short": "该驱动器可用空间不足。",
|
||||
"cw_files_unpack_not_container": "这不是主机能读取的 .ffpfsc。",
|
||||
"cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支持。",
|
||||
"cw_files_unpack_stopped": "解压已停止。没有保留任何内容。",
|
||||
"cw_files_unpack_title": "解压“{name}”",
|
||||
"cw_files_unpack_wait_batch": "另一个任务正在进行。完成后才能开始解压。",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"cw_files_unpack_no_space": "可用空間不足:需要 {need}(映像檔加 1 GiB),可用 {free}。",
|
||||
"cw_files_unpack_no_space_short": "該磁碟機可用空間不足。",
|
||||
"cw_files_unpack_not_container": "這不是主機能讀取的 .ffpfsc。",
|
||||
"cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支援。",
|
||||
"cw_files_unpack_stopped": "解壓縮已停止。沒有保留任何內容。",
|
||||
"cw_files_unpack_title": "解壓縮「{name}」",
|
||||
"cw_files_unpack_wait_batch": "另一個工作正在進行。完成後才能開始解壓縮。",
|
||||
|
||||
@@ -4457,6 +4457,7 @@ c2c_test_deep_bad: "لم يصل الإرسال التجريبي بحجم 4 KiB:
|
||||
"cw_files_unpack_failed_generic": "ردّ الجهاز بـ«{cause}».",
|
||||
"cw_files_unpack_not_container": "هذا ليس ملف .ffpfsc يستطيع الجهاز قراءته.",
|
||||
"cw_files_unpack_damaged": "الملف تالف.",
|
||||
"cw_files_unpack_not_image": "الصورة الموجودة بداخله ليست صورة exFAT، لذا فهي غير مدعومة.",
|
||||
"cw_files_unpack_busy": "يجري فك ضغط آخر على الجهاز.",
|
||||
"cw_files_vw_find": "البحث في هذا الملف",
|
||||
"cw_files_vw_prev": "السابق",
|
||||
|
||||
@@ -4457,6 +4457,7 @@ c2c_test_deep_bad: "4 KiB-এর পরীক্ষামূলক পাঠা
|
||||
"cw_files_unpack_failed_generic": "কনসোল বলেছে “{cause}”।",
|
||||
"cw_files_unpack_not_container": "এটি এমন .ffpfsc নয় যা কনসোল পড়তে পারে।",
|
||||
"cw_files_unpack_damaged": "ফাইলটি ক্ষতিগ্রস্ত।",
|
||||
"cw_files_unpack_not_image": "ভেতরের ইমেজটি exFAT ইমেজ নয়, তাই এটি সমর্থিত নয়।",
|
||||
"cw_files_unpack_busy": "কনসোলে আরেকটি ডিকম্প্রেস চলছে।",
|
||||
"cw_files_vw_find": "এই ফাইলে খুঁজুন",
|
||||
"cw_files_vw_prev": "আগের",
|
||||
|
||||
@@ -4465,6 +4465,7 @@ c2c_test_deep_bad: "Die 4-KiB-Testsendung ist nicht angekommen: {detail}",
|
||||
"cw_files_unpack_failed_generic": "Die Konsole meldete „{cause}“.",
|
||||
"cw_files_unpack_not_container": "Das ist keine .ffpfsc-Datei, die die Konsole lesen kann.",
|
||||
"cw_files_unpack_damaged": "Die Datei ist beschädigt.",
|
||||
"cw_files_unpack_not_image": "Das Image darin ist kein exFAT-Image und wird daher nicht unterstützt.",
|
||||
"cw_files_unpack_busy": "Auf der Konsole läuft schon ein anderes Entpacken.",
|
||||
"cw_files_vw_find": "In dieser Datei suchen",
|
||||
"cw_files_vw_prev": "Zurück",
|
||||
|
||||
@@ -4780,6 +4780,7 @@ c2c_test_deep_bad: "The 4 KiB test send did not arrive: {detail}",
|
||||
"cw_files_unpack_failed_generic": "the console said “{cause}”.",
|
||||
"cw_files_unpack_not_container": "This is not a .ffpfsc the console can read.",
|
||||
"cw_files_unpack_damaged": "The file is damaged.",
|
||||
"cw_files_unpack_not_image": "The image inside is not an exFAT image, so it isn't supported.",
|
||||
"cw_files_unpack_busy": "Another decompress is running on the console.",
|
||||
"cw_files_vw_find": "Find in this file",
|
||||
"cw_files_vw_prev": "Previous",
|
||||
|
||||
@@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "El envío de prueba de 4 KiB no llegó: {detail}",
|
||||
"cw_files_unpack_failed_generic": "la consola dijo «{cause}».",
|
||||
"cw_files_unpack_not_container": "No es un .ffpfsc que la consola pueda leer.",
|
||||
"cw_files_unpack_damaged": "El archivo está dañado.",
|
||||
"cw_files_unpack_not_image": "La imagen que contiene no es una imagen exFAT, así que no es compatible.",
|
||||
"cw_files_unpack_busy": "Ya hay otra descompresión en curso en la consola.",
|
||||
"cw_files_vw_find": "Buscar en este archivo",
|
||||
"cw_files_vw_prev": "Anterior",
|
||||
|
||||
@@ -4758,6 +4758,7 @@ c2c_test_deep_bad: "ارسال آزمایشی ۴ KiB نرسید: {detail}",
|
||||
"cw_files_unpack_failed_generic": "کنسول گفت «{cause}».",
|
||||
"cw_files_unpack_not_container": "این یک .ffpfsc قابل خواندن برای کنسول نیست.",
|
||||
"cw_files_unpack_damaged": "فایل آسیب دیده است.",
|
||||
"cw_files_unpack_not_image": "ایمیج داخل آن ایمیج exFAT نیست، پس پشتیبانی نمیشود.",
|
||||
"cw_files_unpack_busy": "باز کردن دیگری روی کنسول در حال اجراست.",
|
||||
"cw_files_vw_find": "جستجو در این فایل",
|
||||
"cw_files_vw_prev": "قبلی",
|
||||
|
||||
@@ -4475,6 +4475,7 @@ c2c_test_deep_bad: "L’envoi test de 4 Kio n’est pas arrivé : {detail}",
|
||||
"cw_files_unpack_failed_generic": "la console a répondu « {cause} ».",
|
||||
"cw_files_unpack_not_container": "Ce n’est pas un .ffpfsc que la console peut lire.",
|
||||
"cw_files_unpack_damaged": "Le fichier est endommagé.",
|
||||
"cw_files_unpack_not_image": "L’image qu’il contient n’est pas une image exFAT : elle n’est pas prise en charge.",
|
||||
"cw_files_unpack_busy": "Une autre décompression est déjà en cours sur la console.",
|
||||
"cw_files_vw_find": "Rechercher dans ce fichier",
|
||||
"cw_files_vw_prev": "Précédent",
|
||||
|
||||
@@ -4473,6 +4473,7 @@ c2c_test_deep_bad: "4 KiB का परीक्षण नहीं पहुँ
|
||||
"cw_files_unpack_failed_generic": "कंसोल ने कहा “{cause}”।",
|
||||
"cw_files_unpack_not_container": "यह ऐसी .ffpfsc नहीं है जिसे कंसोल पढ़ सके।",
|
||||
"cw_files_unpack_damaged": "फ़ाइल खराब है।",
|
||||
"cw_files_unpack_not_image": "इसके अंदर की इमेज exFAT इमेज नहीं है, इसलिए यह समर्थित नहीं है।",
|
||||
"cw_files_unpack_busy": "कंसोल पर दूसरा डीकंप्रेस चल रहा है।",
|
||||
"cw_files_vw_find": "इस फ़ाइल में खोजें",
|
||||
"cw_files_vw_prev": "पिछला",
|
||||
|
||||
@@ -4772,6 +4772,7 @@ c2c_test_deep_bad: "A 4 KiB-os tesztküldés nem érkezett meg: {detail}",
|
||||
"cw_files_unpack_failed_generic": "a konzol válasza: „{cause}”.",
|
||||
"cw_files_unpack_not_container": "Ez nem olyan .ffpfsc, amelyet a konzol olvasni tud.",
|
||||
"cw_files_unpack_damaged": "A fájl sérült.",
|
||||
"cw_files_unpack_not_image": "A benne lévő image nem exFAT image, ezért nem támogatott.",
|
||||
"cw_files_unpack_busy": "Már fut egy másik kicsomagolás a konzolon.",
|
||||
"cw_files_vw_find": "Keresés ebben a fájlban",
|
||||
"cw_files_vw_prev": "Előző",
|
||||
|
||||
@@ -4466,6 +4466,7 @@ c2c_test_deep_bad: "Kiriman uji 4 KiB tidak tiba: {detail}",
|
||||
"cw_files_unpack_failed_generic": "konsol menjawab “{cause}”.",
|
||||
"cw_files_unpack_not_container": "Ini bukan .ffpfsc yang bisa dibaca konsol.",
|
||||
"cw_files_unpack_damaged": "File rusak.",
|
||||
"cw_files_unpack_not_image": "Image di dalamnya bukan image exFAT, jadi tidak didukung.",
|
||||
"cw_files_unpack_busy": "Ekstrak lain sedang berjalan di konsol.",
|
||||
"cw_files_vw_find": "Cari di file ini",
|
||||
"cw_files_vw_prev": "Sebelumnya",
|
||||
|
||||
@@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "L’invio di prova di 4 KiB non è arrivato: {detail}",
|
||||
"cw_files_unpack_failed_generic": "la console ha risposto «{cause}».",
|
||||
"cw_files_unpack_not_container": "Non è un .ffpfsc che la console può leggere.",
|
||||
"cw_files_unpack_damaged": "Il file è danneggiato.",
|
||||
"cw_files_unpack_not_image": "L’immagine al suo interno non è un’immagine exFAT, quindi non è supportata.",
|
||||
"cw_files_unpack_busy": "Sulla console è già in corso un’altra decompressione.",
|
||||
"cw_files_vw_find": "Cerca in questo file",
|
||||
"cw_files_vw_prev": "Precedente",
|
||||
|
||||
@@ -4478,6 +4478,7 @@ c2c_test_deep_bad: "4 KiB のテスト送信が届きませんでした: {detail
|
||||
"cw_files_unpack_failed_generic": "本体の応答:「{cause}」。",
|
||||
"cw_files_unpack_not_container": "本体が読み取れる .ffpfsc ではありません。",
|
||||
"cw_files_unpack_damaged": "ファイルが破損しています。",
|
||||
"cw_files_unpack_not_image": "中のイメージは exFAT イメージではないため、対応していません。",
|
||||
"cw_files_unpack_busy": "本体で別の展開が実行中です。",
|
||||
"cw_files_vw_find": "このファイル内を検索",
|
||||
"cw_files_vw_prev": "前へ",
|
||||
|
||||
@@ -4476,6 +4476,7 @@ c2c_test_deep_bad: "4 KiB 테스트 전송이 도착하지 않았습니다: {det
|
||||
"cw_files_unpack_failed_generic": "콘솔 응답: “{cause}”.",
|
||||
"cw_files_unpack_not_container": "콘솔이 읽을 수 있는 .ffpfsc가 아닙니다.",
|
||||
"cw_files_unpack_damaged": "파일이 손상되었습니다.",
|
||||
"cw_files_unpack_not_image": "안에 있는 이미지가 exFAT 이미지가 아니어서 지원되지 않습니다.",
|
||||
"cw_files_unpack_busy": "콘솔에서 다른 압축 풀기가 진행 중입니다.",
|
||||
"cw_files_vw_find": "이 파일에서 찾기",
|
||||
"cw_files_vw_prev": "이전",
|
||||
|
||||
@@ -4712,6 +4712,7 @@ c2c_test_deep_bad: "Testowa wysyłka 4 KiB nie dotarła: {detail}",
|
||||
"cw_files_unpack_failed_generic": "konsola zgłosiła „{cause}”.",
|
||||
"cw_files_unpack_not_container": "To nie jest plik .ffpfsc, który konsola potrafi odczytać.",
|
||||
"cw_files_unpack_damaged": "Plik jest uszkodzony.",
|
||||
"cw_files_unpack_not_image": "Obraz w środku nie jest obrazem exFAT, więc nie jest obsługiwany.",
|
||||
"cw_files_unpack_busy": "Na konsoli trwa już inne rozpakowywanie.",
|
||||
"cw_files_vw_find": "Znajdź w tym pliku",
|
||||
"cw_files_vw_prev": "Poprzednie",
|
||||
|
||||
@@ -4474,6 +4474,7 @@ c2c_test_deep_bad: "O envio de teste de 4 KiB não chegou: {detail}",
|
||||
"cw_files_unpack_failed_generic": "o console respondeu “{cause}”.",
|
||||
"cw_files_unpack_not_container": "Não é um .ffpfsc que o console consiga ler.",
|
||||
"cw_files_unpack_damaged": "O arquivo está danificado.",
|
||||
"cw_files_unpack_not_image": "A imagem dentro dele não é uma imagem exFAT, então não é compatível.",
|
||||
"cw_files_unpack_busy": "Outra descompactação já está em andamento no console.",
|
||||
"cw_files_vw_find": "Procurar neste arquivo",
|
||||
"cw_files_vw_prev": "Anterior",
|
||||
|
||||
@@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "Тестовая отправка 4 КиБ не дошла:
|
||||
"cw_files_unpack_failed_generic": "консоль ответила «{cause}».",
|
||||
"cw_files_unpack_not_container": "Это не .ffpfsc, который консоль может прочитать.",
|
||||
"cw_files_unpack_damaged": "Файл повреждён.",
|
||||
"cw_files_unpack_not_image": "Образ внутри не является образом exFAT, поэтому не поддерживается.",
|
||||
"cw_files_unpack_busy": "На консоли уже идёт другая распаковка.",
|
||||
"cw_files_vw_find": "Найти в этом файле",
|
||||
"cw_files_vw_prev": "Назад",
|
||||
|
||||
@@ -4466,6 +4466,7 @@ c2c_test_deep_bad: "การส่งทดสอบ 4 KiB ไปไม่ถ
|
||||
"cw_files_unpack_failed_generic": "เครื่องตอบว่า “{cause}”",
|
||||
"cw_files_unpack_not_container": "นี่ไม่ใช่ .ffpfsc ที่เครื่องอ่านได้",
|
||||
"cw_files_unpack_damaged": "ไฟล์เสียหาย",
|
||||
"cw_files_unpack_not_image": "อิมเมจด้านในไม่ใช่อิมเมจ exFAT จึงไม่รองรับ",
|
||||
"cw_files_unpack_busy": "เครื่องกำลังแตกไฟล์อื่นอยู่",
|
||||
"cw_files_vw_find": "ค้นหาในไฟล์นี้",
|
||||
"cw_files_vw_prev": "ก่อนหน้า",
|
||||
|
||||
@@ -4464,6 +4464,7 @@ c2c_test_deep_bad: "4 KiB'lik test gönderimi ulaşmadı: {detail}",
|
||||
"cw_files_unpack_failed_generic": "konsol “{cause}” dedi.",
|
||||
"cw_files_unpack_not_container": "Bu, konsolun okuyabildiği bir .ffpfsc değil.",
|
||||
"cw_files_unpack_damaged": "Dosya bozuk.",
|
||||
"cw_files_unpack_not_image": "İçindeki imaj bir exFAT imajı değil, bu yüzden desteklenmiyor.",
|
||||
"cw_files_unpack_busy": "Konsolda zaten başka bir sıkıştırma açma işlemi çalışıyor.",
|
||||
"cw_files_vw_find": "Bu dosyada bul",
|
||||
"cw_files_vw_prev": "Önceki",
|
||||
|
||||
@@ -4459,6 +4459,7 @@ c2c_test_deep_bad: "Lượt gửi thử 4 KiB không tới: {detail}",
|
||||
"cw_files_unpack_failed_generic": "máy báo “{cause}”.",
|
||||
"cw_files_unpack_not_container": "Đây không phải tệp .ffpfsc mà máy đọc được.",
|
||||
"cw_files_unpack_damaged": "Tệp bị hỏng.",
|
||||
"cw_files_unpack_not_image": "Ảnh bên trong không phải ảnh exFAT nên không được hỗ trợ.",
|
||||
"cw_files_unpack_busy": "Máy đang chạy một lần giải nén khác.",
|
||||
"cw_files_vw_find": "Tìm trong tệp này",
|
||||
"cw_files_vw_prev": "Trước",
|
||||
|
||||
@@ -4413,6 +4413,7 @@ c2c_test_deep_bad: "4 KiB 测试发送未到达:{detail}",
|
||||
"cw_files_unpack_failed_generic": "主机返回“{cause}”。",
|
||||
"cw_files_unpack_not_container": "这不是主机能读取的 .ffpfsc。",
|
||||
"cw_files_unpack_damaged": "文件已损坏。",
|
||||
"cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支持。",
|
||||
"cw_files_unpack_busy": "主机上已有另一个解压在进行。",
|
||||
"cw_files_vw_find": "在此文件中查找",
|
||||
"cw_files_vw_prev": "上一个",
|
||||
|
||||
@@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "4 KiB 測試傳送未抵達:{detail}",
|
||||
"cw_files_unpack_failed_generic": "主機回應「{cause}」。",
|
||||
"cw_files_unpack_not_container": "這不是主機能讀取的 .ffpfsc。",
|
||||
"cw_files_unpack_damaged": "檔案已損毀。",
|
||||
"cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支援。",
|
||||
"cw_files_unpack_busy": "主機上已有另一個解壓縮在進行。",
|
||||
"cw_files_vw_find": "在此檔案中尋找",
|
||||
"cw_files_vw_prev": "上一個",
|
||||
|
||||
@@ -43,9 +43,9 @@ enum {
|
||||
FILES_PREFS_E_BAD_OP = -5,
|
||||
};
|
||||
|
||||
typedef enum { FILES_PREFS_PIN = 1, FILES_PREFS_UNPIN = 2, FILES_PREFS_VISIT = 3 } files_prefs_op_t;
|
||||
typedef enum { FILES_PREFS_PIN = 1, FILES_PREFS_UNPIN = 2, FILES_PREFS_VISIT = 3, FILES_PREFS_FORGET = 4 } files_prefs_op_t;
|
||||
|
||||
/* "pin" / "unpin" / "visit" -> the op; 0 for anything else. */
|
||||
/* "pin" / "unpin" / "visit" / "forget" -> the op; 0 for anything else. */
|
||||
int files_prefs_op_from_name(const char *name);
|
||||
|
||||
/* Copies `in` into `out` (cap FILES_PREFS_PATH_MAX) in its stored form: '//' collapsed, a trailing
|
||||
@@ -77,10 +77,13 @@ int files_prefs_save(const char *file, const files_prefs_t *p);
|
||||
* unpin: removed when present (absent = no change).
|
||||
* visit: `last` = path, and path moves to the front of recent (once). When the folder before it
|
||||
* was its parent or another ancestor (walking down a tree), that ancestor's entry is
|
||||
* replaced, so one walk leaves one entry. Cut to FILES_PREFS_RECENT_MAX. */
|
||||
* replaced, so one walk leaves one entry. Cut to FILES_PREFS_RECENT_MAX.
|
||||
* forget: removed from recent (a folder that no longer exists); `last` cleared when it is that
|
||||
* path. Absent = no change. */
|
||||
int files_prefs_pin(files_prefs_t *p, const char *path);
|
||||
int files_prefs_unpin(files_prefs_t *p, const char *path);
|
||||
int files_prefs_visit(files_prefs_t *p, const char *path);
|
||||
int files_prefs_forget(files_prefs_t *p, const char *path);
|
||||
|
||||
/* Locked: the file's prefs as JSON into out (cap >= FILES_PREFS_FILE_MAX + 1). Length, or an error. */
|
||||
int files_prefs_get(const char *file, char *out, size_t cap);
|
||||
|
||||
@@ -421,6 +421,8 @@ int fsj_op_ffpfsc_unpack(void *arg, ava1_op_ctx_t *c, const uint8_t *args, size_
|
||||
goto done;
|
||||
}
|
||||
if (ends_with_ci(L.name, ".exfat") && !exfat_boot_ok(part)) {
|
||||
/* Decoded in full, but the image inside is another file system (not a damaged block). */
|
||||
fprintf(stderr, "ffpfsc unpack: %s holds %s, which is not an exFAT image; nothing kept\n", src, L.name);
|
||||
ava1_op_message(c, "fs_unpack_not_exfat");
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ int files_prefs_op_from_name(const char *name) {
|
||||
if (strcmp(name, "pin") == 0) return FILES_PREFS_PIN;
|
||||
if (strcmp(name, "unpin") == 0) return FILES_PREFS_UNPIN;
|
||||
if (strcmp(name, "visit") == 0) return FILES_PREFS_VISIT;
|
||||
if (strcmp(name, "forget") == 0) return FILES_PREFS_FORGET;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -274,6 +275,17 @@ int files_prefs_visit(files_prefs_t *p, const char *path) {
|
||||
return FILES_PREFS_OK;
|
||||
}
|
||||
|
||||
int files_prefs_forget(files_prefs_t *p, const char *path) {
|
||||
char clean[FILES_PREFS_PATH_MAX];
|
||||
if (files_prefs_clean_path(path, clean) != FILES_PREFS_OK) return FILES_PREFS_E_BAD_PATH;
|
||||
remove_at(p->recent, &p->nrecent, find(p->recent, p->nrecent, clean));
|
||||
if (p->has_last && strcmp(p->last, clean) == 0) {
|
||||
p->has_last = 0;
|
||||
p->last[0] = '\0';
|
||||
}
|
||||
return FILES_PREFS_OK;
|
||||
}
|
||||
|
||||
int files_prefs_get(const char *file, char *out, size_t cap) {
|
||||
files_prefs_t *p = malloc(sizeof *p);
|
||||
int len;
|
||||
@@ -296,7 +308,8 @@ static int visit_is_noop(const files_prefs_t *p, const char *path) {
|
||||
int files_prefs_update(const char *file, int op, const char *path, char *out, size_t cap) {
|
||||
files_prefs_t *p;
|
||||
int rc, len;
|
||||
if (op != FILES_PREFS_PIN && op != FILES_PREFS_UNPIN && op != FILES_PREFS_VISIT) return FILES_PREFS_E_BAD_OP;
|
||||
if (op != FILES_PREFS_PIN && op != FILES_PREFS_UNPIN && op != FILES_PREFS_VISIT && op != FILES_PREFS_FORGET)
|
||||
return FILES_PREFS_E_BAD_OP;
|
||||
p = malloc(sizeof *p);
|
||||
if (!p) return FILES_PREFS_E_NOMEM;
|
||||
pthread_mutex_lock(&g_prefs_mu);
|
||||
@@ -309,7 +322,8 @@ int files_prefs_update(const char *file, int op, const char *path, char *out, si
|
||||
}
|
||||
rc = op == FILES_PREFS_PIN ? files_prefs_pin(p, path)
|
||||
: op == FILES_PREFS_UNPIN ? files_prefs_unpin(p, path)
|
||||
: files_prefs_visit(p, path);
|
||||
: op == FILES_PREFS_FORGET ? files_prefs_forget(p, path)
|
||||
: files_prefs_visit(p, path);
|
||||
/* favourites never take the room a visit needs: one recent folder and the last one, at their longest */
|
||||
if (rc == FILES_PREFS_OK && op == FILES_PREFS_PIN && build_json(p, NULL, 0) > FILES_PREFS_FILE_MAX - PIN_RESERVE)
|
||||
rc = FILES_PREFS_E_FULL;
|
||||
|
||||
@@ -514,9 +514,13 @@ static hw_temps_cache_t g_temps_cache = {0};
|
||||
* helper thread was strictly worse — it crashed the payload on the
|
||||
* first read of every console. Inline on the mgmt worker (which the
|
||||
* Sony runtime set up) is the only path that reads cleanly. The
|
||||
* companion desktop change makes this safe to keep simple: live
|
||||
* sensors are no longer auto-polled, only read on an explicit
|
||||
* "Read sensors" click, so a read can never fire unattended.
|
||||
* desktop app reads live sensors only on an explicit "Read sensors"
|
||||
* click. The console page (PS5Upload Files) is the one unattended
|
||||
* reader: its status polls HW_TEMPS every 10 s, inline on a web
|
||||
* worker thread (webui_server.c, 512 KiB stack). That worker is also
|
||||
* a pthread we spawned, and it read cleanly for whole sessions on
|
||||
* FW 13.60 hardware, so the fresh-thread crash above did not
|
||||
* reproduce there; keep these reads inline on the request thread.
|
||||
*
|
||||
* 1-second cache so a double-click / rapid re-read coalesces into one
|
||||
* round of syscalls. */
|
||||
|
||||
+12
-1
@@ -372,6 +372,17 @@ static void r_fs_stat(const char *b, size_t n, uint8_t *out, int fd) {
|
||||
}
|
||||
st = call(AVA1_METHOD_FS_STAT, (const uint8_t *)req, w.len, out, &ol);
|
||||
if (st != AVA1_STATUS_OK) {
|
||||
/* "missing_ok":true (the page probing for a free name): a path that is not there is an
|
||||
* answer, 200 {"missing":true}, not a 404 the browser logs as an error. */
|
||||
int missing_ok = 0;
|
||||
char c[API_CAUSE_MAX + 1];
|
||||
size_t cl = ol > API_CAUSE_MAX ? API_CAUSE_MAX : ol;
|
||||
memcpy(c, out, cl);
|
||||
c[cl] = '\0';
|
||||
if (webui_json_bool(b, n, "missing_ok", &missing_ok) == 1 && missing_ok && webui_http_status_for(st, c) == 404) {
|
||||
send_json(fd, 200, "{\"missing\":true}", 16);
|
||||
return;
|
||||
}
|
||||
send_status_error(fd, st, out, ol);
|
||||
return;
|
||||
}
|
||||
@@ -905,7 +916,7 @@ static void r_files_prefs(const char *b, size_t n, uint8_t *out, int fd) {
|
||||
else send_json(fd, 200, (const char *)out, (size_t)len);
|
||||
}
|
||||
|
||||
/* {"op":"pin"|"unpin"|"visit","path":".."} -> the new prefs, as /api/files/prefs answers. */
|
||||
/* {"op":"pin"|"unpin"|"visit"|"forget","path":".."} -> the new prefs, as /api/files/prefs answers. */
|
||||
static void r_files_prefs_set(const char *b, size_t n, uint8_t *out, int fd) {
|
||||
char op[16], path[API_PATH];
|
||||
int o, len;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ava1_thread.h"
|
||||
#include "proc_identity.h"
|
||||
#include "webui_auth.h"
|
||||
|
||||
@@ -45,7 +46,7 @@
|
||||
#define WEBUI_SLICE_MS 200
|
||||
#define WEBUI_MIN_SEND_MS 1000u /* a route that computed long still gets this to write */
|
||||
#define WEBUI_DRAIN_MS 250u
|
||||
#define WEBUI_STACK (256u * 1024u)
|
||||
#define WEBUI_STACK AVA1_MGMT_STACK /* routes run mgmt handlers sized for 512 KiB */
|
||||
#define WEBUI_MAX_ADDRS 8
|
||||
#define WEBUI_WATCH_MS 5000u /* the accept thread's listener check (resume, IP change) */
|
||||
#define WEBUI_BROKEN_MS 5000u /* a listener failing this long is rebound */
|
||||
|
||||
@@ -71,7 +71,8 @@ static void test_clean_path(void) {
|
||||
big[FILES_PREFS_PATH_MAX] = '\0';
|
||||
CHECK(files_prefs_clean_path(big, out) == FILES_PREFS_E_BAD_PATH);
|
||||
CHECK(files_prefs_op_from_name("pin") == FILES_PREFS_PIN && files_prefs_op_from_name("unpin") == FILES_PREFS_UNPIN &&
|
||||
files_prefs_op_from_name("visit") == FILES_PREFS_VISIT && files_prefs_op_from_name("delete") == 0 &&
|
||||
files_prefs_op_from_name("visit") == FILES_PREFS_VISIT && files_prefs_op_from_name("forget") == FILES_PREFS_FORGET &&
|
||||
files_prefs_op_from_name("delete") == 0 &&
|
||||
files_prefs_op_from_name("") == 0);
|
||||
}
|
||||
|
||||
@@ -116,6 +117,12 @@ static void test_recent_order(void) {
|
||||
files_prefs_visit(p, "/mnt/usb0b"); /* a sibling with the same prefix is not a child */
|
||||
CHECK(p->nrecent == 4 && strcmp(p->recent[1], "/mnt/usb0") == 0);
|
||||
CHECK(strcmp(p->last, "/mnt/usb0b") == 0);
|
||||
/* forget: a gone folder leaves recent (and last, when it is that one); others keep their order */
|
||||
CHECK(files_prefs_forget(p, "/mnt/usb0/") == 0 && p->nrecent == 3 && strcmp(p->recent[0], "/mnt/usb0b") == 0 &&
|
||||
strcmp(p->recent[1], "/data/homebrew") == 0 && p->has_last);
|
||||
CHECK(files_prefs_forget(p, "/mnt/usb0b") == 0 && p->nrecent == 2 && !p->has_last);
|
||||
CHECK(files_prefs_forget(p, "/never") == 0 && p->nrecent == 2);
|
||||
CHECK(files_prefs_forget(p, "rel") == FILES_PREFS_E_BAD_PATH);
|
||||
free(p);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user