mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-06-07 16:39:55 +08:00
Merge pull request #204 from Rain-kl/feat/save-strategy
新增功能: 重名文件时选择保存策略 - 重命名,覆盖,跳过
This commit is contained in:
@@ -73,14 +73,16 @@ func handleAddCallback(ctx *ext.Context, update *ext.Update) error {
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
dirPath = dir.Path
|
dirPath = dir.Path
|
||||||
|
} else if data.SelectedDirPath != "" {
|
||||||
|
dirPath = data.SelectedDirPath
|
||||||
}
|
}
|
||||||
|
|
||||||
switch data.TaskType {
|
switch data.TaskType {
|
||||||
case tasktype.TaskTypeTgfiles:
|
case tasktype.TaskTypeTgfiles:
|
||||||
if data.AsBatch {
|
if data.AsBatch {
|
||||||
return shortcut.CreateAndAddBatchTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files, msgID)
|
return shortcut.CreateAndAddBatchTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files, msgID, data.ConflictStrategy)
|
||||||
}
|
}
|
||||||
return shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files[0], msgID)
|
return shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files[0], msgID, data.ConflictStrategy)
|
||||||
case tasktype.TaskTypeTphpics:
|
case tasktype.TaskTypeTphpics:
|
||||||
return shortcut.CreateAndAddtelegraphWithEdit(ctx, userID, data.TphPageNode, data.TphDirPath, data.TphPics, selectedStorage, msgID)
|
return shortcut.CreateAndAddtelegraphWithEdit(ctx, userID, data.TphPageNode, data.TphDirPath, data.TphPics, selectedStorage, msgID)
|
||||||
case tasktype.TaskTypeParseditem:
|
case tasktype.TaskTypeParseditem:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/celestix/gotgproto/dispatcher"
|
"github.com/celestix/gotgproto/dispatcher"
|
||||||
"github.com/celestix/gotgproto/ext"
|
"github.com/celestix/gotgproto/ext"
|
||||||
"github.com/gotd/td/tg"
|
"github.com/gotd/td/tg"
|
||||||
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/conflictutil"
|
||||||
"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/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
@@ -26,6 +27,10 @@ func handleConfigCmd(ctx *ext.Context, update *ext.Update) error {
|
|||||||
Text: i18n.T(i18nk.BotMsgConfigButtonFilenameStrategy),
|
Text: i18n.T(i18nk.BotMsgConfigButtonFilenameStrategy),
|
||||||
Data: fmt.Appendf(nil, "%s %s", tcbdata.TypeConfig, "fnamest"),
|
Data: fmt.Appendf(nil, "%s %s", tcbdata.TypeConfig, "fnamest"),
|
||||||
},
|
},
|
||||||
|
&tg.KeyboardButtonCallback{
|
||||||
|
Text: i18n.T(i18nk.BotMsgConfigButtonConflictStrategy),
|
||||||
|
Data: fmt.Appendf(nil, "%s %s", tcbdata.TypeConfig, "conflictst"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -51,6 +56,8 @@ func handleConfigCallback(ctx *ext.Context, update *ext.Update) error {
|
|||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "fnamest":
|
case "fnamest":
|
||||||
return handleConfigFnameSTCallback(ctx, update)
|
return handleConfigFnameSTCallback(ctx, update)
|
||||||
|
case "conflictst":
|
||||||
|
return handleConfigConflictSTCallback(ctx, update)
|
||||||
default:
|
default:
|
||||||
return invaildDataAnswer()
|
return invaildDataAnswer()
|
||||||
}
|
}
|
||||||
@@ -110,6 +117,55 @@ func handleConfigFnameSTCallback(ctx *ext.Context, update *ext.Update) error {
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleConfigConflictSTCallback(ctx *ext.Context, update *ext.Update) error {
|
||||||
|
userID := update.CallbackQuery.GetUserID()
|
||||||
|
user, err := database.GetUserByChatID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
args := strings.Fields(string(update.CallbackQuery.Data))
|
||||||
|
if len(args) == 3 {
|
||||||
|
selected := args[2]
|
||||||
|
if !tcbdata.IsConflictStrategy(selected) {
|
||||||
|
return fmt.Errorf("invalid conflict strategy: %s", selected)
|
||||||
|
}
|
||||||
|
user.ConflictStrategy = selected
|
||||||
|
if err := database.UpdateUser(ctx, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: update.CallbackQuery.GetMsgID(),
|
||||||
|
Message: i18n.T(i18nk.BotMsgConfigInfoConflictStrategySet, map[string]any{
|
||||||
|
"Strategy": conflictutil.Display(selected),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := tcbdata.ConflictStrategyValues()
|
||||||
|
rows := make([]tg.KeyboardButtonRow, 0, len(opts))
|
||||||
|
for _, opt := range opts {
|
||||||
|
rows = append(rows, tg.KeyboardButtonRow{
|
||||||
|
Buttons: []tg.KeyboardButtonClass{
|
||||||
|
&tg.KeyboardButtonCallback{
|
||||||
|
Text: conflictutil.Display(opt),
|
||||||
|
Data: fmt.Appendf(nil, "%s %s %s", tcbdata.TypeConfig, "conflictst", opt),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
markup := &tg.ReplyInlineMarkup{Rows: rows}
|
||||||
|
currentSt := conflictutil.EffectiveStrategy(user)
|
||||||
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: update.CallbackQuery.GetMsgID(),
|
||||||
|
Message: i18n.T(i18nk.BotMsgConfigPromptSelectConflictStrategy, map[string]any{
|
||||||
|
"Strategy": conflictutil.Display(currentSt),
|
||||||
|
}),
|
||||||
|
ReplyMarkup: markup,
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
|
||||||
func handleConfigFnameTmpl(ctx *ext.Context, update *ext.Update) error {
|
func handleConfigFnameTmpl(ctx *ext.Context, update *ext.Update) error {
|
||||||
userID := update.GetUserChat().GetID()
|
userID := update.GetUserChat().GetID()
|
||||||
user, err := database.GetUserByChatID(ctx, userID)
|
user, err := database.GetUserByChatID(ctx, userID)
|
||||||
|
|||||||
55
client/bot/handlers/utils/conflictutil/conflict.go
Normal file
55
client/bot/handlers/utils/conflictutil/conflict.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package conflictutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||||
|
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
||||||
|
"github.com/krau/SaveAny-Bot/database"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxConflictLines = 10
|
||||||
|
|
||||||
|
func EffectiveStrategy(user *database.User) string {
|
||||||
|
if user != nil && tcbdata.IsConflictStrategy(user.ConflictStrategy) {
|
||||||
|
return user.ConflictStrategy
|
||||||
|
}
|
||||||
|
return tcbdata.ConflictStrategyRename
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveStrategy(user *database.User, override string) string {
|
||||||
|
if tcbdata.IsConflictStrategy(override) {
|
||||||
|
return override
|
||||||
|
}
|
||||||
|
return EffectiveStrategy(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Display(strategy string) string {
|
||||||
|
switch strategy {
|
||||||
|
case tcbdata.ConflictStrategyRename:
|
||||||
|
return i18n.T(i18nk.BotMsgConfigConflictStrategyRename, nil)
|
||||||
|
case tcbdata.ConflictStrategyAsk:
|
||||||
|
return i18n.T(i18nk.BotMsgConfigConflictStrategyAsk, nil)
|
||||||
|
case tcbdata.ConflictStrategyOverwrite:
|
||||||
|
return i18n.T(i18nk.BotMsgConfigConflictStrategyOverwrite, nil)
|
||||||
|
case tcbdata.ConflictStrategySkip:
|
||||||
|
return i18n.T(i18nk.BotMsgConfigConflictStrategySkip, nil)
|
||||||
|
default:
|
||||||
|
return strategy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatPaths(conflicts []string) string {
|
||||||
|
if len(conflicts) <= maxConflictLines {
|
||||||
|
return strings.Join(conflicts, "\n")
|
||||||
|
}
|
||||||
|
return strings.Join(conflicts[:maxConflictLines], "\n") + "\n" + i18n.T(i18nk.BotMsgCommonPromptConflictMoreFiles, map[string]any{
|
||||||
|
"Count": len(conflicts) - maxConflictLines,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatPath(storageName, storagePath string) string {
|
||||||
|
return fmt.Sprintf("[%s]:%s", storageName, storagePath)
|
||||||
|
}
|
||||||
@@ -38,6 +38,8 @@ func BuildAddSelectStorageKeyboard(stors []storage.Storage, adddata tcbdata.Add)
|
|||||||
data := tcbdata.Add{
|
data := tcbdata.Add{
|
||||||
TaskType: taskType,
|
TaskType: taskType,
|
||||||
SelectedStorName: storage.Name(),
|
SelectedStorName: storage.Name(),
|
||||||
|
SelectedDirPath: adddata.SelectedDirPath,
|
||||||
|
ConflictStrategy: adddata.ConflictStrategy,
|
||||||
|
|
||||||
Files: adddata.Files,
|
Files: adddata.Files,
|
||||||
AsBatch: len(adddata.Files) > 1,
|
AsBatch: len(adddata.Files) > 1,
|
||||||
@@ -109,6 +111,38 @@ func BuildAddOneSelectStorageMessage(ctx context.Context, stors []storage.Storag
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildConflictStrategyMarkup(adddata tcbdata.Add) (*tg.ReplyInlineMarkup, error) {
|
||||||
|
type option struct {
|
||||||
|
text string
|
||||||
|
strategy string
|
||||||
|
}
|
||||||
|
options := []option{
|
||||||
|
{text: i18n.T(i18nk.BotMsgCommonButtonConflictRename, nil), strategy: tcbdata.ConflictStrategyRename},
|
||||||
|
{text: i18n.T(i18nk.BotMsgCommonButtonConflictOverwrite, nil), strategy: tcbdata.ConflictStrategyOverwrite},
|
||||||
|
{text: i18n.T(i18nk.BotMsgCommonButtonConflictSkip, nil), strategy: tcbdata.ConflictStrategySkip},
|
||||||
|
}
|
||||||
|
buttons := make([]tg.KeyboardButtonClass, 0, len(options))
|
||||||
|
for _, opt := range options {
|
||||||
|
data := adddata
|
||||||
|
data.ConflictStrategy = opt.strategy
|
||||||
|
dataid := xid.New().String()
|
||||||
|
if err := cache.Set(dataid, data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buttons = append(buttons, &tg.KeyboardButtonCallback{
|
||||||
|
Text: opt.text,
|
||||||
|
Data: fmt.Appendf(nil, "%s %s", tcbdata.TypeAdd, dataid),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rows := make([]tg.KeyboardButtonRow, 0, len(buttons))
|
||||||
|
for _, button := range buttons {
|
||||||
|
rows = append(rows, tg.KeyboardButtonRow{
|
||||||
|
Buttons: []tg.KeyboardButtonClass{button},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &tg.ReplyInlineMarkup{Rows: rows}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Builds the inline keyboard for setting default storage
|
// Builds the inline keyboard for setting default storage
|
||||||
func BuildSetDefaultStorageMarkup(
|
func BuildSetDefaultStorageMarkup(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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/conflictutil"
|
||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/ruleutil"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/ruleutil"
|
||||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||||
@@ -17,14 +18,17 @@ import (
|
|||||||
"github.com/krau/SaveAny-Bot/core/tasks/batchtfile"
|
"github.com/krau/SaveAny-Bot/core/tasks/batchtfile"
|
||||||
tftask "github.com/krau/SaveAny-Bot/core/tasks/tfile"
|
tftask "github.com/krau/SaveAny-Bot/core/tasks/tfile"
|
||||||
"github.com/krau/SaveAny-Bot/database"
|
"github.com/krau/SaveAny-Bot/database"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
"github.com/krau/SaveAny-Bot/storage"
|
"github.com/krau/SaveAny-Bot/storage"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 创建一个 tfile.TGFileTask 并添加到任务队列中, 以编辑消息的方式反馈结果
|
// 创建一个 tfile.TGFileTask 并添加到任务队列中, 以编辑消息的方式反馈结果
|
||||||
func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, file tfile.TGFileMessage, trackMsgID int) error {
|
func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, file tfile.TGFileMessage, trackMsgID int, conflictStrategy ...string) error {
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
|
strategy := selectedConflictStrategy(conflictStrategy)
|
||||||
user, err := database.GetUserByChatID(ctx, userID)
|
user, err := database.GetUserByChatID(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to get user by chat ID: %s", err)
|
logger.Errorf("Failed to get user by chat ID: %s", err)
|
||||||
@@ -36,6 +40,7 @@ func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage
|
|||||||
})
|
})
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
strategy = conflictutil.ResolveStrategy(user, strategy)
|
||||||
if user.ApplyRule && user.Rules != nil {
|
if user.ApplyRule && user.Rules != nil {
|
||||||
matched, matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
matched, matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||||
if !matched {
|
if !matched {
|
||||||
@@ -60,7 +65,26 @@ func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage
|
|||||||
}
|
}
|
||||||
startCreateTask:
|
startCreateTask:
|
||||||
storagePath := path.Join(dirPath, file.Name())
|
storagePath := path.Join(dirPath, file.Name())
|
||||||
|
if strategy == tcbdata.ConflictStrategyAsk || strategy == tcbdata.ConflictStrategySkip {
|
||||||
|
exists := stor.Exists(ctx, storagePath)
|
||||||
|
if exists && strategy == tcbdata.ConflictStrategyAsk {
|
||||||
|
return promptTGFileConflictStrategy(ctx, userID, stor.Name(), dirPath, []tfile.TGFileMessage{file}, false, []string{conflictutil.FormatPath(stor.Name(), storagePath)}, trackMsgID)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: trackMsgID,
|
||||||
|
Message: i18n.T(i18nk.BotMsgCommonInfoAllConflictFilesSkipped, map[string]any{
|
||||||
|
"Skipped": file.Name(),
|
||||||
|
}),
|
||||||
|
ReplyMarkup: nil,
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
}
|
||||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||||
|
if strategy == tcbdata.ConflictStrategyOverwrite {
|
||||||
|
injectCtx = storage.WithOverwrite(injectCtx)
|
||||||
|
}
|
||||||
taskid := xid.New().String()
|
taskid := xid.New().String()
|
||||||
task, err := tftask.NewTGFileTask(taskid, injectCtx, file, stor, storagePath,
|
task, err := tftask.NewTGFileTask(taskid, injectCtx, file, stor, storagePath,
|
||||||
tftask.NewProgressTrack(
|
tftask.NewProgressTrack(
|
||||||
@@ -97,8 +121,9 @@ startCreateTask:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建一个 batchtfile.BatchTGFileTask 并添加到任务队列中, 以编辑消息的方式反馈结果
|
// 创建一个 batchtfile.BatchTGFileTask 并添加到任务队列中, 以编辑消息的方式反馈结果
|
||||||
func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, files []tfile.TGFileMessage, trackMsgID int) error {
|
func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, files []tfile.TGFileMessage, trackMsgID int, conflictStrategy ...string) error {
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
|
strategy := selectedConflictStrategy(conflictStrategy)
|
||||||
user, err := database.GetUserByChatID(ctx, userID)
|
user, err := database.GetUserByChatID(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to get user by chat ID: %s", err)
|
logger.Errorf("Failed to get user by chat ID: %s", err)
|
||||||
@@ -110,6 +135,7 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
})
|
})
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
strategy = conflictutil.ResolveStrategy(user, strategy)
|
||||||
|
|
||||||
useRule := user.ApplyRule && user.Rules != nil
|
useRule := user.ApplyRule && user.Rules != nil
|
||||||
|
|
||||||
@@ -128,14 +154,17 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
return storname, dirP
|
return storname, dirP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipped := make([]string, 0)
|
||||||
|
conflicts := make([]string, 0)
|
||||||
elems := make([]batchtfile.TaskElement, 0, len(files))
|
elems := make([]batchtfile.TaskElement, 0, len(files))
|
||||||
type albumFile struct {
|
type albumFile struct {
|
||||||
file tfile.TGFileMessage
|
file tfile.TGFileMessage
|
||||||
storage storage.Storage
|
storage storage.Storage
|
||||||
|
dirPath string
|
||||||
}
|
}
|
||||||
albumFiles := make(map[int64][]albumFile, 0)
|
albumFiles := make(map[int64][]albumFile, 0)
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
storName, dirPath := applyRule(file)
|
storName, matchedDirPath := applyRule(file)
|
||||||
fileStor := stor
|
fileStor := stor
|
||||||
if storName != stor.Name() && storName != "" {
|
if storName != stor.Name() && storName != "" {
|
||||||
fileStor, err = storage.GetStorageByUserIDAndName(ctx, user.ChatID, storName)
|
fileStor, err = storage.GetStorageByUserIDAndName(ctx, user.ChatID, storName)
|
||||||
@@ -150,8 +179,19 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !dirPath.NeedNewForAlbum() {
|
if !matchedDirPath.NeedNewForAlbum() {
|
||||||
storPath := path.Join(dirPath.String(), file.Name())
|
storPath := path.Join(matchedDirPath.String(), file.Name())
|
||||||
|
if strategy == tcbdata.ConflictStrategyAsk || strategy == tcbdata.ConflictStrategySkip {
|
||||||
|
exists := fileStor.Exists(ctx, storPath)
|
||||||
|
if exists && strategy == tcbdata.ConflictStrategyAsk {
|
||||||
|
conflicts = append(conflicts, conflictutil.FormatPath(fileStor.Name(), storPath))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
skipped = append(skipped, file.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
elem, err := batchtfile.NewTaskElement(fileStor, storPath, file)
|
elem, err := batchtfile.NewTaskElement(fileStor, storPath, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to create task element: %s", err)
|
logger.Errorf("Failed to create task element: %s", err)
|
||||||
@@ -170,12 +210,17 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
logger.Warnf("File %s is not in a group, skipping album handling", file.Name())
|
logger.Warnf("File %s is not in a group, skipping album handling", file.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
fileDirPath := matchedDirPath.String()
|
||||||
|
if matchedDirPath.NeedNewForAlbum() {
|
||||||
|
fileDirPath = dirPath
|
||||||
|
}
|
||||||
if _, ok := albumFiles[groupId]; !ok {
|
if _, ok := albumFiles[groupId]; !ok {
|
||||||
albumFiles[groupId] = make([]albumFile, 0)
|
albumFiles[groupId] = make([]albumFile, 0)
|
||||||
}
|
}
|
||||||
albumFiles[groupId] = append(albumFiles[groupId], albumFile{
|
albumFiles[groupId] = append(albumFiles[groupId], albumFile{
|
||||||
file: file,
|
file: file,
|
||||||
storage: fileStor,
|
storage: fileStor,
|
||||||
|
dirPath: fileDirPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +233,18 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
albumDir := strings.TrimSuffix(path.Base(afiles[0].file.Name()), path.Ext(afiles[0].file.Name()))
|
albumDir := strings.TrimSuffix(path.Base(afiles[0].file.Name()), path.Ext(afiles[0].file.Name()))
|
||||||
albumStor := afiles[0].storage
|
albumStor := afiles[0].storage
|
||||||
for _, af := range afiles {
|
for _, af := range afiles {
|
||||||
afstorPath := path.Join(dirPath, albumDir, af.file.Name())
|
afstorPath := path.Join(af.dirPath, albumDir, af.file.Name())
|
||||||
|
if strategy == tcbdata.ConflictStrategyAsk || strategy == tcbdata.ConflictStrategySkip {
|
||||||
|
exists := albumStor.Exists(ctx, afstorPath)
|
||||||
|
if exists && strategy == tcbdata.ConflictStrategyAsk {
|
||||||
|
conflicts = append(conflicts, conflictutil.FormatPath(albumStor.Name(), afstorPath))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
skipped = append(skipped, af.file.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
elem, err := batchtfile.NewTaskElement(albumStor, afstorPath, af.file)
|
elem, err := batchtfile.NewTaskElement(albumStor, afstorPath, af.file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to create task element for album file: %s", err)
|
logger.Errorf("Failed to create task element for album file: %s", err)
|
||||||
@@ -204,9 +260,26 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strategy == tcbdata.ConflictStrategyAsk && len(conflicts) > 0 {
|
||||||
|
return promptTGFileConflictStrategy(ctx, userID, stor.Name(), dirPath, files, true, conflicts, trackMsgID)
|
||||||
|
}
|
||||||
|
|
||||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||||
|
if strategy == tcbdata.ConflictStrategyOverwrite {
|
||||||
|
injectCtx = storage.WithOverwrite(injectCtx)
|
||||||
|
}
|
||||||
|
if len(elems) == 0 {
|
||||||
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: trackMsgID,
|
||||||
|
Message: i18n.T(i18nk.BotMsgCommonInfoAllConflictFilesSkipped, map[string]any{
|
||||||
|
"Skipped": strings.Join(skipped, "\n"),
|
||||||
|
}),
|
||||||
|
ReplyMarkup: nil,
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
taskid := xid.New().String()
|
taskid := xid.New().String()
|
||||||
task := batchtfile.NewBatchTGFileTask(taskid, injectCtx, elems, batchtfile.NewProgressTracker(trackMsgID, userID), true)
|
task := batchtfile.NewBatchTGFileTask(taskid, injectCtx, elems, batchtfile.NewProgressTrackerWithSkipped(trackMsgID, userID, skipped), true)
|
||||||
if err := core.AddTask(injectCtx, task); err != nil {
|
if err := core.AddTask(injectCtx, task); err != nil {
|
||||||
logger.Errorf("Failed to add batch task: %s", err)
|
logger.Errorf("Failed to add batch task: %s", err)
|
||||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
@@ -218,11 +291,48 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
ID: trackMsgID,
|
ID: trackMsgID,
|
||||||
Message: i18n.T(i18nk.BotMsgCommonInfoBatchTasksAdded, map[string]any{
|
Message: buildBatchAddedMessage(len(elems), skipped),
|
||||||
"Count": len(files),
|
|
||||||
}),
|
|
||||||
ReplyMarkup: nil,
|
ReplyMarkup: nil,
|
||||||
})
|
})
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func promptTGFileConflictStrategy(ctx *ext.Context, userID int64, storageName, dirPath string, files []tfile.TGFileMessage, asBatch bool, conflicts []string, trackMsgID int) error {
|
||||||
|
markup, err := msgelem.BuildConflictStrategyMarkup(tcbdata.Add{
|
||||||
|
TaskType: tasktype.TaskTypeTgfiles,
|
||||||
|
SelectedStorName: storageName,
|
||||||
|
SettedDir: true,
|
||||||
|
SelectedDirPath: dirPath,
|
||||||
|
Files: files,
|
||||||
|
AsBatch: asBatch,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: trackMsgID,
|
||||||
|
Message: i18n.T(i18nk.BotMsgCommonPromptSelectConflictStrategy, map[string]any{"Files": conflictutil.FormatPaths(conflicts)}),
|
||||||
|
ReplyMarkup: markup,
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectedConflictStrategy(strategies []string) string {
|
||||||
|
if len(strategies) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strategies[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBatchAddedMessage(count int, skipped []string) string {
|
||||||
|
if len(skipped) == 0 {
|
||||||
|
return i18n.T(i18nk.BotMsgCommonInfoBatchTasksAdded, map[string]any{
|
||||||
|
"Count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return i18n.T(i18nk.BotMsgCommonInfoBatchTasksAddedWithSkipped, map[string]any{
|
||||||
|
"Count": count,
|
||||||
|
"Skipped": strings.Join(skipped, "\n"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ const (
|
|||||||
BotMsgCmdUpdate Key = "bot.msg.cmd.update"
|
BotMsgCmdUpdate Key = "bot.msg.cmd.update"
|
||||||
BotMsgCmdWatch Key = "bot.msg.cmd.watch"
|
BotMsgCmdWatch Key = "bot.msg.cmd.watch"
|
||||||
BotMsgCmdYtdlp Key = "bot.msg.cmd.ytdlp"
|
BotMsgCmdYtdlp Key = "bot.msg.cmd.ytdlp"
|
||||||
|
BotMsgCommonButtonConflictOverwrite Key = "bot.msg.common.button_conflict_overwrite"
|
||||||
|
BotMsgCommonButtonConflictRename Key = "bot.msg.common.button_conflict_rename"
|
||||||
|
BotMsgCommonButtonConflictSkip Key = "bot.msg.common.button_conflict_skip"
|
||||||
BotMsgCommonCancelButtonText Key = "bot.msg.common.cancel_button_text"
|
BotMsgCommonCancelButtonText Key = "bot.msg.common.cancel_button_text"
|
||||||
BotMsgCommonErrorBuildDirSelectKeyboardFailed Key = "bot.msg.common.error_build_dir_select_keyboard_failed"
|
BotMsgCommonErrorBuildDirSelectKeyboardFailed Key = "bot.msg.common.error_build_dir_select_keyboard_failed"
|
||||||
BotMsgCommonErrorBuildStorageSelectKeyboardFailed Key = "bot.msg.common.error_build_storage_select_keyboard_failed"
|
BotMsgCommonErrorBuildStorageSelectKeyboardFailed Key = "bot.msg.common.error_build_storage_select_keyboard_failed"
|
||||||
@@ -63,7 +66,10 @@ const (
|
|||||||
BotMsgCommonErrorTaskAddFailed Key = "bot.msg.common.error_task_add_failed"
|
BotMsgCommonErrorTaskAddFailed Key = "bot.msg.common.error_task_add_failed"
|
||||||
BotMsgCommonErrorTaskCreateFailed Key = "bot.msg.common.error_task_create_failed"
|
BotMsgCommonErrorTaskCreateFailed Key = "bot.msg.common.error_task_create_failed"
|
||||||
BotMsgCommonErrorUpdateUserInfoFailed Key = "bot.msg.common.error_update_user_info_failed"
|
BotMsgCommonErrorUpdateUserInfoFailed Key = "bot.msg.common.error_update_user_info_failed"
|
||||||
|
BotMsgCommonInfoAllConflictFilesSkipped Key = "bot.msg.common.info_all_conflict_files_skipped"
|
||||||
BotMsgCommonInfoBatchTasksAdded Key = "bot.msg.common.info_batch_tasks_added"
|
BotMsgCommonInfoBatchTasksAdded Key = "bot.msg.common.info_batch_tasks_added"
|
||||||
|
BotMsgCommonInfoBatchTasksAddedWithSkipped Key = "bot.msg.common.info_batch_tasks_added_with_skipped"
|
||||||
|
BotMsgCommonInfoConflictFilesSkipped Key = "bot.msg.common.info_conflict_files_skipped"
|
||||||
BotMsgCommonInfoDefaultStorageSet Key = "bot.msg.common.info_default_storage_set"
|
BotMsgCommonInfoDefaultStorageSet Key = "bot.msg.common.info_default_storage_set"
|
||||||
BotMsgCommonInfoDefaultStorageWithDirSet Key = "bot.msg.common.info_default_storage_with_dir_set"
|
BotMsgCommonInfoDefaultStorageWithDirSet Key = "bot.msg.common.info_default_storage_with_dir_set"
|
||||||
BotMsgCommonInfoFetchingFileInfo Key = "bot.msg.common.info_fetching_file_info"
|
BotMsgCommonInfoFetchingFileInfo Key = "bot.msg.common.info_fetching_file_info"
|
||||||
@@ -73,16 +79,25 @@ const (
|
|||||||
BotMsgCommonInfoSilentModeOff Key = "bot.msg.common.info_silent_mode_off"
|
BotMsgCommonInfoSilentModeOff Key = "bot.msg.common.info_silent_mode_off"
|
||||||
BotMsgCommonInfoSilentModeOn Key = "bot.msg.common.info_silent_mode_on"
|
BotMsgCommonInfoSilentModeOn Key = "bot.msg.common.info_silent_mode_on"
|
||||||
BotMsgCommonInfoTaskAdded Key = "bot.msg.common.info_task_added"
|
BotMsgCommonInfoTaskAdded Key = "bot.msg.common.info_task_added"
|
||||||
|
BotMsgCommonPromptConflictMoreFiles Key = "bot.msg.common.prompt_conflict_more_files"
|
||||||
|
BotMsgCommonPromptSelectConflictStrategy Key = "bot.msg.common.prompt_select_conflict_strategy"
|
||||||
BotMsgCommonPromptSelectDefaultDir Key = "bot.msg.common.prompt_select_default_dir"
|
BotMsgCommonPromptSelectDefaultDir Key = "bot.msg.common.prompt_select_default_dir"
|
||||||
BotMsgCommonPromptSelectDefaultStorage Key = "bot.msg.common.prompt_select_default_storage"
|
BotMsgCommonPromptSelectDefaultStorage Key = "bot.msg.common.prompt_select_default_storage"
|
||||||
BotMsgCommonPromptSelectDir Key = "bot.msg.common.prompt_select_dir"
|
BotMsgCommonPromptSelectDir Key = "bot.msg.common.prompt_select_dir"
|
||||||
BotMsgConfigButtonFilenameStrategy Key = "bot.msg.config.button_filename_strategy"
|
BotMsgConfigButtonFilenameStrategy Key = "bot.msg.config.button_filename_strategy"
|
||||||
|
BotMsgConfigButtonConflictStrategy Key = "bot.msg.config.button_conflict_strategy"
|
||||||
|
BotMsgConfigConflictStrategyAsk Key = "bot.msg.config.conflict_strategy_ask"
|
||||||
|
BotMsgConfigConflictStrategyOverwrite Key = "bot.msg.config.conflict_strategy_overwrite"
|
||||||
|
BotMsgConfigConflictStrategyRename Key = "bot.msg.config.conflict_strategy_rename"
|
||||||
|
BotMsgConfigConflictStrategySkip Key = "bot.msg.config.conflict_strategy_skip"
|
||||||
BotMsgConfigErrorInvalidCallbackData Key = "bot.msg.config.error_invalid_callback_data"
|
BotMsgConfigErrorInvalidCallbackData Key = "bot.msg.config.error_invalid_callback_data"
|
||||||
BotMsgConfigErrorInvalidTemplate Key = "bot.msg.config.error_invalid_template"
|
BotMsgConfigErrorInvalidTemplate Key = "bot.msg.config.error_invalid_template"
|
||||||
BotMsgConfigFnametmplHelp Key = "bot.msg.config.fnametmpl_help"
|
BotMsgConfigFnametmplHelp Key = "bot.msg.config.fnametmpl_help"
|
||||||
BotMsgConfigInfoCurrentTemplatePrefix Key = "bot.msg.config.info_current_template_prefix"
|
BotMsgConfigInfoCurrentTemplatePrefix Key = "bot.msg.config.info_current_template_prefix"
|
||||||
|
BotMsgConfigInfoConflictStrategySet Key = "bot.msg.config.info_conflict_strategy_set"
|
||||||
BotMsgConfigInfoFilenameStrategySet Key = "bot.msg.config.info_filename_strategy_set"
|
BotMsgConfigInfoFilenameStrategySet Key = "bot.msg.config.info_filename_strategy_set"
|
||||||
BotMsgConfigInfoTemplateUpdated Key = "bot.msg.config.info_template_updated"
|
BotMsgConfigInfoTemplateUpdated Key = "bot.msg.config.info_template_updated"
|
||||||
|
BotMsgConfigPromptSelectConflictStrategy Key = "bot.msg.config.prompt_select_conflict_strategy"
|
||||||
BotMsgConfigPromptSelectFilenameStrategy Key = "bot.msg.config.prompt_select_filename_strategy"
|
BotMsgConfigPromptSelectFilenameStrategy Key = "bot.msg.config.prompt_select_filename_strategy"
|
||||||
BotMsgConfigPromptSelectOption Key = "bot.msg.config.prompt_select_option"
|
BotMsgConfigPromptSelectOption Key = "bot.msg.config.prompt_select_option"
|
||||||
BotMsgDirButtonDefault Key = "bot.msg.dir.button_default"
|
BotMsgDirButtonDefault Key = "bot.msg.dir.button_default"
|
||||||
|
|||||||
@@ -112,9 +112,17 @@ bot:
|
|||||||
error_task_add_failed: "Failed to add task: {{.Error}}"
|
error_task_add_failed: "Failed to add task: {{.Error}}"
|
||||||
info_task_added: "Task added"
|
info_task_added: "Task added"
|
||||||
info_batch_tasks_added: "Batch tasks added, total {{.Count}} files"
|
info_batch_tasks_added: "Batch tasks added, total {{.Count}} files"
|
||||||
|
info_batch_tasks_added_with_skipped: "Batch tasks added, total {{.Count}} files\nSkipped conflicting files:\n{{.Skipped}}"
|
||||||
|
info_all_conflict_files_skipped: "All conflicting files were skipped:\n{{.Skipped}}"
|
||||||
|
info_conflict_files_skipped: "Skipped conflicting files:\n{{.Skipped}}"
|
||||||
error_task_create_failed: "Failed to create task: {{.Error}}"
|
error_task_create_failed: "Failed to create task: {{.Error}}"
|
||||||
error_get_dir_failed: "Failed to get directory: {{.Error}}"
|
error_get_dir_failed: "Failed to get directory: {{.Error}}"
|
||||||
prompt_select_dir: "Please select a directory to store to"
|
prompt_select_dir: "Please select a directory to store to"
|
||||||
|
prompt_select_conflict_strategy: "Files with the same name already exist. Please select a save strategy:\n{{.Files}}"
|
||||||
|
prompt_conflict_more_files: "...and {{.Count}} more files"
|
||||||
|
button_conflict_rename: "Rename"
|
||||||
|
button_conflict_overwrite: "Overwrite"
|
||||||
|
button_conflict_skip: "Skip"
|
||||||
prompt_select_default_dir: "Please select a default directory to save to"
|
prompt_select_default_dir: "Please select a default directory to save to"
|
||||||
info_default_storage_set: "Default storage set to: {{.Name}}"
|
info_default_storage_set: "Default storage set to: {{.Name}}"
|
||||||
info_default_storage_with_dir_set: "Default storage set to: {{.Name}}:/{{.Dir}}"
|
info_default_storage_with_dir_set: "Default storage set to: {{.Name}}:/{{.Dir}}"
|
||||||
@@ -266,10 +274,17 @@ bot:
|
|||||||
config:
|
config:
|
||||||
prompt_select_option: "Please select an option to configure"
|
prompt_select_option: "Please select an option to configure"
|
||||||
button_filename_strategy: "Filename strategy"
|
button_filename_strategy: "Filename strategy"
|
||||||
|
button_conflict_strategy: "Duplicate file strategy"
|
||||||
error_invalid_callback_data: "Invalid callback data"
|
error_invalid_callback_data: "Invalid callback data"
|
||||||
error_invalid_template: "Invalid template, please check syntax\n{{.Error}}"
|
error_invalid_template: "Invalid template, please check syntax\n{{.Error}}"
|
||||||
info_filename_strategy_set: "Filename strategy set to: {{.Strategy}}"
|
info_filename_strategy_set: "Filename strategy set to: {{.Strategy}}"
|
||||||
|
info_conflict_strategy_set: "Duplicate file strategy set to: {{.Strategy}}"
|
||||||
prompt_select_filename_strategy: "Please select filename strategy, current strategy: {{.Strategy}}"
|
prompt_select_filename_strategy: "Please select filename strategy, current strategy: {{.Strategy}}"
|
||||||
|
prompt_select_conflict_strategy: "Please select duplicate file strategy, current strategy: {{.Strategy}}"
|
||||||
|
conflict_strategy_rename: "Always rename"
|
||||||
|
conflict_strategy_ask: "Ask every time"
|
||||||
|
conflict_strategy_overwrite: "Always overwrite"
|
||||||
|
conflict_strategy_skip: "Always skip"
|
||||||
fnametmpl_help: |-
|
fnametmpl_help: |-
|
||||||
Use this command to set filename template, for example:
|
Use this command to set filename template, for example:
|
||||||
/fnametmpl Image_{{"{{.msgid}}"}}_{{"{{.msgdate}}"}}.jpg
|
/fnametmpl Image_{{"{{.msgid}}"}}_{{"{{.msgdate}}"}}.jpg
|
||||||
|
|||||||
@@ -113,9 +113,17 @@ bot:
|
|||||||
error_task_add_failed: "任务添加失败: {{.Error}}"
|
error_task_add_failed: "任务添加失败: {{.Error}}"
|
||||||
info_task_added: "任务已添加"
|
info_task_added: "任务已添加"
|
||||||
info_batch_tasks_added: "已添加批量任务, 共 {{.Count}} 个文件"
|
info_batch_tasks_added: "已添加批量任务, 共 {{.Count}} 个文件"
|
||||||
|
info_batch_tasks_added_with_skipped: "已添加批量任务, 共 {{.Count}} 个文件\n已跳过同名文件:\n{{.Skipped}}"
|
||||||
|
info_all_conflict_files_skipped: "全部同名文件已跳过:\n{{.Skipped}}"
|
||||||
|
info_conflict_files_skipped: "已跳过同名文件:\n{{.Skipped}}"
|
||||||
error_task_create_failed: "任务创建失败: {{.Error}}"
|
error_task_create_failed: "任务创建失败: {{.Error}}"
|
||||||
error_get_dir_failed: "获取目录失败: {{.Error}}"
|
error_get_dir_failed: "获取目录失败: {{.Error}}"
|
||||||
prompt_select_dir: "请选择要存储到的目录"
|
prompt_select_dir: "请选择要存储到的目录"
|
||||||
|
prompt_select_conflict_strategy: "检测到同名文件, 请选择保存策略:\n{{.Files}}"
|
||||||
|
prompt_conflict_more_files: "...还有 {{.Count}} 个文件"
|
||||||
|
button_conflict_rename: "重命名"
|
||||||
|
button_conflict_overwrite: "覆盖"
|
||||||
|
button_conflict_skip: "跳过"
|
||||||
prompt_select_default_dir: "请选择要保存到的默认文件夹"
|
prompt_select_default_dir: "请选择要保存到的默认文件夹"
|
||||||
info_default_storage_set: "已将默认存储位置设为: {{.Name}}"
|
info_default_storage_set: "已将默认存储位置设为: {{.Name}}"
|
||||||
info_default_storage_with_dir_set: "已将默认存储位置设为: {{.Name}}:/{{.Dir}}"
|
info_default_storage_with_dir_set: "已将默认存储位置设为: {{.Name}}:/{{.Dir}}"
|
||||||
@@ -267,10 +275,17 @@ bot:
|
|||||||
config:
|
config:
|
||||||
prompt_select_option: "请选择要配置的选项"
|
prompt_select_option: "请选择要配置的选项"
|
||||||
button_filename_strategy: "文件名策略"
|
button_filename_strategy: "文件名策略"
|
||||||
|
button_conflict_strategy: "重名文件保存策略"
|
||||||
error_invalid_callback_data: "无效的回调数据"
|
error_invalid_callback_data: "无效的回调数据"
|
||||||
error_invalid_template: "无效的模板, 请检查语法\n{{.Error}}"
|
error_invalid_template: "无效的模板, 请检查语法\n{{.Error}}"
|
||||||
info_filename_strategy_set: "已将文件名策略设置为: {{.Strategy}}"
|
info_filename_strategy_set: "已将文件名策略设置为: {{.Strategy}}"
|
||||||
|
info_conflict_strategy_set: "已将重名文件保存策略设置为: {{.Strategy}}"
|
||||||
prompt_select_filename_strategy: "请选择文件名策略, 当前策略: {{.Strategy}}"
|
prompt_select_filename_strategy: "请选择文件名策略, 当前策略: {{.Strategy}}"
|
||||||
|
prompt_select_conflict_strategy: "请选择重名文件保存策略, 当前策略: {{.Strategy}}"
|
||||||
|
conflict_strategy_rename: "始终重命名"
|
||||||
|
conflict_strategy_ask: "每次询问"
|
||||||
|
conflict_strategy_overwrite: "始终覆盖"
|
||||||
|
conflict_strategy_skip: "始终跳过"
|
||||||
fnametmpl_help: |-
|
fnametmpl_help: |-
|
||||||
使用该命令设置文件名模板, 示例:
|
使用该命令设置文件名模板, 示例:
|
||||||
/fnametmpl 图片_{{"{{.msgid}}"}}_{{"{{.msgdate}}"}}.jpg
|
/fnametmpl 图片_{{"{{.msgid}}"}}_{{"{{.msgdate}}"}}.jpg
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ type Progress struct {
|
|||||||
ChatID int64
|
ChatID int64
|
||||||
start time.Time
|
start time.Time
|
||||||
lastUpdatePercent atomic.Int32
|
lastUpdatePercent atomic.Int32
|
||||||
|
skippedFiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Progress) OnStart(ctx context.Context, info TaskInfo) {
|
func (p *Progress) OnStart(ctx context.Context, info TaskInfo) {
|
||||||
@@ -151,6 +153,14 @@ func (p *Progress) OnDone(ctx context.Context, info TaskInfo, err error) {
|
|||||||
styling.Code(strconv.Itoa(info.Count())),
|
styling.Code(strconv.Itoa(info.Count())),
|
||||||
styling.Plain(i18n.T(i18nk.BotMsgProgressTotalSizePrefix, nil)),
|
styling.Plain(i18n.T(i18nk.BotMsgProgressTotalSizePrefix, nil)),
|
||||||
styling.Code(fmt.Sprintf("%.2f MB", float64(info.TotalSize())/(1024*1024))),
|
styling.Code(fmt.Sprintf("%.2f MB", float64(info.TotalSize())/(1024*1024))),
|
||||||
|
func() styling.StyledTextOption {
|
||||||
|
if len(p.skippedFiles) == 0 {
|
||||||
|
return styling.Plain("")
|
||||||
|
}
|
||||||
|
return styling.Plain("\n\n" + i18n.T(i18nk.BotMsgCommonInfoConflictFilesSkipped, map[string]any{
|
||||||
|
"Skipped": strings.Join(p.skippedFiles, "\n"),
|
||||||
|
}))
|
||||||
|
}(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +183,13 @@ func (p *Progress) OnDone(ctx context.Context, info TaskInfo, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewProgressTracker(messageID int, chatID int64) ProgressTracker {
|
func NewProgressTracker(messageID int, chatID int64) ProgressTracker {
|
||||||
|
return NewProgressTrackerWithSkipped(messageID, chatID, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProgressTrackerWithSkipped(messageID int, chatID int64, skippedFiles []string) ProgressTracker {
|
||||||
return &Progress{
|
return &Progress{
|
||||||
MessageID: messageID,
|
MessageID: messageID,
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
|
skippedFiles: skippedFiles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type User struct {
|
|||||||
WatchChats []WatchChat
|
WatchChats []WatchChat
|
||||||
FilenameStrategy string
|
FilenameStrategy string
|
||||||
FilenameTemplate string
|
FilenameTemplate string
|
||||||
|
ConflictStrategy string
|
||||||
}
|
}
|
||||||
|
|
||||||
type WatchChat struct {
|
type WatchChat struct {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package ctxkey
|
package ctxkey
|
||||||
|
|
||||||
// ENUM(content-length)
|
// ENUM(content-length, overwrite-existing)
|
||||||
//
|
//
|
||||||
//go:generate go-enum --values --names --flag --nocase --noprefix
|
//go:generate go-enum --values --names --flag --nocase --noprefix
|
||||||
type ContextKey string
|
type ContextKey string
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ import (
|
|||||||
const (
|
const (
|
||||||
// ContentLength is a ContextKey of type content-length.
|
// ContentLength is a ContextKey of type content-length.
|
||||||
ContentLength ContextKey = "content-length"
|
ContentLength ContextKey = "content-length"
|
||||||
|
// OverwriteExisting is a ContextKey of type overwrite-existing.
|
||||||
|
OverwriteExisting ContextKey = "overwrite-existing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidContextKey = fmt.Errorf("not a valid ContextKey, try [%s]", strings.Join(_ContextKeyNames, ", "))
|
var ErrInvalidContextKey = fmt.Errorf("not a valid ContextKey, try [%s]", strings.Join(_ContextKeyNames, ", "))
|
||||||
|
|
||||||
var _ContextKeyNames = []string{
|
var _ContextKeyNames = []string{
|
||||||
string(ContentLength),
|
string(ContentLength),
|
||||||
|
string(OverwriteExisting),
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextKeyNames returns a list of possible string values of ContextKey.
|
// ContextKeyNames returns a list of possible string values of ContextKey.
|
||||||
@@ -33,6 +36,7 @@ func ContextKeyNames() []string {
|
|||||||
func ContextKeyValues() []ContextKey {
|
func ContextKeyValues() []ContextKey {
|
||||||
return []ContextKey{
|
return []ContextKey{
|
||||||
ContentLength,
|
ContentLength,
|
||||||
|
OverwriteExisting,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +53,8 @@ func (x ContextKey) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ContextKeyValue = map[string]ContextKey{
|
var _ContextKeyValue = map[string]ContextKey{
|
||||||
"content-length": ContentLength,
|
"content-length": ContentLength,
|
||||||
|
"overwrite-existing": OverwriteExisting,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseContextKey attempts to convert a string to a ContextKey.
|
// ParseContextKey attempts to convert a string to a ContextKey.
|
||||||
|
|||||||
@@ -14,6 +14,31 @@ const (
|
|||||||
TypeCancel = "cancel"
|
TypeCancel = "cancel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConflictStrategyRename = "rename"
|
||||||
|
ConflictStrategyAsk = "ask"
|
||||||
|
ConflictStrategyOverwrite = "overwrite"
|
||||||
|
ConflictStrategySkip = "skip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConflictStrategyValues() []string {
|
||||||
|
return []string{
|
||||||
|
ConflictStrategyRename,
|
||||||
|
ConflictStrategyAsk,
|
||||||
|
ConflictStrategyOverwrite,
|
||||||
|
ConflictStrategySkip,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsConflictStrategy(strategy string) bool {
|
||||||
|
for _, value := range ConflictStrategyValues() {
|
||||||
|
if strategy == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// type TaskDataTGFiles struct {
|
// type TaskDataTGFiles struct {
|
||||||
// Files []tfile.TGFileMessage
|
// Files []tfile.TGFileMessage
|
||||||
// AsBatch bool
|
// AsBatch bool
|
||||||
@@ -34,6 +59,8 @@ type Add struct {
|
|||||||
SelectedStorName string
|
SelectedStorName string
|
||||||
DirID uint
|
DirID uint
|
||||||
SettedDir bool
|
SettedDir bool
|
||||||
|
SelectedDirPath string
|
||||||
|
ConflictStrategy string
|
||||||
// tfiles
|
// tfiles
|
||||||
Files []tfile.TGFileMessage
|
Files []tfile.TGFileMessage
|
||||||
AsBatch bool
|
AsBatch bool
|
||||||
|
|||||||
@@ -108,8 +108,10 @@ func (a *Alist) Save(ctx context.Context, reader io.Reader, storagePath string)
|
|||||||
ext := path.Ext(storagePath)
|
ext := path.Ext(storagePath)
|
||||||
base := strings.TrimSuffix(storagePath, ext)
|
base := strings.TrimSuffix(storagePath, ext)
|
||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
for i := 1; a.Exists(ctx, candidate); i++ {
|
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
for i := 1; a.existsPath(ctx, candidate); i++ {
|
||||||
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, a.baseURL+"/api/fs/put", reader)
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, a.baseURL+"/api/fs/put", reader)
|
||||||
@@ -158,6 +160,10 @@ func (a *Alist) JoinStoragePath(p string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Alist) Exists(ctx context.Context, storagePath string) bool {
|
func (a *Alist) Exists(ctx context.Context, storagePath string) bool {
|
||||||
|
return a.existsPath(ctx, a.JoinStoragePath(storagePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Alist) existsPath(ctx context.Context, storagePath string) bool {
|
||||||
// POST /api/fs/get
|
// POST /api/fs/get
|
||||||
/*
|
/*
|
||||||
body:
|
body:
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
||||||
|
)
|
||||||
|
|
||||||
type contextKey struct{}
|
type contextKey struct{}
|
||||||
|
|
||||||
@@ -20,3 +24,7 @@ func FromContext(ctx context.Context) Storage {
|
|||||||
}
|
}
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithOverwrite(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, ctxkey.OverwriteExisting, true)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/duke-git/lancet/v2/fileutil"
|
"github.com/duke-git/lancet/v2/fileutil"
|
||||||
config "github.com/krau/SaveAny-Bot/config/storage"
|
config "github.com/krau/SaveAny-Bot/config/storage"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
||||||
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
|
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
|
||||||
)
|
)
|
||||||
@@ -56,8 +57,10 @@ func (l *Local) Save(ctx context.Context, r io.Reader, storagePath string) error
|
|||||||
ext := filepath.Ext(storagePath)
|
ext := filepath.Ext(storagePath)
|
||||||
base := strings.TrimSuffix(storagePath, ext)
|
base := strings.TrimSuffix(storagePath, ext)
|
||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
for i := 1; l.Exists(ctx, candidate); i++ {
|
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
for i := 1; l.existsPath(candidate); i++ {
|
||||||
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
absPath, err := filepath.Abs(candidate)
|
absPath, err := filepath.Abs(candidate)
|
||||||
@@ -77,6 +80,10 @@ func (l *Local) Save(ctx context.Context, r io.Reader, storagePath string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Local) Exists(ctx context.Context, storagePath string) bool {
|
func (l *Local) Exists(ctx context.Context, storagePath string) bool {
|
||||||
|
return l.existsPath(l.JoinStoragePath(storagePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Local) existsPath(storagePath string) bool {
|
||||||
absPath, err := filepath.Abs(storagePath)
|
absPath, err := filepath.Abs(storagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -81,12 +81,14 @@ func (m *Minio) Save(ctx context.Context, r io.Reader, storagePath string) error
|
|||||||
ext := path.Ext(storagePath)
|
ext := path.Ext(storagePath)
|
||||||
base := strings.TrimSuffix(storagePath, ext)
|
base := strings.TrimSuffix(storagePath, ext)
|
||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
for i := 1; m.Exists(ctx, candidate); i++ {
|
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
for i := 1; m.existsObject(ctx, candidate); i++ {
|
||||||
if i > 10 {
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
m.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
if i > 10 {
|
||||||
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
m.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
||||||
break
|
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size := int64(-1)
|
size := int64(-1)
|
||||||
@@ -106,6 +108,10 @@ func (m *Minio) Save(ctx context.Context, r io.Reader, storagePath string) error
|
|||||||
|
|
||||||
func (m *Minio) Exists(ctx context.Context, storagePath string) bool {
|
func (m *Minio) Exists(ctx context.Context, storagePath string) bool {
|
||||||
m.logger.Debugf("Checking if file exists at %s", storagePath)
|
m.logger.Debugf("Checking if file exists at %s", storagePath)
|
||||||
|
return m.existsObject(ctx, m.JoinStoragePath(storagePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Minio) existsObject(ctx context.Context, storagePath string) bool {
|
||||||
_, err := m.client.StatObject(ctx, m.config.BucketName, storagePath, minio.StatObjectOptions{})
|
_, err := m.client.StatObject(ctx, m.config.BucketName, storagePath, minio.StatObjectOptions{})
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
config "github.com/krau/SaveAny-Bot/config/storage"
|
config "github.com/krau/SaveAny-Bot/config/storage"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
||||||
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
|
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
@@ -107,12 +108,14 @@ func (r *Rclone) Save(ctx context.Context, reader io.Reader, storagePath string)
|
|||||||
ext := path.Ext(storagePath)
|
ext := path.Ext(storagePath)
|
||||||
base := strings.TrimSuffix(storagePath, ext)
|
base := strings.TrimSuffix(storagePath, ext)
|
||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
for i := 1; r.Exists(ctx, candidate); i++ {
|
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
for i := 1; r.Exists(ctx, candidate); i++ {
|
||||||
if i > 100 {
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
r.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
if i > 100 {
|
||||||
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
r.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
||||||
break
|
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,13 +70,15 @@ func (m *S3) Save(ctx context.Context, r io.Reader, storagePath string) error {
|
|||||||
base := strings.TrimSuffix(storagePath, ext)
|
base := strings.TrimSuffix(storagePath, ext)
|
||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
|
|
||||||
// Unique filename
|
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
|
||||||
for i := 1; m.Exists(ctx, candidate); i++ {
|
// Unique filename
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
for i := 1; m.existsKey(ctx, candidate); i++ {
|
||||||
if i > 10 {
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
m.logger.Errorf("Too many attempts for unique filename: %s", storagePath)
|
if i > 10 {
|
||||||
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
m.logger.Errorf("Too many attempts for unique filename: %s", storagePath)
|
||||||
break
|
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,5 +101,9 @@ func (m *S3) Save(ctx context.Context, r io.Reader, storagePath string) error {
|
|||||||
func (m *S3) Exists(ctx context.Context, storagePath string) bool {
|
func (m *S3) Exists(ctx context.Context, storagePath string) bool {
|
||||||
m.logger.Debugf("Checking if file exists at %s", storagePath)
|
m.logger.Debugf("Checking if file exists at %s", storagePath)
|
||||||
|
|
||||||
return m.client.Exists(ctx, storagePath)
|
return m.existsKey(ctx, m.JoinStoragePath(storagePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *S3) existsKey(ctx context.Context, key string) bool {
|
||||||
|
return m.client.Exists(ctx, key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
config "github.com/krau/SaveAny-Bot/config/storage"
|
config "github.com/krau/SaveAny-Bot/config/storage"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
||||||
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
|
"github.com/krau/SaveAny-Bot/pkg/storagetypes"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
@@ -57,12 +58,14 @@ func (w *Webdav) Save(ctx context.Context, r io.Reader, storagePath string) erro
|
|||||||
ext := path.Ext(storagePath)
|
ext := path.Ext(storagePath)
|
||||||
base := strings.TrimSuffix(storagePath, ext)
|
base := strings.TrimSuffix(storagePath, ext)
|
||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
for i := 1; w.Exists(ctx, candidate); i++ {
|
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
for i := 1; w.existsPath(ctx, candidate); i++ {
|
||||||
if i > 1000 {
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
w.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
if i > 1000 {
|
||||||
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
w.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
||||||
break
|
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +82,10 @@ func (w *Webdav) Save(ctx context.Context, r io.Reader, storagePath string) erro
|
|||||||
|
|
||||||
func (w *Webdav) Exists(ctx context.Context, storagePath string) bool {
|
func (w *Webdav) Exists(ctx context.Context, storagePath string) bool {
|
||||||
w.logger.Debugf("Checking if file exists at %s", storagePath)
|
w.logger.Debugf("Checking if file exists at %s", storagePath)
|
||||||
|
return w.existsPath(ctx, w.JoinStoragePath(storagePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webdav) existsPath(ctx context.Context, storagePath string) bool {
|
||||||
exists, err := w.client.Exists(ctx, storagePath)
|
exists, err := w.client.Exists(ctx, storagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.logger.Errorf("Failed to check if file exists at %s: %v", storagePath, err)
|
w.logger.Errorf("Failed to check if file exists at %s: %v", storagePath, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user