From 40a74ce393902087014671643bc02b4360aa745c Mon Sep 17 00:00:00 2001 From: Nash Aschenbath Date: Fri, 13 Nov 2026 10:02:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6=E5=9B=9E=E9=80=80=E5=8F=8A=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/services/download_service.dart | 37 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/services/download_service.dart b/lib/services/download_service.dart index 0dab022..8e9f436 100644 --- a/lib/services/download_service.dart +++ b/lib/services/download_service.dart @@ -32,6 +32,12 @@ class DownloadStopped implements Exception { const DownloadStopped(); } +/// Carries only the locally selected message from a failed permission preview. +class _DownloadUnavailable implements Exception { + const _DownloadUnavailable(this.message); + final String message; +} + /// Cooperative cancellation also interrupts cooldowns. A backend must checkpoint /// before opening a request and between chunks, and close its response on stop. class DownloadControl { @@ -1138,9 +1144,10 @@ class DownloadService { _automaticGuards[task.id] = allowed; } } - _tasks - ..clear() - ..addAll(next); + // Other workers can report progress while this snapshot is being saved. + for (final task in admitted) { + _tasks[task.id] = task; + } if (album != null) _albums[album.id] = album; _suspended = false; _notify(); @@ -1869,7 +1876,7 @@ class DownloadService { final prepared = batch.items.single; await control.checkpoint(); if (prepared.error != null) { - throw XmlyException('download_unavailable', prepared.error!); + throw _DownloadUnavailable(prepared.error!); } final settings = Map.from(current.settings) ..remove('billingPreviewPending'); @@ -1928,20 +1935,23 @@ class DownloadService { if (claimed != null && claimed > 0) { await _updates.synchronized(() async { final next = Map.from(_tasks); + final balances = {}; for (final item in next.values.toList()) { if (item.accountIdentity == current.accountIdentity && item.expectedBalance != null && item.state != DownloadState.completed && !_hasDebit(item.accountIdentity, item.id, item.track.id)) { + balances[item.id] = item.expectedBalance! + claimed; next[item.id] = item.copyWith( - expectedBalance: item.expectedBalance! + claimed, + expectedBalance: balances[item.id], ); } } await _save(next); - _tasks - ..clear() - ..addAll(next); + for (final entry in balances.entries) { + _tasks[entry.key] = task(entry.key) + .copyWith(expectedBalance: entry.value); + } }); expectedBalance = task(id).expectedBalance; } @@ -2189,7 +2199,18 @@ class DownloadService { ), ); static String _message(Object error) { + if (error is _DownloadUnavailable) return error.message; // Never persist third-party messages (they can contain signed URLs/secrets). + if (error is AccountException) { + switch (error.code) { + case 'missing': + return '原账号未登录,请重新登录原账号后重试'; + case 'credential_missing': + return '原账号安全凭据缺失,请重新导入原账号后重试'; + case 'paused': + return '账号正在切换,请稍后重试'; + } + } if (error is XmlyException) { switch (error.code) { case 'original_account_required':