feat!: Refactor batch import task to transfer task
- Updated command usage in English and Simplified Chinese localization files to reflect changes in transfer command syntax. - Removed batch import task implementation, replacing it with a new transfer task implementation. - Introduced new task structure and progress tracking for file transfers. - Updated task type enumeration to replace batch import with transfer. - Added new fields in data structures to support transfer operations. - Implemented file handling and progress reporting for the transfer task.
This commit is contained in:
@@ -101,6 +101,8 @@ func handleAddCallback(ctx *ext.Context, update *ext.Update) error {
|
||||
shortcut.CreateAndAddAria2TaskWithEdit(ctx, selectedStorage, dirPath, data.Aria2URIs, client, msgID, userID)
|
||||
case tasktype.TaskTypeYtdlp:
|
||||
shortcut.CreateAndAddYtdlpTaskWithEdit(ctx, selectedStorage, dirPath, data.YtdlpURLs, data.YtdlpFlags, msgID, userID)
|
||||
case tasktype.TaskTypeTransfer:
|
||||
return handleTransferCallback(ctx, userID, selectedStorage, dirPath, data, msgID)
|
||||
default:
|
||||
return fmt.Errorf("unexcept task type: %s", data.TaskType)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -9,13 +10,16 @@ import (
|
||||
"github.com/celestix/gotgproto/ext"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
||||
"github.com/krau/SaveAny-Bot/common/utils/strutil"
|
||||
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
|
||||
"github.com/krau/SaveAny-Bot/core"
|
||||
"github.com/krau/SaveAny-Bot/core/tasks/batchimport"
|
||||
"github.com/krau/SaveAny-Bot/core/tasks/transfer"
|
||||
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
|
||||
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
|
||||
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
|
||||
"github.com/krau/SaveAny-Bot/storage"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
@@ -38,14 +42,8 @@ func handleTransferCmd(ctx *ext.Context, update *ext.Update) error {
|
||||
sourceStorageName := sourceParts[0]
|
||||
sourcePath := sourceParts[1]
|
||||
|
||||
// Parse target: storage_name:/path
|
||||
targetParts := strings.SplitN(args[2], ":", 2)
|
||||
if len(targetParts) != 2 {
|
||||
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgTransferErrorInvalidTarget, nil)), nil)
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
targetStorageName := targetParts[0]
|
||||
targetPath := targetParts[1]
|
||||
// Parse target path (without storage name)
|
||||
targetPath := args[2]
|
||||
|
||||
userID := update.GetUserChat().GetID()
|
||||
|
||||
@@ -78,17 +76,6 @@ func handleTransferCmd(ctx *ext.Context, update *ext.Update) error {
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
// Get target storage
|
||||
targetStorage, err := storage.GetStorageByUserIDAndName(ctx, userID, targetStorageName)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to get target storage by user ID and name: %s", err)
|
||||
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgTransferErrorTargetNotFound, map[string]any{
|
||||
"StorageName": targetStorageName,
|
||||
"Error": err,
|
||||
})), nil)
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
// Fetch file list
|
||||
replied, err := ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgTransferInfoFetchingFiles, nil)), nil)
|
||||
if err != nil {
|
||||
@@ -138,36 +125,133 @@ func handleTransferCmd(ctx *ext.Context, update *ext.Update) error {
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
// Create task elements
|
||||
elems := make([]batchimport.TaskElement, 0, len(filteredFiles))
|
||||
// Prepare file paths for callback data
|
||||
filePaths := make([]string, 0, len(filteredFiles))
|
||||
var totalSize int64
|
||||
for _, file := range filteredFiles {
|
||||
elem := batchimport.NewTaskElement(sourceStorage, file, targetStorage, targetPath)
|
||||
elems = append(elems, *elem)
|
||||
filePaths = append(filePaths, file.Path)
|
||||
totalSize += file.Size
|
||||
}
|
||||
|
||||
// Create and add task
|
||||
taskID := xid.New().String()
|
||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||
task := batchimport.NewBatchImportTask(
|
||||
taskID,
|
||||
injectCtx,
|
||||
elems,
|
||||
batchimport.NewProgressTracker(replied.ID, userID),
|
||||
true, // IgnoreErrors
|
||||
)
|
||||
|
||||
if err := core.AddTask(injectCtx, task); err != nil {
|
||||
// Build storage selection keyboard
|
||||
markup, err := msgelem.BuildAddSelectStorageKeyboard(storage.GetUserStorages(ctx, userID), tcbdata.Add{
|
||||
TaskType: tasktype.TaskTypeTransfer,
|
||||
TransferSourceStorName: sourceStorageName,
|
||||
TransferSourcePath: sourcePath,
|
||||
TransferFiles: filePaths,
|
||||
TransferTargetPath: targetPath,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to build storage selection keyboard: %s", err)
|
||||
ctx.EditMessage(update.EffectiveChat().GetID(), &tg.MessagesEditMessageRequest{
|
||||
ID: replied.ID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferErrorAddTaskFailed, map[string]any{"Error": err}),
|
||||
Message: i18n.T(i18nk.BotMsgTransferErrorBuildStorageSelectKeyboardFailed, map[string]any{"Error": err}),
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
ctx.EditMessage(update.EffectiveChat().GetID(), &tg.MessagesEditMessageRequest{
|
||||
ID: replied.ID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferInfoFilesSelectStorage, map[string]any{
|
||||
"Count": len(filteredFiles),
|
||||
"SizeMB": fmt.Sprintf("%.2f", float64(totalSize)/(1024*1024)),
|
||||
}),
|
||||
ReplyMarkup: markup,
|
||||
})
|
||||
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
func handleTransferCallback(ctx *ext.Context, userID int64, targetStorage storage.Storage, dirPath string, data tcbdata.Add, msgID int) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Get source storage
|
||||
sourceStorage, err := storage.GetStorageByUserIDAndName(ctx, userID, data.TransferSourceStorName)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to get source storage: %s", err)
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: msgID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferErrorStorageNotFound, map[string]any{"StorageName": data.TransferSourceStorName, "Error": err}),
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
// Check if source storage supports listing
|
||||
listable, ok := sourceStorage.(storage.StorageListable)
|
||||
if !ok {
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: msgID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferErrorStorageNotListable, map[string]any{"StorageName": data.TransferSourceStorName}),
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
// Re-fetch files to get FileInfo (since we only stored paths)
|
||||
// This is necessary to get size and other metadata
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: msgID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferInfoFetchingFiles, nil),
|
||||
})
|
||||
|
||||
allFiles, err := listable.ListFiles(ctx, data.TransferSourcePath)
|
||||
if err != nil {
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: msgID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferErrorListFilesFailed, map[string]any{"Error": err}),
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
// Create a map for quick lookup
|
||||
fileMap := make(map[string]storagetypes.FileInfo)
|
||||
for _, file := range allFiles {
|
||||
fileMap[file.Path] = file
|
||||
}
|
||||
|
||||
// Build task elements for the selected files
|
||||
targetPath := path.Join(dirPath, data.TransferTargetPath)
|
||||
elems := make([]transfer.TaskElement, 0, len(data.TransferFiles))
|
||||
var totalSize int64
|
||||
for _, filePath := range data.TransferFiles {
|
||||
fileInfo, ok := fileMap[filePath]
|
||||
if !ok {
|
||||
logger.Warnf("File not found in source storage: %s", filePath)
|
||||
continue
|
||||
}
|
||||
elem := transfer.NewTaskElement(sourceStorage, fileInfo, targetStorage, targetPath)
|
||||
elems = append(elems, *elem)
|
||||
totalSize += fileInfo.Size
|
||||
}
|
||||
|
||||
if len(elems) == 0 {
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: msgID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferErrorNoFilesToTransfer, nil),
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
// Create and add task
|
||||
taskID := xid.New().String()
|
||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||
task := transfer.NewTransferTask(
|
||||
taskID,
|
||||
injectCtx,
|
||||
elems,
|
||||
transfer.NewProgressTracker(msgID, userID),
|
||||
true, // IgnoreErrors
|
||||
)
|
||||
|
||||
if err := core.AddTask(injectCtx, task); err != nil {
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: msgID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferErrorAddTaskFailed, map[string]any{"Error": err}),
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: msgID,
|
||||
Message: i18n.T(i18nk.BotMsgTransferInfoTaskAdded, map[string]any{
|
||||
"Count": len(elems),
|
||||
"SizeMB": fmt.Sprintf("%.2f", float64(totalSize)/(1024*1024)),
|
||||
|
||||
@@ -53,6 +53,11 @@ func BuildAddSelectStorageKeyboard(stors []storage.Storage, adddata tcbdata.Add)
|
||||
Aria2URIs: adddata.Aria2URIs,
|
||||
YtdlpURLs: adddata.YtdlpURLs,
|
||||
YtdlpFlags: adddata.YtdlpFlags,
|
||||
|
||||
TransferSourceStorName: adddata.TransferSourceStorName,
|
||||
TransferSourcePath: adddata.TransferSourcePath,
|
||||
TransferFiles: adddata.TransferFiles,
|
||||
TransferTargetPath: adddata.TransferTargetPath,
|
||||
}
|
||||
dataid := xid.New().String()
|
||||
err := cache.Set(dataid, data)
|
||||
|
||||
@@ -233,6 +233,7 @@ const (
|
||||
BotMsgTelegraphInfoPromptSelectStorage Key = "bot.msg.telegraph.info_prompt_select_storage"
|
||||
BotMsgTelegraphInfoTitlePrefix Key = "bot.msg.telegraph.info_title_prefix"
|
||||
BotMsgTransferErrorAddTaskFailed Key = "bot.msg.transfer.error_add_task_failed"
|
||||
BotMsgTransferErrorBuildStorageSelectKeyboardFailed Key = "bot.msg.transfer.error_build_storage_select_keyboard_failed"
|
||||
BotMsgTransferErrorInvalidRegex Key = "bot.msg.transfer.error_invalid_regex"
|
||||
BotMsgTransferErrorInvalidSource Key = "bot.msg.transfer.error_invalid_source"
|
||||
BotMsgTransferErrorInvalidTarget Key = "bot.msg.transfer.error_invalid_target"
|
||||
@@ -243,6 +244,7 @@ const (
|
||||
BotMsgTransferErrorStorageNotReadable Key = "bot.msg.transfer.error_storage_not_readable"
|
||||
BotMsgTransferErrorTargetNotFound Key = "bot.msg.transfer.error_target_not_found"
|
||||
BotMsgTransferInfoFetchingFiles Key = "bot.msg.transfer.info_fetching_files"
|
||||
BotMsgTransferInfoFilesSelectStorage Key = "bot.msg.transfer.info_files_select_storage"
|
||||
BotMsgTransferInfoTaskAdded Key = "bot.msg.transfer.info_task_added"
|
||||
BotMsgTransferStartStats Key = "bot.msg.transfer.start_stats"
|
||||
BotMsgTransferUsage Key = "bot.msg.transfer.usage"
|
||||
|
||||
@@ -299,11 +299,11 @@ bot:
|
||||
error_download_failed: "yt-dlp download failed: {{.Error}}"
|
||||
transfer:
|
||||
usage: |
|
||||
Usage: /transfer <source_storage>:/<source_path> <target_storage>:/<target_path> [filter]
|
||||
Usage: /transfer <source_storage>:/<source_path> <target_path> [filter]
|
||||
Examples:
|
||||
/transfer local1:/downloads tg1:/backup
|
||||
/transfer alist1:/media/photos local1:/photos
|
||||
/transfer webdav1:/files tg1:/archive ".*\.mp4$"
|
||||
/transfer local1:/downloads /backup
|
||||
/transfer alist1:/media/photos /photos
|
||||
/transfer webdav1:/files /archive ".*\.mp4$"
|
||||
error_invalid_source: "Invalid source path format, should be: storage_name:/path"
|
||||
error_invalid_target: "Invalid target path format, should be: storage_name:/path"
|
||||
error_storage_not_found: "Storage '{{.StorageName}}' not found or access denied: {{.Error}}"
|
||||
@@ -317,6 +317,8 @@ bot:
|
||||
error_add_task_failed: "Failed to add task: {{.Error}}"
|
||||
info_task_added: "Added {{.Count}} files to transfer queue\nTotal size: {{.SizeMB}} MB\nTask ID: {{.TaskID}}"
|
||||
start_stats: "Total files: {{.Count}}\nTotal size: {{.SizeMB}} MB"
|
||||
info_files_select_storage: "Total {{.Count}} files ({{.SizeMB}} MB), please select target storage"
|
||||
error_build_storage_select_keyboard_failed: "Failed to build storage selection keyboard: {{.Error}}"
|
||||
cancel:
|
||||
usage: "Usage: /cancel <task_id>"
|
||||
error_cancel_failed: "Failed to cancel task: {{.Error}}"
|
||||
@@ -365,15 +367,15 @@ bot:
|
||||
ytdlp_done: "yt-dlp download completed and transferred ({{.Count}} files)\n"
|
||||
downloaded_prefix: "\nDownloaded: "
|
||||
current_speed_prefix: "\nCurrent speed: "
|
||||
transfer_start_prefix: "Importing: "
|
||||
transfer_progress_prefix: "Import progress: "
|
||||
transfer_start_prefix: "Transfering: "
|
||||
transfer_progress_prefix: "Transfer progress: "
|
||||
transfer_uploaded_prefix: "\nUploaded: "
|
||||
transfer_speed_prefix: "\nSpeed: "
|
||||
transfer_remaining_time_prefix: "\nRemaining time: "
|
||||
transfer_processing_prefix: "\nProcessing:\n"
|
||||
transfer_processing_more: "...and {{.Count}} more files\n"
|
||||
transfer_failed_prefix: "Import failed\n"
|
||||
transfer_success_prefix: "Import completed\n"
|
||||
transfer_failed_prefix: "Transfer failed\n"
|
||||
transfer_success_prefix: "Transfer completed\n"
|
||||
transfer_total_files_prefix: "\nTotal files: "
|
||||
transfer_total_size_prefix: "\nTotal size: "
|
||||
transfer_elapsed_time_prefix: "\nElapsed time: "
|
||||
|
||||
@@ -300,11 +300,11 @@ bot:
|
||||
error_download_failed: "yt-dlp 下载失败: {{.Error}}"
|
||||
transfer:
|
||||
usage: |
|
||||
用法: /transfer <source_storage>:/<source_path> <target_storage>:/<target_path> [filter]
|
||||
用法: /transfer <source_storage>:/<source_path> <target_path> [filter]
|
||||
示例:
|
||||
/transfer local1:/downloads tg1:/backup
|
||||
/transfer alist1:/media/photos local1:/photos
|
||||
/transfer webdav1:/files tg1:/archive ".*\.mp4$"
|
||||
/transfer local1:/downloads /backup
|
||||
/transfer alist1:/media/photos /photos
|
||||
/transfer webdav1:/files /archive ".*\.mp4$"
|
||||
error_invalid_source: "源路径格式无效,应为: storage_name:/path"
|
||||
error_invalid_target: "目标路径格式无效,应为: storage_name:/path"
|
||||
error_storage_not_found: "存储端 '{{.StorageName}}' 不存在或您无权访问: {{.Error}}"
|
||||
@@ -318,6 +318,8 @@ bot:
|
||||
error_add_task_failed: "添加任务失败: {{.Error}}"
|
||||
info_task_added: "已添加 {{.Count}} 个文件到传输队列\n总大小: {{.SizeMB}} MB\n任务 ID: {{.TaskID}}"
|
||||
start_stats: "总文件数: {{.Count}}\n总大小: {{.SizeMB}} MB"
|
||||
info_files_select_storage: "共 {{.Count}} 个文件 (总大小: {{.SizeMB}} MB),请选择目标存储位置"
|
||||
error_build_storage_select_keyboard_failed: "构建存储选择键盘失败: {{.Error}}"
|
||||
cancel:
|
||||
usage: "用法: /cancel <task_id>"
|
||||
error_cancel_failed: "取消任务失败: {{.Error}}"
|
||||
@@ -366,15 +368,15 @@ bot:
|
||||
ytdlp_done: "yt-dlp 下载完成并已转存 ({{.Count}} 个文件)\n"
|
||||
downloaded_prefix: "\n已下载: "
|
||||
current_speed_prefix: "\n当前速度: "
|
||||
transfer_start_prefix: "正在导入: "
|
||||
transfer_progress_prefix: "导入进度: "
|
||||
transfer_start_prefix: "正在转存: "
|
||||
transfer_progress_prefix: "转存进度: "
|
||||
transfer_uploaded_prefix: "\n已上传: "
|
||||
transfer_speed_prefix: "\n速度: "
|
||||
transfer_remaining_time_prefix: "\n剩余时间: "
|
||||
transfer_processing_prefix: "\n正在处理:\n"
|
||||
transfer_processing_more: "...和其他 {{.Count}} 个文件\n"
|
||||
transfer_failed_prefix: "导入失败\n"
|
||||
transfer_success_prefix: "导入完成\n"
|
||||
transfer_failed_prefix: "转存失败\n"
|
||||
transfer_success_prefix: "转存完成\n"
|
||||
transfer_total_files_prefix: "\n总文件数: "
|
||||
transfer_total_size_prefix: "\n总大小: "
|
||||
transfer_elapsed_time_prefix: "\n耗时: "
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package batchimport
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package batchimport
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package batchimport
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -44,7 +44,7 @@ func (t *Task) Title() string {
|
||||
|
||||
// Type implements core.Executable.
|
||||
func (t *Task) Type() tasktype.TaskType {
|
||||
return tasktype.TaskTypeBatchimport
|
||||
return tasktype.TaskTypeTransfer
|
||||
}
|
||||
|
||||
// TaskID implements core.Executable.
|
||||
@@ -69,7 +69,7 @@ func NewTaskElement(
|
||||
}
|
||||
}
|
||||
|
||||
func NewBatchImportTask(
|
||||
func NewTransferTask(
|
||||
id string,
|
||||
ctx context.Context,
|
||||
elems []TaskElement,
|
||||
@@ -1,4 +1,4 @@
|
||||
package batchimport
|
||||
package transfer
|
||||
|
||||
type TaskElementInfo interface {
|
||||
FileName() string
|
||||
@@ -1,5 +1,5 @@
|
||||
package tasktype
|
||||
|
||||
//go:generate go-enum --values --names --flag --nocase
|
||||
// ENUM(tgfiles,tphpics,parseditem,directlinks,aria2,ytdlp,batchimport)
|
||||
// ENUM(tgfiles,tphpics,parseditem,directlinks,aria2,ytdlp,transfer)
|
||||
type TaskType string
|
||||
|
||||
@@ -24,8 +24,8 @@ const (
|
||||
TaskTypeAria2 TaskType = "aria2"
|
||||
// TaskTypeYtdlp is a TaskType of type ytdlp.
|
||||
TaskTypeYtdlp TaskType = "ytdlp"
|
||||
// TaskTypeBatchimport is a TaskType of type batchimport.
|
||||
TaskTypeBatchimport TaskType = "batchimport"
|
||||
// TaskTypeTransfer is a TaskType of type transfer.
|
||||
TaskTypeTransfer TaskType = "transfer"
|
||||
)
|
||||
|
||||
var ErrInvalidTaskType = fmt.Errorf("not a valid TaskType, try [%s]", strings.Join(_TaskTypeNames, ", "))
|
||||
@@ -37,7 +37,7 @@ var _TaskTypeNames = []string{
|
||||
string(TaskTypeDirectlinks),
|
||||
string(TaskTypeAria2),
|
||||
string(TaskTypeYtdlp),
|
||||
string(TaskTypeBatchimport),
|
||||
string(TaskTypeTransfer),
|
||||
}
|
||||
|
||||
// TaskTypeNames returns a list of possible string values of TaskType.
|
||||
@@ -56,7 +56,7 @@ func TaskTypeValues() []TaskType {
|
||||
TaskTypeDirectlinks,
|
||||
TaskTypeAria2,
|
||||
TaskTypeYtdlp,
|
||||
TaskTypeBatchimport,
|
||||
TaskTypeTransfer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ var _TaskTypeValue = map[string]TaskType{
|
||||
"directlinks": TaskTypeDirectlinks,
|
||||
"aria2": TaskTypeAria2,
|
||||
"ytdlp": TaskTypeYtdlp,
|
||||
"batchimport": TaskTypeBatchimport,
|
||||
"transfer": TaskTypeTransfer,
|
||||
}
|
||||
|
||||
// ParseTaskType attempts to convert a string to a TaskType.
|
||||
|
||||
@@ -50,6 +50,11 @@ type Add struct {
|
||||
// ytdlp
|
||||
YtdlpURLs []string
|
||||
YtdlpFlags []string
|
||||
// transfer
|
||||
TransferSourceStorName string
|
||||
TransferSourcePath string
|
||||
TransferFiles []string // file paths relative to source storage
|
||||
TransferTargetPath string
|
||||
}
|
||||
|
||||
type SetDefaultStorage struct {
|
||||
|
||||
Reference in New Issue
Block a user