mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-09 01:50:30 +00:00
feat(protocol/http, mcpserver, download): add checksum verification, MCP speed tool, pause webhook, and CLI flags (#1542)
This commit is contained in:
1 parent
fa622a4063
commit
6f20790b17
41 files changed
+1852
-25
No files matched your search
@@ -17,6 +17,21 @@ class Options {
|
||||
Map<String, dynamic> toJson() => _$OptionsToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ChecksumOption {
|
||||
String algorithm;
|
||||
String expected;
|
||||
|
||||
ChecksumOption({
|
||||
this.algorithm = '',
|
||||
this.expected = '',
|
||||
});
|
||||
|
||||
factory ChecksumOption.fromJson(Map<String, dynamic> json) => _$ChecksumOptionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ChecksumOptionToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class OptsExtraHttp {
|
||||
int connections;
|
||||
@@ -25,6 +40,7 @@ class OptsExtraHttp {
|
||||
bool? autoExtract;
|
||||
String archivePassword;
|
||||
bool deleteAfterExtract;
|
||||
ChecksumOption? checksum;
|
||||
|
||||
OptsExtraHttp({
|
||||
this.connections = 0,
|
||||
@@ -33,6 +49,7 @@ class OptsExtraHttp {
|
||||
this.autoExtract,
|
||||
this.archivePassword = '',
|
||||
this.deleteAfterExtract = false,
|
||||
this.checksum,
|
||||
});
|
||||
|
||||
factory OptsExtraHttp.fromJson(Map<String, dynamic> json) => _$OptsExtraHttpFromJson(json);
|
||||
|
||||
@@ -22,6 +22,16 @@ Map<String, dynamic> _$OptionsToJson(Options instance) => <String, dynamic>{
|
||||
'extra': ?instance.extra,
|
||||
};
|
||||
|
||||
ChecksumOption _$ChecksumOptionFromJson(Map<String, dynamic> json) => ChecksumOption(
|
||||
algorithm: json['algorithm'] as String? ?? '',
|
||||
expected: json['expected'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ChecksumOptionToJson(ChecksumOption instance) => <String, dynamic>{
|
||||
'algorithm': instance.algorithm,
|
||||
'expected': instance.expected,
|
||||
};
|
||||
|
||||
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) => OptsExtraHttp(
|
||||
connections: (json['connections'] as num?)?.toInt() ?? 0,
|
||||
autoTorrent: json['autoTorrent'] as bool?,
|
||||
@@ -29,6 +39,9 @@ OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) => OptsExtraHtt
|
||||
autoExtract: json['autoExtract'] as bool?,
|
||||
archivePassword: json['archivePassword'] as String? ?? '',
|
||||
deleteAfterExtract: json['deleteAfterExtract'] as bool? ?? false,
|
||||
checksum: json['checksum'] == null
|
||||
? null
|
||||
: ChecksumOption.fromJson(json['checksum'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) => <String, dynamic>{
|
||||
@@ -38,4 +51,5 @@ Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) => <String, d
|
||||
'autoExtract': ?instance.autoExtract,
|
||||
'archivePassword': instance.archivePassword,
|
||||
'deleteAfterExtract': instance.deleteAfterExtract,
|
||||
'checksum': ?instance.checksum?.toJson(),
|
||||
};
|
||||
@@ -66,6 +66,7 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
final _httpHeadersController = AppHttpHeadersController(defaultNames: const ['User-Agent', 'Cookie', 'Referer']);
|
||||
final _trackersController = TextEditingController();
|
||||
final _archivePasswordController = TextEditingController();
|
||||
final _checksumHashController = TextEditingController();
|
||||
final _formScrollController = ScrollController();
|
||||
|
||||
String _httpMethod = 'GET';
|
||||
@@ -74,6 +75,7 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
Map<String, String>? _initialLabels;
|
||||
RequestProxyMode _proxyMode = RequestProxyMode.follow;
|
||||
String _proxyScheme = 'http';
|
||||
String _checksumAlgorithm = 'md5';
|
||||
bool _skipVerifyCert = false;
|
||||
bool? _autoTorrent;
|
||||
bool? _deleteTorrentAfterDownload;
|
||||
@@ -134,6 +136,7 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
_httpHeadersController.dispose();
|
||||
_trackersController.dispose();
|
||||
_archivePasswordController.dispose();
|
||||
_checksumHashController.dispose();
|
||||
_formScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -184,6 +187,10 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
if (!_programmaticUrlChange && _fileDataUri.isNotEmpty) {
|
||||
_fileDataUri = '';
|
||||
}
|
||||
if (_inputUrls().length > 1) {
|
||||
_checksumHashController.clear();
|
||||
_checksumAlgorithm = 'md5';
|
||||
}
|
||||
}
|
||||
_recognizeMagnetUri(urlText.trim());
|
||||
}
|
||||
@@ -193,6 +200,8 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
final req = task.req;
|
||||
final opts = task.opts;
|
||||
setState(() {
|
||||
_checksumAlgorithm = 'md5';
|
||||
_checksumHashController.clear();
|
||||
if (req?.url.isNotEmpty == true) {
|
||||
_fileDataUri = '';
|
||||
_renameController.clear();
|
||||
@@ -243,6 +252,12 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
_autoExtract = extra.autoExtract;
|
||||
_archivePasswordController.text = extra.archivePassword;
|
||||
_deleteAfterExtract = extra.deleteAfterExtract;
|
||||
if (extra.checksum != null) {
|
||||
if (extra.checksum!.algorithm.isNotEmpty) {
|
||||
_checksumAlgorithm = extra.checksum!.algorithm;
|
||||
}
|
||||
_checksumHashController.text = extra.checksum!.expected;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -668,6 +683,34 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
),
|
||||
),
|
||||
],
|
||||
if (_inputUrls().length <= 1) ...[
|
||||
const SizedBox(height: 14),
|
||||
AppFormPair(
|
||||
direction: stacked ? Axis.vertical : Axis.horizontal,
|
||||
firstLabel: context.l10n.checksumAlgorithm,
|
||||
secondLabel: context.l10n.checksumHash,
|
||||
firstFlex: 382,
|
||||
secondFlex: 618,
|
||||
first: AppChoiceSegmentedControl<String>(
|
||||
key: const ValueKey('create-task-checksum-algorithm'),
|
||||
showIcons: false,
|
||||
value: _checksumAlgorithm,
|
||||
buttonKeyPrefix: 'create-task-checksum-algorithm',
|
||||
alignment: WrapAlignment.start,
|
||||
options: const [
|
||||
AppChoiceOption(value: 'md5', label: 'MD5', icon: Icons.tag),
|
||||
AppChoiceOption(value: 'sha1', label: 'SHA-1', icon: Icons.tag),
|
||||
AppChoiceOption(value: 'sha256', label: 'SHA-256', icon: Icons.tag),
|
||||
],
|
||||
onChanged: (value) => setState(() => _checksumAlgorithm = value),
|
||||
),
|
||||
second: _WindowTextField(
|
||||
key: const ValueKey('create-task-checksum-expected'),
|
||||
controller: _checksumHashController,
|
||||
hintText: context.l10n.checksumExpectedHint,
|
||||
),
|
||||
),
|
||||
],
|
||||
] else ...[
|
||||
AppFormRow(
|
||||
direction: stacked ? Axis.vertical : Axis.horizontal,
|
||||
@@ -950,6 +993,25 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
}
|
||||
|
||||
bool _validateAdvancedOptions() {
|
||||
if (_protocolTab == 0 && _inputUrls().length <= 1) {
|
||||
final checksumExpected = _checksumHashController.text.trim();
|
||||
if (checksumExpected.isNotEmpty) {
|
||||
final expectedLen = switch (_checksumAlgorithm.toLowerCase()) {
|
||||
'md5' => 32,
|
||||
'sha1' || 'sha-1' => 40,
|
||||
'sha256' || 'sha-256' => 64,
|
||||
_ => 0,
|
||||
};
|
||||
if (expectedLen > 0 && checksumExpected.length != expectedLen) {
|
||||
_showToast(context.l10n.checksumInvalidLength(_checksumAlgorithm.toUpperCase(), expectedLen.toString()));
|
||||
return false;
|
||||
}
|
||||
if (!RegExp(r'^[0-9a-fA-F]+$').hasMatch(checksumExpected)) {
|
||||
_showToast(context.l10n.checksumInvalidHex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_proxyMode != RequestProxyMode.custom) {
|
||||
return true;
|
||||
}
|
||||
@@ -1022,8 +1084,15 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
return text.length == 40 && RegExp(r'^[0-9a-fA-F]+$').hasMatch(text);
|
||||
}
|
||||
|
||||
api_options.Options _buildOptions({String? name, List<int> selectFiles = const []}) {
|
||||
api_options.Options _buildOptions({String? name, List<int> selectFiles = const [], bool omitChecksum = false}) {
|
||||
final connections = int.tryParse(_connectionsController.text.trim()) ?? 0;
|
||||
final checksumExpected = _checksumHashController.text.trim();
|
||||
final checksum = (!omitChecksum && _inputUrls().length <= 1 && checksumExpected.isNotEmpty)
|
||||
? api_options.ChecksumOption(
|
||||
algorithm: _checksumAlgorithm,
|
||||
expected: checksumExpected,
|
||||
)
|
||||
: null;
|
||||
return api_options.Options(
|
||||
name: name ?? _renameController.text.trim(),
|
||||
path: _directoryController.text.trim(),
|
||||
@@ -1036,6 +1105,7 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
autoExtract: _autoExtract,
|
||||
archivePassword: _archivePasswordController.text,
|
||||
deleteAfterExtract: _deleteAfterExtract,
|
||||
checksum: checksum,
|
||||
).toJson(),
|
||||
);
|
||||
}
|
||||
@@ -1050,13 +1120,14 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
Future<void> _submitResolved(Request request, ResolveResult result, List<int> selectedIndexes) async {
|
||||
final selected = (selectedIndexes.isEmpty ? result.res.files.asMap().keys.toList() : selectedIndexes.toList())
|
||||
..sort();
|
||||
final isMultiFile = selected.length > 1;
|
||||
if (result.id.isNotEmpty) {
|
||||
await ref
|
||||
.read(gopeedServiceProvider)
|
||||
.createTask(
|
||||
CreateTask(
|
||||
rid: result.id,
|
||||
opts: _buildOptions(selectFiles: selected),
|
||||
opts: _buildOptions(selectFiles: selected, omitChecksum: isMultiFile),
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -1070,11 +1141,11 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
opts: api_options.Options(
|
||||
name: file.name,
|
||||
path: path.join(_directoryController.text.trim(), result.res.name, file.path),
|
||||
extra: _buildOptions().extra,
|
||||
extra: _buildOptions(omitChecksum: isMultiFile).extra,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
await ref.read(gopeedServiceProvider).createTaskBatch(CreateTaskBatch(reqs: reqs, opts: _buildOptions()));
|
||||
await ref.read(gopeedServiceProvider).createTaskBatch(CreateTaskBatch(reqs: reqs, opts: _buildOptions(omitChecksum: isMultiFile)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "فك الضغط تلقائياً",
|
||||
"archivePassword": "كلمة مرور الملف المضغوط",
|
||||
"archivePasswordHint": "اتركه فارغاً إذا لم يكن محمياً بكلمة مرور",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "حذف الملف المضغوط بعد فك الضغط",
|
||||
"extracting": "جاري فك الضغط",
|
||||
"extractDone": "تم فك الضغط بنجاح",
|
||||
|
||||
@@ -171,6 +171,11 @@
|
||||
"autoExtract": "Arxivləri Avtomatik Çıxar",
|
||||
"archivePassword": "Arxiv Parolu",
|
||||
"archivePasswordHint": "Parolla qorunmayıbsa boş buraxın",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Çıxarıldıqdan sonra arxivi sil",
|
||||
"extracting": "Çıxarılır",
|
||||
"extractDone": "Çıxarma tamamlandı",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Extreu els comprimits automàticament",
|
||||
"archivePassword": "Contrasenya del comprimit",
|
||||
"archivePasswordHint": "Deixeu-lo en blanc si no està protegit amb contrasenya",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Suprimeix el comprimit després de l'extracció",
|
||||
"extracting": "S'està extraient",
|
||||
"extractDone": "Extracció completada",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Archive automatisch entpacken",
|
||||
"archivePassword": "Archiv-Passwort",
|
||||
"archivePasswordHint": "Leer lassen, wenn nicht passwortgeschützt",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Archiv nach Entpacken löschen",
|
||||
"extracting": "Wird entpackt",
|
||||
"extractDone": "Entpacken abgeschlossen",
|
||||
|
||||
@@ -178,6 +178,11 @@
|
||||
"autoExtract": "Extract Archives Automatically",
|
||||
"archivePassword": "Archive Password",
|
||||
"archivePasswordHint": "Leave empty if not password protected",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Delete Archives After Extraction",
|
||||
"extracting": "Extracting",
|
||||
"extractDone": "Extraction completed",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Extraer archivos automáticamente",
|
||||
"archivePassword": "Contraseña del archivo",
|
||||
"archivePasswordHint": "Dejar vacío si no tiene contraseña",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Eliminar archivo después de extraer",
|
||||
"extracting": "Extrayendo",
|
||||
"extractDone": "Extracción completada",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "استخراج خودکار بایگانیها",
|
||||
"archivePassword": "رمز عبور بایگانی",
|
||||
"archivePasswordHint": "اگر رمز ندارد خالی بگذارید",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "حذف آرشیو پس از استخراج",
|
||||
"extracting": "در حال استخراج",
|
||||
"extractDone": "استخراج کامل شد",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Extraire automatiquement les archives",
|
||||
"archivePassword": "Mot de passe de l'archive",
|
||||
"archivePasswordHint": "Laisser vide si non protégé par mot de passe",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Supprimer l'archive après extraction",
|
||||
"extracting": "Extraction en cours",
|
||||
"extractDone": "Extraction terminée",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Archívumok automatikus kicsomagolása",
|
||||
"archivePassword": "Archívum jelszó",
|
||||
"archivePasswordHint": "Hagyja üresen, ha nincs jelszóval védve",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Archívum törlése kicsomagolás után",
|
||||
"extracting": "Kicsomagolás",
|
||||
"extractDone": "Kicsomagolás befejezve",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Ekstrak Arsip Otomatis",
|
||||
"archivePassword": "Kata Sandi Arsip",
|
||||
"archivePasswordHint": "Kosongkan jika tidak dilindungi kata sandi",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Hapus arsip setelah ekstraksi",
|
||||
"extracting": "Mengekstrak",
|
||||
"extractDone": "Ekstraksi selesai",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Estrai automaticamente gli archivi",
|
||||
"archivePassword": "Password archivio",
|
||||
"archivePasswordHint": "Lasciare vuoto se non protetto da password",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Elimina l'archivio dopo l'estrazione",
|
||||
"extracting": "Estrazione in corso",
|
||||
"extractDone": "Estrazione completata",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "アーカイブを自動展開",
|
||||
"archivePassword": "アーカイブパスワード",
|
||||
"archivePasswordHint": "パスワードなしの場合は空のままにしてください",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "展開後にアーカイブを削除",
|
||||
"extracting": "展開中",
|
||||
"extractDone": "展開完了",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "압축 파일 자동 압축 해제",
|
||||
"archivePassword": "압축 파일 비밀번호",
|
||||
"archivePasswordHint": "비밀번호가 없으면 비워 두세요",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "압축 해제 후 압축 파일 삭제",
|
||||
"extracting": "압축 해제 중",
|
||||
"extractDone": "압축 해제 완료",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Automatycznie wyodrębnij archiwa",
|
||||
"archivePassword": "Hasło archiwum",
|
||||
"archivePasswordHint": "Pozostaw puste, jeśli nie ma hasła",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Usuń archiwum po rozpakowaniu",
|
||||
"extracting": "Rozpakowywanie",
|
||||
"extractDone": "Rozpakowywanie zakończone",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Extrair arquivos automaticamente",
|
||||
"archivePassword": "Senha do arquivo",
|
||||
"archivePasswordHint": "Deixar vazio se não estiver protegido por senha",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Excluir arquivo após extração",
|
||||
"extracting": "Extraindo",
|
||||
"extractDone": "Extração concluída",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Автоматически распаковать архивы",
|
||||
"archivePassword": "Пароль архива",
|
||||
"archivePasswordHint": "Оставить пустым, если архив не защищён паролем",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Удалить архив после распаковки",
|
||||
"extracting": "Распаковка",
|
||||
"extractDone": "Распаковка завершена",
|
||||
|
||||
@@ -178,6 +178,11 @@
|
||||
"autoExtract": "Automaticky rozbaliť archívy",
|
||||
"archivePassword": "Heslo archívu",
|
||||
"archivePasswordHint": "Nechajte prázdne, ak nie je chránený heslom",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Po rozbalení odstrániť archívy",
|
||||
"extracting": "Rozbaľovanie",
|
||||
"extractDone": "Rozbalenie dokončené",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "காப்பகங்களை தானாக பிரித்தெடு",
|
||||
"archivePassword": "காப்பக கடவுச்சொல்",
|
||||
"archivePasswordHint": "கடவுச்சொல் இல்லையென்றால் காலியாக விடவும்",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "பிரித்தெடுத்த பிறகு காப்பகத்தை நீக்கு",
|
||||
"extracting": "பிரித்தெடுக்கிறது",
|
||||
"extractDone": "பிரித்தெடுத்தல் முடிந்தது",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Arşivleri Otomatik Çıkar",
|
||||
"archivePassword": "Arşiv Şifresi",
|
||||
"archivePasswordHint": "Şifre korumalı değilse boş bırakın",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Çıkardıktan sonra arşivi sil",
|
||||
"extracting": "Çıkarılıyor",
|
||||
"extractDone": "Çıkarma tamamlandı",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Автоматично розпаковувати архіви",
|
||||
"archivePassword": "Пароль архіву",
|
||||
"archivePasswordHint": "Залишити порожнім, якщо архів не захищений паролем",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Видалити архів після розпакування",
|
||||
"extracting": "Розпакування",
|
||||
"extractDone": "Розпакування завершено",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "Tự động giải nén tập tin nén",
|
||||
"archivePassword": "Mật khẩu tập tin nén",
|
||||
"archivePasswordHint": "Để trống nếu không có mật khẩu",
|
||||
"checksumAlgorithm": "Checksum Algorithm",
|
||||
"checksumHash": "Checksum Hash",
|
||||
"checksumExpectedHint": "Expected hash (optional)",
|
||||
"checksumInvalidLength": "Invalid checksum hash length for {algorithm}: expected {length} characters",
|
||||
"checksumInvalidHex": "Invalid checksum hash: expected hexadecimal characters (0-9, a-f)",
|
||||
"deleteAfterExtract": "Xóa tập tin nén sau khi giải nén",
|
||||
"extracting": "Đang giải nén",
|
||||
"extractDone": "Giải nén hoàn tất",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "自动解压压缩包",
|
||||
"archivePassword": "压缩包密码",
|
||||
"archivePasswordHint": "如无密码请留空",
|
||||
"checksumAlgorithm": "校验和算法",
|
||||
"checksumHash": "校验和",
|
||||
"checksumExpectedHint": "预期哈希值(可选)",
|
||||
"checksumInvalidLength": "{algorithm} 校验和哈希长度无效:应为 {length} 个字符",
|
||||
"checksumInvalidHex": "校验和哈希无效:应为十六进制字符(0-9,a-f)",
|
||||
"deleteAfterExtract": "解压后删除压缩包",
|
||||
"extracting": "正在解压",
|
||||
"extractDone": "解压完成",
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
"autoExtract": "自動解壓縮壓縮包",
|
||||
"archivePassword": "壓縮包密碼",
|
||||
"archivePasswordHint": "如無密碼請留空",
|
||||
"checksumAlgorithm": "校驗和演算法",
|
||||
"checksumHash": "校驗和",
|
||||
"checksumExpectedHint": "預期雜湊值(可選)",
|
||||
"checksumInvalidLength": "{algorithm} 校驗和雜湊長度無效:應為 {length} 個字元",
|
||||
"checksumInvalidHex": "校驗和雜湊無效:應為十六進位字元(0-9,a-f)",
|
||||
"deleteAfterExtract": "解壓後刪除壓縮檔",
|
||||
"extracting": "正在解壓",
|
||||
"extractDone": "解壓完成",
|
||||
|
||||
@@ -26,6 +26,7 @@ import 'package:gopeed/shared/theme/app_design_tokens.dart';
|
||||
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||
import 'package:gopeed/shared/widgets/app_loading_button.dart';
|
||||
import 'package:gopeed/shared/widgets/app_tooltip.dart';
|
||||
import 'package:gopeed/l10n/app_localizations.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||
|
||||
void main() {
|
||||
@@ -611,6 +612,355 @@ void main() {
|
||||
expect(find.byKey(const ValueKey('create-history-clear')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('create-history-close')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('checksum state is reset for each incoming task and not inherited (T1)', (tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
try {
|
||||
tester.view.physicalSize = const Size(700, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
CreateTask? submitted;
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => DownloaderConfig(downloadDir: '/downloads'))
|
||||
..bind(GopeedMethods.createTask, (task) {
|
||||
submitted = task;
|
||||
return 'task-id';
|
||||
})
|
||||
..bind(StorageMethods.saveCreateHistory, (_) => const RpcUnit());
|
||||
|
||||
final container = ProviderContainer(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry)))],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
final pending = container.read(pendingCreateTaskProvider.notifier);
|
||||
// Task A: with checksum
|
||||
pending.set(
|
||||
CreateTask(
|
||||
req: Request(url: 'https://example.com/taskA.zip'),
|
||||
opts: Options(
|
||||
extra: OptsExtraHttp(
|
||||
checksum: ChecksumOption(
|
||||
algorithm: 'sha256',
|
||||
expected: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
||||
),
|
||||
).toJson(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final router = GoRouter(
|
||||
initialLocation: '/create',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (_, _) => const SizedBox()),
|
||||
GoRoute(path: '/create', builder: (_, _) => const CreateTaskWindowPage()),
|
||||
],
|
||||
);
|
||||
addTearDown(router.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: shad.ShadcnApp.router(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
routerConfig: router,
|
||||
builder: (_, child) => AppComponentThemes(child: child!),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expand advanced
|
||||
await tester.tap(find.text('Advanced'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(_fieldText(tester, 'create-task-checksum-expected'), 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
||||
|
||||
// Task B form 1: null opts
|
||||
pending.set(CreateTask(req: Request(url: 'https://example.com/taskB1.zip')));
|
||||
router.go('/create');
|
||||
await tester.pumpAndSettle();
|
||||
expect(_fieldText(tester, 'create-task-checksum-expected'), '');
|
||||
|
||||
// Task B form 2: empty Options
|
||||
pending.set(CreateTask(req: Request(url: 'https://example.com/taskB2.zip'), opts: Options()));
|
||||
router.go('/create');
|
||||
await tester.pumpAndSettle();
|
||||
expect(_fieldText(tester, 'create-task-checksum-expected'), '');
|
||||
|
||||
// Task B form 3: HTTP extra with no checksum
|
||||
pending.set(
|
||||
CreateTask(
|
||||
req: Request(url: 'https://example.com/taskB3.zip'),
|
||||
opts: Options(extra: OptsExtraHttp(connections: 8).toJson()),
|
||||
),
|
||||
);
|
||||
router.go('/create');
|
||||
await tester.pumpAndSettle();
|
||||
expect(_fieldText(tester, 'create-task-checksum-expected'), '');
|
||||
|
||||
// Submit task B3 and ensure no checksum is sent
|
||||
await tester.tap(find.byKey(const ValueKey('create-task-direct-download-toggle')));
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(submitted?.opts?.extra, isNotNull);
|
||||
final extra = OptsExtraHttp.fromJson(Map<String, dynamic>.from(submitted!.opts!.extra! as Map));
|
||||
expect(extra.checksum, isNull);
|
||||
} finally {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('checksum input validation blocks submission on invalid input (T3-UI)', (tester) async {
|
||||
tester.view.physicalSize = const Size(1024, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
CreateTask? submitted;
|
||||
final config = DownloaderConfig(downloadDir: '/downloads')..extra.defaultDirectDownload = true;
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => config)
|
||||
..bind(GopeedMethods.createTask, (task) {
|
||||
submitted = task;
|
||||
return 'task-id';
|
||||
})
|
||||
..bind(StorageMethods.saveCreateHistory, (_) => const RpcUnit());
|
||||
|
||||
final container = ProviderContainer(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry)))],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
final pending = container.read(pendingCreateTaskProvider.notifier);
|
||||
pending.set(CreateTask(req: Request(url: 'https://example.com/file.zip')));
|
||||
|
||||
final router = GoRouter(
|
||||
initialLocation: '/create',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (_, _) => const SizedBox()),
|
||||
GoRoute(path: '/create', builder: (_, _) => const CreateTaskWindowPage()),
|
||||
],
|
||||
);
|
||||
addTearDown(router.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: shad.ShadcnApp.router(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
routerConfig: router,
|
||||
builder: (_, child) => AppComponentThemes(child: child!),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expand advanced options
|
||||
await tester.tap(find.text('Advanced'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test 1: "abc" for SHA-256
|
||||
await tester.tap(find.text('SHA-256'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), 'abc');
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNull);
|
||||
|
||||
// Test 2: 64 x "g" (non-hex) for SHA-256
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), 'g' * 64);
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNull);
|
||||
|
||||
// Test 3: 63 x "a" (invalid length) for SHA-256
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), 'a' * 63);
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNull);
|
||||
|
||||
// Test 4: 65 x "a" (invalid length) for SHA-256
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), 'a' * 65);
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNull);
|
||||
|
||||
// Test 5: MD5 with sha256-length hash
|
||||
await tester.tap(find.text('MD5'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), 'a' * 64);
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNull);
|
||||
|
||||
// Test 6: Valid MD5 in UPPER case
|
||||
submitted = null;
|
||||
await tester.tap(find.text('MD5'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), '0123456789ABCDEF0123456789ABCDEF');
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNotNull);
|
||||
var extra = OptsExtraHttp.fromJson(Map<String, dynamic>.from(submitted!.opts!.extra! as Map));
|
||||
expect(extra.checksum?.algorithm, 'md5');
|
||||
expect(extra.checksum?.expected, '0123456789ABCDEF0123456789ABCDEF');
|
||||
|
||||
// Test 7: Valid SHA-1 in lower case
|
||||
submitted = null;
|
||||
pending.set(CreateTask(req: Request(url: 'https://example.com/file2.zip')));
|
||||
router.go('/create');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Advanced'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('SHA-1'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), 'da39a3ee5e6b4b0d3255bfef95601890afd80709');
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNotNull);
|
||||
extra = OptsExtraHttp.fromJson(Map<String, dynamic>.from(submitted!.opts!.extra! as Map));
|
||||
expect(extra.checksum?.algorithm, 'sha1');
|
||||
expect(extra.checksum?.expected, 'da39a3ee5e6b4b0d3255bfef95601890afd80709');
|
||||
|
||||
// Test 8: Valid SHA-256 in lower case
|
||||
submitted = null;
|
||||
pending.set(CreateTask(req: Request(url: 'https://example.com/file3.zip')));
|
||||
router.go('/create');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Advanced'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('SHA-256'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNotNull);
|
||||
extra = OptsExtraHttp.fromJson(Map<String, dynamic>.from(submitted!.opts!.extra! as Map));
|
||||
expect(extra.checksum?.algorithm, 'sha256');
|
||||
expect(extra.checksum?.expected, 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
||||
|
||||
// Test 9: Omitted checksum (empty hash)
|
||||
submitted = null;
|
||||
pending.set(CreateTask(req: Request(url: 'https://example.com/file4.zip')));
|
||||
router.go('/create');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Advanced'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-checksum-expected')), ' ');
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted, isNotNull);
|
||||
extra = OptsExtraHttp.fromJson(Map<String, dynamic>.from(submitted!.opts!.extra! as Map));
|
||||
expect(extra.checksum, isNull);
|
||||
});
|
||||
|
||||
testWidgets('multi-URL and multi-file batch creation omits checksum (T4)', (tester) async {
|
||||
tester.view.physicalSize = const Size(1024, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
final createdTasks = <CreateTask>[];
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => DownloaderConfig(downloadDir: '/downloads'))
|
||||
..bind(GopeedMethods.createTask, (task) {
|
||||
createdTasks.add(task);
|
||||
return 'task-id';
|
||||
})
|
||||
..bind(StorageMethods.saveCreateHistory, (_) => const RpcUnit());
|
||||
|
||||
final container = ProviderContainer(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry)))],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
final router = GoRouter(
|
||||
initialLocation: '/create',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (_, _) => const SizedBox()),
|
||||
GoRoute(path: '/create', builder: (_, _) => const CreateTaskWindowPage()),
|
||||
],
|
||||
);
|
||||
addTearDown(router.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: shad.ShadcnApp.router(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
routerConfig: router,
|
||||
builder: (_, child) => AppComponentThemes(child: child!),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Enter 2 URLs
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('create-task-url-input')),
|
||||
'https://example.com/file1.zip\nhttps://example.com/file2.zip',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Advanced options should hide the checksum field
|
||||
await tester.tap(find.text('Advanced'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('create-task-checksum-expected')), findsNothing);
|
||||
|
||||
// Confirm creation
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(createdTasks.length, 2);
|
||||
for (final t in createdTasks) {
|
||||
final extra = OptsExtraHttp.fromJson(Map<String, dynamic>.from(t.opts!.extra! as Map));
|
||||
expect(extra.checksum, isNull);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('checksum UI uses localized labels (T5)', (tester) async {
|
||||
tester.view.physicalSize = const Size(700, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => DownloaderConfig(downloadDir: '/downloads'));
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry)))],
|
||||
child: shad.ShadcnApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
home: AppComponentThemes(
|
||||
child: CreateTaskWindowPage(
|
||||
initialTask: CreateTask(req: Request(url: 'https://example.com/file.zip')),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Advanced'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Checksum Algorithm'), findsOneWidget);
|
||||
expect(find.text('Checksum Hash'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
String _fieldText(WidgetTester tester, String key) {
|
||||
|
||||
Reference in new issue
Block a user