fix full cloud index pagination and unify proxy settings
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,22 @@ class StorageManager {
|
||||
|
||||
static T? get<T>(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<String>(key)?.trim();
|
||||
return value == null || value.isEmpty ? null : value;
|
||||
}
|
||||
|
||||
static Future<void> set(String key, dynamic value) async {
|
||||
if (value == null) {
|
||||
await _instance.delete(key);
|
||||
|
||||
@@ -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<SettingsDialog> {
|
||||
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<SettingsDialog> {
|
||||
super.initState();
|
||||
_tmdbApiKeyController.text =
|
||||
StorageManager.get<String>(StorageKeys.tmdbApiKey) ?? '';
|
||||
_tmdbProxyHostController.text =
|
||||
StorageManager.get<String>(StorageKeys.tmdbProxyHost) ?? '';
|
||||
_tmdbProxyPortController.text =
|
||||
StorageManager.get<String>(StorageKeys.tmdbProxyPort) ?? '';
|
||||
_tmdbImageProxyController.text =
|
||||
StorageManager.get<String>(StorageKeys.tmdbImageProxy) ??
|
||||
'https://wsrv.nl';
|
||||
_httpProxyHostController.text =
|
||||
StorageManager.get<String>(StorageKeys.httpProxyHost) ?? '';
|
||||
_httpProxyPortController.text =
|
||||
StorageManager.get<String>(StorageKeys.httpProxyPort) ?? '';
|
||||
_httpProxyHostController.text = StorageManager.networkProxyHost;
|
||||
_httpProxyPortController.text = StorageManager.networkProxyPort;
|
||||
_scanConcurrencyController.text =
|
||||
StorageManager.get<String>(StorageKeys.mediaScanConcurrency) ?? '3';
|
||||
_transferConcurrencyController.text =
|
||||
@@ -58,8 +51,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||
@override
|
||||
void dispose() {
|
||||
_tmdbApiKeyController.dispose();
|
||||
_tmdbProxyHostController.dispose();
|
||||
_tmdbProxyPortController.dispose();
|
||||
_tmdbImageProxyController.dispose();
|
||||
_httpProxyHostController.dispose();
|
||||
_httpProxyPortController.dispose();
|
||||
@@ -172,7 +163,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||
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<SettingsDialog> {
|
||||
),
|
||||
_SettingsRow(
|
||||
icon: Icons.numbers_rounded,
|
||||
label: 'HTTP 代理端口',
|
||||
label: '网络代理端口',
|
||||
child: SizedBox(
|
||||
width: 200,
|
||||
child: ShadInput(
|
||||
@@ -295,30 +286,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||
),
|
||||
),
|
||||
),
|
||||
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<SettingsDialog> {
|
||||
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(),
|
||||
|
||||
@@ -1604,8 +1604,11 @@ class MediaLibraryNotifier extends StateNotifier<MediaLibraryState> {
|
||||
}
|
||||
|
||||
Future<List<CloudFile>> _allGlobalRemoteFilesByType({int? resType}) async {
|
||||
const pageSize = 10000;
|
||||
// get_file_list 会将任意更大的 pageSize 静默限制为 1000;若用请求值
|
||||
// 10000 判断末页,会在第一页被错误地视为已读完。
|
||||
const pageSize = 1000;
|
||||
final values = <CloudFile>[];
|
||||
final seenIDs = <String>{};
|
||||
for (var page = 0; !_cancelScan; page++) {
|
||||
final typeLabel = resType == 2 ? '目录' : '文件';
|
||||
AppLogger.info('CloudIndex', '正在获取全盘$typeLabel索引,第 ${page + 1} 页');
|
||||
@@ -1618,18 +1621,18 @@ class MediaLibraryNotifier extends StateNotifier<MediaLibraryState> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user