Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions cmd/rename/rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package rename

import (
"fmt"
"path/filepath"
"strings"

"github.com/52funny/pikpakcli/conf"
"github.com/52funny/pikpakcli/internal/pikpak"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var RenameCmd = &cobra.Command{
Use: "rename <path> <new-name>",
Short: "Rename a file or folder on the PikPak drive",
Long: `Rename a file or folder on the PikPak drive.
Example: pikpakcli rename /my-folder/old-name.txt new-name.txt`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
p := pikpak.NewPikPak(conf.Config.Username, conf.Config.Password)
if err := p.Login(); err != nil {
logrus.Errorln("Login failed:", err)
return err
}

oldPath := args[0]
newName := strings.TrimSpace(args[1])
if newName == "" {
return fmt.Errorf("new name cannot be empty")
}
if filepath.Base(newName) != newName {
return fmt.Errorf("new name must not contain path separators")
}

fileStat, err := p.GetFileByPath(oldPath)
if err != nil {
logrus.Errorf("Could not find file or folder at path '%s': %v", oldPath, err)
return err
}

if err := p.Rename(fileStat.ID, newName); err != nil {
logrus.Errorf("Failed to rename %s: %v", oldPath, err)
return err
}

fmt.Printf("Successfully renamed '%s' to '%s'\n", oldPath, newName)
return nil
},
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/52funny/pikpakcli/cmd/list"
"github.com/52funny/pikpakcli/cmd/new"
"github.com/52funny/pikpakcli/cmd/quota"
"github.com/52funny/pikpakcli/cmd/rename"
"github.com/52funny/pikpakcli/cmd/share"
"github.com/52funny/pikpakcli/cmd/upload"

Expand Down Expand Up @@ -53,6 +54,7 @@ func init() {
rootCmd.AddCommand(embed.EmbedCmd)
rootCmd.AddCommand(quota.QuotaCmd)
rootCmd.AddCommand(list.ListCmd)
rootCmd.AddCommand(rename.RenameCmd)
}

// Execute the command line interface
Expand Down
14 changes: 14 additions & 0 deletions docs/command.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,17 @@
```bash
pikpakcli ls -lH -p /
```

## Rename

- Rename a file or folder by full path.

```bash
pikpakcli rename /Movies/Peppa_Pig.mp4 Peppa_Pig_S01E01.mp4
```

- Rename a folder.

```bash
pikpakcli rename /Movies/Cartoons Kids
```
14 changes: 14 additions & 0 deletions docs/command_zhCN.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,17 @@
```bash
pikpakcli ls -lH -p /
```

## 重命名

- 按完整路径重命名文件或文件夹

```bash
pikpakcli rename /Movies/Peppa_Pig.mp4 Peppa_Pig_S01E01.mp4
```

- 重命名文件夹

```bash
pikpakcli rename /Movies/Cartoons Kids
```
63 changes: 63 additions & 0 deletions internal/pikpak/file.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package pikpak

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"time"
"unsafe"

"github.com/52funny/pikpakcli/internal/utils"
"github.com/tidwall/gjson"
)

Expand Down Expand Up @@ -141,6 +144,24 @@ func (p *PikPak) GetFileStat(parentId string, name string) (FileStat, error) {
return FileStat{}, errors.New("file not found")
}

func (p *PikPak) GetFileByPath(path string) (FileStat, error) {
parentPath, name := utils.SplitRemotePath(path)
if name == "" {
return FileStat{}, errors.New("cannot get info of root directory")
}

parentID := ""
var err error
if parentPath != "" {
parentID, err = p.GetPathFolderId(parentPath)
if err != nil {
return FileStat{}, err
}
}

return p.GetFileStat(parentID, name)
}

func (p *PikPak) GetFile(fileId string) (File, error) {
var fileInfo File
query := url.Values{}
Expand Down Expand Up @@ -173,3 +194,45 @@ func (p *PikPak) GetFile(fileId string) (File, error) {
}
return fileInfo, err
}

func (p *PikPak) Rename(fileId string, newName string) error {
if newName == "" {
return errors.New("new name cannot be empty")
}

apiURL := "https://api-drive.mypikpak.com/drive/v1/files/" + fileId
body := map[string]string{"name": newName}
jsonBody, err := json.Marshal(body)
if err != nil {
return err
}

START:
req, err := http.NewRequest("PATCH", apiURL, bytes.NewBuffer(jsonBody))
if err != nil {
return err
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Captcha-Token", p.CaptchaToken)
req.Header.Set("X-Device-Id", p.DeviceId)

bs, err := p.sendRequest(req)
if err != nil {
return err
}

errorCode := gjson.GetBytes(bs, "error_code").Int()
if errorCode != 0 {
if errorCode == 9 {
err = p.AuthCaptchaToken("PATCH:/drive/v1/files")
if err != nil {
return err
}
goto START
}
return fmt.Errorf("%s: %s", gjson.GetBytes(bs, "error").String(), fileId)
}

return nil
}
22 changes: 22 additions & 0 deletions internal/utils/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@ func Slash(path string) string {
return path
}

func SplitRemotePath(path string) (dir string, name string) {
path = filepath.Clean(path)
if path == "." || path == string(filepath.Separator) {
return "", ""
}

name = filepath.Base(path)
if name == "." || name == string(filepath.Separator) {
return "", ""
}

dir = filepath.Dir(path)
if dir == "." {
dir = ""
}
if dir == "" {
return "", name
}

return Slash(dir), name
}

// 获取目录文件夹下的所有文件路径名
func GetUploadFilePath(basePath string, defaultRegexp []*regexp.Regexp) ([]string, error) {
rawPath := make([]string, 0)
Expand Down
52 changes: 52 additions & 0 deletions internal/utils/path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package utils

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestSplitRemotePath(t *testing.T) {
separator := string(filepath.Separator)

tests := []struct {
name string
input string
wantDir string
wantName string
}{
{
name: "full path",
input: separator + filepath.Join("Movies", "Peppa_Pig.mp4"),
wantDir: "Movies",
wantName: "Peppa_Pig.mp4",
},
{
name: "relative nested path",
input: filepath.Join("Movies", "Kids", "Peppa_Pig.mp4"),
wantDir: filepath.Join("Movies", "Kids"),
wantName: "Peppa_Pig.mp4",
},
{
name: "file name only",
input: "Peppa_Pig.mp4",
wantDir: "",
wantName: "Peppa_Pig.mp4",
},
{
name: "root path",
input: separator,
wantDir: "",
wantName: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir, name := SplitRemotePath(tt.input)
require.Equal(t, tt.wantDir, dir)
require.Equal(t, tt.wantName, name)
})
}
}