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