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
|
2023-01-03 11:12:26 +08:00
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
// 批量重命名
|
2023-01-03 11:12:26 +08:00
|
|
|
|
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: "",
|
|
|
|
|
},
|
2023-01-03 11:12:26 +08:00
|
|
|
|
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 := ""
|
2024-03-02 17:38:21 +08:00
|
|
|
|
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
|
|
|
|
|
|
2024-03-02 17:38:21 +08:00
|
|
|
|
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 批量重命名文件
|
2023-01-03 11:12:26 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确认
|
2023-01-03 11:12:26 +08:00
|
|
|
|
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 {
|
2024-03-02 17:38:21 +08:00
|
|
|
|
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
|
|
|
|
|
}
|