feat: add concurrent fast transfer parsing

This commit is contained in:
ngfchl
2026-07-18 11:39:51 +08:00
parent a0acd6060d
commit 27aba268e2
2 changed files with 109 additions and 59 deletions
+60
View File
@@ -0,0 +1,60 @@
import 'dart:convert';
class FastTransferEntry {
final String path;
final int size;
final String? md5;
final String? gcid;
const FastTransferEntry({
required this.path,
required this.size,
this.md5,
this.gcid,
});
String get name => path.split('/').last;
String get directoryPath =>
path.contains('/') ? path.substring(0, path.lastIndexOf('/')) : '';
Map<String, dynamic> toJson() => {
'path': path,
'size': size,
if (md5 != null) 'etag': md5,
if (gcid != null) 'gcid': gcid,
};
}
List<FastTransferEntry> parseFastTransferJSON(String text) {
final root = jsonDecode(text);
final raw = root is Map ? root['files'] : root;
if (raw is! List) throw const FormatException('顶层需要包含 files 数组');
final entries = <FastTransferEntry>[];
for (var index = 0; index < raw.length; index++) {
if (raw[index] is! Map) throw FormatException('${index + 1} 项格式无效');
final value = Map<String, dynamic>.from(raw[index] as Map);
final path = (value['path'] ?? value['filePath'] ?? value['name'] ?? '')
.toString()
.replaceAll('\\', '/')
.replaceFirst(RegExp(r'^/+'), '');
final size = value['size'] is int
? value['size'] as int
: int.tryParse('${value['size'] ?? value['fileSize'] ?? ''}');
final md5 = (value['etag'] ?? value['eTag'] ?? value['md5'])
?.toString()
.toLowerCase();
final gcid = (value['gcid'] ?? value['gcId'])?.toString().toUpperCase();
if (path.isEmpty ||
path.split('/').contains('..') ||
size == null ||
size < 0 ||
((md5 == null || !RegExp(r'^[a-f0-9]{32}$').hasMatch(md5)) &&
(gcid == null || !RegExp(r'^[A-F0-9]{40}$').hasMatch(gcid)))) {
throw FormatException('${index + 1} 项缺少有效 path、size 或 MD5/GCID');
}
entries.add(
FastTransferEntry(path: path, size: size, md5: md5, gcid: gcid),
);
}
if (entries.isEmpty) throw const FormatException('files 不能为空');
return entries;
}
+49 -59
View File
@@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
@@ -7,6 +5,7 @@ import 'package:shadcn_ui/shadcn_ui.dart';
import '../core/storage/storage_manager.dart';
import '../models/cloud_file.dart';
import '../models/batch_rename.dart';
import '../models/fast_transfer.dart';
import '../models/media_library.dart';
import '../providers/auth_provider.dart';
import '../providers/file_provider.dart';
@@ -489,9 +488,11 @@ class _FastTransferToolState extends ConsumerState<_FastTransferTool> {
}
Future<void> _submit() async {
final entries = _parseEntries(_json.text);
if (entries.isEmpty) {
setState(() => _result = '未找到包含 name、size、gcid 的秒传条目。');
late final List<FastTransferEntry> entries;
try {
entries = parseFastTransferJSON(_json.text);
} catch (error) {
setState(() => _result = error.toString());
return;
}
setState(() {
@@ -499,21 +500,55 @@ class _FastTransferToolState extends ConsumerState<_FastTransferTool> {
_result = '';
});
var completed = 0;
var failed = 0;
var nextIndex = 0;
final api = ref.read(authProvider.notifier).api;
final parentID = ref.read(fileProvider).folderPath.lastOrNull?.id;
final concurrency =
(int.tryParse(
StorageManager.get<String>(
StorageKeys.fastTransferConcurrency,
) ??
'3',
) ??
3)
.clamp(1, 20);
try {
for (final entry in entries) {
await api.flashTransferGCIDToken(
name: entry.name,
fileSize: entry.size,
parentID: parentID,
gcid: entry.gcid,
);
completed += 1;
Future<void> worker() async {
while (nextIndex < entries.length) {
final entry = entries[nextIndex++];
try {
if (entry.md5 != null) {
await api.flashTransferToken(
name: entry.name,
fileSize: entry.size,
parentID: parentID,
md5: entry.md5!,
);
} else {
await api.flashTransferGCIDToken(
name: entry.name,
fileSize: entry.size,
parentID: parentID,
gcid: entry.gcid!,
);
}
completed += 1;
} catch (_) {
failed += 1;
}
if (mounted) {
setState(
() => _result = '已处理 ${completed + failed}/${entries.length}',
);
}
}
}
await Future.wait(List.generate(concurrency, (_) => worker()));
await ref.read(fileProvider.notifier).loadFiles();
if (mounted) {
setState(() => _result = '已提交 $completed / ${entries.length} 个秒传任务。');
setState(() => _result = '秒传完成:成功 $completed,失败 $failed');
}
} catch (error) {
if (mounted) {
@@ -524,43 +559,6 @@ class _FastTransferToolState extends ConsumerState<_FastTransferTool> {
}
}
List<_TransferEntry> _parseEntries(String text) {
try {
final value = jsonDecode(text);
final output = <_TransferEntry>[];
void visit(dynamic node) {
if (node is Map) {
final name = node['name']?.toString() ?? node['fileName']?.toString();
final gcid = node['gcid']?.toString() ?? node['gcId']?.toString();
final rawSize = node['size'] ?? node['fileSize'];
final size = rawSize is int
? rawSize
: int.tryParse(rawSize?.toString() ?? '');
if (name != null &&
name.isNotEmpty &&
gcid != null &&
gcid.isNotEmpty &&
size != null &&
size >= 0) {
output.add(_TransferEntry(name, gcid, size));
}
for (final child in node.values) {
visit(child);
}
} else if (node is List) {
for (final child in node) {
visit(child);
}
}
}
visit(value);
return output;
} catch (_) {
return const [];
}
}
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
@@ -606,14 +604,6 @@ class _FastTransferToolState extends ConsumerState<_FastTransferTool> {
}
}
class _TransferEntry {
final String name;
final String gcid;
final int size;
const _TransferEntry(this.name, this.gcid, this.size);
}
class _ToolSection extends StatelessWidget {
final String title;
final String description;