diff --git a/pkg/base/model.go b/pkg/base/model.go index 62ec8d50..5b64570a 100644 --- a/pkg/base/model.go +++ b/pkg/base/model.go @@ -178,17 +178,15 @@ type CreateTaskBatchItem struct { type DownloaderStoreConfig struct { FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store - DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files - MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count - ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol - Extra map[string]any `json:"extra"` - Proxy *DownloaderProxyConfig `json:"proxy"` - Webhook *WebhookConfig `json:"webhook"` // Webhook is the webhook configuration - Archive *ArchiveConfig `json:"archive"` // Archive is the archive extraction configuration - - // New fields added for auto-management - AutoDeleteTorrents bool `json:"autoDeleteTorrents"` // AutoDeleteTorrents enables automatic deletion of .torrent files after download - AutoCleanMissingFiles bool `json:"autoCleanMissingFiles"` // AutoCleanMissingFiles enables automatic cleanup of tasks with missing files + DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files + MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count + ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol + Extra map[string]any `json:"extra"` + Proxy *DownloaderProxyConfig `json:"proxy"` + Webhook *WebhookConfig `json:"webhook"` // Webhook is the webhook configuration + AutoTorrent *AutoTorrentConfig `json:"autoTorrent"` // AutoTorrent is the auto torrent task creation configuration + Archive *ArchiveConfig `json:"archive"` // Archive is the archive extraction configuration + AutoDeleteMissingFileTasks bool `json:"autoDeleteMissingFileTasks"` // AutoDeleteMissingFileTasks enables automatic deletion of tasks with missing files } func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig { @@ -204,6 +202,12 @@ func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig { if cfg.Webhook == nil { cfg.Webhook = &WebhookConfig{} } + if cfg.AutoTorrent == nil { + cfg.AutoTorrent = &AutoTorrentConfig{ + Enable: false, + DeleteAfterDownload: false, + } + } if cfg.Archive == nil { cfg.Archive = &ArchiveConfig{ AutoExtract: false, @@ -235,6 +239,9 @@ func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConfig) *Downl if cfg.Webhook == nil { cfg.Webhook = beforeCfg.Webhook } + if cfg.AutoTorrent == nil { + cfg.AutoTorrent = beforeCfg.AutoTorrent + } if cfg.Archive == nil { cfg.Archive = beforeCfg.Archive } @@ -247,6 +254,12 @@ type WebhookConfig struct { URLs []string `json:"urls"` // URLs is the list of webhook URLs } +// AutoTorrentConfig is the auto torrent task creation configuration +type AutoTorrentConfig struct { + Enable bool `json:"enable"` // Enable enables automatic BT task creation when downloading .torrent files + DeleteAfterDownload bool `json:"deleteAfterDownload"` // DeleteAfterDownload deletes the .torrent file after BT task creation +} + // ArchiveConfig is the archive extraction configuration type ArchiveConfig struct { AutoExtract bool `json:"autoExtract"` // AutoExtract enables automatic extraction of archives after download diff --git a/pkg/base/model_test.go b/pkg/base/model_test.go index 5fff4dbf..2cac7e27 100644 --- a/pkg/base/model_test.go +++ b/pkg/base/model_test.go @@ -19,6 +19,10 @@ func TestDownloaderStoreConfig_Init(t *testing.T) { ProtocolConfig: map[string]any{}, Proxy: &DownloaderProxyConfig{}, Webhook: &WebhookConfig{}, + AutoTorrent: &AutoTorrentConfig{ + Enable: false, + DeleteAfterDownload: false, + }, Archive: &ArchiveConfig{ AutoExtract: false, DeleteAfterExtract: false, @@ -35,6 +39,10 @@ func TestDownloaderStoreConfig_Init(t *testing.T) { ProtocolConfig: map[string]any{}, Proxy: &DownloaderProxyConfig{}, Webhook: &WebhookConfig{}, + AutoTorrent: &AutoTorrentConfig{ + Enable: false, + DeleteAfterDownload: false, + }, Archive: &ArchiveConfig{ AutoExtract: false, DeleteAfterExtract: false, @@ -55,6 +63,10 @@ func TestDownloaderStoreConfig_Init(t *testing.T) { }, Proxy: &DownloaderProxyConfig{}, Webhook: &WebhookConfig{}, + AutoTorrent: &AutoTorrentConfig{ + Enable: false, + DeleteAfterDownload: false, + }, Archive: &ArchiveConfig{ AutoExtract: false, DeleteAfterExtract: false, @@ -75,6 +87,33 @@ func TestDownloaderStoreConfig_Init(t *testing.T) { Enable: true, }, Webhook: &WebhookConfig{}, + AutoTorrent: &AutoTorrentConfig{ + Enable: false, + DeleteAfterDownload: false, + }, + Archive: &ArchiveConfig{ + AutoExtract: false, + DeleteAfterExtract: false, + }, + }, + }, + { + "Init AutoTorrent", + &DownloaderStoreConfig{ + AutoTorrent: &AutoTorrentConfig{ + Enable: true, + DeleteAfterDownload: true, + }, + }, + &DownloaderStoreConfig{ + MaxRunning: 5, + ProtocolConfig: map[string]any{}, + Proxy: &DownloaderProxyConfig{}, + Webhook: &WebhookConfig{}, + AutoTorrent: &AutoTorrentConfig{ + Enable: true, + DeleteAfterDownload: true, + }, Archive: &ArchiveConfig{ AutoExtract: false, DeleteAfterExtract: false, @@ -94,6 +133,10 @@ func TestDownloaderStoreConfig_Init(t *testing.T) { ProtocolConfig: map[string]any{}, Proxy: &DownloaderProxyConfig{}, Webhook: &WebhookConfig{}, + AutoTorrent: &AutoTorrentConfig{ + Enable: false, + DeleteAfterDownload: false, + }, Archive: &ArchiveConfig{ AutoExtract: true, DeleteAfterExtract: false, @@ -111,6 +154,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) { Extra: tt.fields.Extra, Proxy: tt.fields.Proxy, Webhook: tt.fields.Webhook, + AutoTorrent: tt.fields.AutoTorrent, Archive: tt.fields.Archive, } if got := cfg.Init(); !reflect.DeepEqual(got, tt.want) { @@ -286,6 +330,45 @@ func TestDownloaderStoreConfig_Merge(t *testing.T) { }, }, }, + { + "Merge AutoTorrent No Override", + &DownloaderStoreConfig{ + AutoTorrent: &AutoTorrentConfig{ + Enable: true, + }, + }, + args{ + beforeCfg: &DownloaderStoreConfig{ + AutoTorrent: &AutoTorrentConfig{ + Enable: false, + DeleteAfterDownload: false, + }, + }, + }, + &DownloaderStoreConfig{ + AutoTorrent: &AutoTorrentConfig{ + Enable: true, + }, + }, + }, + { + "Merge AutoTorrent Override", + &DownloaderStoreConfig{}, + args{ + beforeCfg: &DownloaderStoreConfig{ + AutoTorrent: &AutoTorrentConfig{ + Enable: true, + DeleteAfterDownload: true, + }, + }, + }, + &DownloaderStoreConfig{ + AutoTorrent: &AutoTorrentConfig{ + Enable: true, + DeleteAfterDownload: true, + }, + }, + }, { "Merge Archive No Override", &DownloaderStoreConfig{ @@ -336,6 +419,7 @@ func TestDownloaderStoreConfig_Merge(t *testing.T) { Extra: tt.fields.Extra, Proxy: tt.fields.Proxy, Webhook: tt.fields.Webhook, + AutoTorrent: tt.fields.AutoTorrent, Archive: tt.fields.Archive, } if got := cfg.Merge(tt.args.beforeCfg); !reflect.DeepEqual(got, tt.want) { diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 03114767..7240f560 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -283,7 +283,7 @@ func (d *Downloader) cleanupNonExistingTasks() { } // If the feature is disabled, do nothing - if !cfg.AutoCleanMissingFiles { + if !cfg.AutoDeleteMissingFileTasks { return } @@ -957,10 +957,25 @@ func (d *Downloader) watch(task *Task) { if e, ok := task.Meta.Opts.Extra.(*http.OptsExtra); ok { downloadFilePath := task.Meta.SingleFilepath() - if e.AutoTorrent && strings.HasSuffix(downloadFilePath, ".torrent") { - cfg, err := d.GetConfig() - shouldDelete := err == nil && cfg.AutoDeleteTorrents + cfg, _ := d.GetConfig() + + // Determine if auto-torrent is enabled (use global config if not explicitly set) + autoTorrentEnabled := false + if e.AutoTorrent != nil { + autoTorrentEnabled = *e.AutoTorrent + } else if cfg != nil && cfg.AutoTorrent != nil { + autoTorrentEnabled = cfg.AutoTorrent.Enable + } + + if autoTorrentEnabled && strings.HasSuffix(downloadFilePath, ".torrent") { + // Determine if should delete torrent file after creating BT task + shouldDelete := false + if e.DeleteTorrentAfterDownload != nil { + shouldDelete = *e.DeleteTorrentAfterDownload + } else if cfg != nil && cfg.AutoTorrent != nil { + shouldDelete = cfg.AutoTorrent.DeleteAfterDownload + } go func() { _, err2 := d.CreateDirect( @@ -973,18 +988,26 @@ func (d *Downloader) watch(task *Task) { }) if err2 != nil { d.Logger.Error().Err(err2).Msgf("auto create torrent task failed, task id: %s", task.ID) - } else { + return + } - if shouldDelete { - d.Delete(&TaskFilter{IDs: []string{task.ID}}, true) - } + if shouldDelete { + d.Delete(&TaskFilter{IDs: []string{task.ID}}, true) } }() } + // Determine if auto-extract is enabled (use global config if not explicitly set) + autoExtractEnabled := false + if e.AutoExtract != nil { + autoExtractEnabled = *e.AutoExtract + } else if cfg != nil && cfg.Archive != nil { + autoExtractEnabled = cfg.Archive.AutoExtract + } + // Auto-extract archive files using the extraction queue // This ensures only one extraction runs at a time to prevent resource exhaustion - if e.AutoExtract && isArchiveFile(downloadFilePath) { + if autoExtractEnabled && isArchiveFile(downloadFilePath) { d.enqueueExtraction(task, downloadFilePath, e) } } diff --git a/pkg/download/downloader_test.go b/pkg/download/downloader_test.go index 0b809730..9e838ac6 100644 --- a/pkg/download/downloader_test.go +++ b/pkg/download/downloader_test.go @@ -18,6 +18,7 @@ import ( "github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/protocol/http" + "github.com/GopeedLab/gopeed/pkg/util" ) var testDownloadOpt = &base.Options{ @@ -1037,7 +1038,7 @@ func TestDownloader_AutoExtractWithProgress(t *testing.T) { Name: "archive.zip", Extra: http.OptsExtra{ Connections: 1, - AutoExtract: true, + AutoExtract: util.BoolPtr(true), }, }) if err != nil { @@ -1164,7 +1165,7 @@ func TestDownloader_AutoExtractWithDeleteAfterExtract(t *testing.T) { Name: "archive.zip", Extra: http.OptsExtra{ Connections: 1, - AutoExtract: true, + AutoExtract: util.BoolPtr(true), DeleteAfterExtract: true, }, }) @@ -1258,7 +1259,7 @@ func TestDownloader_AutoExtractError(t *testing.T) { Name: "corrupt.zip", Extra: http.OptsExtra{ Connections: 1, - AutoExtract: true, + AutoExtract: util.BoolPtr(true), }, }) if err != nil { @@ -2332,3 +2333,318 @@ func TestDownloader_CheckMultiPartArchiveReady_EmptyBaseName(t *testing.T) { t.Error("checkMultiPartArchiveReady() should return true for non-multi-part files") } } + +// startTestTorrentServer starts a simple HTTP server that serves a torrent file +func startTestTorrentServer(torrentPath string) net.Listener { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + panic(err) + } + + server := &gohttp.Server{ + Handler: gohttp.HandlerFunc(func(w gohttp.ResponseWriter, r *gohttp.Request) { + data, err := os.ReadFile(torrentPath) + if err != nil { + w.WriteHeader(gohttp.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/x-bittorrent") + w.Header().Set("Content-Disposition", "attachment; filename=ubuntu.torrent") + w.Write(data) + }), + } + go server.Serve(listener) + return listener +} + +// TestDownloader_AutoTorrent tests the auto-torrent functionality +// When a .torrent file is downloaded with AutoTorrent enabled, it should automatically create a BT task +func TestDownloader_AutoTorrent(t *testing.T) { + // Path to the test torrent file + torrentPath := "../../internal/protocol/bt/testdata/ubuntu-22.04-live-server-amd64.iso.torrent" + if _, err := os.Stat(torrentPath); os.IsNotExist(err) { + t.Skip("Test torrent file not found, skipping test") + } + + // Start a simple HTTP server to serve the torrent file + server := startTestTorrentServer(torrentPath) + defer server.Close() + + // Create a temporary directory for the test + tempDir, err := os.MkdirTemp("", "auto_torrent_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + + // Create downloader + downloader := NewDownloader(nil) + if err := downloader.Setup(); err != nil { + t.Fatal(err) + } + defer func() { + // Delete all tasks before clearing to avoid panic from BT tasks trying to access deleted resources + downloader.Delete(nil, true) + downloader.Clear() + }() + + // Track created tasks + btTaskCreated := make(chan struct{}, 1) + var originalTaskId string + + downloader.Listener(func(event *Event) { + if event.Key == EventKeyStart { + // A new task started - if it's not the original, it's the BT task + if event.Task != nil && event.Task.ID != originalTaskId && originalTaskId != "" { + select { + case btTaskCreated <- struct{}{}: + default: + } + } + } + }) + + // Create request to download the torrent file + req := &base.Request{ + URL: "http://" + server.Addr().String() + "/ubuntu.torrent", + } + + // Create task with AutoTorrent enabled + downloadDir := tempDir + "/downloads" + originalTaskId, err = downloader.CreateDirect(req, &base.Options{ + Path: downloadDir, + Name: "ubuntu.torrent", + Extra: http.OptsExtra{ + Connections: 1, + AutoTorrent: util.BoolPtr(true), + }, + }) + if err != nil { + t.Fatal(err) + } + + t.Logf("Original task ID: %s", originalTaskId) + + // Wait for BT task to be created (with timeout) + select { + case <-btTaskCreated: + t.Log("BT task created") + case <-time.After(10 * time.Second): + t.Log("Timeout waiting for BT task creation") + } + + // Give a small buffer for task creation to complete + time.Sleep(200 * time.Millisecond) + + // Verify that a BT task was created + tasks := downloader.GetTasks() + + // At minimum, we should have 2 tasks: the original torrent download and the BT task + if len(tasks) < 2 { + t.Errorf("Expected at least 2 tasks (torrent download + BT task), got %d", len(tasks)) + } else { + t.Logf("Successfully created %d tasks", len(tasks)) + } +} + +// TestDownloader_AutoTorrentWithDelete tests the auto-torrent with DeleteTorrentAfterDownload option +func TestDownloader_AutoTorrentWithDelete(t *testing.T) { + // Path to the test torrent file + torrentPath := "../../internal/protocol/bt/testdata/ubuntu-22.04-live-server-amd64.iso.torrent" + if _, err := os.Stat(torrentPath); os.IsNotExist(err) { + t.Skip("Test torrent file not found, skipping test") + } + + // Start a simple HTTP server to serve the torrent file + server := startTestTorrentServer(torrentPath) + defer server.Close() + + // Create a temporary directory for the test + tempDir, err := os.MkdirTemp("", "auto_torrent_delete_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + + // Create downloader + downloader := NewDownloader(nil) + if err := downloader.Setup(); err != nil { + t.Fatal(err) + } + defer func() { + // Delete all tasks before clearing to avoid panic from BT tasks trying to access deleted resources + downloader.Delete(nil, true) + downloader.Clear() + }() + + // Track task events + var originalTaskId string + originalTaskDeleted := make(chan struct{}, 1) + btTaskCreated := make(chan struct{}, 1) + + downloader.Listener(func(event *Event) { + if event.Key == EventKeyStart { + // BT task was created and started + if event.Task != nil && event.Task.ID != originalTaskId && originalTaskId != "" { + select { + case btTaskCreated <- struct{}{}: + default: + } + } + } + if event.Key == EventKeyDelete { + // Check if the deleted task is the original torrent task + if event.Task != nil && event.Task.ID == originalTaskId { + select { + case originalTaskDeleted <- struct{}{}: + default: + } + } + } + }) + + // Create request to download the torrent file + req := &base.Request{ + URL: "http://" + server.Addr().String() + "/ubuntu.torrent", + } + + // Create task with AutoTorrent and DeleteTorrentAfterDownload enabled + downloadDir := tempDir + "/downloads" + originalTaskId, err = downloader.CreateDirect(req, &base.Options{ + Path: downloadDir, + Name: "ubuntu.torrent", + Extra: http.OptsExtra{ + Connections: 1, + AutoTorrent: util.BoolPtr(true), + DeleteTorrentAfterDownload: util.BoolPtr(true), + }, + }) + if err != nil { + t.Fatal(err) + } + + t.Logf("Original task ID: %s", originalTaskId) + + // Wait for original task to be deleted (this happens after BT task is created) + select { + case <-originalTaskDeleted: + t.Log("Original torrent task was deleted as expected") + case <-time.After(10 * time.Second): + // Check manually if the task still exists + originalTask := downloader.GetTask(originalTaskId) + if originalTask != nil { + t.Error("Original torrent task should have been deleted but still exists") + } else { + t.Log("Original torrent task was deleted (detected via GetTask)") + } + } + + // Give a moment for BT task creation + time.Sleep(200 * time.Millisecond) + + // Verify that original task is deleted and BT task exists + originalTask := downloader.GetTask(originalTaskId) + if originalTask != nil { + t.Error("Original torrent task should have been deleted") + } + + // Verify remaining tasks (should have at least the BT task) + tasks := downloader.GetTasks() + t.Logf("Remaining tasks: %d", len(tasks)) + + // At least one task should remain (the BT task) + if len(tasks) == 0 { + t.Error("Expected at least one task (BT task) to remain") + } + + // None of the remaining tasks should be the original torrent task + for _, task := range tasks { + if task.ID == originalTaskId { + t.Error("Original torrent task should have been deleted") + } + } +} + +// TestDownloader_AutoTorrentDisabled tests that auto-torrent does not create BT task when disabled +func TestDownloader_AutoTorrentDisabled(t *testing.T) { + // Path to the test torrent file + torrentPath := "../../internal/protocol/bt/testdata/ubuntu-22.04-live-server-amd64.iso.torrent" + if _, err := os.Stat(torrentPath); os.IsNotExist(err) { + t.Skip("Test torrent file not found, skipping test") + } + + // Start a simple HTTP server to serve the torrent file + server := startTestTorrentServer(torrentPath) + defer server.Close() + + // Create a temporary directory for the test + tempDir, err := os.MkdirTemp("", "auto_torrent_disabled_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + + // Create downloader + downloader := NewDownloader(nil) + if err := downloader.Setup(); err != nil { + t.Fatal(err) + } + defer func() { + downloader.Delete(nil, true) + downloader.Clear() + }() + + // Track task completion + taskDone := make(chan struct{}, 1) + + downloader.Listener(func(event *Event) { + if event.Key == EventKeyDone { + select { + case taskDone <- struct{}{}: + default: + } + } + }) + + // Create request to download the torrent file + req := &base.Request{ + URL: "http://" + server.Addr().String() + "/ubuntu.torrent", + } + + // Create task with AutoTorrent explicitly disabled + downloadDir := tempDir + "/downloads" + _, err = downloader.CreateDirect(req, &base.Options{ + Path: downloadDir, + Name: "ubuntu.torrent", + Extra: http.OptsExtra{ + Connections: 1, + AutoTorrent: util.BoolPtr(false), + }, + }) + if err != nil { + t.Fatal(err) + } + + // Wait for task to complete + select { + case <-taskDone: + // Task completed + case <-time.After(10 * time.Second): + t.Fatal("Timeout waiting for task to complete") + } + + // Give a small buffer + time.Sleep(200 * time.Millisecond) + + // Verify that only 1 task exists (no BT task was created) + tasks := downloader.GetTasks() + if len(tasks) != 1 { + t.Errorf("Expected exactly 1 task (torrent download only), got %d", len(tasks)) + } + + // Verify the torrent file was downloaded + torrentFilePath := downloadDir + "/ubuntu.torrent" + if _, err := os.Stat(torrentFilePath); os.IsNotExist(err) { + t.Error("Torrent file should have been downloaded") + } +} diff --git a/pkg/protocol/http/model.go b/pkg/protocol/http/model.go index 7b18e4c1..3199c981 100644 --- a/pkg/protocol/http/model.go +++ b/pkg/protocol/http/model.go @@ -9,9 +9,14 @@ type ReqExtra struct { type OptsExtra struct { Connections int `json:"connections"` // AutoTorrent when task download complete, and it is a .torrent file, it will be auto create a new task for the torrent file - AutoTorrent bool `json:"autoTorrent"` + // nil means use global config, true/false means explicit setting + AutoTorrent *bool `json:"autoTorrent"` + // DeleteTorrentAfterDownload when true, deletes the .torrent file after creating BT task + // nil means use global config, true/false means explicit setting + DeleteTorrentAfterDownload *bool `json:"deleteTorrentAfterDownload"` // AutoExtract when task download complete, and it is an archive file, it will be auto extracted - AutoExtract bool `json:"autoExtract"` + // nil means use global config, true/false means explicit setting + AutoExtract *bool `json:"autoExtract"` // ArchivePassword is the password for extracting password-protected archives ArchivePassword string `json:"archivePassword"` // DeleteAfterExtract when true, deletes the archive file after successful extraction diff --git a/pkg/util/json.go b/pkg/util/json.go index b4b7dc88..e2e39177 100644 --- a/pkg/util/json.go +++ b/pkg/util/json.go @@ -26,3 +26,13 @@ func DeepClone[T any](v *T) *T { json.Unmarshal(b, &t) return &t } + +// Ptr returns a pointer to the given value. +func Ptr[T any](v T) *T { + return &v +} + +// BoolPtr returns a pointer to a bool value. +func BoolPtr(v bool) *bool { + return &v +} diff --git a/ui/flutter/lib/api/model/downloader_config.dart b/ui/flutter/lib/api/model/downloader_config.dart index 064b398a..ae1d45d5 100644 --- a/ui/flutter/lib/api/model/downloader_config.dart +++ b/ui/flutter/lib/api/model/downloader_config.dart @@ -10,11 +10,11 @@ class DownloaderConfig { ExtraConfig extra = ExtraConfig(); ProxyConfig proxy = ProxyConfig(); WebhookConfig webhook = WebhookConfig(); + AutoTorrentConfig autoTorrent = AutoTorrentConfig(); ArchiveConfig archive = ArchiveConfig(); // New fields for auto-management - bool autoDeleteTorrents = false; - bool autoCleanMissingFiles = false; + bool autoDeleteMissingFileTasks = false; DownloaderConfig({ this.downloadDir = '', @@ -234,6 +234,22 @@ class ExtraConfigGithubMirror { Map toJson() => _$ExtraConfigGithubMirrorToJson(this); } +@JsonSerializable() +class AutoTorrentConfig { + bool enable; + bool deleteAfterDownload; + + AutoTorrentConfig({ + this.enable = false, + this.deleteAfterDownload = false, + }); + + factory AutoTorrentConfig.fromJson(Map? json) => + json == null ? AutoTorrentConfig() : _$AutoTorrentConfigFromJson(json); + + Map toJson() => _$AutoTorrentConfigToJson(this); +} + @JsonSerializable() class ArchiveConfig { bool autoExtract; @@ -248,4 +264,4 @@ class ArchiveConfig { json == null ? ArchiveConfig() : _$ArchiveConfigFromJson(json); Map toJson() => _$ArchiveConfigToJson(this); -} \ No newline at end of file +} diff --git a/ui/flutter/lib/api/model/downloader_config.g.dart b/ui/flutter/lib/api/model/downloader_config.g.dart index efc835dc..3849b31f 100644 --- a/ui/flutter/lib/api/model/downloader_config.g.dart +++ b/ui/flutter/lib/api/model/downloader_config.g.dart @@ -17,10 +17,11 @@ DownloaderConfig _$DownloaderConfigFromJson(Map json) => ..proxy = ProxyConfig.fromJson(json['proxy'] as Map) ..webhook = WebhookConfig.fromJson(json['webhook'] as Map?) + ..autoTorrent = AutoTorrentConfig.fromJson( + json['autoTorrent'] as Map?) ..archive = ArchiveConfig.fromJson(json['archive'] as Map?) - ..autoDeleteTorrents = json['autoDeleteTorrents'] as bool - ..autoCleanMissingFiles = json['autoCleanMissingFiles'] as bool; + ..autoDeleteMissingFileTasks = json['autoDeleteMissingFileTasks'] as bool; Map _$DownloaderConfigToJson(DownloaderConfig instance) => { @@ -30,9 +31,9 @@ Map _$DownloaderConfigToJson(DownloaderConfig instance) => 'extra': instance.extra.toJson(), 'proxy': instance.proxy.toJson(), 'webhook': instance.webhook.toJson(), + 'autoTorrent': instance.autoTorrent.toJson(), 'archive': instance.archive.toJson(), - 'autoDeleteTorrents': instance.autoDeleteTorrents, - 'autoCleanMissingFiles': instance.autoCleanMissingFiles, + 'autoDeleteMissingFileTasks': instance.autoDeleteMissingFileTasks, }; ProtocolConfig _$ProtocolConfigFromJson(Map json) => @@ -242,6 +243,18 @@ Map _$ExtraConfigGithubMirrorToJson( 'mirrors': instance.mirrors.map((e) => e.toJson()).toList(), }; +AutoTorrentConfig _$AutoTorrentConfigFromJson(Map json) => + AutoTorrentConfig( + enable: json['enable'] as bool? ?? false, + deleteAfterDownload: json['deleteAfterDownload'] as bool? ?? false, + ); + +Map _$AutoTorrentConfigToJson(AutoTorrentConfig instance) => + { + 'enable': instance.enable, + 'deleteAfterDownload': instance.deleteAfterDownload, + }; + ArchiveConfig _$ArchiveConfigFromJson(Map json) => ArchiveConfig( autoExtract: json['autoExtract'] as bool? ?? true, diff --git a/ui/flutter/lib/api/model/options.dart b/ui/flutter/lib/api/model/options.dart index a1e6e3a7..ca17bd35 100644 --- a/ui/flutter/lib/api/model/options.dart +++ b/ui/flutter/lib/api/model/options.dart @@ -25,15 +25,17 @@ class Options { @JsonSerializable() class OptsExtraHttp { int connections; - bool autoTorrent; - bool autoExtract; + bool? autoTorrent; + bool? deleteTorrentAfterDownload; + bool? autoExtract; String archivePassword; bool deleteAfterExtract; OptsExtraHttp({ this.connections = 0, - this.autoTorrent = false, - this.autoExtract = false, + this.autoTorrent, + this.deleteTorrentAfterDownload, + this.autoExtract, this.archivePassword = '', this.deleteAfterExtract = false, }); diff --git a/ui/flutter/lib/api/model/options.g.dart b/ui/flutter/lib/api/model/options.g.dart index 296a5046..e8e3840d 100644 --- a/ui/flutter/lib/api/model/options.g.dart +++ b/ui/flutter/lib/api/model/options.g.dart @@ -36,17 +36,29 @@ Map _$OptionsToJson(Options instance) { OptsExtraHttp _$OptsExtraHttpFromJson(Map json) => OptsExtraHttp( connections: (json['connections'] as num?)?.toInt() ?? 0, - autoTorrent: json['autoTorrent'] as bool? ?? false, - autoExtract: json['autoExtract'] as bool? ?? false, + autoTorrent: json['autoTorrent'] as bool?, + deleteTorrentAfterDownload: json['deleteTorrentAfterDownload'] as bool?, + autoExtract: json['autoExtract'] as bool?, archivePassword: json['archivePassword'] as String? ?? '', deleteAfterExtract: json['deleteAfterExtract'] as bool? ?? false, ); -Map _$OptsExtraHttpToJson(OptsExtraHttp instance) => - { - 'connections': instance.connections, - 'autoTorrent': instance.autoTorrent, - 'autoExtract': instance.autoExtract, - 'archivePassword': instance.archivePassword, - 'deleteAfterExtract': instance.deleteAfterExtract, - }; +Map _$OptsExtraHttpToJson(OptsExtraHttp instance) { + final val = { + 'connections': instance.connections, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('autoTorrent', instance.autoTorrent); + writeNotNull( + 'deleteTorrentAfterDownload', instance.deleteTorrentAfterDownload); + writeNotNull('autoExtract', instance.autoExtract); + val['archivePassword'] = instance.archivePassword; + val['deleteAfterExtract'] = instance.deleteAfterExtract; + return val; +} diff --git a/ui/flutter/lib/app/modules/create/controllers/create_controller.dart b/ui/flutter/lib/app/modules/create/controllers/create_controller.dart index 83f3c84b..5f8eef69 100644 --- a/ui/flutter/lib/app/modules/create/controllers/create_controller.dart +++ b/ui/flutter/lib/app/modules/create/controllers/create_controller.dart @@ -26,7 +26,7 @@ class CreateController extends GetxController @override void onInit() { super.onInit(); - advancedTabController = TabController(length: 3, vsync: this); + advancedTabController = TabController(length: 2, vsync: this); directDownload.value = Get.find() .downloaderConfig .value diff --git a/ui/flutter/lib/app/modules/create/views/create_view.dart b/ui/flutter/lib/app/modules/create/views/create_view.dart index 6e23cd7e..0b6fd186 100644 --- a/ui/flutter/lib/app/modules/create/views/create_view.dart +++ b/ui/flutter/lib/app/modules/create/views/create_view.dart @@ -62,6 +62,8 @@ class CreateView extends GetView { final _availableSchemes = ["http:", "https:", "magnet:"]; final _skipVerifyCertController = false.obs; + final _autoTorrentController = Rxn(); + final _deleteTorrentAfterDownloadController = Rxn(); final _autoExtractController = Rxn(); final _deleteAfterExtractController = Rxn(); @@ -81,11 +83,6 @@ class CreateView extends GetView { final downloadDir = appController.downloaderConfig.value.downloadDir; _pathController.text = renderPathPlaceholders(downloadDir); } - // Initialize archive settings from global config if not already set - _autoExtractController.value ??= - appController.downloaderConfig.value.archive.autoExtract; - _deleteAfterExtractController.value ??= - appController.downloaderConfig.value.archive.deleteAfterExtract; // Handle pending create task from deep link final CreateTask? routerParams = Get.rootDelegate.arguments(); @@ -509,20 +506,17 @@ class CreateView extends GetView { const Divider(), TabBar( controller: controller.advancedTabController, - tabs: [ - const Tab( + tabs: const [ + Tab( text: 'HTTP', ), - const Tab( + Tab( text: 'BitTorrent', ), - Tab( - text: 'archives'.tr, - ) ], ), DefaultTabController( - length: 3, + length: 2, child: ContentSizeTabBarView( controller: controller.advancedTabController, @@ -605,21 +599,52 @@ class CreateView extends GetView { ), ), ), - ], - ), - Column( - children: [ - TextFormField( - controller: _btTrackerController, - maxLines: 5, - decoration: InputDecoration( - labelText: 'Trackers', - hintText: 'addTrackerHit'.tr, - )), - ], - ), - Column( - children: [ + // AutoTorrent options + Padding( + padding: + const EdgeInsets.only(top: 10), + child: CompactCheckbox( + label: 'autoTorrentEnable'.tr, + value: _autoTorrentController + .value ?? + false, + onChanged: (bool? value) { + _autoTorrentController.value = + value ?? false; + }, + textStyle: const TextStyle( + color: Colors.grey, + ), + ), + ), + Obx( + () => Visibility( + visible: _autoTorrentController + .value ?? + false, + child: Padding( + padding: const EdgeInsets.only( + top: 10, left: 20), + child: CompactCheckbox( + label: + 'autoTorrentDeleteAfterDownload' + .tr, + value: + _deleteTorrentAfterDownloadController + .value ?? + false, + onChanged: (bool? value) { + _deleteTorrentAfterDownloadController + .value = value ?? false; + }, + textStyle: const TextStyle( + color: Colors.grey, + ), + ), + ), + ), + ), + // AutoExtract options Padding( padding: const EdgeInsets.only(top: 10), @@ -647,7 +672,7 @@ class CreateView extends GetView { Padding( padding: const EdgeInsets.only( - top: 10), + top: 10, left: 20), child: TextFormField( controller: _archivePasswordController, @@ -666,7 +691,7 @@ class CreateView extends GetView { Padding( padding: const EdgeInsets.only( - top: 10), + top: 10, left: 20), child: CompactCheckbox( label: 'deleteAfterExtract' @@ -691,7 +716,18 @@ class CreateView extends GetView { ), ), ], - ) + ), + Column( + children: [ + TextFormField( + controller: _btTrackerController, + maxLines: 5, + decoration: InputDecoration( + labelText: 'Trackers', + hintText: 'addTrackerHit'.tr, + )), + ], + ), ], ), ) @@ -892,8 +928,9 @@ class CreateView extends GetView { Object? parseReqOptsExtra() { return OptsExtraHttp() ..connections = int.tryParse(_connectionsController.text) ?? 0 - ..autoTorrent = true - ..autoExtract = _autoExtractController.value ?? false + ..autoTorrent = _autoTorrentController.value + ..deleteTorrentAfterDownload = _deleteTorrentAfterDownloadController.value + ..autoExtract = _autoExtractController.value ..archivePassword = _archivePasswordController.text ..deleteAfterExtract = _deleteAfterExtractController.value ?? false; } diff --git a/ui/flutter/lib/app/modules/setting/views/setting_view.dart b/ui/flutter/lib/app/modules/setting/views/setting_view.dart index 5e23c4a3..8766cc0e 100644 --- a/ui/flutter/lib/app/modules/setting/views/setting_view.dart +++ b/ui/flutter/lib/app/modules/setting/views/setting_view.dart @@ -169,19 +169,19 @@ class SettingView extends GetView { ); }); - // New: Auto Delete Torrents - final buildAutoDeleteTorrents = _buildConfigItem('autoDeleteTorrents', () { - return appController.downloaderConfig.value.autoDeleteTorrents + // AutoTorrent: Enable auto create BT tasks from .torrent files + final buildAutoTorrentEnable = _buildConfigItem('autoTorrentEnable', () { + return appController.downloaderConfig.value.autoTorrent.enable ? 'on'.tr : 'off'.tr; }, (Key key) { return Container( alignment: Alignment.centerLeft, child: Switch( - value: appController.downloaderConfig.value.autoDeleteTorrents, + value: appController.downloaderConfig.value.autoTorrent.enable, onChanged: (bool value) async { appController.downloaderConfig.update((val) { - val!.autoDeleteTorrents = value; + val!.autoTorrent.enable = value; }); await debounceSave(); }, @@ -189,19 +189,44 @@ class SettingView extends GetView { ); }); - // New: Auto Clean Missing Files - final buildAutoCleanMissingFiles = _buildConfigItem('autoCleanMissingFiles', () { - return appController.downloaderConfig.value.autoCleanMissingFiles + // AutoTorrent: Delete .torrent file after BT task creation + final buildAutoTorrentDeleteAfterDownload = + _buildConfigItem('autoTorrentDeleteAfterDownload', () { + return appController + .downloaderConfig.value.autoTorrent.deleteAfterDownload ? 'on'.tr : 'off'.tr; }, (Key key) { return Container( alignment: Alignment.centerLeft, child: Switch( - value: appController.downloaderConfig.value.autoCleanMissingFiles, + value: appController + .downloaderConfig.value.autoTorrent.deleteAfterDownload, onChanged: (bool value) async { appController.downloaderConfig.update((val) { - val!.autoCleanMissingFiles = value; + val!.autoTorrent.deleteAfterDownload = value; + }); + await debounceSave(); + }, + ), + ); + }); + + // New: Auto Delete Missing File Tasks + final buildAutoDeleteMissingFileTasks = + _buildConfigItem('autoDeleteMissingFileTasks', () { + return appController.downloaderConfig.value.autoDeleteMissingFileTasks + ? 'on'.tr + : 'off'.tr; + }, (Key key) { + return Container( + alignment: Alignment.centerLeft, + child: Switch( + value: + appController.downloaderConfig.value.autoDeleteMissingFileTasks, + onChanged: (bool value) async { + appController.downloaderConfig.update((val) { + val!.autoDeleteMissingFileTasks = value; }); await debounceSave(); }, @@ -1454,8 +1479,13 @@ class SettingView extends GetView { buildMaxRunning(), buildDefaultDirectDownload(), buildAutoStartTasks(), - buildAutoDeleteTorrents(), - buildAutoCleanMissingFiles(), + buildAutoTorrentEnable(), + Obx(() => Visibility( + visible: appController.downloaderConfig.value + .autoTorrent.enable, + child: buildAutoTorrentDeleteAfterDownload(), + )), + buildAutoDeleteMissingFileTasks(), buildBrowserExtension(), buildAutoStartup(), buildMenubarMode(), @@ -1466,7 +1496,11 @@ class SettingView extends GetView { child: Column( children: _addDivider([ buildAutoExtract(), - buildDeleteAfterExtract(), + Obx(() => Visibility( + visible: appController.downloaderConfig.value + .archive.autoExtract, + child: buildDeleteAfterExtract(), + )), ]), )), const Text('HTTP'), @@ -1990,4 +2024,4 @@ extension ProxyMode on ProxyConfig { break; } } -} \ No newline at end of file +} diff --git a/ui/flutter/lib/i18n/langs/de_de.dart b/ui/flutter/lib/i18n/langs/de_de.dart index 740e0fe5..7a26c42e 100644 --- a/ui/flutter/lib/i18n/langs/de_de.dart +++ b/ui/flutter/lib/i18n/langs/de_de.dart @@ -42,8 +42,11 @@ const deDE = { 'maxRunning': 'Maximale Anzahl ausgeführter Aufgaben', 'defaultDirectDownload': 'Direkten Download standardmäßig aktivieren', 'autoStartTasks': 'Unvollständige Aufgaben beim Start automatisch starten', - 'autoDeleteTorrents': 'Automatisches Löschen von .torrent-Dateien', - 'autoCleanMissingFiles': 'Automatisches Bereinigen fehlender Dateien', + 'autoTorrentEnable': + 'BT-Aufgaben automatisch aus .torrent-Dateien erstellen', + 'autoTorrentDeleteAfterDownload': + '.torrent-Datei nach BT-Aufgabenerstellung löschen', + 'autoDeleteMissingFileTasks': 'Fehlende Dateiaufgaben automatisch löschen', 'items': '@count Artikel', 'subscribeTracker': 'Abonnement-Tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/en_us.dart b/ui/flutter/lib/i18n/langs/en_us.dart index 9f3cec18..44540804 100644 --- a/ui/flutter/lib/i18n/langs/en_us.dart +++ b/ui/flutter/lib/i18n/langs/en_us.dart @@ -39,8 +39,10 @@ const enUS = { 'maxRunning': 'Max Running Tasks', 'defaultDirectDownload': 'Check direct download by default', 'autoStartTasks': 'Auto-start incomplete tasks on startup', - 'autoDeleteTorrents': 'Auto delete .torrent files', - 'autoCleanMissingFiles': 'Auto clean missing files', + 'autoTorrentEnable': 'Auto create BT tasks from .torrent files', + 'autoTorrentDeleteAfterDownload': + 'Delete .torrent file after BT task creation', + 'autoDeleteMissingFileTasks': 'Auto delete missing file tasks', 'items': '@count items', 'subscribeTracker': 'Subscribe Tracker', 'subscribeFail': @@ -174,4 +176,4 @@ const enUS = { 'githubMirrorUrl': 'Mirror URL', 'githubMirrorUrlHint': 'Enter mirror URL', }, -}; \ No newline at end of file +}; diff --git a/ui/flutter/lib/i18n/langs/es_es.dart b/ui/flutter/lib/i18n/langs/es_es.dart index 537ddb99..e9029d57 100644 --- a/ui/flutter/lib/i18n/langs/es_es.dart +++ b/ui/flutter/lib/i18n/langs/es_es.dart @@ -39,8 +39,12 @@ const esES = { 'useServerCtime': 'Usar tiempo del servidor para la creación de archivos', 'maxRunning': 'Máximo de Tareas en Ejecución', 'autoStartTasks': 'Iniciar automáticamente tareas incompletas al arrancar', - 'autoDeleteTorrents': 'Eliminar automáticamente archivos .torrent', - 'autoCleanMissingFiles': 'Limpiar automáticamente archivos faltantes', + 'autoTorrentEnable': + 'Crear automáticamente tareas BT desde archivos .torrent', + 'autoTorrentDeleteAfterDownload': + 'Eliminar archivo .torrent después de crear tarea BT', + 'autoDeleteMissingFileTasks': + 'Eliminar automáticamente tareas con archivos faltantes', 'items': '@count elementos', 'subscribeTracker': 'Suscribirse al Tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/fa_ir.dart b/ui/flutter/lib/i18n/langs/fa_ir.dart index db43da8d..8612520a 100644 --- a/ui/flutter/lib/i18n/langs/fa_ir.dart +++ b/ui/flutter/lib/i18n/langs/fa_ir.dart @@ -26,8 +26,9 @@ const faIR = { 'downloadDir': 'دایرکتوری دانلود', 'downloadDirValid': 'لطفا دایرکتوری دانلود را انتخاب کنید', 'connections': 'اتصالات', - 'autoDeleteTorrents': 'حذف خودکار فایل‌های .torrent', - 'autoCleanMissingFiles': 'پاکسازی خودکار فایل‌های گم شده', + 'autoTorrentEnable': 'ایجاد خودکار وظایف BT از فایل‌های .torrent', + 'autoTorrentDeleteAfterDownload': 'حذف فایل .torrent پس از ایجاد وظیفه BT', + 'autoDeleteMissingFileTasks': 'حذف خودکار وظایف با فایل‌های گم شده', 'items': '@count آیتم ها', 'subscribeTracker': 'Subscribe Tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/fr_fr.dart b/ui/flutter/lib/i18n/langs/fr_fr.dart index a805822b..68e2fffc 100644 --- a/ui/flutter/lib/i18n/langs/fr_fr.dart +++ b/ui/flutter/lib/i18n/langs/fr_fr.dart @@ -40,8 +40,12 @@ const frFR = { 'maxRunning': 'Tâches en cours maximum', 'autoStartTasks': 'Démarrer automatiquement les tâches incomplètes au lancement', - 'autoDeleteTorrents': 'Supprimer automatiquement les fichiers .torrent', - 'autoCleanMissingFiles': 'Nettoyer automatiquement les fichiers manquants', + 'autoTorrentEnable': + 'Créer automatiquement des tâches BT à partir de fichiers .torrent', + 'autoTorrentDeleteAfterDownload': + 'Supprimer le fichier .torrent après création de tâche BT', + 'autoDeleteMissingFileTasks': + 'Supprimer automatiquement les tâches avec fichiers manquants', 'items': '@count éléments', 'subscribeTracker': 'S\'abonner au tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/hu_hu.dart b/ui/flutter/lib/i18n/langs/hu_hu.dart index 60bbb7e1..8c727d63 100644 --- a/ui/flutter/lib/i18n/langs/hu_hu.dart +++ b/ui/flutter/lib/i18n/langs/hu_hu.dart @@ -40,8 +40,12 @@ const huHU = { 'maxRunning': 'Maximális futó feladatok', 'defaultDirectDownload': 'Alapértelmezettként ellenőrizze a közvetlen letöltést', - 'autoDeleteTorrents': 'Automatikus .torrent fájlok törlése', - 'autoCleanMissingFiles': 'Hiányzó fájlok automatikus tisztítása', + 'autoTorrentEnable': + 'BT feladatok automatikus létrehozása .torrent fájlokból', + 'autoTorrentDeleteAfterDownload': + '.torrent fájl törlése a BT feladat létrehozása után', + 'autoDeleteMissingFileTasks': + 'Hiányzó fájlokkal rendelkező feladatok automatikus törlése', 'items': '@count elem', 'subscribeTracker': 'Nyomkövető előfizetése', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/id_id.dart b/ui/flutter/lib/i18n/langs/id_id.dart index f29d1ce0..64dfa9dc 100644 --- a/ui/flutter/lib/i18n/langs/id_id.dart +++ b/ui/flutter/lib/i18n/langs/id_id.dart @@ -39,8 +39,11 @@ const idID = { 'useServerCtime': 'Gunakan waktu server untuk pembuatan file', 'maxRunning': 'Tugas Maksimum yang Berjalan', 'defaultDirectDownload': 'Pilih unduhan langsung sebagai opsi standar', - 'autoDeleteTorrents': 'Otomatis hapus file .torrent', - 'autoCleanMissingFiles': 'Otomatis bersihkan file yang hilang', + 'autoTorrentEnable': 'Buat tugas BT secara otomatis dari file .torrent', + 'autoTorrentDeleteAfterDownload': + 'Hapus file .torrent setelah membuat tugas BT', + 'autoDeleteMissingFileTasks': + 'Otomatis hapus tugas dengan file yang hilang', 'items': '@count items', 'subscribeTracker': 'Berlangganan Tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/it_it.dart b/ui/flutter/lib/i18n/langs/it_it.dart index 0589c9ca..530e4030 100644 --- a/ui/flutter/lib/i18n/langs/it_it.dart +++ b/ui/flutter/lib/i18n/langs/it_it.dart @@ -37,8 +37,11 @@ const itIT = { 'connections': 'Connessioni', 'useServerCtime': "Utilizza l'ora del server per la creazione dei file", 'maxRunning': 'Numero massimo di attività in esecuzione', - 'autoDeleteTorrents': 'Elimina automaticamente i file .torrent', - 'autoCleanMissingFiles': 'Pulisci automaticamente i file mancanti', + 'autoTorrentEnable': 'Crea automaticamente attività BT da file .torrent', + 'autoTorrentDeleteAfterDownload': + 'Elimina file .torrent dopo creazione attività BT', + 'autoDeleteMissingFileTasks': + 'Elimina automaticamente le attività con file mancanti', 'items': '@count elementi', 'subscribeTracker': 'Iscriviti al tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/ja_jp.dart b/ui/flutter/lib/i18n/langs/ja_jp.dart index bf294b91..c10864c4 100644 --- a/ui/flutter/lib/i18n/langs/ja_jp.dart +++ b/ui/flutter/lib/i18n/langs/ja_jp.dart @@ -30,8 +30,9 @@ const jaJP = { 'connections': '接続', 'maxRunning': '最大実行タスク', 'autoStartTasks': '起動時に未完了のタスクを自動的に開始', - 'autoDeleteTorrents': '.torrentファイルを自動削除', - 'autoCleanMissingFiles': '欠落ファイルを自動クリーン', + 'autoTorrentEnable': '.torrentファイルからBTタスクを自動作成', + 'autoTorrentDeleteAfterDownload': 'BTタスク作成後に.torrentファイルを削除', + 'autoDeleteMissingFileTasks': '欠落ファイルタスクを自動削除', 'items': '@count アイテム', 'subscribeTracker': 'トラッカーを購読', 'subscribeFail': '登録に失敗しました。ネットワークを確認するか、後でもう一度お試しください', diff --git a/ui/flutter/lib/i18n/langs/pl_pl.dart b/ui/flutter/lib/i18n/langs/pl_pl.dart index 8d4aa1b4..9fa613d5 100644 --- a/ui/flutter/lib/i18n/langs/pl_pl.dart +++ b/ui/flutter/lib/i18n/langs/pl_pl.dart @@ -36,8 +36,11 @@ const plPL = { 'connections': 'Połączenia', 'useServerCtime': 'Użyj czasu serwera do utworzenia pliku', 'maxRunning': 'Maksymalna liczba zadań', - 'autoDeleteTorrents': 'Automatycznie usuń pliki .torrent', - 'autoCleanMissingFiles': 'Automatycznie wyczyść brakujące pliki', + 'autoTorrentEnable': 'Automatyczne tworzenie zadań BT z plików .torrent', + 'autoTorrentDeleteAfterDownload': + 'Usuń plik .torrent po utworzeniu zadania BT', + 'autoDeleteMissingFileTasks': + 'Automatycznie usuwaj zadania z brakującymi plikami', 'items': '@count items', 'subscribeTracker': 'Subskrybuj tracker', 'subscribeFail': 'Błąd subskrypcji, sprawdź łączność z internetem', diff --git a/ui/flutter/lib/i18n/langs/pt_br.dart b/ui/flutter/lib/i18n/langs/pt_br.dart index 2c42ae2c..1e7b3273 100644 --- a/ui/flutter/lib/i18n/langs/pt_br.dart +++ b/ui/flutter/lib/i18n/langs/pt_br.dart @@ -39,8 +39,12 @@ const ptBR = { 'useServerCtime': 'Use server time for file creation', 'maxRunning': 'Máximo de Tarefas Simultâneas', 'defaultDirectDownload': 'Marcar download direto por padrão', - 'autoDeleteTorrents': 'Excluir automaticamente arquivos .torrent', - 'autoCleanMissingFiles': 'Limpar automaticamente arquivos ausentes', + 'autoTorrentEnable': + 'Criar automaticamente tarefas BT de arquivos .torrent', + 'autoTorrentDeleteAfterDownload': + 'Excluir arquivo .torrent após criação da tarefa BT', + 'autoDeleteMissingFileTasks': + 'Excluir automaticamente tarefas com arquivos ausentes', 'items': '@count itens', 'subscribeTracker': 'Assinar Rastreador', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/ru_ru.dart b/ui/flutter/lib/i18n/langs/ru_ru.dart index 6a5e9976..e85496aa 100644 --- a/ui/flutter/lib/i18n/langs/ru_ru.dart +++ b/ui/flutter/lib/i18n/langs/ru_ru.dart @@ -40,8 +40,10 @@ const ruRU = { 'maxRunning': 'Максимальное количество активных задач', 'defaultDirectDownload': 'Прямая загрузка по умолчанию', 'autoStartTasks': 'Автоматически запускать незавершённые задачи при старте', - 'autoDeleteTorrents': 'Автоудаление торрент-файлов', - 'autoCleanMissingFiles': 'Автоочистка отсутствующих файлов', + 'autoTorrentEnable': 'Автоматически создавать BT-задачи из .torrent файлов', + 'autoTorrentDeleteAfterDownload': + 'Удалить .torrent файл после создания BT-задачи', + 'autoDeleteMissingFileTasks': 'Автоудаление задач с отсутствующими файлами', 'items': '@подсчет элементов', 'subscribeTracker': 'Подпись трекера', 'subscribeFail': @@ -151,4 +153,4 @@ const ruRU = { 'builtInCategory': 'Встроенную категорию нельзя удалить', 'selectCategory': 'Выбрать категорию', } -}; \ No newline at end of file +}; diff --git a/ui/flutter/lib/i18n/langs/ta_ta.dart b/ui/flutter/lib/i18n/langs/ta_ta.dart index af02b75c..56694a9d 100644 --- a/ui/flutter/lib/i18n/langs/ta_ta.dart +++ b/ui/flutter/lib/i18n/langs/ta_ta.dart @@ -37,8 +37,11 @@ const taTA = { 'connections': 'தொடர்புகள்', 'useServerCtime': 'Use server time for file creation', 'maxRunning': 'அதிகபட்ச இயங்கும் பணிகள்', - 'autoDeleteTorrents': 'தானாக .torrent கோப்புகளை நீக்கு', - 'autoCleanMissingFiles': 'காணாமல் போன கோப்புகளை தானாக சுத்தம் செய்', + 'autoTorrentEnable': '.torrent கோப்புகளிலிருந்து BT பணிகளை தானாக உருவாக்கு', + 'autoTorrentDeleteAfterDownload': + 'BT பணி உருவாக்கத்திற்குப் பிறகு .torrent கோப்பை நீக்கு', + 'autoDeleteMissingFileTasks': + 'காணாமல் போன கோப்புகளுடன் பணிகளை தானாக நீக்கு', 'items': '@count items', 'subscribeTracker': 'Subscribe Tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/tr_tr.dart b/ui/flutter/lib/i18n/langs/tr_tr.dart index 64741ab9..f1efc8dc 100644 --- a/ui/flutter/lib/i18n/langs/tr_tr.dart +++ b/ui/flutter/lib/i18n/langs/tr_tr.dart @@ -39,8 +39,10 @@ const trTR = { 'useServerCtime': 'Dosya oluşturma için sunucu zamanını kullan', 'maxRunning': 'Maksimum Çalışan Görev', 'defaultDirectDownload': 'Varsayılan olarak doğrudan indirmeyi seç', - 'autoDeleteTorrents': '.torrent dosyalarını otomatik sil', - 'autoCleanMissingFiles': 'Eksik dosyaları otomatik temizle', + 'autoTorrentEnable': '.torrent dosyalarından otomatik BT görevi oluştur', + 'autoTorrentDeleteAfterDownload': + 'BT görevi oluşturulduktan sonra .torrent dosyasını sil', + 'autoDeleteMissingFileTasks': 'Eksik dosya görevlerini otomatik sil', 'items': '@count öğe', 'subscribeTracker': 'Tracker Aboneliği', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/uk_ua.dart b/ui/flutter/lib/i18n/langs/uk_ua.dart index 5d2f571e..983dbc1b 100644 --- a/ui/flutter/lib/i18n/langs/uk_ua.dart +++ b/ui/flutter/lib/i18n/langs/uk_ua.dart @@ -39,8 +39,11 @@ const ukUA = { 'useServerCtime': 'Використовувати час сервера для створення файлу', 'maxRunning': 'Макс. завдань у черзі', 'defaultDirectDownload': 'Пряме завантаження за замовчуванням', - 'autoDeleteTorrents': 'Автоматично видаляти файли .torrent', - 'autoCleanMissingFiles': 'Автоматично очищати відсутні файли', + 'autoTorrentEnable': 'Автоматично створювати BT-завдання з .torrent файлів', + 'autoTorrentDeleteAfterDownload': + 'Видалити .torrent файл після створення BT-завдання', + 'autoDeleteMissingFileTasks': + 'Автоматично видаляти завдання з відсутніми файлами', 'items': '@count елементів', 'subscribeTracker': 'Підписатися на трекер', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/vi_vn.dart b/ui/flutter/lib/i18n/langs/vi_vn.dart index 5d2e4666..687a9837 100644 --- a/ui/flutter/lib/i18n/langs/vi_vn.dart +++ b/ui/flutter/lib/i18n/langs/vi_vn.dart @@ -36,8 +36,10 @@ const viVN = { 'connections': 'Kết nối', 'useServerCtime': 'Sử dụng thời gian máy chủ cho việc tạo tệp', 'maxRunning': 'Số nhiệm vụ tối đa', - 'autoDeleteTorrents': 'Tự động xóa tệp .torrent', - 'autoCleanMissingFiles': 'Tự động dọn dẹp tệp bị thiếu', + 'autoTorrentEnable': 'Tự động tạo nhiệm vụ BT từ file .torrent', + 'autoTorrentDeleteAfterDownload': + 'Xóa file .torrent sau khi tạo nhiệm vụ BT', + 'autoDeleteMissingFileTasks': 'Tự động xóa tác vụ có tệp bị thiếu', 'items': '@count mục', 'subscribeTracker': 'Theo dõi Tracker', 'subscribeFail': diff --git a/ui/flutter/lib/i18n/langs/zh_cn.dart b/ui/flutter/lib/i18n/langs/zh_cn.dart index 30ff6b3f..223dc764 100644 --- a/ui/flutter/lib/i18n/langs/zh_cn.dart +++ b/ui/flutter/lib/i18n/langs/zh_cn.dart @@ -39,8 +39,9 @@ const zhCN = { 'maxRunning': '最大下载数', 'defaultDirectDownload': '默认勾选直接下载', 'autoStartTasks': '启动时自动开始未完成的任务', - 'autoDeleteTorrents': '自动删除 .torrent 文件', - 'autoCleanMissingFiles': '自动清理缺失文件', + 'autoTorrentEnable': '自动从 .torrent 文件创建 BT 任务', + 'autoTorrentDeleteAfterDownload': '创建 BT 任务后删除 .torrent 文件', + 'autoDeleteMissingFileTasks': '自动删除缺失文件任务', 'items': '@count 项', 'subscribeTracker': '订阅 Tracker', 'subscribeFail': '订阅失败,请检查网络或稍后重试', diff --git a/ui/flutter/lib/i18n/langs/zh_tw.dart b/ui/flutter/lib/i18n/langs/zh_tw.dart index f44c0bc0..19113611 100644 --- a/ui/flutter/lib/i18n/langs/zh_tw.dart +++ b/ui/flutter/lib/i18n/langs/zh_tw.dart @@ -39,8 +39,9 @@ const zhTW = { 'maxRunning': '最大執行任務數', 'defaultDirectDownload': '預設勾選直接下載', 'autoStartTasks': '啟動時自動開始未完成的任務', - 'autoDeleteTorrents': '自動刪除 .torrent 檔案', - 'autoCleanMissingFiles': '自動清理缺失檔案', + 'autoTorrentEnable': '自動從 .torrent 檔案建立 BT 任務', + 'autoTorrentDeleteAfterDownload': '建立 BT 任務後刪除 .torrent 檔案', + 'autoDeleteMissingFileTasks': '自動刪除缺失檔案任務', 'items': '@count 個項目', 'subscribeTracker': '訂閱 Tracker', 'subscribeFail': '訂閱失敗,請檢查網路或稍後再試',