Skip to content

新增功能: 重名文件时选择保存策略 - 重命名,覆盖,跳过#204

Open
Rain-kl wants to merge 2 commits into
krau:mainfrom
Rain-kl:feat/save-strategy
Open

新增功能: 重名文件时选择保存策略 - 重命名,覆盖,跳过#204
Rain-kl wants to merge 2 commits into
krau:mainfrom
Rain-kl:feat/save-strategy

Conversation

@Rain-kl
Copy link
Copy Markdown

@Rain-kl Rain-kl commented May 4, 2026

bot 下载文件前先检查文件名是否存在重复, 如果重复则要求用户选择保存策略

重命名: 现有策略-重命名文件末尾加_1
覆盖: 覆盖文件
跳过: 不处理

image

feat: implement conflict resolution strategy for file uploads
Copilot AI review requested due to automatic review settings May 4, 2026 04:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds duplicate-name handling for Telegram file downloads by prompting the user to choose a save strategy when a target filename already exists.

Changes:

  • Adds conflict-strategy selection (rename, overwrite, skip) to the add-task flow and persists the choice through callback data.
  • Propagates overwrite behavior into storage backends and adds skipped-file reporting to batch task messages/progress.
  • Adds new i18n strings and context keys needed for the conflict prompt and result summaries.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
storage/webdav/webdav.go Updates WebDAV save logic to honor overwrite mode and refactors existence checks.
storage/s3/s3.go Updates S3 save logic to skip renaming when overwrite is selected.
storage/rclone/rclone.go Gates the existing rename loop behind the overwrite flag for rclone storage.
storage/minio/client.go Adds overwrite-aware save behavior and a helper for object existence checks.
storage/local/local.go Adds overwrite-aware local save behavior and refactors path existence checks.
storage/context.go Adds context helpers for setting and reading overwrite mode.
storage/alist/alist.go Adds overwrite-aware save behavior and refactors existence checks for Alist.
pkg/tcbdata/data.go Extends callback payload data with conflict-strategy and selected directory path.
pkg/enums/ctxkey/context_key_enum.go Registers the new overwrite-existing context key.
pkg/enums/ctxkey/context_key.go Updates enum source definition for the new context key.
core/tasks/batchtfile/progress.go Adds skipped-file reporting to batch progress completion output.
common/i18n/locale/zh-Hans.yaml Adds Chinese strings for conflict prompts/buttons and skipped-file notices.
common/i18n/locale/en.yaml Adds English strings for conflict prompts/buttons and skipped-file notices.
common/i18n/i18nk/keys.go Adds i18n key constants for the new conflict-related messages.
client/bot/handlers/utils/shortcut/tftask.go Adds conflict prompting, skip/overwrite handling, and skipped-file summaries in task creation helpers.
client/bot/handlers/utils/msgelem/storage.go Adds inline keyboard generation for conflict-strategy selection.
client/bot/handlers/add_task.go Adds conflict pre-checking in the add-task callback flow before creating TG file tasks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread storage/webdav/webdav.go
Comment on lines +61 to +63
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
for i := 1; w.existsPath(ctx, candidate); i++ {
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
Comment thread storage/webdav/webdav.go
Comment on lines +85 to 89
return w.existsPath(ctx, w.JoinStoragePath(storagePath))
}

func (w *Webdav) existsPath(ctx context.Context, storagePath string) bool {
exists, err := w.client.Exists(ctx, storagePath)
Comment thread storage/s3/s3.go
Comment on lines +73 to +76
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
// Unique filename
for i := 1; m.existsKey(ctx, candidate); i++ {
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
Comment thread storage/s3/s3.go
Comment on lines +104 to +108
return m.existsKey(ctx, m.JoinStoragePath(storagePath))
}

func (m *S3) existsKey(ctx context.Context, key string) bool {
return m.client.Exists(ctx, key)
Comment thread storage/minio/client.go
Comment on lines +84 to +86
if overwrite, _ := ctx.Value(ctxkey.OverwriteExisting).(bool); !overwrite {
for i := 1; m.existsObject(ctx, candidate); i++ {
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
Comment on lines +84 to +102
conflicts, err := findTGFileConflicts(ctx, userID, selectedStorage, dirPath, data.Files)
if err != nil {
ctx.AnswerCallback(msgelem.AlertCallbackAnswer(queryID, err.Error()))
return dispatcher.EndGroups
}
if len(conflicts) > 0 && data.ConflictStrategy == "" {
markup, err := msgelem.BuildConflictStrategyMarkup(data)
if err != nil {
ctx.AnswerCallback(msgelem.AlertCallbackAnswer(queryID, i18n.T(i18nk.BotMsgCommonErrorBuildStorageSelectKeyboardFailed, map[string]any{
"Error": err.Error(),
})))
return dispatcher.EndGroups
}
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
ID: update.CallbackQuery.GetMsgID(),
Message: i18n.T(i18nk.BotMsgCommonPromptSelectConflictStrategy, map[string]any{"Files": formatConflictFiles(conflicts)}),
ReplyMarkup: markup,
})
return dispatcher.EndGroups
Comment thread client/bot/handlers/add_task.go Outdated
Comment on lines +105 to +107
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)
if len(conflicts) > 0 && data.ConflictStrategy == "" {
markup, err := msgelem.BuildConflictStrategyMarkup(data)
if err != nil {
ctx.AnswerCallback(msgelem.AlertCallbackAnswer(queryID, i18n.T(i18nk.BotMsgCommonErrorBuildStorageSelectKeyboardFailed, map[string]any{
Comment on lines +329 to +332
return i18n.T(i18nk.BotMsgCommonInfoBatchTasksAddedWithSkipped, map[string]any{
"Count": count,
"Skipped": strings.Join(skipped, "\n"),
})
Comment on lines +160 to +162
return styling.Plain("\n\n" + i18n.T(i18nk.BotMsgCommonInfoConflictFilesSkipped, map[string]any{
"Skipped": strings.Join(p.skippedFiles, "\n"),
}))
@krau
Copy link
Copy Markdown
Owner

krau commented May 5, 2026

hi, 感谢贡献. 该功能在本地已经测试过了吗? 有没有增加把某个策略设为默认的选项呢? (比如某些频道的几乎所有文件的文件名完全一样, 保存它们的时候如果要一一选择会不太方便)

@Rain-kl
Copy link
Copy Markdown
Author

Rain-kl commented May 6, 2026

你说得对, 我补充提交了默认策略选项, 可以通过/config 命令进行配置,
默认策略为"始终重命名"防止升级上去出现问题
另外我已经在本地测试了,没有发现问题

image iShot_2026-05-06_15 11 01

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants