aliyunpan/internal/command/rename.go

276 lines
7.7 KiB
Go
Raw Normal View History

2021-10-10 10:48:53 +08:00
// Copyright (c) 2020 tickstep.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
2024-03-02 00:55:46 +08:00
// http://www.apache.org/licenses/LICENSE-2.0
2021-10-10 10:48:53 +08:00
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package command
import (
"fmt"
2023-01-01 13:42:49 +08:00
"github.com/tickstep/aliyunpan-api/aliyunpan"
2021-10-10 10:48:53 +08:00
"github.com/tickstep/aliyunpan-api/aliyunpan/apiutil"
"github.com/tickstep/aliyunpan/internal/config"
"github.com/urfave/cli"
"path"
2023-01-01 13:42:49 +08:00
"regexp"
"sort"
"strconv"
2021-10-10 10:48:53 +08:00
"strings"
)
2023-01-01 13:42:49 +08:00
type (
fileItem struct {
file *aliyunpan.FileEntity
newFileName string
}
fileArray []*fileItem
)
func newFileItem(file *aliyunpan.FileEntity) *fileItem {
return &fileItem{
file: file,
newFileName: "",
}
}
func (x fileArray) Len() int {
return len(x)
}
func (x fileArray) Less(i, j int) bool {
return x[i].file.FileName < x[j].file.FileName
}
func (x fileArray) Swap(i, j int) {
x[i], x[j] = x[j], x[i]
}
2021-10-10 10:48:53 +08:00
func CmdRename() cli.Command {
return cli.Command{
Name: "rename",
Usage: "重命名文件",
UsageText: `重命名文件:
aliyunpan rename <旧文件/目录名> <新文件/目录名>`,
Description: `
示例:
2023-01-01 13:42:49 +08:00
1. 将文件 1.mp4 重命名为 2.mp4
aliyunpan rename 1.mp4 2.mp4
2021-10-10 10:48:53 +08:00
2023-01-01 13:42:49 +08:00
2. 将文件 /test/1.mp4 重命名为 /test/2.mp4
要求必须是同一个文件目录内
aliyunpan rename /test/1.mp4 /test/2.mp4
2021-10-10 10:48:53 +08:00
2023-01-01 13:42:49 +08:00
批量重命名规则rename [expression] [replacement] [file]
其中
expression - 表达式即文件名中需要去掉的部分如果为*代表旧的文件名全部去掉
replacement - 需要替换的新文件名如果包含 # 号会按顺序替换成数字编号
file - 文件匹配模式指定需要查找的文件匹配字符串支持通配符例如*.mp4 即当前目录下所有的mp4文件
3. 批量重命名将当前目录下所有.mp4文件中包含的字符串 "我的视频" 全部改成 "视频"
aliyunpan rename 我的视频 视频 *.mp4
4. 批量重命名将当前目录下所有.mp4文件全部进行 "视频+编号.mp4" 的重命名操作旧的名称全部去掉视频001.mp4, 视频002.mp4, 视频003.mp4...
aliyunpan rename * 视频###.mp4 *.mp4
5. 批量重命名将当前目录下所有.mp4文件全部进行 "视频+编号.mp4" 的重命名操作旧的名称全部去掉直接重命名无需人工确认操作
aliyunpan rename -y * 视频###.mp4 *.mp4
2021-10-10 10:48:53 +08:00
`,
Category: "阿里云盘",
2022-12-10 14:07:28 +08:00
Before: ReloadConfigFunc,
2021-10-10 10:48:53 +08:00
Action: func(c *cli.Context) error {
2023-01-01 13:42:49 +08:00
if c.NArg() != 2 && c.NArg() != 3 {
2021-10-10 10:48:53 +08:00
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
2023-01-01 13:42:49 +08:00
if c.NArg() == 2 {
RunRename(parseDriveId(c), c.Args().Get(0), c.Args().Get(1))
} else if c.NArg() == 3 {
// 批量重命名
RunRenameBatch(c.Bool("y"), parseDriveId(c), c.Args().Get(0), c.Args().Get(1), c.Args().Get(2))
2023-01-01 13:42:49 +08:00
}
2021-10-10 10:48:53 +08:00
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "driveId",
Usage: "网盘ID",
Value: "",
},
cli.BoolFlag{
Name: "y",
Usage: "跳过人工确认,对批量操作有效",
},
2021-10-10 10:48:53 +08:00
},
}
}
func RunRename(driveId string, oldName string, newName string) {
if oldName == "" {
fmt.Println("请指定命名文件")
return
}
if newName == "" {
fmt.Println("请指定文件新名称")
return
}
activeUser := GetActiveUser()
oldName = activeUser.PathJoin(driveId, strings.TrimSpace(oldName))
newName = activeUser.PathJoin(driveId, strings.TrimSpace(newName))
if path.Dir(oldName) != path.Dir(newName) {
fmt.Println("只能命名同一个目录的文件")
return
}
if !apiutil.CheckFileNameValid(path.Base(newName)) {
fmt.Println("文件名不能包含特殊字符:" + apiutil.FileNameSpecialChars)
return
}
fileId := ""
r, err := GetActivePanClient().OpenapiPanClient().FileInfoByPath(driveId, activeUser.PathJoin(driveId, oldName))
2021-10-10 10:48:53 +08:00
if err != nil {
fmt.Printf("原文件不存在: %s, %s\n", oldName, err)
return
}
fileId = r.FileId
b, e := activeUser.PanClient().OpenapiPanClient().FileRename(driveId, fileId, path.Base(newName))
2021-10-10 10:48:53 +08:00
if e != nil {
fmt.Println(e.Err)
return
}
if !b {
fmt.Println("重命名文件失败")
return
}
fmt.Printf("重命名文件成功:%s -> %s\n", path.Base(oldName), path.Base(newName))
2021-10-31 15:53:27 +08:00
activeUser.DeleteOneCache(path.Dir(newName))
2021-10-10 10:48:53 +08:00
}
2023-01-01 13:42:49 +08:00
// RunRenameBatch 批量重命名文件
func RunRenameBatch(skipConfirm bool, driveId string, expression, replacement, filePattern string) {
2023-01-01 13:42:49 +08:00
if len(expression) == 0 {
fmt.Println("旧文件名不能为空")
return
}
if !apiutil.CheckFileNameValid(replacement) {
fmt.Println("新文件名不能包含特殊字符:" + apiutil.FileNameSpecialChars)
return
}
if len(filePattern) == 0 {
fmt.Println("文件匹配模式不能为空")
return
}
if strings.ContainsAny(filePattern, "/") {
fmt.Println("文件匹配模式不能包含路径分隔符")
return
}
if strings.ContainsAny(filePattern, "\\") {
fmt.Println("文件匹配模式不能包含路径分隔符")
return
}
activeUser := GetActiveUser()
absolutePath := path.Clean(activeUser.PathJoin(driveId, filePattern))
fileList, err1 := matchPathByShellPattern(driveId, absolutePath)
if err1 != nil {
fmt.Println("查询文件出错:" + err1.Error())
return
}
if fileList == nil || len(fileList) == 0 {
fmt.Println("没有找到符合的文件")
return
}
// 文件列表按照名字排序
files := fileArray{}
for _, f := range fileList {
files = append(files, newFileItem(f))
}
if len(files) > 1 {
sort.Sort(files)
}
// 依次处理
var index = 1
for _, file := range files {
replacementStr := replaceNumStr(replacement, index)
index += 1
if expression == "*" {
// 旧的名称全部替换
file.newFileName = replacementStr
} else {
// 替换指定的名称片段
file.newFileName = strings.ReplaceAll(file.file.FileName, expression, replacementStr)
}
}
// 确认
if !skipConfirm {
fmt.Printf("以下文件将进行对应的重命名\n\n")
idx := 1
for _, file := range files {
fmt.Printf("%d) %s -> %s\n", idx, file.file.FileName, file.newFileName)
idx += 1
}
fmt.Printf("\n是否进行批量重命名该操作不可逆(y/n): ")
confirm := ""
_, err := fmt.Scanln(&confirm)
if err != nil || (confirm != "y" && confirm != "Y") {
fmt.Println("用户取消了操作")
return
}
2023-01-01 13:42:49 +08:00
}
// 重命名
for _, file := range files {
b, e := activeUser.PanClient().OpenapiPanClient().FileRename(driveId, file.file.FileId, file.newFileName)
2023-01-01 13:42:49 +08:00
if e != nil {
fmt.Println(e.Err)
return
}
if !b {
fmt.Println("重命名文件失败")
return
}
fmt.Printf("重命名文件成功:%s -> %s\n", file.file.FileName, file.newFileName)
activeUser.DeleteOneCache(path.Dir(file.file.Path))
}
}
// replaceNumStr 将#替换成数字编号
func replaceNumStr(name string, num int) string {
pattern, _ := regexp.Compile("[#]+")
r := pattern.FindAllStringSubmatchIndex(name, -1)
if len(r) == 0 {
return name
}
newName := name
c := r[0]
return replaceNumStr(newName[:c[0]]+generateNumStr(num, c[1]-c[0])+newName[c[1]:], num)
}
func generateNumStr(num, count int) string {
s := strconv.Itoa(num)
return generateZeroStr(count-len(s)) + s
}
func generateZeroStr(count int) string {
if count <= 0 {
return ""
}
s := ""
for i := 0; i < count; i++ {
s += "0"
}
return s
}