console files: handoff and upload-badge hardening

- The Uploading badge reads upload roots through ava1_data_upload_roots,
  under the open lock (where roots are written) and the table lock, and
  leaves out parked, finished, stopping and recovery-placeholder jobs.
- files_handoff_take_json checks its buffer before taking, so a too-small
  buffer can no longer empty the slot and lose the handoff.
- PC app: the Show on PC poll skips its ticks while a transfer runs on
  that console (like the other background management pollers); away from
  File System a handoff becomes a toast with "Open" instead of navigating.
- Console "Show on PC" now says "Sent to your PC." (no promise it opens),
  in all 21 languages; new PC strings for the Open offer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
This commit is contained in:
phantomptrandClaude Opus 5.5 committed 2026-10-17 13:52:35 -07:00
1 parent 4b51f802d3
commit 0ffeba67f8
50 files changed
+207 -79

No files matched your search

+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "إلغاء التحديد",
"cw_files_selected": "تم تحديد {n}",
"cw_files_show_pc": "العرض على الكمبيوتر",
"cw_files_show_pc_done": "أُرسل إلى الكمبيوتر. يُفتح في نظام ملفات PS5Upload.",
"cw_files_show_pc_done": "أُرسل إلى الكمبيوتر.",
"cw_files_show_pc_failed": "تعذّر الإرسال إلى الكمبيوتر.",
"cw_files_some_failed": "فشل {n}: {reason}",
"cw_files_sort_date": "ترتيب حسب التاريخ",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "বাছাই সরান",
"cw_files_selected": "{n}টি নির্বাচিত",
"cw_files_show_pc": "PC-তে দেখান",
"cw_files_show_pc_done": "আপনার PC-তে পাঠানো হয়েছে। এটি PS5Upload-এর ফাইল সিস্টেমে খুলবে।",
"cw_files_show_pc_done": "আপনার PC-তে পাঠানো হয়েছে।",
"cw_files_show_pc_failed": "PC-তে পাঠানো যায়নি।",
"cw_files_some_failed": "{n}টি ব্যর্থ: {reason}",
"cw_files_sort_date": "তারিখ অনুযায়ী সাজান",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Auswahl aufheben",
"cw_files_selected": "{n} ausgewählt",
"cw_files_show_pc": "Auf dem PC zeigen",
"cw_files_show_pc_done": "An deinen PC gesendet. Es öffnet sich im Dateisystem von PS5Upload.",
"cw_files_show_pc_done": "An deinen PC gesendet.",
"cw_files_show_pc_failed": "Konnte nicht an den PC gesendet werden.",
"cw_files_some_failed": "{n} fehlgeschlagen: {reason}",
"cw_files_sort_date": "Nach Datum sortieren",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Select none",
"cw_files_selected": "{n} selected",
"cw_files_show_pc": "Show on PC",
"cw_files_show_pc_done": "Sent to your PC. It opens in PS5Upload's File System.",
"cw_files_show_pc_done": "Sent to your PC.",
"cw_files_show_pc_failed": "Couldn't send it to the PC.",
"cw_files_some_failed": "{n} failed: {reason}",
"cw_files_sort_date": "Sort by date",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Quitar selección",
"cw_files_selected": "{n} seleccionados",
"cw_files_show_pc": "Mostrar en el PC",
"cw_files_show_pc_done": "Enviado a tu PC. Se abre en el sistema de archivos de PS5Upload.",
"cw_files_show_pc_done": "Enviado a tu PC.",
"cw_files_show_pc_failed": "No se pudo enviar al PC.",
"cw_files_some_failed": "{n} fallaron: {reason}",
"cw_files_sort_date": "Ordenar por fecha",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "لغو انتخاب",
"cw_files_selected": "{n} انتخاب شده",
"cw_files_show_pc": "نمایش روی رایانه",
"cw_files_show_pc_done": "به رایانهٔ شما فرستاده شد. در سیستم فایل PS5Upload باز می‌شود.",
"cw_files_show_pc_done": "به رایانهٔ شما فرستاده شد.",
"cw_files_show_pc_failed": "فرستادن به رایانه ممکن نشد.",
"cw_files_some_failed": "{n} ناموفق: {reason}",
"cw_files_sort_date": "مرتب‌سازی بر اساس تاریخ",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Tout désélectionner",
"cw_files_selected": "{n} sélectionné(s)",
"cw_files_show_pc": "Afficher sur le PC",
"cw_files_show_pc_done": "Envoyé à votre PC. Il s'ouvre dans le système de fichiers de PS5Upload.",
"cw_files_show_pc_done": "Envoyé à votre PC.",
"cw_files_show_pc_failed": "Impossible de l'envoyer au PC.",
"cw_files_some_failed": "{n} en échec : {reason}",
"cw_files_sort_date": "Trier par date",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "चयन हटाएँ",
"cw_files_selected": "{n} चुने गए",
"cw_files_show_pc": "PC पर दिखाएँ",
"cw_files_show_pc_done": "आपके PC पर भेजा गया। यह PS5Upload के फ़ाइल सिस्टम में खुलेगा।",
"cw_files_show_pc_done": "आपके PC पर भेजा गया।",
"cw_files_show_pc_failed": "PC पर नहीं भेज सके।",
"cw_files_some_failed": "{n} विफल: {reason}",
"cw_files_sort_date": "तारीख़ के अनुसार क्रम",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Kijelölés törlése",
"cw_files_selected": "{n} kijelölve",
"cw_files_show_pc": "Megjelenítés a PC-n",
"cw_files_show_pc_done": "Elküldve a PC-dre. A PS5Upload fájlrendszerében nyílik meg.",
"cw_files_show_pc_done": "Elküldve a PC-dre.",
"cw_files_show_pc_failed": "Nem sikerült elküldeni a PC-re.",
"cw_files_some_failed": "{n} sikertelen: {reason}",
"cw_files_sort_date": "Rendezés dátum szerint",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Batalkan pilihan",
"cw_files_selected": "{n} dipilih",
"cw_files_show_pc": "Tampilkan di PC",
"cw_files_show_pc_done": "Dikirim ke PC Anda. Terbuka di Sistem File PS5Upload.",
"cw_files_show_pc_done": "Dikirim ke PC Anda.",
"cw_files_show_pc_failed": "Tidak dapat mengirim ke PC.",
"cw_files_some_failed": "{n} gagal: {reason}",
"cw_files_sort_date": "Urutkan menurut tanggal",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Deseleziona tutto",
"cw_files_selected": "{n} selezionati",
"cw_files_show_pc": "Mostra sul PC",
"cw_files_show_pc_done": "Inviato al tuo PC. Si apre nel file system di PS5Upload.",
"cw_files_show_pc_done": "Inviato al tuo PC.",
"cw_files_show_pc_failed": "Impossibile inviarlo al PC.",
"cw_files_some_failed": "{n} non riusciti: {reason}",
"cw_files_sort_date": "Ordina per data",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "選択を解除",
"cw_files_selected": "{n} 件選択中",
"cw_files_show_pc": "PC で表示",
"cw_files_show_pc_done": "PC に送信しました。PS5Upload のファイルシステムで開きます。",
"cw_files_show_pc_done": "PC に送信しました。",
"cw_files_show_pc_failed": "PC に送信できませんでした。",
"cw_files_some_failed": "{n} 件失敗: {reason}",
"cw_files_sort_date": "日付順",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "선택 해제",
"cw_files_selected": "{n}개 선택됨",
"cw_files_show_pc": "PC에서 보기",
"cw_files_show_pc_done": "PC로 보냈습니다. PS5Upload의 파일 시스템에서 열립니다.",
"cw_files_show_pc_done": "PC로 보냈습니다.",
"cw_files_show_pc_failed": "PC로 보낼 수 없습니다.",
"cw_files_some_failed": "{n}개 실패: {reason}",
"cw_files_sort_date": "날짜순",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Odznacz wszystko",
"cw_files_selected": "Zaznaczono: {n}",
"cw_files_show_pc": "Pokaż na PC",
"cw_files_show_pc_done": "Wysłano do twojego PC. Otworzy się w systemie plików PS5Upload.",
"cw_files_show_pc_done": "Wysłano do twojego PC.",
"cw_files_show_pc_failed": "Nie udało się wysłać do PC.",
"cw_files_some_failed": "Nieudane ({n}): {reason}",
"cw_files_sort_date": "Sortuj według daty",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Limpar seleção",
"cw_files_selected": "{n} selecionados",
"cw_files_show_pc": "Mostrar no PC",
"cw_files_show_pc_done": "Enviado ao seu PC. Abre no sistema de arquivos do PS5Upload.",
"cw_files_show_pc_done": "Enviado ao seu PC.",
"cw_files_show_pc_failed": "Não foi possível enviar ao PC.",
"cw_files_some_failed": "{n} falharam: {reason}",
"cw_files_sort_date": "Ordenar por data",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Снять выбор",
"cw_files_selected": "Выбрано: {n}",
"cw_files_show_pc": "Показать на ПК",
"cw_files_show_pc_done": "Отправлено на ваш ПК. Откроется в файловой системе PS5Upload.",
"cw_files_show_pc_done": "Отправлено на ваш ПК.",
"cw_files_show_pc_failed": "Не удалось отправить на ПК.",
"cw_files_some_failed": "Не удалось ({n}): {reason}",
"cw_files_sort_date": "По дате",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "ไม่เลือกเลย",
"cw_files_selected": "เลือกแล้ว {n}",
"cw_files_show_pc": "แสดงบน PC",
"cw_files_show_pc_done": "ส่งไปยัง PC ของคุณแล้ว จะเปิดในระบบไฟล์ของ PS5Upload",
"cw_files_show_pc_done": "ส่งไปยัง PC ของคุณแล้ว",
"cw_files_show_pc_failed": "ไม่สามารถส่งไปยัง PC ได้",
"cw_files_some_failed": "ล้มเหลว {n}: {reason}",
"cw_files_sort_date": "เรียงตามวันที่",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Seçimi kaldır",
"cw_files_selected": "{n} seçili",
"cw_files_show_pc": "PC'de göster",
"cw_files_show_pc_done": "PC'ne gönderildi. PS5Upload'ın Dosya Sistemi'nde açılır.",
"cw_files_show_pc_done": "PC'ne gönderildi.",
"cw_files_show_pc_failed": "PC'ye gönderilemedi.",
"cw_files_some_failed": "{n} başarısız: {reason}",
"cw_files_sort_date": "Tarihe göre sırala",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "Bỏ chọn",
"cw_files_selected": "Đã chọn {n}",
"cw_files_show_pc": "Hiển thị trên PC",
"cw_files_show_pc_done": "Đã gửi đến PC của bạn. Sẽ mở trong Hệ thống tệp của PS5Upload.",
"cw_files_show_pc_done": "Đã gửi đến PC của bạn.",
"cw_files_show_pc_failed": "Không thể gửi đến PC.",
"cw_files_some_failed": "{n} thất bại: {reason}",
"cw_files_sort_date": "Sắp xếp theo ngày",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "取消全选",
"cw_files_selected": "已选 {n} 项",
"cw_files_show_pc": "在电脑上显示",
"cw_files_show_pc_done": "已发送到你的电脑,将在 PS5Upload 的文件系统中打开。",
"cw_files_show_pc_done": "已发送到你的电脑。",
"cw_files_show_pc_failed": "无法发送到电脑。",
"cw_files_some_failed": "{n} 项失败:{reason}",
"cw_files_sort_date": "按日期排序",
+1 -1
View File
@@ -158,7 +158,7 @@
"cw_files_select_none": "取消全選",
"cw_files_selected": "已選取 {n} 項",
"cw_files_show_pc": "在電腦上顯示",
"cw_files_show_pc_done": "已傳送到你的電腦,將在 PS5Upload 的檔案系統中開啟。",
"cw_files_show_pc_done": "已傳送到你的電腦。",
"cw_files_show_pc_failed": "無法傳送到電腦。",
"cw_files_some_failed": "{n} 項失敗:{reason}",
"cw_files_sort_date": "依日期排序",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "الوجهات الأخيرة",
"fs_show_on_console_sent": "أُرسل إلى PS5 — يُفتح في PS5Upload Files",
"fs_show_on_console_failed": "تعذّر الإرسال إلى PS5: {error}",
"fs_opened_from_ps5": "فُتح من جهاز PS5 الخاص بك: {path}",
"fs_handoff_from_ps5": "أرسل جهاز PS5 الخاص بك {path}",
"fs_handoff_open": "فتح",
"cw_files_show_pc": "العرض على الكمبيوتر",
"cw_files_show_pc_done": "أُرسل إلى الكمبيوتر. يُفتح في نظام ملفات PS5Upload.",
"cw_files_show_pc_done": "أُرسل إلى الكمبيوتر.",
"cw_files_show_pc_failed": "تعذّر الإرسال إلى الكمبيوتر.",
"cw_files_from_pc": "فُتح من الكمبيوتر.",
"cw_files_from_pc_gone": "أرسل الكمبيوتر {path}، لكنه لم يعد على الجهاز.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "সাম্প্রতিক গন্তব্য",
"fs_show_on_console_sent": "PS5-এ পাঠানো হয়েছে — এটি PS5Upload Files-এ খুলবে",
"fs_show_on_console_failed": "PS5-এ পাঠানো যায়নি: {error}",
"fs_opened_from_ps5": "আপনার PS5 থেকে খোলা হয়েছে: {path}",
"fs_handoff_from_ps5": "আপনার PS5 {path} পাঠিয়েছে",
"fs_handoff_open": "খুলুন",
"cw_files_show_pc": "PC-তে দেখান",
"cw_files_show_pc_done": "আপনার PC-তে পাঠানো হয়েছে। এটি PS5Upload-এর ফাইল সিস্টেমে খুলবে।",
"cw_files_show_pc_done": "আপনার PC-তে পাঠানো হয়েছে।",
"cw_files_show_pc_failed": "PC-তে পাঠানো যায়নি।",
"cw_files_from_pc": "আপনার PC থেকে খোলা হয়েছে।",
"cw_files_from_pc_gone": "আপনার PC {path} পাঠিয়েছে, কিন্তু এটি আর কনসোলে নেই।",
+3 -1
View File
@@ -636,8 +636,10 @@ fs_recent_tooltip: "Letzte Ziele",
"fs_show_on_console_sent": "An die PS5 gesendet – es öffnet sich in PS5Upload Files",
"fs_show_on_console_failed": "Konnte nicht an die PS5 gesendet werden: {error}",
"fs_opened_from_ps5": "Von deiner PS5 geöffnet: {path}",
"fs_handoff_from_ps5": "Deine PS5 hat {path} gesendet",
"fs_handoff_open": "Öffnen",
"cw_files_show_pc": "Auf dem PC zeigen",
"cw_files_show_pc_done": "An deinen PC gesendet. Es öffnet sich im Dateisystem von PS5Upload.",
"cw_files_show_pc_done": "An deinen PC gesendet.",
"cw_files_show_pc_failed": "Konnte nicht an den PC gesendet werden.",
"cw_files_from_pc": "Von deinem PC geöffnet.",
"cw_files_from_pc_gone": "Dein PC hat {path} gesendet, aber es ist nicht mehr auf der Konsole.",
+3 -1
View File
@@ -1088,8 +1088,10 @@ fs_recent_tooltip: "Recent destinations",
"fs_show_on_console_sent": "Sent to the PS5 — it opens in PS5Upload Files",
"fs_show_on_console_failed": "Couldn't send it to the PS5: {error}",
"fs_opened_from_ps5": "Opened from your PS5: {path}",
"fs_handoff_from_ps5": "Your PS5 sent {path}",
"fs_handoff_open": "Open",
"cw_files_show_pc": "Show on PC",
"cw_files_show_pc_done": "Sent to your PC. It opens in PS5Upload's File System.",
"cw_files_show_pc_done": "Sent to your PC.",
"cw_files_show_pc_failed": "Couldn't send it to the PC.",
"cw_files_from_pc": "Opened from your PC.",
"cw_files_from_pc_gone": "Your PC sent {path}, but it isn't on the console any more.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "Destinos recientes",
"fs_show_on_console_sent": "Enviado a la PS5: se abre en PS5Upload Files",
"fs_show_on_console_failed": "No se pudo enviar a la PS5: {error}",
"fs_opened_from_ps5": "Abierto desde tu PS5: {path}",
"fs_handoff_from_ps5": "Tu PS5 envió {path}",
"fs_handoff_open": "Abrir",
"cw_files_show_pc": "Mostrar en el PC",
"cw_files_show_pc_done": "Enviado a tu PC. Se abre en el sistema de archivos de PS5Upload.",
"cw_files_show_pc_done": "Enviado a tu PC.",
"cw_files_show_pc_failed": "No se pudo enviar al PC.",
"cw_files_from_pc": "Abierto desde tu PC.",
"cw_files_from_pc_gone": "Tu PC envió {path}, pero ya no está en la consola.",
+3 -1
View File
@@ -1011,8 +1011,10 @@ fs_recent_tooltip: "مقصدهای اخیر",
"fs_show_on_console_sent": "به PS5 فرستاده شد — در PS5Upload Files باز می‌شود",
"fs_show_on_console_failed": "فرستادن به PS5 ممکن نشد: {error}",
"fs_opened_from_ps5": "از PS5 شما باز شد: {path}",
"fs_handoff_from_ps5": "PS5 شما {path} را فرستاد",
"fs_handoff_open": "باز کردن",
"cw_files_show_pc": "نمایش روی رایانه",
"cw_files_show_pc_done": "به رایانهٔ شما فرستاده شد. در سیستم فایل PS5Upload باز می‌شود.",
"cw_files_show_pc_done": "به رایانهٔ شما فرستاده شد.",
"cw_files_show_pc_failed": "فرستادن به رایانه ممکن نشد.",
"cw_files_from_pc": "از رایانهٔ شما باز شد.",
"cw_files_from_pc_gone": "رایانهٔ شما {path} را فرستاد، اما دیگر روی کنسول نیست.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "Destinations récentes",
"fs_show_on_console_sent": "Envoyé à la PS5 — il s'ouvre dans PS5Upload Files",
"fs_show_on_console_failed": "Impossible de l'envoyer à la PS5 : {error}",
"fs_opened_from_ps5": "Ouvert depuis votre PS5 : {path}",
"fs_handoff_from_ps5": "Votre PS5 a envoyé {path}",
"fs_handoff_open": "Ouvrir",
"cw_files_show_pc": "Afficher sur le PC",
"cw_files_show_pc_done": "Envoyé à votre PC. Il s'ouvre dans le système de fichiers de PS5Upload.",
"cw_files_show_pc_done": "Envoyé à votre PC.",
"cw_files_show_pc_failed": "Impossible de l'envoyer au PC.",
"cw_files_from_pc": "Ouvert depuis votre PC.",
"cw_files_from_pc_gone": "Votre PC a envoyé {path}, mais il n'est plus sur la console.",
+3 -1
View File
@@ -636,8 +636,10 @@ fs_recent_tooltip: "हाल के गंतव्य",
"fs_show_on_console_sent": "PS5 पर भेजा गया — यह PS5Upload Files में खुलेगा",
"fs_show_on_console_failed": "PS5 पर नहीं भेज सके: {error}",
"fs_opened_from_ps5": "आपके PS5 से खोला गया: {path}",
"fs_handoff_from_ps5": "आपके PS5 ने {path} भेजा",
"fs_handoff_open": "खोलें",
"cw_files_show_pc": "PC पर दिखाएँ",
"cw_files_show_pc_done": "आपके PC पर भेजा गया। यह PS5Upload के फ़ाइल सिस्टम में खुलेगा।",
"cw_files_show_pc_done": "आपके PC पर भेजा गया।",
"cw_files_show_pc_failed": "PC पर नहीं भेज सके।",
"cw_files_from_pc": "आपके PC से खोला गया।",
"cw_files_from_pc_gone": "आपके PC ने {path} भेजा, पर यह अब कंसोल पर नहीं है।",
+3 -1
View File
@@ -947,8 +947,10 @@ fs_recent_tooltip: "Legutóbbi célok",
"fs_show_on_console_sent": "Elküldve a PS5-re – a PS5Upload Files-ban nyílik meg",
"fs_show_on_console_failed": "Nem sikerült elküldeni a PS5-re: {error}",
"fs_opened_from_ps5": "Megnyitva a PS5-ödről: {path}",
"fs_handoff_from_ps5": "A PS5-öd elküldte ezt: {path}",
"fs_handoff_open": "Megnyitás",
"cw_files_show_pc": "Megjelenítés a PC-n",
"cw_files_show_pc_done": "Elküldve a PC-dre. A PS5Upload fájlrendszerében nyílik meg.",
"cw_files_show_pc_done": "Elküldve a PC-dre.",
"cw_files_show_pc_failed": "Nem sikerült elküldeni a PC-re.",
"cw_files_from_pc": "Megnyitva a PC-dről.",
"cw_files_from_pc_gone": "A PC-d elküldte ezt: {path}, de már nincs a konzolon.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "Tujuan terbaru",
"fs_show_on_console_sent": "Dikirim ke PS5 — terbuka di PS5Upload Files",
"fs_show_on_console_failed": "Tidak dapat mengirim ke PS5: {error}",
"fs_opened_from_ps5": "Dibuka dari PS5 Anda: {path}",
"fs_handoff_from_ps5": "PS5 Anda mengirim {path}",
"fs_handoff_open": "Buka",
"cw_files_show_pc": "Tampilkan di PC",
"cw_files_show_pc_done": "Dikirim ke PC Anda. Terbuka di Sistem File PS5Upload.",
"cw_files_show_pc_done": "Dikirim ke PC Anda.",
"cw_files_show_pc_failed": "Tidak dapat mengirim ke PC.",
"cw_files_from_pc": "Dibuka dari PC Anda.",
"cw_files_from_pc_gone": "PC Anda mengirim {path}, tetapi sudah tidak ada di konsol.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "Destinazioni recenti",
"fs_show_on_console_sent": "Inviato alla PS5: si apre in PS5Upload Files",
"fs_show_on_console_failed": "Impossibile inviarlo alla PS5: {error}",
"fs_opened_from_ps5": "Aperto dalla tua PS5: {path}",
"fs_handoff_from_ps5": "La tua PS5 ha inviato {path}",
"fs_handoff_open": "Apri",
"cw_files_show_pc": "Mostra sul PC",
"cw_files_show_pc_done": "Inviato al tuo PC. Si apre nel file system di PS5Upload.",
"cw_files_show_pc_done": "Inviato al tuo PC.",
"cw_files_show_pc_failed": "Impossibile inviarlo al PC.",
"cw_files_from_pc": "Aperto dal tuo PC.",
"cw_files_from_pc_gone": "Il tuo PC ha inviato {path}, ma non è più sulla console.",
+3 -1
View File
@@ -636,8 +636,10 @@ fs_recent_tooltip: "最近の保存先",
"fs_show_on_console_sent": "PS5 に送信しました — PS5Upload Files で開きます",
"fs_show_on_console_failed": "PS5 に送信できませんでした: {error}",
"fs_opened_from_ps5": "PS5 から開きました: {path}",
"fs_handoff_from_ps5": "PS5 から {path} が送られました",
"fs_handoff_open": "開く",
"cw_files_show_pc": "PC で表示",
"cw_files_show_pc_done": "PC に送信しました。PS5Upload のファイルシステムで開きます。",
"cw_files_show_pc_done": "PC に送信しました。",
"cw_files_show_pc_failed": "PC に送信できませんでした。",
"cw_files_from_pc": "PC から開きました。",
"cw_files_from_pc_gone": "PC から {path} が送られましたが、もうコンソールにありません。",
+3 -1
View File
@@ -636,8 +636,10 @@ fs_recent_tooltip: "최근 대상 위치",
"fs_show_on_console_sent": "PS5로 보냈습니다 — PS5Upload Files에서 열립니다",
"fs_show_on_console_failed": "PS5로 보낼 수 없습니다: {error}",
"fs_opened_from_ps5": "PS5에서 열림: {path}",
"fs_handoff_from_ps5": "PS5가 {path}을(를) 보냈습니다",
"fs_handoff_open": "열기",
"cw_files_show_pc": "PC에서 보기",
"cw_files_show_pc_done": "PC로 보냈습니다. PS5Upload의 파일 시스템에서 열립니다.",
"cw_files_show_pc_done": "PC로 보냈습니다.",
"cw_files_show_pc_failed": "PC로 보낼 수 없습니다.",
"cw_files_from_pc": "PC에서 열었습니다.",
"cw_files_from_pc_gone": "PC가 {path}을(를) 보냈지만 더 이상 콘솔에 없습니다.",
+3 -1
View File
@@ -889,8 +889,10 @@ fs_recent_tooltip: "Ostatnie miejsca docelowe",
"fs_show_on_console_sent": "Wysłano do PS5 — otworzy się w PS5Upload Files",
"fs_show_on_console_failed": "Nie udało się wysłać do PS5: {error}",
"fs_opened_from_ps5": "Otwarto z twojej PS5: {path}",
"fs_handoff_from_ps5": "Twoja PS5 wysłała {path}",
"fs_handoff_open": "Otwórz",
"cw_files_show_pc": "Pokaż na PC",
"cw_files_show_pc_done": "Wysłano do twojego PC. Otworzy się w systemie plików PS5Upload.",
"cw_files_show_pc_done": "Wysłano do twojego PC.",
"cw_files_show_pc_failed": "Nie udało się wysłać do PC.",
"cw_files_from_pc": "Otwarto z twojego PC.",
"cw_files_from_pc_gone": "Twój PC wysłał {path}, ale tego nie ma już na konsoli.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "Destinos recentes",
"fs_show_on_console_sent": "Enviado ao PS5 — abre no PS5Upload Files",
"fs_show_on_console_failed": "Não foi possível enviar ao PS5: {error}",
"fs_opened_from_ps5": "Aberto pelo seu PS5: {path}",
"fs_handoff_from_ps5": "Seu PS5 enviou {path}",
"fs_handoff_open": "Abrir",
"cw_files_show_pc": "Mostrar no PC",
"cw_files_show_pc_done": "Enviado ao seu PC. Abre no sistema de arquivos do PS5Upload.",
"cw_files_show_pc_done": "Enviado ao seu PC.",
"cw_files_show_pc_failed": "Não foi possível enviar ao PC.",
"cw_files_from_pc": "Aberto pelo seu PC.",
"cw_files_from_pc_gone": "Seu PC enviou {path}, mas não está mais no console.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "Недавние места назначения",
"fs_show_on_console_sent": "Отправлено на PS5 — откроется в PS5Upload Files",
"fs_show_on_console_failed": "Не удалось отправить на PS5: {error}",
"fs_opened_from_ps5": "Открыто с вашей PS5: {path}",
"fs_handoff_from_ps5": "Ваша PS5 отправила {path}",
"fs_handoff_open": "Открыть",
"cw_files_show_pc": "Показать на ПК",
"cw_files_show_pc_done": "Отправлено на ваш ПК. Откроется в файловой системе PS5Upload.",
"cw_files_show_pc_done": "Отправлено на ваш ПК.",
"cw_files_show_pc_failed": "Не удалось отправить на ПК.",
"cw_files_from_pc": "Открыто с вашего ПК.",
"cw_files_from_pc_gone": "Ваш ПК отправил {path}, но этого больше нет на консоли.",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "ปลายทางล่าสุด",
"fs_show_on_console_sent": "ส่งไปยัง PS5 แล้ว — จะเปิดใน PS5Upload Files",
"fs_show_on_console_failed": "ไม่สามารถส่งไปยัง PS5: {error}",
"fs_opened_from_ps5": "เปิดจาก PS5 ของคุณ: {path}",
"fs_handoff_from_ps5": "PS5 ของคุณส่ง {path} มา",
"fs_handoff_open": "เปิด",
"cw_files_show_pc": "แสดงบน PC",
"cw_files_show_pc_done": "ส่งไปยัง PC ของคุณแล้ว จะเปิดในระบบไฟล์ของ PS5Upload",
"cw_files_show_pc_done": "ส่งไปยัง PC ของคุณแล้ว",
"cw_files_show_pc_failed": "ไม่สามารถส่งไปยัง PC ได้",
"cw_files_from_pc": "เปิดจาก PC ของคุณ",
"cw_files_from_pc_gone": "PC ของคุณส่ง {path} มา แต่ไม่มีอยู่บนเครื่องแล้ว",
+3 -1
View File
@@ -635,8 +635,10 @@ fs_recent_tooltip: "Son hedefler",
"fs_show_on_console_sent": "PS5'e gönderildi — PS5Upload Files'ta açılır",
"fs_show_on_console_failed": "PS5'e gönderilemedi: {error}",
"fs_opened_from_ps5": "PS5'inden açıldı: {path}",
"fs_handoff_from_ps5": "PS5'in {path} gönderdi",
"fs_handoff_open": "Aç",
"cw_files_show_pc": "PC'de göster",
"cw_files_show_pc_done": "PC'ne gönderildi. PS5Upload'ın Dosya Sistemi'nde açılır.",
"cw_files_show_pc_done": "PC'ne gönderildi.",
"cw_files_show_pc_failed": "PC'ye gönderilemedi.",
"cw_files_from_pc": "PC'nden açıldı.",
"cw_files_from_pc_gone": "PC'n {path} gönderdi, ancak artık konsolda değil.",
+3 -1
View File
@@ -637,8 +637,10 @@ fs_recent_tooltip: "Đích đến gần đây",
"fs_show_on_console_sent": "Đã gửi đến PS5 — sẽ mở trong PS5Upload Files",
"fs_show_on_console_failed": "Không thể gửi đến PS5: {error}",
"fs_opened_from_ps5": "Đã mở từ PS5 của bạn: {path}",
"fs_handoff_from_ps5": "PS5 của bạn đã gửi {path}",
"fs_handoff_open": "Mở",
"cw_files_show_pc": "Hiển thị trên PC",
"cw_files_show_pc_done": "Đã gửi đến PC của bạn. Sẽ mở trong Hệ thống tệp của PS5Upload.",
"cw_files_show_pc_done": "Đã gửi đến PC của bạn.",
"cw_files_show_pc_failed": "Không thể gửi đến PC.",
"cw_files_from_pc": "Đã mở từ PC của bạn.",
"cw_files_from_pc_gone": "PC của bạn đã gửi {path}, nhưng nó không còn trên máy nữa.",
+3 -1
View File
@@ -642,8 +642,10 @@ fs_recent_tooltip: "最近的目标位置",
"fs_show_on_console_sent": "已发送到 PS5 — 将在 PS5Upload Files 中打开",
"fs_show_on_console_failed": "无法发送到 PS5:{error}",
"fs_opened_from_ps5": "已从你的 PS5 打开:{path}",
"fs_handoff_from_ps5": "你的 PS5 发送了 {path}",
"fs_handoff_open": "打开",
"cw_files_show_pc": "在电脑上显示",
"cw_files_show_pc_done": "已发送到你的电脑,将在 PS5Upload 的文件系统中打开。",
"cw_files_show_pc_done": "已发送到你的电脑。",
"cw_files_show_pc_failed": "无法发送到电脑。",
"cw_files_from_pc": "已从你的电脑打开。",
"cw_files_from_pc_gone": "你的电脑发送了 {path},但它已不在主机上。",
+3 -1
View File
@@ -636,8 +636,10 @@ fs_recent_tooltip: "最近的目的地",
"fs_show_on_console_sent": "已傳送到 PS5 — 將在 PS5Upload Files 中開啟",
"fs_show_on_console_failed": "無法傳送到 PS5:{error}",
"fs_opened_from_ps5": "已從你的 PS5 開啟:{path}",
"fs_handoff_from_ps5": "你的 PS5 傳送了 {path}",
"fs_handoff_open": "開啟",
"cw_files_show_pc": "在電腦上顯示",
"cw_files_show_pc_done": "已傳送到你的電腦,將在 PS5Upload 的檔案系統中開啟。",
"cw_files_show_pc_done": "已傳送到你的電腦。",
"cw_files_show_pc_failed": "無法傳送到電腦。",
"cw_files_from_pc": "已從你的電腦開啟。",
"cw_files_from_pc_gone": "你的電腦傳送了 {path},但它已不在主機上。",
+35 -1
View File
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useFsNavStore, openInFileSystem } from "../state/fsNavigation";
import { createHandoffPoller, HANDOFF_POLL_FILES_MS } from "./useConsoleHandoff";
import { createHandoffPoller, deliverHandoff, HANDOFF_POLL_FILES_MS } from "./useConsoleHandoff";
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
@@ -50,6 +50,40 @@ describe("Show on PC: the handoff poll", () => {
p.stop();
});
it("skips its ticks while a transfer runs, then asks again", async () => {
let busy = true;
const take = vi.fn().mockResolvedValue({ supported: true, path: null, ageMs: 0 });
const p = createHandoffPoller({ take, delay: () => 1000, onPath: vi.fn(), skip: () => busy });
await vi.advanceTimersByTimeAsync(3000);
expect(take).not.toHaveBeenCalled();
busy = false;
await vi.advanceTimersByTimeAsync(1000);
expect(take).toHaveBeenCalledTimes(1);
p.stop();
});
it("on File System it opens at once; elsewhere a toast offers Open and nothing navigates", () => {
const tr = ((_k: string, _v?: unknown, fallback?: string) => fallback ?? _k) as never;
const navigate = vi.fn();
const toast = vi.fn();
deliverHandoff("/data/a", { onFiles: true, navigate, tr, toast });
expect(navigate).toHaveBeenCalledWith("/files");
expect(useFsNavStore.getState().consume()).toBe("/data/a");
expect(toast.mock.calls[0][0].action).toBeUndefined();
navigate.mockClear();
toast.mockClear();
deliverHandoff("/data/b", { onFiles: false, navigate, tr, toast });
expect(navigate).not.toHaveBeenCalled();
expect(useFsNavStore.getState().requestedPath).toBeNull();
const offer = toast.mock.calls[0][0];
expect(offer.message).toBe("Your PS5 sent /data/b");
expect(offer.action.label).toBe("Open");
offer.action.onClick();
expect(navigate).toHaveBeenCalledWith("/files");
expect(useFsNavStore.getState().consume()).toBe("/data/b");
});
it("the folder reaches File System as a one-shot request", () => {
const navigate = vi.fn();
openInFileSystem(navigate, "/mnt/usb0/games");
+41 -7
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
import { useLocation, useNavigate } from "react-router";
import { takeHandoffForPc, type HandoffTake } from "../api/consoleFiles";
import { transferScreenBusy } from "../lib/ps5Transfers";
import { useDocumentVisible } from "../lib/visibility";
import { useConnectionStore } from "../state/connection";
import { openInFileSystem } from "../state/fsNavigation";
@@ -11,6 +12,8 @@ import { useToast } from "../state/toasts";
/** How often the console is asked for a "Show on PC" handoff: often on File System, less elsewhere. */
export const HANDOFF_POLL_FILES_MS = 3_000;
export const HANDOFF_POLL_OTHER_MS = 10_000;
/** How long the "Open" offer stays up when a handoff arrives away from File System. */
export const HANDOFF_OFFER_MS = 15_000;
export interface HandoffPoller {
stop(): void;
@@ -26,12 +29,18 @@ export function createHandoffPoller(opts: {
delay: () => number;
onPath: (path: string) => void;
onUnsupported?: () => void;
/** True to skip this tick (a transfer runs: background management calls wait). */
skip?: () => boolean;
}): HandoffPoller {
let stopped = false;
let timer: ReturnType<typeof setTimeout> | undefined;
const tick = async () => {
timer = undefined;
if (stopped) return;
if (opts.skip?.()) {
timer = setTimeout(() => void tick(), opts.delay());
return;
}
try {
const r = await opts.take();
if (stopped) return;
@@ -55,10 +64,38 @@ export function createHandoffPoller(opts: {
};
}
/**
* A handoff arrived: on File System it opens at once with a toast; elsewhere the user is never
* pulled off the screen they are on, a toast offers "Open" instead.
*/
export function deliverHandoff(
path: string,
ctx: {
onFiles: boolean;
navigate: (to: string) => void;
tr: ReturnType<typeof useTr>;
toast: ReturnType<typeof useToast>["toast"];
},
): void {
const { navigate: go, tr: t, toast: show } = ctx;
if (ctx.onFiles) {
openInFileSystem(go, path);
show({ tone: "info", message: t("fs_opened_from_ps5", { path }, `Opened from your PS5: ${path}`) });
return;
}
show({
tone: "info",
duration: HANDOFF_OFFER_MS,
message: t("fs_handoff_from_ps5", { path }, `Your PS5 sent ${path}`),
action: { label: t("fs_handoff_open", undefined, "Open"), onClick: () => openInFileSystem(go, path) },
});
}
/**
* "Show on PC": the console's Files page left a folder for this app. While the helper is up and
* the window is visible, the handoff is taken (every 3 s on File System, every 10 s elsewhere)
* and File System opens it with a toast. An older helper is asked once per connection.
* the window is visible and no transfer runs on it, the handoff is taken (every 3 s on File System,
* every 10 s elsewhere). On File System it opens at once with a toast; elsewhere a toast offers
* "Open". An older helper is asked once per connection.
*/
export function useConsoleHandoff(): void {
const host = useConnectionStore((s) => s.host);
@@ -79,11 +116,8 @@ export function useConsoleHandoff(): void {
const p = createHandoffPoller({
take: () => takeHandoffForPc(host),
delay: () => (live.current.onFiles ? HANDOFF_POLL_FILES_MS : HANDOFF_POLL_OTHER_MS),
onPath: (path) => {
const { navigate: go, tr: t, toast: show } = live.current;
openInFileSystem(go, path);
show({ tone: "info", message: t("fs_opened_from_ps5", { path }, `Opened from your PS5: ${path}`) });
},
skip: () => transferScreenBusy(host),
onPath: (path) => deliverHandoff(path, live.current),
});
return () => p.stop();
}, [host, up, visible]);
+32
View File
@@ -933,6 +933,38 @@ static int root_in_use(const uint8_t id[16], const char *root) {
return busy;
}
typedef struct {
char (*roots)[AVA1_MAX_PATH + 1];
unsigned max, n;
} upload_roots_t;
/* Under the table lock and g_open_mu (ava1_data_upload_roots): kind and root are written by
* ava1_recv_open under g_open_mu, `attached` under the table lock, so both reads are stable. */
static void upload_root_each(ava1_job_t *j, void *ctx) {
static const uint8_t zero_owner[32];
upload_roots_t *r = ctx;
size_t len;
if (r->n >= r->max || j->kind != AVA1_JOB_UPLOAD || !j->attached) return; /* parked: nobody is sending */
if (memcmp(j->owner, zero_owner, 32) == 0) return; /* a start-time recovery placeholder */
if (__atomic_load_n(&j->finished, __ATOMIC_ACQUIRE) || __atomic_load_n(&j->stopping, __ATOMIC_ACQUIRE)) return;
len = strnlen(j->root, AVA1_MAX_PATH);
if (!len) return;
memcpy(r->roots[r->n], j->root, len);
r->roots[r->n][len] = '\0';
r->n++;
}
int ava1_data_upload_roots(char (*roots)[AVA1_MAX_PATH + 1], unsigned max) {
upload_roots_t r;
r.roots = roots;
r.max = max;
r.n = 0;
pthread_mutex_lock(&g_open_mu);
ava1_job_foreach(upload_root_each, &r);
pthread_mutex_unlock(&g_open_mu);
return (int)r.n;
}
/* Consumes `o`'s refusal flag under O.mu. The slot cannot be reused while its open runs
* (the drain that frees it follows open_now), so the flag always belongs to this open.
* 1 = a pipelined frame was not queued; `status`/`msg` carry why. */
+3
View File
@@ -73,6 +73,9 @@ void ava1_unswept_add(int64_t delta);
uint64_t ava1_unswept_total(void);
/* 0 once ava1_data_stop has begun: long background work (recovery) checks it and returns. */
int ava1_data_running(void);
/* The roots of up to `max` uploads a session is sending now (attached, not finished or stopping,
* not a recovery placeholder), read under the open lock: the console page's "Uploading" badge. */
int ava1_data_upload_roots(char (*roots)[AVA1_MAX_PATH + 1], unsigned max);
/* Housekeeping loop iterations since start (tests: a slow recovery must not stall the reaper). */
extern unsigned ava1_house_ticks;
+1 -25
View File
@@ -460,32 +460,8 @@ int ava1_jobs_list_all(const uint8_t me[32], uint8_t *out, size_t cap, size_t *o
return list_jobs(NULL, out, cap, out_len);
}
typedef struct {
char (*roots)[AVA1_MAX_PATH + 1];
unsigned max, n;
} roots_t;
static void root_one(ava1_job_t *j, void *ctx) {
roots_t *r = ctx;
size_t len;
if (r->n >= r->max || j->kind != AVA1_JOB_UPLOAD || __atomic_load_n(&j->finished, __ATOMIC_ACQUIRE)) return;
len = strnlen(j->root, AVA1_MAX_PATH);
if (!len) return;
memcpy(r->roots[r->n], j->root, len);
r->roots[r->n][len] = '\0';
r->n++;
}
int ava1_jobs_upload_roots(char (*roots)[AVA1_MAX_PATH + 1], unsigned max) {
roots_t r;
r.roots = roots;
r.max = max;
r.n = 0;
/* Under the table lock, without the job's: a root is written once, as the job opens, so a listing
* at that moment may read it half-written (bounded: the buffer is fixed and NUL-capped here). The
* cost is one wrong badge for one poll; nothing acts on these paths. */
ava1_job_foreach(root_one, &r);
return (int)r.n;
return ava1_data_upload_roots(roots, max);
}
typedef struct {
+4 -3
View File
@@ -55,9 +55,10 @@ int ava1_op_list_rpc(const uint8_t owner[32], uint8_t *out, size_t cap, size_t *
* downloads, copies, operations) as a JobListResult. `me` is the page's identity; the entries
* carry no owner, so the page marks its own with ava1_jobs_owner_is. */
int ava1_jobs_list_all(const uint8_t me[32], uint8_t *out, size_t cap, size_t *out_len);
/* The roots (destination paths, as requested) of up to `max` running uploads, any owner's, into
* roots[i] (each AVA1_MAX_PATH + 1 bytes, NUL-terminated). Returns how many. The console page shows
* an "Uploading" badge on those folders. */
/* The roots (destination paths, as requested) of up to `max` uploads a session is sending now
* (ava1_data_upload_roots: attached, not finished, parked or a recovery placeholder), into roots[i]
* (each AVA1_MAX_PATH + 1 bytes, NUL-terminated). Returns how many. The console page shows an
* "Uploading" badge on those folders. */
int ava1_jobs_upload_roots(char (*roots)[AVA1_MAX_PATH + 1], unsigned max);
/* 1 when job `id` is listed and `me` owns it, else 0 (an absent job included). */
int ava1_jobs_owner_is(const uint8_t id[16], const uint8_t me[32]);
+2 -1
View File
@@ -84,8 +84,9 @@ int files_handoff_take_json(int side, long long now_ms, char *out, size_t cap) {
char path[FILES_PREFS_PATH_MAX];
long long age = 0;
size_t at;
if (!files_handoff_take(side, now_ms, path, &age)) return answer(out, cap, "{}");
/* before the take: a buffer too small must not empty the slot and lose the handoff */
if (cap < FILES_HANDOFF_OUT_MAX) return -1;
if (!files_handoff_take(side, now_ms, path, &age)) return answer(out, cap, "{}");
at = (size_t)snprintf(out, cap, "{\"path\":\"");
at += webui_json_escape(out + at, cap - at, path, strlen(path)); /* fits: 6x the path + 64 */
snprintf(out + at, cap - at, "\",\"age_ms\":%lld}", age);
+5
View File
@@ -82,6 +82,11 @@ static void test_json(void) {
CHECK(files_handoff_put(FILES_HANDOFF_TO_CONSOLE, "/data/My \"Game\" \\ 游戏", 100) == 0);
n = files_handoff_take_json(FILES_HANDOFF_TO_CONSOLE, 350, out, sizeof out);
CHECK(n > 0 && strcmp(out, "{\"path\":\"/data/My \\\"Game\\\" \\\\ 游戏\",\"age_ms\":250}") == 0);
/* a buffer too small refuses before taking: the handoff is still there */
CHECK(files_handoff_put(FILES_HANDOFF_TO_CONSOLE, "/data/keep", 0) == 0);
CHECK(files_handoff_take_json(FILES_HANDOFF_TO_CONSOLE, 1, out, 16) == -1);
n = files_handoff_take_json(FILES_HANDOFF_TO_CONSOLE, 1, out, sizeof out);
CHECK(n > 0 && strstr(out, "/data/keep") != NULL);
/* through the method bodies */
n = files_mgmt_handoff_put(B("{\"to\":\"console\",\"path\":\"/mnt/usb0/x\"}"), 0, out, sizeof out);
CHECK(n > 0 && strcmp(out, "{\"ok\":true}") == 0);