feat: auto extract archives (#1212)

This commit is contained in:
Matteo Pietro Dazzi authored and GitHub committed 2025-12-29 17:27:49 +08:00
1 parent 5515a1c48c
commit 0d5c4e8e7f
35 files changed
+950 -40

No files matched your search

@@ -10,6 +10,7 @@ class DownloaderConfig {
ExtraConfig extra = ExtraConfig();
ProxyConfig proxy = ProxyConfig();
WebhookConfig webhook = WebhookConfig();
ArchiveConfig archive = ArchiveConfig();
DownloaderConfig({
this.downloadDir = '',
@@ -226,3 +227,19 @@ class ExtraConfigGithubMirror {
Map<String, dynamic> toJson() => _$ExtraConfigGithubMirrorToJson(this);
}
@JsonSerializable()
class ArchiveConfig {
bool autoExtract;
bool deleteAfterExtract;
ArchiveConfig({
this.autoExtract = true,
this.deleteAfterExtract = true,
});
factory ArchiveConfig.fromJson(Map<String, dynamic>? json) =>
json == null ? ArchiveConfig() : _$ArchiveConfigFromJson(json);
Map<String, dynamic> toJson() => _$ArchiveConfigToJson(this);
}
@@ -16,7 +16,9 @@ DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json) =>
..extra = ExtraConfig.fromJson(json['extra'] as Map<String, dynamic>?)
..proxy = ProxyConfig.fromJson(json['proxy'] as Map<String, dynamic>)
..webhook =
WebhookConfig.fromJson(json['webhook'] as Map<String, dynamic>?);
WebhookConfig.fromJson(json['webhook'] as Map<String, dynamic>?)
..archive =
ArchiveConfig.fromJson(json['archive'] as Map<String, dynamic>?);
Map<String, dynamic> _$DownloaderConfigToJson(DownloaderConfig instance) =>
<String, dynamic>{
@@ -26,6 +28,7 @@ Map<String, dynamic> _$DownloaderConfigToJson(DownloaderConfig instance) =>
'extra': instance.extra.toJson(),
'proxy': instance.proxy.toJson(),
'webhook': instance.webhook.toJson(),
'archive': instance.archive.toJson(),
};
ProtocolConfig _$ProtocolConfigFromJson(Map<String, dynamic> json) =>
@@ -232,3 +235,15 @@ Map<String, dynamic> _$ExtraConfigGithubMirrorToJson(
'enabled': instance.enabled,
'mirrors': instance.mirrors.map((e) => e.toJson()).toList(),
};
ArchiveConfig _$ArchiveConfigFromJson(Map<String, dynamic> json) =>
ArchiveConfig(
autoExtract: json['autoExtract'] as bool? ?? true,
deleteAfterExtract: json['deleteAfterExtract'] as bool? ?? true,
);
Map<String, dynamic> _$ArchiveConfigToJson(ArchiveConfig instance) =>
<String, dynamic>{
'autoExtract': instance.autoExtract,
'deleteAfterExtract': instance.deleteAfterExtract,
};
+6
View File
@@ -26,10 +26,16 @@ class Options {
class OptsExtraHttp {
int connections;
bool autoTorrent;
bool autoExtract;
String archivePassword;
bool deleteAfterExtract;
OptsExtraHttp({
this.connections = 0,
this.autoTorrent = false,
this.autoExtract = false,
this.archivePassword = '',
this.deleteAfterExtract = false,
});
factory OptsExtraHttp.fromJson(Map<String, dynamic> json) =>
+6
View File
@@ -37,10 +37,16 @@ OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
OptsExtraHttp(
connections: (json['connections'] as num?)?.toInt() ?? 0,
autoTorrent: json['autoTorrent'] as bool? ?? false,
autoExtract: json['autoExtract'] as bool? ?? false,
archivePassword: json['archivePassword'] as String? ?? '',
deleteAfterExtract: json['deleteAfterExtract'] as bool? ?? false,
);
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
<String, dynamic>{
'connections': instance.connections,
'autoTorrent': instance.autoTorrent,
'autoExtract': instance.autoExtract,
'archivePassword': instance.archivePassword,
'deleteAfterExtract': instance.deleteAfterExtract,
};
@@ -23,7 +23,7 @@ class CreateController extends GetxController
@override
void onInit() {
super.onInit();
advancedTabController = TabController(length: 2, vsync: this);
advancedTabController = TabController(length: 3, vsync: this);
directDownload.value = Get.find<AppController>()
.downloaderConfig
.value
@@ -56,10 +56,13 @@ class CreateView extends GetView<CreateController> {
),
];
final _btTrackerController = TextEditingController();
final _archivePasswordController = TextEditingController();
final _availableSchemes = ["http:", "https:", "magnet:"];
final _skipVerifyCertController = false.obs;
final _autoExtractController = Rxn<bool>();
final _deleteAfterExtractController = Rxn<bool>();
CreateView({Key? key}) : super(key: key);
@@ -77,6 +80,15 @@ class CreateView extends GetView<CreateController> {
final downloadDir = appController.downloaderConfig.value.downloadDir;
_pathController.text = renderPathPlaceholders(downloadDir);
}
// Initialize archive settings from global config if not already set
if (_autoExtractController.value == null) {
_autoExtractController.value =
appController.downloaderConfig.value.archive.autoExtract;
}
if (_deleteAfterExtractController.value == null) {
_deleteAfterExtractController.value =
appController.downloaderConfig.value.archive.deleteAfterExtract;
}
final CreateTask? routerParams = Get.rootDelegate.arguments();
if (routerParams?.req?.url.isNotEmpty ?? false) {
@@ -497,17 +509,20 @@ class CreateView extends GetView<CreateController> {
const Divider(),
TabBar(
controller: controller.advancedTabController,
tabs: const [
Tab(
tabs: [
const Tab(
text: 'HTTP',
),
Tab(
const Tab(
text: 'BitTorrent',
),
Tab(
text: 'archives'.tr,
)
],
),
DefaultTabController(
length: 2,
length: 3,
child: ContentSizeTabBarView(
controller:
controller.advancedTabController,
@@ -602,6 +617,63 @@ class CreateView extends GetView<CreateController> {
hintText: 'addTrackerHit'.tr,
)),
],
),
Column(
children: [
Padding(
padding:
const EdgeInsets.only(top: 10),
child: CompactCheckbox(
label: 'autoExtract'.tr,
value: _autoExtractController
.value ?? false,
onChanged: (bool? value) {
_autoExtractController
.value = value ?? false;
},
textStyle: const TextStyle(
color: Colors.grey,
),
),
),
Obx(
() => Visibility(
visible: _autoExtractController.value ?? false,
child: Column(
children: [
Padding(
padding:
const EdgeInsets.only(top: 10),
child: TextFormField(
controller: _archivePasswordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'archivePassword'.tr,
hintText: 'archivePasswordHint'.tr,
),
),
),
Padding(
padding:
const EdgeInsets.only(top: 10),
child: CompactCheckbox(
label: 'deleteAfterExtract'.tr,
value: _deleteAfterExtractController
.value ?? false,
onChanged: (bool? value) {
_deleteAfterExtractController
.value = value ?? false;
},
textStyle: const TextStyle(
color: Colors.grey,
),
),
),
],
),
),
),
],
)
],
),
@@ -798,7 +870,10 @@ class CreateView extends GetView<CreateController> {
Object? parseReqOptsExtra() {
return OptsExtraHttp()
..connections = int.tryParse(_connectionsController.text) ?? 0
..autoTorrent = true;
..autoTorrent = true
..autoExtract = _autoExtractController.value ?? false
..archivePassword = _archivePasswordController.text
..deleteAfterExtract = _deleteAfterExtractController.value ?? false;
}
String _hitText() {
@@ -148,6 +148,46 @@ class SettingView extends GetView<SettingController> {
);
});
// Archive auto extract configuration
final buildAutoExtract = _buildConfigItem('autoExtract', () {
return appController.downloaderConfig.value.archive.autoExtract
? 'on'.tr
: 'off'.tr;
}, (Key key) {
return Container(
alignment: Alignment.centerLeft,
child: Switch(
value: appController.downloaderConfig.value.archive.autoExtract,
onChanged: (bool value) async {
appController.downloaderConfig.update((val) {
val!.archive.autoExtract = value;
});
await debounceSave();
},
),
);
});
// Archive delete after extract configuration
final buildDeleteAfterExtract = _buildConfigItem('deleteAfterExtract', () {
return appController.downloaderConfig.value.archive.deleteAfterExtract
? 'on'.tr
: 'off'.tr;
}, (Key key) {
return Container(
alignment: Alignment.centerLeft,
child: Switch(
value: appController.downloaderConfig.value.archive.deleteAfterExtract,
onChanged: (bool value) async {
appController.downloaderConfig.update((val) {
val!.archive.deleteAfterExtract = value;
});
await debounceSave();
},
),
);
});
// Download categories configuration
buildDownloadCategories() {
final categories = downloaderCfg.value.extra.downloadCategories
@@ -1315,6 +1355,14 @@ class SettingView extends GetView<SettingController> {
buildAutoStartup(),
]),
)),
Text('archives'.tr),
Card(
child: Column(
children: _addDivider([
buildAutoExtract(),
buildDeleteAfterExtract(),
]),
)),
const Text('HTTP'),
Card(
child: Column(
+5
View File
@@ -116,6 +116,11 @@ const deDE = {
'taskUrl': 'Aufgaben-URL',
'downloadPath': 'Download-Pfad',
'skipVerifyCert': 'Zertifikatsüberprüfung überspringen',
'archives': 'Archive',
'autoExtract': 'Archive automatisch entpacken',
'archivePassword': 'Archiv-Passwort',
'archivePasswordHint': 'Leer lassen, wenn nicht passwortgeschützt',
'deleteAfterExtract': 'Archiv nach Entpacken löschen',
'name': 'Name',
'size': 'Größe',
'unknown': 'Unbekannt',
+5
View File
@@ -124,6 +124,11 @@ const enUS = {
'taskUrl': 'Task URL',
'downloadPath': 'Download Path',
'skipVerifyCert': 'Skip Certificate Verification',
'archives': 'Archives',
'autoExtract': 'Auto Extract Archives',
'archivePassword': 'Archive Password',
'archivePasswordHint': 'Leave empty if not password protected',
'deleteAfterExtract': 'Delete Archive After Extraction',
'name': 'Name',
'size': 'Size',
'unknown': 'Unknown',
+5
View File
@@ -108,6 +108,11 @@ const esES = {
'taskUrl': 'URL de la Tarea',
'downloadPath': 'Ruta de Descarga',
'skipVerifyCert': 'Omitir Verificación de Certificado',
'archives': 'Archivos',
'autoExtract': 'Extraer archivos automáticamente',
'archivePassword': 'Contraseña del archivo',
'archivePasswordHint': 'Dejar vacío si no tiene contraseña',
'deleteAfterExtract': 'Eliminar archivo después de extraer',
'insertPlaceholder': 'Insertar marcador de posición',
'placeholderYear': 'Año actual',
'placeholderMonth': 'Mes actual (01-12)',
+5
View File
@@ -63,6 +63,11 @@ const faIR = {
'از تمامی افرادی که در ساخت و توسعه‌ی جامعه‌ی Gopeed مشارکت کرده‌اند، سپاسگزاری می‌کنیم!',
'browserExtension': 'افزونه مرورگر',
'skipVerifyCert': 'رد کردن تأیید گواهینامه',
'archives': 'آرشیوها',
'autoExtract': 'استخراج خودکار بایگانی‌ها',
'archivePassword': 'رمز عبور بایگانی',
'archivePasswordHint': 'اگر رمز ندارد خالی بگذارید',
'deleteAfterExtract': 'حذف آرشیو پس از استخراج',
'insertPlaceholder': 'درج نگهدارنده',
'placeholderYear': 'سال جاری',
'placeholderMonth': 'ماه جاری (01-12)',
+5
View File
@@ -100,6 +100,11 @@ const frFR = {
'browserExtension': 'Extension du navigateur',
'launchAtStartup': 'Lancer au démarrage',
'skipVerifyCert': 'Ignorer la vérification du certificat',
'archives': 'Archives',
'autoExtract': 'Extraire automatiquement les archives',
'archivePassword': 'Mot de passe de l\'archive',
'archivePasswordHint': 'Laisser vide si non protégé par mot de passe',
'deleteAfterExtract': "Supprimer l'archive après extraction",
'insertPlaceholder': 'Insérer un espace réservé',
'placeholderYear': 'Année en cours',
'placeholderMonth': 'Mois en cours (01-12)',
+5
View File
@@ -116,6 +116,11 @@ const huHU = {
'taskUrl': 'Feladat URL',
'downloadPath': 'Letöltési útvonal',
'skipVerifyCert': 'Tanúsítvány ellenőrzés átugrása',
'archives': 'Archívumok',
'autoExtract': 'Archívumok automatikus kicsomagolása',
'archivePassword': 'Archívum jelszó',
'archivePasswordHint': 'Hagyja üresen, ha nincs jelszóval védve',
'deleteAfterExtract': 'Archívum törlése kicsomagolás után',
'name': 'Név',
'size': 'Méret',
'unknown': 'Ismeretlen',
+5
View File
@@ -110,6 +110,11 @@ const idID = {
'taskUrl': 'URL Tugas',
'downloadPath': 'Direktori Unduhan',
'skipVerifyCert': 'Lupakan verifikasi sertifikat',
'archives': 'Arsip',
'autoExtract': 'Ekstrak Arsip Otomatis',
'archivePassword': 'Kata Sandi Arsip',
'archivePasswordHint': 'Kosongkan jika tidak dilindungi kata sandi',
'deleteAfterExtract': 'Hapus arsip setelah ekstraksi',
'insertPlaceholder': 'Sisipkan placeholder',
'placeholderYear': 'Tahun saat ini',
'placeholderMonth': 'Bulan saat ini (01-12)',
+5
View File
@@ -98,6 +98,11 @@ const itIT = {
'browserExtension': 'Estensione del browser',
'launchAtStartup': "Lancia all'avvio",
'skipVerifyCert': 'Salta la verifica del certificato',
'archives': 'Archivi',
'autoExtract': 'Estrai automaticamente gli archivi',
'archivePassword': 'Password archivio',
'archivePasswordHint': 'Lasciare vuoto se non protetto da password',
'deleteAfterExtract': "Elimina l'archivio dopo l'estrazione",
'insertPlaceholder': 'Inserisci segnaposto',
'placeholderYear': 'Anno corrente',
'placeholderMonth': 'Mese corrente (01-12)',
+5
View File
@@ -64,6 +64,11 @@ const jaJP = {
'thanksDesc': 'Gopeedコミュニティの建設に協力してくださったすべての貢献者の方々に感謝します!',
'browserExtension': 'ブラウザ拡張機能',
'skipVerifyCert': '証明書の検証をスキップ',
'archives': 'アーカイブ',
'autoExtract': 'アーカイブを自動展開',
'archivePassword': 'アーカイブパスワード',
'archivePasswordHint': 'パスワードなしの場合は空のままにしてください',
'deleteAfterExtract': '展開後にアーカイブを削除',
'insertPlaceholder': 'プレースホルダーを挿入',
'placeholderYear': '現在の年',
'placeholderMonth': '現在の月 (01-12)',
+5
View File
@@ -96,6 +96,11 @@ const plPL = {
'browserExtension': 'Wtyczka dla przeglądarki',
'launchAtStartup': 'Uruchom na starcie',
'skipVerifyCert': 'Pomiń weryfikację certyfikatu',
'archives': 'Archiwa',
'autoExtract': 'Automatycznie wyodrębnij archiwa',
'archivePassword': 'Hasło archiwum',
'archivePasswordHint': 'Pozostaw puste, jeśli nie ma hasła',
'deleteAfterExtract': 'Usuń archiwum po rozpakowaniu',
'insertPlaceholder': 'Wstaw placeholder',
'placeholderYear': 'Bieżący rok',
'placeholderMonth': 'Bieżący miesiąc (01-12)',
+5
View File
@@ -113,6 +113,11 @@ const ptBR = {
'taskUrl': 'Tarefa URL',
'downloadPath': 'Download Path',
'skipVerifyCert': 'Pular Verificação de Certificado',
'archives': 'Arquivos',
'autoExtract': 'Extrair arquivos automaticamente',
'archivePassword': 'Senha do arquivo',
'archivePasswordHint': 'Deixar vazio se não estiver protegido por senha',
'deleteAfterExtract': 'Excluir arquivo após extração',
'name': 'Nome',
'size': 'Tamanho',
'unknown': 'Desconhecido',
+5
View File
@@ -113,6 +113,11 @@ const ruRU = {
'taskUrl': 'URL задачи',
'downloadPath': 'Путь загрузки',
'skipVerifyCert': 'Пропустить проверку сертификата',
'archives': 'Архивы',
'autoExtract': 'Автоматически распаковать архивы',
'archivePassword': 'Пароль архива',
'archivePasswordHint': 'Оставить пустым, если архив не защищён паролем',
'deleteAfterExtract': 'Удалить архив после распаковки',
'name': 'Название',
'size': 'Размер',
'unknown': 'Неизвестно',
+5
View File
@@ -98,6 +98,11 @@ const taTA = {
'browserExtension': 'உலாவி நீட்டிப்பு',
'launchAtStartup': 'தொடக்கத்தின் போது இயங்கு',
'skipVerifyCert': 'திருத்தி சான்று சரிபார்க்கவும்',
'archives': 'காப்பகங்கள்',
'autoExtract': 'காப்பகங்களை தானாக பிரித்தெடு',
'archivePassword': 'காப்பக கடவுச்சொல்',
'archivePasswordHint': 'கடவுச்சொல் இல்லையென்றால் காலியாக விடவும்',
'deleteAfterExtract': 'பிரித்தெடுத்த பிறகு காப்பகத்தை நீக்கு',
'insertPlaceholder': 'இடங்காட்டி செருக',
'placeholderYear': 'தற்போதைய ஆண்டு',
'placeholderMonth': 'தற்போதைய மாதம் (01-12)',
+5
View File
@@ -114,6 +114,11 @@ const trTR = {
'taskUrl': 'Görev URL’si',
'downloadPath': 'İndirme Yolu',
'skipVerifyCert': 'Sertifika Doğrulamayı Atla',
'archives': 'Arşivler',
'autoExtract': 'Arşivleri otomatik çıkart',
'archivePassword': 'Arşiv şifresi',
'archivePasswordHint': 'Şifre yoksa boş bırakın',
'deleteAfterExtract': 'Çıkarmadan sonra arşivi sil',
'name': 'Ad',
'size': 'Boyut',
'unknown': 'Bilinmeyen',
+5
View File
@@ -113,6 +113,11 @@ const ukUA = {
'taskUrl': 'URL завдання',
'downloadPath': 'Шлях завантаження',
'skipVerifyCert': 'Пропустити перевірку сертифіката',
'archives': 'Архіви',
'autoExtract': 'Автоматично розпаковувати архіви',
'archivePassword': 'Пароль архіву',
'archivePasswordHint': 'Залишити порожнім, якщо архів не захищений паролем',
'deleteAfterExtract': 'Видалити архів після розпакування',
'name': 'Назва',
'size': 'Розмір',
'unknown': 'Невідомо',
+5
View File
@@ -91,6 +91,11 @@ const viVN = {
'Cảm ơn tất cả những người đóng góp đã giúp xây dựng và phát triển cộng đồng Gopeed!',
'browserExtension': 'Tiện ích mở rộng trình duyệt',
'skipVerifyCert': 'Bỏ qua xác thực chứng chỉ',
'archives': 'Tập tin nén',
'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',
'deleteAfterExtract': 'Xóa tập tin nén sau khi giải nén',
'insertPlaceholder': 'Chèn placeholder',
'placeholderYear': 'Năm hiện tại',
'placeholderMonth': 'Tháng hiện tại (01-12)',
+5
View File
@@ -122,6 +122,11 @@ const zhCN = {
'taskUrl': '任务链接',
'downloadPath': '下载路径',
'skipVerifyCert': '跳过证书验证',
'archives': '压缩包',
'autoExtract': '自动解压压缩包',
'archivePassword': '压缩包密码',
'archivePasswordHint': '如无密码请留空',
'deleteAfterExtract': '解压后删除压缩包',
'name': '名称',
'size': '大小',
'unknown': '未知',
+5
View File
@@ -110,6 +110,11 @@ const zhTW = {
'taskUrl': '任務連結',
'downloadPath': '下載路徑',
'skipVerifyCert': '跳過憑證驗證',
'archives': '壓縮檔',
'autoExtract': '自動解壓縮壓縮包',
'archivePassword': '壓縮包密碼',
'archivePasswordHint': '如無密碼請留空',
'deleteAfterExtract': '解壓後刪除壓縮檔',
'name': '名稱',
'size': '大小',
'unknown': '未知',