From 93c2a8b1f379d666f616fc5cc8b29664d7ee1b3d Mon Sep 17 00:00:00 2001 From: ngfchl Date: Sat, 18 Jul 2026 22:43:30 +0800 Subject: [PATCH] fix full cloud index pagination and unify proxy settings --- lib/core/http/dio_client.dart | 34 +++++++++---- lib/core/storage/storage_manager.dart | 16 +++++++ lib/pages/settings_page.dart | 58 ++++------------------- lib/providers/media_library_provider.dart | 23 +++++---- 4 files changed, 64 insertions(+), 67 deletions(-) diff --git a/lib/core/http/dio_client.dart b/lib/core/http/dio_client.dart index d501b58..5a995d1 100644 --- a/lib/core/http/dio_client.dart +++ b/lib/core/http/dio_client.dart @@ -4,6 +4,7 @@ import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import '../config/app_config.dart'; +import '../storage/storage_manager.dart'; import 'interceptors/app_log_interceptor.dart'; import 'interceptors/auth_interceptor.dart'; import 'interceptors/response_interceptor.dart'; @@ -40,6 +41,12 @@ class DioClient { } } + /// 设置变更后重建连接客户端,让全局 HTTP 代理立即生效。 + static void updateNetworkProxy() { + _configureHttpClient(dio); + _configureHttpClient(accountDio); + } + /// 重新设置 onLogout 回调(例如切换 provider 时) static void setOnLogout(OnLogout? onLogout) { if (_authInterceptor != null) { @@ -70,17 +77,26 @@ class DioClient { ), ); - // Windows 下优化连接池 - if (Platform.isWindows) { - (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { - final client = HttpClient(); - client.maxConnectionsPerHost = 10; - client.idleTimeout = const Duration(seconds: 60); - return client; - }; - } + _configureHttpClient(dio); dio.interceptors.addAll(interceptors); return dio; } + + static void _configureHttpClient(Dio dio) { + dio.httpClientAdapter.close(force: true); + final adapter = IOHttpClientAdapter(); + adapter.createHttpClient = () { + final client = HttpClient() + ..maxConnectionsPerHost = Platform.isWindows ? 10 : 6 + ..idleTimeout = const Duration(seconds: 60); + final host = StorageManager.networkProxyHost; + final port = StorageManager.networkProxyPort; + if (host.isNotEmpty && port.isNotEmpty) { + client.findProxy = (_) => 'PROXY $host:$port'; + } + return client; + }; + dio.httpClientAdapter = adapter; + } } diff --git a/lib/core/storage/storage_manager.dart b/lib/core/storage/storage_manager.dart index efffbec..f9e0832 100644 --- a/lib/core/storage/storage_manager.dart +++ b/lib/core/storage/storage_manager.dart @@ -52,6 +52,22 @@ class StorageManager { static T? get(String key) => _box?.get(key) as T?; + /// 全局网络代理。保留旧 TMDB 配置作为一次性迁移兼容。 + static String get networkProxyHost => + _nonEmptyString(StorageKeys.httpProxyHost) ?? + _nonEmptyString(StorageKeys.tmdbProxyHost) ?? + ''; + + static String get networkProxyPort => + _nonEmptyString(StorageKeys.httpProxyPort) ?? + _nonEmptyString(StorageKeys.tmdbProxyPort) ?? + ''; + + static String? _nonEmptyString(String key) { + final value = get(key)?.trim(); + return value == null || value.isEmpty ? null : value; + } + static Future set(String key, dynamic value) async { if (value == null) { await _instance.delete(key); diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 0478289..a81b224 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -4,6 +4,7 @@ import 'package:shadcn_ui/shadcn_ui.dart'; import '../providers/theme_provider.dart'; import '../providers/media_library_provider.dart'; import '../widgets/app_log_dialog.dart'; +import '../core/http/dio_client.dart'; import '../core/storage/storage_manager.dart'; class SettingsDialog extends ConsumerStatefulWidget { @@ -15,8 +16,6 @@ class SettingsDialog extends ConsumerStatefulWidget { class _SettingsDialogState extends ConsumerState { final _tmdbApiKeyController = TextEditingController(); - final _tmdbProxyHostController = TextEditingController(); - final _tmdbProxyPortController = TextEditingController(); final _tmdbImageProxyController = TextEditingController(); final _httpProxyHostController = TextEditingController(); final _httpProxyPortController = TextEditingController(); @@ -31,17 +30,11 @@ class _SettingsDialogState extends ConsumerState { super.initState(); _tmdbApiKeyController.text = StorageManager.get(StorageKeys.tmdbApiKey) ?? ''; - _tmdbProxyHostController.text = - StorageManager.get(StorageKeys.tmdbProxyHost) ?? ''; - _tmdbProxyPortController.text = - StorageManager.get(StorageKeys.tmdbProxyPort) ?? ''; _tmdbImageProxyController.text = StorageManager.get(StorageKeys.tmdbImageProxy) ?? 'https://wsrv.nl'; - _httpProxyHostController.text = - StorageManager.get(StorageKeys.httpProxyHost) ?? ''; - _httpProxyPortController.text = - StorageManager.get(StorageKeys.httpProxyPort) ?? ''; + _httpProxyHostController.text = StorageManager.networkProxyHost; + _httpProxyPortController.text = StorageManager.networkProxyPort; _scanConcurrencyController.text = StorageManager.get(StorageKeys.mediaScanConcurrency) ?? '3'; _transferConcurrencyController.text = @@ -58,8 +51,6 @@ class _SettingsDialogState extends ConsumerState { @override void dispose() { _tmdbApiKeyController.dispose(); - _tmdbProxyHostController.dispose(); - _tmdbProxyPortController.dispose(); _tmdbImageProxyController.dispose(); _httpProxyHostController.dispose(); _httpProxyPortController.dispose(); @@ -172,7 +163,7 @@ class _SettingsDialogState extends ConsumerState { const SizedBox(height: 12), _SettingsRow( icon: Icons.http_rounded, - label: 'HTTP 代理地址', + label: '网络代理地址', child: SizedBox( width: 200, child: ShadInput( @@ -183,7 +174,7 @@ class _SettingsDialogState extends ConsumerState { ), _SettingsRow( icon: Icons.numbers_rounded, - label: 'HTTP 代理端口', + label: '网络代理端口', child: SizedBox( width: 200, child: ShadInput( @@ -295,30 +286,6 @@ class _SettingsDialogState extends ConsumerState { ), ), ), - const SizedBox(height: 8), - _SettingsRow( - icon: Icons.dns_rounded, - label: '代理地址', - child: SizedBox( - width: 200, - child: ShadInput( - controller: _tmdbProxyHostController, - placeholder: const Text('例: 127.0.0.1'), - ), - ), - ), - const SizedBox(height: 8), - _SettingsRow( - icon: Icons.numbers_rounded, - label: '代理端口', - child: SizedBox( - width: 200, - child: ShadInput( - controller: _tmdbProxyPortController, - placeholder: const Text('例: 7890'), - ), - ), - ), _SettingsRow( icon: Icons.image_rounded, label: '图片加速代理', @@ -386,26 +353,21 @@ class _SettingsDialogState extends ConsumerState { StorageKeys.tmdbApiKey, _tmdbApiKeyController.text.trim(), ); - StorageManager.set( - StorageKeys.tmdbProxyHost, - _tmdbProxyHostController.text.trim(), - ); - StorageManager.set( - StorageKeys.tmdbProxyPort, - _tmdbProxyPortController.text.trim(), - ); StorageManager.set( StorageKeys.tmdbImageProxy, _tmdbImageProxyController.text.trim(), ); - StorageManager.set( + await StorageManager.set( StorageKeys.httpProxyHost, _httpProxyHostController.text.trim(), ); - StorageManager.set( + await StorageManager.set( StorageKeys.httpProxyPort, _httpProxyPortController.text.trim(), ); + await StorageManager.delete(StorageKeys.tmdbProxyHost); + await StorageManager.delete(StorageKeys.tmdbProxyPort); + DioClient.updateNetworkProxy(); StorageManager.set( StorageKeys.mediaScanConcurrency, _scanConcurrencyController.text.trim(), diff --git a/lib/providers/media_library_provider.dart b/lib/providers/media_library_provider.dart index 6107ae9..68428bb 100644 --- a/lib/providers/media_library_provider.dart +++ b/lib/providers/media_library_provider.dart @@ -1604,8 +1604,11 @@ class MediaLibraryNotifier extends StateNotifier { } Future> _allGlobalRemoteFilesByType({int? resType}) async { - const pageSize = 10000; + // get_file_list 会将任意更大的 pageSize 静默限制为 1000;若用请求值 + // 10000 判断末页,会在第一页被错误地视为已读完。 + const pageSize = 1000; final values = []; + final seenIDs = {}; for (var page = 0; !_cancelScan; page++) { final typeLabel = resType == 2 ? '目录' : '文件'; AppLogger.info('CloudIndex', '正在获取全盘$typeLabel索引,第 ${page + 1} 页'); @@ -1618,18 +1621,18 @@ class MediaLibraryNotifier extends StateNotifier { resType: resType, ); final batch = _extractFiles(response); - values.addAll(batch); + final added = batch.where((file) => seenIDs.add(file.id)).toList(); + values.addAll(added); AppLogger.info( 'CloudIndex', - '全盘$typeLabel索引第 ${page + 1} 页完成,获取 ${batch.length} 项,累计 ${values.length} 项', + '全盘$typeLabel索引第 ${page + 1} 页完成,获取 ${batch.length} 项,新增 ${added.length} 项,累计 ${values.length} 项', ); - final total = _findIntDeep(response, const [ - 'total', - 'totalCount', - 'count', - ]); - if (batch.length < pageSize || - (total != null && values.length >= total)) { + if (batch.length < pageSize) break; + if (added.isEmpty) { + AppLogger.warning( + 'CloudIndex', + '全盘$typeLabel索引第 ${page + 1} 页与上一页重复,已停止继续分页', + ); break; } }