Skip to content

Commit dd0dea8

Browse files
committed
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.
1 parent 3d20fbd commit dd0dea8

File tree

13 files changed

+160
-58
lines changed

13 files changed

+160
-58
lines changed

client/bot/handlers/add_task.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ func handleAddCallback(ctx *ext.Context, update *ext.Update) error {
101101
shortcut.CreateAndAddAria2TaskWithEdit(ctx, selectedStorage, dirPath, data.Aria2URIs, client, msgID, userID)
102102
case tasktype.TaskTypeYtdlp:
103103
shortcut.CreateAndAddYtdlpTaskWithEdit(ctx, selectedStorage, dirPath, data.YtdlpURLs, data.YtdlpFlags, msgID, userID)
104+
case tasktype.TaskTypeTransfer:
105+
return handleTransferCallback(ctx, userID, selectedStorage, dirPath, data, msgID)
104106
default:
105107
return fmt.Errorf("unexcept task type: %s", data.TaskType)
106108
}

client/bot/handlers/transfer.go

Lines changed: 114 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@ package handlers
22

33
import (
44
"fmt"
5+
"path"
56
"regexp"
67
"strings"
78

89
"github.com/celestix/gotgproto/dispatcher"
910
"github.com/celestix/gotgproto/ext"
1011
"github.com/charmbracelet/log"
1112
"github.com/gotd/td/tg"
13+
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
1214
"github.com/krau/SaveAny-Bot/common/i18n"
1315
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
1416
"github.com/krau/SaveAny-Bot/common/utils/strutil"
1517
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
1618
"github.com/krau/SaveAny-Bot/core"
17-
"github.com/krau/SaveAny-Bot/core/tasks/batchimport"
19+
"github.com/krau/SaveAny-Bot/core/tasks/transfer"
20+
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
1821
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
22+
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
1923
"github.com/krau/SaveAny-Bot/storage"
2024
"github.com/rs/xid"
2125
)
@@ -38,14 +42,8 @@ func handleTransferCmd(ctx *ext.Context, update *ext.Update) error {
3842
sourceStorageName := sourceParts[0]
3943
sourcePath := sourceParts[1]
4044

41-
// Parse target: storage_name:/path
42-
targetParts := strings.SplitN(args[2], ":", 2)
43-
if len(targetParts) != 2 {
44-
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgTransferErrorInvalidTarget, nil)), nil)
45-
return dispatcher.EndGroups
46-
}
47-
targetStorageName := targetParts[0]
48-
targetPath := targetParts[1]
45+
// Parse target path (without storage name)
46+
targetPath := args[2]
4947

5048
userID := update.GetUserChat().GetID()
5149

@@ -78,17 +76,6 @@ func handleTransferCmd(ctx *ext.Context, update *ext.Update) error {
7876
return dispatcher.EndGroups
7977
}
8078

81-
// Get target storage
82-
targetStorage, err := storage.GetStorageByUserIDAndName(ctx, userID, targetStorageName)
83-
if err != nil {
84-
logger.Errorf("Failed to get target storage by user ID and name: %s", err)
85-
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgTransferErrorTargetNotFound, map[string]any{
86-
"StorageName": targetStorageName,
87-
"Error": err,
88-
})), nil)
89-
return dispatcher.EndGroups
90-
}
91-
9279
// Fetch file list
9380
replied, err := ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgTransferInfoFetchingFiles, nil)), nil)
9481
if err != nil {
@@ -138,36 +125,133 @@ func handleTransferCmd(ctx *ext.Context, update *ext.Update) error {
138125
return dispatcher.EndGroups
139126
}
140127

141-
// Create task elements
142-
elems := make([]batchimport.TaskElement, 0, len(filteredFiles))
128+
// Prepare file paths for callback data
129+
filePaths := make([]string, 0, len(filteredFiles))
143130
var totalSize int64
144131
for _, file := range filteredFiles {
145-
elem := batchimport.NewTaskElement(sourceStorage, file, targetStorage, targetPath)
146-
elems = append(elems, *elem)
132+
filePaths = append(filePaths, file.Path)
147133
totalSize += file.Size
148134
}
149135

136+
// Build storage selection keyboard
137+
markup, err := msgelem.BuildAddSelectStorageKeyboard(storage.GetUserStorages(ctx, userID), tcbdata.Add{
138+
TaskType: tasktype.TaskTypeTransfer,
139+
TransferSourceStorName: sourceStorageName,
140+
TransferSourcePath: sourcePath,
141+
TransferFiles: filePaths,
142+
TransferTargetPath: targetPath,
143+
})
144+
if err != nil {
145+
logger.Errorf("Failed to build storage selection keyboard: %s", err)
146+
ctx.EditMessage(update.EffectiveChat().GetID(), &tg.MessagesEditMessageRequest{
147+
ID: replied.ID,
148+
Message: i18n.T(i18nk.BotMsgTransferErrorBuildStorageSelectKeyboardFailed, map[string]any{"Error": err}),
149+
})
150+
return dispatcher.EndGroups
151+
}
152+
153+
ctx.EditMessage(update.EffectiveChat().GetID(), &tg.MessagesEditMessageRequest{
154+
ID: replied.ID,
155+
Message: i18n.T(i18nk.BotMsgTransferInfoFilesSelectStorage, map[string]any{
156+
"Count": len(filteredFiles),
157+
"SizeMB": fmt.Sprintf("%.2f", float64(totalSize)/(1024*1024)),
158+
}),
159+
ReplyMarkup: markup,
160+
})
161+
162+
return dispatcher.EndGroups
163+
}
164+
165+
func handleTransferCallback(ctx *ext.Context, userID int64, targetStorage storage.Storage, dirPath string, data tcbdata.Add, msgID int) error {
166+
logger := log.FromContext(ctx)
167+
168+
// Get source storage
169+
sourceStorage, err := storage.GetStorageByUserIDAndName(ctx, userID, data.TransferSourceStorName)
170+
if err != nil {
171+
logger.Errorf("Failed to get source storage: %s", err)
172+
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
173+
ID: msgID,
174+
Message: i18n.T(i18nk.BotMsgTransferErrorStorageNotFound, map[string]any{"StorageName": data.TransferSourceStorName, "Error": err}),
175+
})
176+
return dispatcher.EndGroups
177+
}
178+
179+
// Check if source storage supports listing
180+
listable, ok := sourceStorage.(storage.StorageListable)
181+
if !ok {
182+
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
183+
ID: msgID,
184+
Message: i18n.T(i18nk.BotMsgTransferErrorStorageNotListable, map[string]any{"StorageName": data.TransferSourceStorName}),
185+
})
186+
return dispatcher.EndGroups
187+
}
188+
189+
// Re-fetch files to get FileInfo (since we only stored paths)
190+
// This is necessary to get size and other metadata
191+
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
192+
ID: msgID,
193+
Message: i18n.T(i18nk.BotMsgTransferInfoFetchingFiles, nil),
194+
})
195+
196+
allFiles, err := listable.ListFiles(ctx, data.TransferSourcePath)
197+
if err != nil {
198+
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
199+
ID: msgID,
200+
Message: i18n.T(i18nk.BotMsgTransferErrorListFilesFailed, map[string]any{"Error": err}),
201+
})
202+
return dispatcher.EndGroups
203+
}
204+
205+
// Create a map for quick lookup
206+
fileMap := make(map[string]storagetypes.FileInfo)
207+
for _, file := range allFiles {
208+
fileMap[file.Path] = file
209+
}
210+
211+
// Build task elements for the selected files
212+
targetPath := path.Join(dirPath, data.TransferTargetPath)
213+
elems := make([]transfer.TaskElement, 0, len(data.TransferFiles))
214+
var totalSize int64
215+
for _, filePath := range data.TransferFiles {
216+
fileInfo, ok := fileMap[filePath]
217+
if !ok {
218+
logger.Warnf("File not found in source storage: %s", filePath)
219+
continue
220+
}
221+
elem := transfer.NewTaskElement(sourceStorage, fileInfo, targetStorage, targetPath)
222+
elems = append(elems, *elem)
223+
totalSize += fileInfo.Size
224+
}
225+
226+
if len(elems) == 0 {
227+
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
228+
ID: msgID,
229+
Message: i18n.T(i18nk.BotMsgTransferErrorNoFilesToTransfer, nil),
230+
})
231+
return dispatcher.EndGroups
232+
}
233+
150234
// Create and add task
151235
taskID := xid.New().String()
152236
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
153-
task := batchimport.NewBatchImportTask(
237+
task := transfer.NewTransferTask(
154238
taskID,
155239
injectCtx,
156240
elems,
157-
batchimport.NewProgressTracker(replied.ID, userID),
241+
transfer.NewProgressTracker(msgID, userID),
158242
true, // IgnoreErrors
159243
)
160244

161245
if err := core.AddTask(injectCtx, task); err != nil {
162-
ctx.EditMessage(update.EffectiveChat().GetID(), &tg.MessagesEditMessageRequest{
163-
ID: replied.ID,
246+
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
247+
ID: msgID,
164248
Message: i18n.T(i18nk.BotMsgTransferErrorAddTaskFailed, map[string]any{"Error": err}),
165249
})
166250
return dispatcher.EndGroups
167251
}
168252

169-
ctx.EditMessage(update.EffectiveChat().GetID(), &tg.MessagesEditMessageRequest{
170-
ID: replied.ID,
253+
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
254+
ID: msgID,
171255
Message: i18n.T(i18nk.BotMsgTransferInfoTaskAdded, map[string]any{
172256
"Count": len(elems),
173257
"SizeMB": fmt.Sprintf("%.2f", float64(totalSize)/(1024*1024)),

client/bot/handlers/utils/msgelem/storage.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ func BuildAddSelectStorageKeyboard(stors []storage.Storage, adddata tcbdata.Add)
5353
Aria2URIs: adddata.Aria2URIs,
5454
YtdlpURLs: adddata.YtdlpURLs,
5555
YtdlpFlags: adddata.YtdlpFlags,
56+
57+
TransferSourceStorName: adddata.TransferSourceStorName,
58+
TransferSourcePath: adddata.TransferSourcePath,
59+
TransferFiles: adddata.TransferFiles,
60+
TransferTargetPath: adddata.TransferTargetPath,
5661
}
5762
dataid := xid.New().String()
5863
err := cache.Set(dataid, data)

common/i18n/i18nk/keys.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/i18n/locale/en.yaml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,11 @@ bot:
299299
error_download_failed: "yt-dlp download failed: {{.Error}}"
300300
transfer:
301301
usage: |
302-
Usage: /transfer <source_storage>:/<source_path> <target_storage>:/<target_path> [filter]
302+
Usage: /transfer <source_storage>:/<source_path> <target_path> [filter]
303303
Examples:
304-
/transfer local1:/downloads tg1:/backup
305-
/transfer alist1:/media/photos local1:/photos
306-
/transfer webdav1:/files tg1:/archive ".*\.mp4$"
304+
/transfer local1:/downloads /backup
305+
/transfer alist1:/media/photos /photos
306+
/transfer webdav1:/files /archive ".*\.mp4$"
307307
error_invalid_source: "Invalid source path format, should be: storage_name:/path"
308308
error_invalid_target: "Invalid target path format, should be: storage_name:/path"
309309
error_storage_not_found: "Storage '{{.StorageName}}' not found or access denied: {{.Error}}"
@@ -317,6 +317,8 @@ bot:
317317
error_add_task_failed: "Failed to add task: {{.Error}}"
318318
info_task_added: "Added {{.Count}} files to transfer queue\nTotal size: {{.SizeMB}} MB\nTask ID: {{.TaskID}}"
319319
start_stats: "Total files: {{.Count}}\nTotal size: {{.SizeMB}} MB"
320+
info_files_select_storage: "Total {{.Count}} files ({{.SizeMB}} MB), please select target storage"
321+
error_build_storage_select_keyboard_failed: "Failed to build storage selection keyboard: {{.Error}}"
320322
cancel:
321323
usage: "Usage: /cancel <task_id>"
322324
error_cancel_failed: "Failed to cancel task: {{.Error}}"
@@ -365,15 +367,15 @@ bot:
365367
ytdlp_done: "yt-dlp download completed and transferred ({{.Count}} files)\n"
366368
downloaded_prefix: "\nDownloaded: "
367369
current_speed_prefix: "\nCurrent speed: "
368-
transfer_start_prefix: "Importing: "
369-
transfer_progress_prefix: "Import progress: "
370+
transfer_start_prefix: "Transfering: "
371+
transfer_progress_prefix: "Transfer progress: "
370372
transfer_uploaded_prefix: "\nUploaded: "
371373
transfer_speed_prefix: "\nSpeed: "
372374
transfer_remaining_time_prefix: "\nRemaining time: "
373375
transfer_processing_prefix: "\nProcessing:\n"
374376
transfer_processing_more: "...and {{.Count}} more files\n"
375-
transfer_failed_prefix: "Import failed\n"
376-
transfer_success_prefix: "Import completed\n"
377+
transfer_failed_prefix: "Transfer failed\n"
378+
transfer_success_prefix: "Transfer completed\n"
377379
transfer_total_files_prefix: "\nTotal files: "
378380
transfer_total_size_prefix: "\nTotal size: "
379381
transfer_elapsed_time_prefix: "\nElapsed time: "

common/i18n/locale/zh-Hans.yaml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,11 @@ bot:
300300
error_download_failed: "yt-dlp 下载失败: {{.Error}}"
301301
transfer:
302302
usage: |
303-
用法: /transfer <source_storage>:/<source_path> <target_storage>:/<target_path> [filter]
303+
用法: /transfer <source_storage>:/<source_path> <target_path> [filter]
304304
示例:
305-
/transfer local1:/downloads tg1:/backup
306-
/transfer alist1:/media/photos local1:/photos
307-
/transfer webdav1:/files tg1:/archive ".*\.mp4$"
305+
/transfer local1:/downloads /backup
306+
/transfer alist1:/media/photos /photos
307+
/transfer webdav1:/files /archive ".*\.mp4$"
308308
error_invalid_source: "源路径格式无效,应为: storage_name:/path"
309309
error_invalid_target: "目标路径格式无效,应为: storage_name:/path"
310310
error_storage_not_found: "存储端 '{{.StorageName}}' 不存在或您无权访问: {{.Error}}"
@@ -318,6 +318,8 @@ bot:
318318
error_add_task_failed: "添加任务失败: {{.Error}}"
319319
info_task_added: "已添加 {{.Count}} 个文件到传输队列\n总大小: {{.SizeMB}} MB\n任务 ID: {{.TaskID}}"
320320
start_stats: "总文件数: {{.Count}}\n总大小: {{.SizeMB}} MB"
321+
info_files_select_storage: "共 {{.Count}} 个文件 (总大小: {{.SizeMB}} MB),请选择目标存储位置"
322+
error_build_storage_select_keyboard_failed: "构建存储选择键盘失败: {{.Error}}"
321323
cancel:
322324
usage: "用法: /cancel <task_id>"
323325
error_cancel_failed: "取消任务失败: {{.Error}}"
@@ -366,15 +368,15 @@ bot:
366368
ytdlp_done: "yt-dlp 下载完成并已转存 ({{.Count}} 个文件)\n"
367369
downloaded_prefix: "\n已下载: "
368370
current_speed_prefix: "\n当前速度: "
369-
transfer_start_prefix: "正在导入: "
370-
transfer_progress_prefix: "导入进度: "
371+
transfer_start_prefix: "正在转存: "
372+
transfer_progress_prefix: "转存进度: "
371373
transfer_uploaded_prefix: "\n已上传: "
372374
transfer_speed_prefix: "\n速度: "
373375
transfer_remaining_time_prefix: "\n剩余时间: "
374376
transfer_processing_prefix: "\n正在处理:\n"
375377
transfer_processing_more: "...和其他 {{.Count}} 个文件\n"
376-
transfer_failed_prefix: "导入失败\n"
377-
transfer_success_prefix: "导入完成\n"
378+
transfer_failed_prefix: "转存失败\n"
379+
transfer_success_prefix: "转存完成\n"
378380
transfer_total_files_prefix: "\n总文件数: "
379381
transfer_total_size_prefix: "\n总大小: "
380382
transfer_elapsed_time_prefix: "\n耗时: "
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package batchimport
1+
package transfer
22

33
import (
44
"context"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package batchimport
1+
package transfer
22

33
import (
44
"context"
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package batchimport
1+
package transfer
22

33
import (
44
"context"
@@ -44,7 +44,7 @@ func (t *Task) Title() string {
4444

4545
// Type implements core.Executable.
4646
func (t *Task) Type() tasktype.TaskType {
47-
return tasktype.TaskTypeBatchimport
47+
return tasktype.TaskTypeTransfer
4848
}
4949

5050
// TaskID implements core.Executable.
@@ -69,7 +69,7 @@ func NewTaskElement(
6969
}
7070
}
7171

72-
func NewBatchImportTask(
72+
func NewTransferTask(
7373
id string,
7474
ctx context.Context,
7575
elems []TaskElement,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package batchimport
1+
package transfer
22

33
type TaskElementInfo interface {
44
FileName() string

0 commit comments

Comments
 (0)