refractor command

This commit is contained in:
xiaoyaofenfen 2021-08-02 15:11:05 +08:00
parent 70776dea87
commit f3b12f34bf
22 changed files with 1776 additions and 1581 deletions

139
cmder/cmder_helper.go Normal file
View File

@ -0,0 +1,139 @@
package cmder
import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-go/cmder/cmdliner"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/logger"
"github.com/urfave/cli"
"sync"
)
var (
appInstance *cli.App
saveConfigMutex *sync.Mutex = new(sync.Mutex)
ReloadConfigFunc = func(c *cli.Context) error {
err := config.Config.Reload()
if err != nil {
fmt.Printf("重载配置错误: %s\n", err)
}
return nil
}
SaveConfigFunc = func(c *cli.Context) error {
saveConfigMutex.Lock()
defer saveConfigMutex.Unlock()
err := config.Config.Save()
if err != nil {
fmt.Printf("保存配置错误: %s\n", err)
}
return nil
}
)
func SetApp(app *cli.App) {
appInstance = app
}
func App() *cli.App {
return appInstance
}
func DoLoginHelper(username, password string) (usernameStr, passwordStr string, webToken cloudpan.WebLoginToken, appToken cloudpan.AppLoginToken, error error) {
line := cmdliner.NewLiner()
defer line.Close()
if username == "" {
username, error = line.State.Prompt("请输入用户名(手机号/邮箱/别名), 回车键提交 > ")
if error != nil {
return
}
}
if password == "" {
// liner 的 PasswordPrompt 不安全, 拆行之后密码就会显示出来了
fmt.Printf("请输入密码(输入的密码无回显, 确认输入完成, 回车提交即可) > ")
password, error = line.State.PasswordPrompt("")
if error != nil {
return
}
}
// app login
atoken, apperr := cloudpan.AppLogin(username, password)
if apperr != nil {
fmt.Println("APP登录失败", apperr)
return "", "", webToken, appToken, fmt.Errorf("登录失败")
}
// web cookie
wtoken := &cloudpan.WebLoginToken{}
cookieLoginUser := cloudpan.RefreshCookieToken(atoken.SessionKey)
if cookieLoginUser != "" {
logger.Verboseln("get COOKIE_LOGIN_USER by session key")
wtoken.CookieLoginUser = cookieLoginUser
} else {
// try login directly
wtoken, apperr = cloudpan.Login(username, password)
if apperr != nil {
if apperr.Code == apierror.ApiCodeNeedCaptchaCode {
for i := 0; i < 10; i++ {
// 需要认证码
savePath, apiErr := cloudpan.GetCaptchaImage()
if apiErr != nil {
fmt.Errorf("获取认证码错误")
return "", "", webToken, appToken, apiErr
}
fmt.Printf("打开以下路径, 以查看验证码\n%s\n\n", savePath)
vcode, err := line.State.Prompt("请输入验证码 > ")
if err != nil {
return "", "", webToken, appToken, err
}
wtoken, apiErr = cloudpan.LoginWithCaptcha(username, password, vcode)
if apiErr != nil {
return "", "", webToken, appToken, apiErr
} else {
return
}
}
} else {
return "", "", webToken, appToken, fmt.Errorf("登录失败")
}
}
}
webToken = *wtoken
appToken = *atoken
usernameStr = username
passwordStr = password
return
}
func TryLogin() *config.PanUser {
// can do automatically login?
for _, u := range config.Config.UserList {
if u.UID == config.Config.ActiveUID {
// login
_, _, webToken, appToken, err := DoLoginHelper(config.DecryptString(u.LoginUserName), config.DecryptString(u.LoginUserPassword))
if err != nil {
logger.Verboseln("automatically login error")
break
}
// success
u.WebToken = webToken
u.AppToken = appToken
// save
SaveConfigFunc(nil)
// reload
ReloadConfigFunc(nil)
return config.Config.ActiveUser()
}
}
return nil
}

View File

@ -13,8 +13,11 @@
// limitations under the License.
package command
import "C"
import (
"fmt"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"os"
"path"
"path/filepath"
@ -25,16 +28,9 @@ import (
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/internal/functions/panupload"
"github.com/urfave/cli"
"github.com/tickstep/cloudpan189-go/internal/config"
)
type backupFunc struct {
User *config.PanUser
Client *cloudpan.PanClient
}
func CmdBackup() cli.Command {
cmd := &backupFunc{}
return cli.Command{
Name: "backup",
Description: `备份指定 <文件/目录> 到云盘 <目标目录>
@ -51,8 +47,8 @@ func CmdBackup() cli.Command {
Usage: "备份文件或目录",
UsageText: "backup <文件/目录路径1> <文件/目录2> <文件/目录3> ... <目标目录>",
Category: "天翼云盘",
Before: cmd.Before,
Action: cmd.Backup,
Before: cmder.ReloadConfigFunc,
Action: Backup,
Flags: append(UploadFlags, cli.BoolFlag{
Name: "delete",
Usage: "通过本地数据库记录同步删除网盘文件",
@ -63,25 +59,13 @@ func CmdBackup() cli.Command {
}
}
func (c *backupFunc) Before(cli *cli.Context) error {
if cli.NArg() < 2 {
return ErrBadArgs
}
User := config.Config.ActiveUser()
if User == nil {
return ErrNotLogined
}
c.User = User
c.Client = User.PanClient()
return nil
}
func OpenSyncDb(path string) (panupload.SyncDb, error) {
return panupload.OpenSyncDb(path, "ecloud")
}
// 删除那些本地不存在而网盘存在的网盘文件 默认使用本地数据库判断,如果 flagSync 为 true 则遍历网盘文件列表进行判断(速度较慢)。
func (c *backupFunc) DelRemoteFileFromDB(familyId int64, localDir string, savePath string, flagSync bool) {
func DelRemoteFileFromDB(familyId int64, localDir string, savePath string, flagSync bool) {
activeUser := config.Config.ActiveUser()
var db panupload.SyncDb
var err error
dbpath := filepath.Join(localDir, ".ecloud")
@ -120,7 +104,7 @@ func (c *backupFunc) DelRemoteFileFromDB(familyId int64, localDir string, savePa
}
if ent.FileID == "" || ent.ParentId == "" {
efi, err := c.Client.AppGetBasicFileInfo(&cloudpan.AppGetFileInfoParam{
efi, err := activeUser.PanClient().AppGetBasicFileInfo(&cloudpan.AppGetFileInfoParam{
FileId: ent.FileID,
FilePath: ent.Path,
})
@ -158,9 +142,9 @@ func (c *backupFunc) DelRemoteFileFromDB(familyId int64, localDir string, savePa
}
if familyId > 0 {
taskId, err = c.Client.AppCreateBatchTask(familyId, delParam)
taskId, err = activeUser.PanClient().AppCreateBatchTask(familyId, delParam)
} else {
taskId, err = c.Client.CreateBatchTask(delParam)
taskId, err = activeUser.PanClient().CreateBatchTask(delParam)
}
if err != nil || taskId == "" {
fmt.Println("删除网盘文件或目录失败", ent.Path, err)
@ -182,7 +166,7 @@ func (c *backupFunc) DelRemoteFileFromDB(familyId int64, localDir string, savePa
parent := db.Get(savePath)
if parent.FileID == "" {
efi, err := c.Client.AppGetBasicFileInfo(&cloudpan.AppGetFileInfoParam{
efi, err := activeUser.PanClient().AppGetBasicFileInfo(&cloudpan.AppGetFileInfoParam{
FilePath: savePath,
})
if err != nil {
@ -197,7 +181,7 @@ func (c *backupFunc) DelRemoteFileFromDB(familyId int64, localDir string, savePa
param := cloudpan.NewAppFileListParam()
param.FileId = parentID
param.FamilyId = familyId
fileResult, err := c.Client.AppGetAllFileList(param)
fileResult, err := activeUser.PanClient().AppGetAllFileList(param)
if err != nil {
return
}
@ -236,7 +220,7 @@ func (c *backupFunc) DelRemoteFileFromDB(familyId int64, localDir string, savePa
syncFunc(savePath, parent.FileID)
}
func (c *backupFunc) checkPath(localdir string) (string, error) {
func checkPath(localdir string) (string, error) {
fullPath, err := filepath.Abs(localdir)
if err != nil {
fullPath = localdir
@ -266,7 +250,7 @@ func (c *backupFunc) checkPath(localdir string) (string, error) {
return fullPath, nil
}
func (c *backupFunc) Backup(cli *cli.Context) error {
func Backup(cli *cli.Context) error {
subArgs := cli.Args()
localpaths := make([]string, 0)
flagSync := cli.Bool("sync")
@ -291,11 +275,11 @@ func (c *backupFunc) Backup(cli *cli.Context) error {
for _, p := range subArgs[:localCount] {
go func(p string) {
defer wg.Done()
fullPath, err := c.checkPath(p)
fullPath, err := checkPath(p)
switch err {
case nil:
if flagSync || flagDelete {
c.DelRemoteFileFromDB(opt.FamilyId, fullPath, savePath, flagSync)
DelRemoteFileFromDB(opt.FamilyId, fullPath, savePath, flagSync)
}
case os.ErrInvalid:
default:

View File

@ -15,9 +15,76 @@ package command
import (
"fmt"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/urfave/cli"
)
func CmdCd() cli.Command {
return cli.Command{
Name: "cd",
Category: "天翼云盘",
Usage: "切换工作目录",
Description: `
cloudpan189-go cd <目录, 绝对路径或相对路径>
示例:
切换 /我的资源 工作目录:
cloudpan189-go cd /我的资源
切换上级目录:
cloudpan189-go cd ..
切换根目录:
cloudpan189-go cd /
`,
Before: cmder.ReloadConfigFunc,
After: cmder.SaveConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
RunChangeDirectory(parseFamilyId(c), c.Args().Get(0))
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
func CmdPwd() cli.Command {
return cli.Command{
Name: "pwd",
Usage: "输出工作目录",
UsageText: cmder.App().Name + " pwd",
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
if IsFamilyCloud(config.Config.ActiveUser().ActiveFamilyId) {
fmt.Println(config.Config.ActiveUser().FamilyWorkdir)
} else {
fmt.Println(config.Config.ActiveUser().Workdir)
}
return nil
},
}
}
func RunChangeDirectory(familyId int64, targetPath string) {
user := config.Config.ActiveUser()
targetPath = user.PathJoin(familyId, targetPath)

View File

@ -15,6 +15,11 @@ package command
import (
"errors"
"fmt"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/cmder/cmdutil"
"github.com/tickstep/cloudpan189-go/library/crypto"
"github.com/tickstep/library-go/getip"
"strconv"
"github.com/urfave/cli"
@ -23,6 +28,22 @@ import (
"github.com/tickstep/cloudpan189-go/internal/config"
)
const (
cryptoDescription = `
可用的方法 <method>:
aes-128-ctr, aes-192-ctr, aes-256-ctr,
aes-128-cfb, aes-192-cfb, aes-256-cfb,
aes-128-ofb, aes-192-ofb, aes-256-ofb.
密钥 <key>:
aes-128 对应key长度为16, aes-192 对应key长度为24, aes-256 对应key长度为32,
如果key长度不符合, 则自动修剪key, 舍弃超出长度的部分, 长度不足的部分用'\0'填充.
GZIP <disable-gzip>:
在文件加密之前, 启用GZIP压缩文件; 文件解密之后启用GZIP解压缩文件, 默认启用,
如果不启用, 则无法检测文件是否解密成功, 解密文件时会保留源文件, 避免解密失败造成文件数据丢失.`
)
var ErrBadArgs = errors.New("参数错误")
var ErrNotLogined = errors.New("未登录账号")
@ -46,3 +67,246 @@ func parseFamilyId(c *cli.Context) int64 {
}
return familyId
}
func CmdConfig() cli.Command {
return cli.Command{
Name: "config",
Usage: "显示和修改程序配置项",
Description: "显示和修改程序配置项",
Category: "配置",
Before: cmder.ReloadConfigFunc,
After: cmder.SaveConfigFunc,
Action: func(c *cli.Context) error {
fmt.Printf("----\n运行 %s config set 可进行设置配置\n\n当前配置:\n", cmder.App().Name)
config.Config.PrintTable()
return nil
},
Subcommands: []cli.Command{
{
Name: "set",
Usage: "修改程序配置项",
UsageText: cmder.App().Name + " config set [arguments...]",
Description: `
注意:
可通过设置环境变量 CLOUD189_CONFIG_DIR, 指定配置文件存放的目录.
cache_size 的值支持可选设置单位, 单位不区分大小写, b B 均表示字节的意思, 64KB, 1MB, 32kb, 65536b, 65536
max_download_rate, max_upload_rate 的值支持可选设置单位, 单位为每秒的传输速率, 后缀'/s' 可省略, 2MB/s, 2MB, 2m, 2mb 均为一个意思
例子:
cloudpan189-go config set -cache_size 64KB
cloudpan189-go config set -cache_size 16384 -max_download_parallel 200 -savedir D:/download`,
Action: func(c *cli.Context) error {
if c.NumFlags() <= 0 || c.NArg() > 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if c.IsSet("cache_size") {
err := config.Config.SetCacheSizeByStr(c.String("cache_size"))
if err != nil {
fmt.Printf("设置 cache_size 错误: %s\n", err)
return nil
}
}
if c.IsSet("max_download_parallel") {
config.Config.MaxDownloadParallel = c.Int("max_download_parallel")
}
if c.IsSet("max_upload_parallel") {
config.Config.MaxUploadParallel = c.Int("max_upload_parallel")
}
if c.IsSet("max_download_load") {
config.Config.MaxDownloadLoad = c.Int("max_download_load")
}
if c.IsSet("max_download_rate") {
err := config.Config.SetMaxDownloadRateByStr(c.String("max_download_rate"))
if err != nil {
fmt.Printf("设置 max_download_rate 错误: %s\n", err)
return nil
}
}
if c.IsSet("max_upload_rate") {
err := config.Config.SetMaxUploadRateByStr(c.String("max_upload_rate"))
if err != nil {
fmt.Printf("设置 max_upload_rate 错误: %s\n", err)
return nil
}
}
if c.IsSet("savedir") {
config.Config.SaveDir = c.String("savedir")
}
if c.IsSet("proxy") {
config.Config.SetProxy(c.String("proxy"))
}
if c.IsSet("local_addrs") {
config.Config.SetLocalAddrs(c.String("local_addrs"))
}
err := config.Config.Save()
if err != nil {
fmt.Println(err)
return err
}
config.Config.PrintTable()
fmt.Printf("\n保存配置成功!\n\n")
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "cache_size",
Usage: "下载缓存",
},
cli.IntFlag{
Name: "max_download_parallel",
Usage: "下载网络连接的最大并发量",
},
cli.IntFlag{
Name: "max_upload_parallel",
Usage: "上传网络连接的最大并发量",
},
cli.IntFlag{
Name: "max_download_load",
Usage: "同时进行下载文件的最大数量",
},
cli.StringFlag{
Name: "max_download_rate",
Usage: "限制最大下载速度, 0代表不限制",
},
cli.StringFlag{
Name: "max_upload_rate",
Usage: "限制最大上传速度, 0代表不限制",
},
cli.StringFlag{
Name: "savedir",
Usage: "下载文件的储存目录",
},
cli.StringFlag{
Name: "proxy",
Usage: "设置代理, 支持 http/socks5 代理",
},
cli.StringFlag{
Name: "local_addrs",
Usage: "设置本地网卡地址, 多个地址用逗号隔开",
},
},
},
},
}
}
func CmdTool() cli.Command {
return cli.Command{
Name: "tool",
Usage: "工具箱",
Action: func(c *cli.Context) error {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
},
Subcommands: []cli.Command{
{
Name: "getip",
Usage: "获取IP地址",
Action: func(c *cli.Context) error {
fmt.Printf("内网IP地址: \n")
for _, address := range cmdutil.ListAddresses() {
fmt.Printf("%s\n", address)
}
fmt.Printf("\n")
ipAddr, err := getip.IPInfoFromTechainBaiduByClient(config.Config.HTTPClient(""))
if err != nil {
fmt.Printf("获取公网IP错误: %s\n", err)
return nil
}
fmt.Printf("公网IP地址: %s\n", ipAddr)
return nil
},
},
{
Name: "enc",
Usage: "加密文件",
UsageText: cmder.App().Name + " enc -method=<method> -key=<key> [files...]",
Description: cryptoDescription,
Action: func(c *cli.Context) error {
if c.NArg() <= 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
for _, filePath := range c.Args() {
encryptedFilePath, err := crypto.EncryptFile(c.String("method"), []byte(c.String("key")), filePath, !c.Bool("disable-gzip"))
if err != nil {
fmt.Printf("%s\n", err)
continue
}
fmt.Printf("加密成功, %s -> %s\n", filePath, encryptedFilePath)
}
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "method",
Usage: "加密方法",
Value: "aes-128-ctr",
},
cli.StringFlag{
Name: "key",
Usage: "加密密钥",
Value: cmder.App().Name,
},
cli.BoolFlag{
Name: "disable-gzip",
Usage: "不启用GZIP",
},
},
},
{
Name: "dec",
Usage: "解密文件",
UsageText: cmder.App().Name + " dec -method=<method> -key=<key> [files...]",
Description: cryptoDescription,
Action: func(c *cli.Context) error {
if c.NArg() <= 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
for _, filePath := range c.Args() {
decryptedFilePath, err := crypto.DecryptFile(c.String("method"), []byte(c.String("key")), filePath, !c.Bool("disable-gzip"))
if err != nil {
fmt.Printf("%s\n", err)
continue
}
fmt.Printf("解密成功, %s -> %s\n", filePath, decryptedFilePath)
}
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "method",
Usage: "加密方法",
Value: "aes-128-ctr",
},
cli.StringFlag{
Name: "key",
Usage: "加密密钥",
Value: cmder.App().Name,
},
cli.BoolFlag{
Name: "disable-gzip",
Usage: "不启用GZIP",
},
},
},
},
}
}

View File

@ -16,11 +16,91 @@ package command
import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/logger"
"github.com/urfave/cli"
"path"
"time"
)
func CmdCp() cli.Command {
return cli.Command{
Name: "cp",
Usage: "拷贝文件/目录",
UsageText: cmder.App().Name + ` cp <文件/目录> <目标文件/目录>
cloudpan189-go cp <文件/目录1> <文件/目录2> <文件/目录3> ... <目标目录>`,
Description: `
注意: 拷贝多个文件和目录时, 请确保每一个文件和目录都存在, 否则拷贝操作会失败.
示例:
/我的资源/1.mp4 复制到 根目录 /
cloudpan189-go cp /我的资源/1.mp4 /
/我的资源/1.mp4 /我的资源/2.mp4 复制到 根目录 /
cloudpan189-go cp /我的资源/1.mp4 /我的资源/2.mp4 /
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() <= 1 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
if IsFamilyCloud(config.Config.ActiveUser().ActiveFamilyId) {
fmt.Println("家庭云不支持复制操作")
return nil
}
RunCopy(c.Args()...)
return nil
},
}
}
func CmdMv() cli.Command {
return cli.Command{
Name: "mv",
Usage: "移动文件/目录",
UsageText: `移动:
cloudpan189-go mv <文件/目录1> <文件/目录2> <文件/目录3> ... <目标目录>`,
Description: `
注意: 移动多个文件和目录时, 请确保每一个文件和目录都存在, 否则移动操作会失败.
示例:
/我的资源/1.mp4 移动到 根目录 /
cloudpan189-go mv /我的资源/1.mp4 /
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() <= 1 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
RunMove(parseFamilyId(c), c.Args()...)
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
// RunCopy 执行复制文件/目录
func RunCopy(paths ...string) {
// 只支持个人云

View File

@ -17,6 +17,7 @@ import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/cmder/cmdtable"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/cloudpan189-go/internal/file/downloader"
@ -24,6 +25,7 @@ import (
"github.com/tickstep/cloudpan189-go/internal/taskframework"
"github.com/tickstep/library-go/converter"
"github.com/tickstep/cloudpan189-go/library/requester/transfer"
"github.com/urfave/cli"
"os"
"path/filepath"
"runtime"
@ -58,6 +60,120 @@ var (
DownloadCacheSize = 64 * converter.KB
)
func CmdDownload() cli.Command {
return cli.Command{
Name: "download",
Aliases: []string{"d"},
Usage: "下载文件/目录",
UsageText: cmder.App().Name + " download <文件/目录路径1> <文件/目录2> <文件/目录3> ...",
Description: `
下载的文件默认保存到, 程序所在目录的 download/ 目录.
通过 cloudpan189-go config set -savedir <savedir>, 自定义保存的目录.
支持多个文件或目录下载.
自动跳过下载重名的文件!
示例:
设置保存目录, 保存到 D:\Downloads
注意区别反斜杠 "\" 和 斜杠 "/" !!!
cloudpan189-go config set -savedir D:\\Downloads
或者
cloudpan189-go config set -savedir D:/Downloads
下载 /我的资源/1.mp4
cloudpan189-go d /我的资源/1.mp4
下载 /我的资源 整个目录!!
cloudpan189-go d /我的资源
下载 /我的资源/1.mp4 并保存下载的文件到本地的 d:/panfile
cloudpan189-go d --saveto d:/panfile /我的资源/1.mp4
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
// 处理saveTo
var (
saveTo string
)
if c.Bool("save") {
saveTo = "."
} else if c.String("saveto") != "" {
saveTo = filepath.Clean(c.String("saveto"))
}
do := &DownloadOptions{
IsPrintStatus: c.Bool("status"),
IsExecutedPermission: c.Bool("x"),
IsOverwrite: c.Bool("ow"),
SaveTo: saveTo,
Parallel: c.Int("p"),
Load: c.Int("l"),
MaxRetry: c.Int("retry"),
NoCheck: c.Bool("nocheck"),
ShowProgress: !c.Bool("np"),
FamilyId: parseFamilyId(c),
}
RunDownload(c.Args(), do)
return nil
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "ow",
Usage: "overwrite, 覆盖已存在的文件",
},
cli.BoolFlag{
Name: "status",
Usage: "输出所有线程的工作状态",
},
cli.BoolFlag{
Name: "save",
Usage: "将下载的文件直接保存到当前工作目录",
},
cli.StringFlag{
Name: "saveto",
Usage: "将下载的文件直接保存到指定的目录",
},
cli.BoolFlag{
Name: "x",
Usage: "为文件加上执行权限, (windows系统无效)",
},
cli.IntFlag{
Name: "p",
Usage: "指定下载线程数",
},
cli.IntFlag{
Name: "l",
Usage: "指定同时进行下载文件的数量",
},
cli.IntFlag{
Name: "retry",
Usage: "下载失败最大重试次数",
Value: pandownload.DefaultDownloadMaxRetry,
},
cli.BoolFlag{
Name: "nocheck",
Usage: "下载文件完成后不校验文件",
},
cli.BoolFlag{
Name: "np",
Usage: "no progress 不展示下载进度条",
},
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
func downloadPrintFormat(load int) string {
if load <= 1 {
return pandownload.DefaultPrintFormat

View File

@ -18,8 +18,10 @@ import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/logger"
"github.com/urfave/cli"
"log"
"os"
"path"
@ -36,6 +38,53 @@ type (
}
)
func CmdExport() cli.Command {
return cli.Command{
Name: "export",
Usage: "导出文件/目录元数据",
UsageText: cmder.App().Name + " export <网盘文件/目录的路径1> <文件/目录2> <文件/目录3> ... <本地保存文件路径>",
Description: `
导出指定文件/目录下面的所有文件的元数据信息并保存到指定的本地文件里面导出的文件元信息可以使用 import 命令秒传文件功能导入到网盘中
支持多个文件或目录的导出.
示例:
导出 /我的资源/1.mp4 元数据到文件 /Users/tickstep/Downloads/export_files.txt
cloudpan189-go export /我的资源/1.mp4 /Users/tickstep/Downloads/export_files.txt
导出 /我的资源 整个目录 元数据到文件 /Users/tickstep/Downloads/export_files.txt
cloudpan189-go export /我的资源 /Users/tickstep/Downloads/export_files.txt
导出 网盘 整个目录 元数据到文件 /Users/tickstep/Downloads/export_files.txt
cloudpan189-go export / /Users/tickstep/Downloads/export_files.txt
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() < 2 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
subArgs := c.Args()
RunExportFiles(parseFamilyId(c), c.Bool("ow"), subArgs[:len(subArgs)-1], subArgs[len(subArgs)-1])
return nil
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "ow",
Usage: "overwrite, 覆盖已存在的导出文件",
},
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
func RunExportFiles(familyId int64, overwrite bool, panPaths []string, saveLocalFilePath string) {
activeUser := config.Config.ActiveUser()
panClient := activeUser.PanClient()

View File

@ -17,12 +17,41 @@ import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/cmder/cmdtable"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/urfave/cli"
"strconv"
"strings"
)
func CmdFamily() cli.Command {
return cli.Command{
Name: "family",
Usage: "切换云工作模式(家庭云/个人云)",
Description: `
切换已登录的天翼帐号的云工作模式家庭云/个人云
如果运行该条命令没有提供参数, 程序将会列出所有的家庭云, 供选择切换.
示例:
cloudpan189-go family
cloudpan189-go family <familyId>
`,
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc,
After: cmder.SaveConfigFunc,
Action: func(c *cli.Context) error {
inputData := c.Args().Get(0)
targetFamilyId := int64(-1)
if inputData != "" && len(inputData) > 0 {
targetFamilyId, _ = strconv.ParseInt(inputData, 10, 0)
}
RunSwitchFamilyList(targetFamilyId)
return nil
},
}
}
func RunSwitchFamilyList(targetFamilyId int64) {
currentFamilyId := config.Config.ActiveUser().ActiveFamilyId
var activeFamilyInfo *cloudpan.AppFamilyInfo = nil

View File

@ -18,12 +18,15 @@ import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/logger"
"github.com/urfave/cli"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
)
@ -39,6 +42,66 @@ const (
DefaultSaveToPanPath = "/cloudpan189-go"
)
func CmdImport() cli.Command {
return cli.Command{
Name: "import",
Usage: "导入文件",
UsageText: cmder.App().Name + " export <本地元数据文件路径>",
Description: `
导入文件中记录的元数据文件到网盘保存到网盘的文件会使用文件元数据记录的路径位置如果没有指定云盘目录(saveto)则默认导入到目录 cloudpan189-go
导入的文件可以使用 export 命令获得
导入文件每一行是一个文件元数据样例如下
{"md5":"3F9EEEBC4E583574D9D64A75E5061E56","size":6365224,"path":"/test/file.dmg"}
注意导入文件依赖秒传功能即会消耗你每日上传文件的限额如果你导入的文件过多达到每日限额则剩余的文件无法在当日完成导入
示例:
导入文件 /Users/tickstep/Downloads/export_files.txt
cloudpan189-go import /Users/tickstep/Downloads/export_files.txt
导入文件 /Users/tickstep/Downloads/export_files.txt 并保存到目录 /my2020
cloudpan189-go import -saveto=/my2020 /Users/tickstep/Downloads/export_files.txt
导入文件 /Users/tickstep/Downloads/export_files.txt 并保存到网盘根目录 /
cloudpan189-go import -saveto=/ /Users/tickstep/Downloads/export_files.txt
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
saveTo := ""
if c.String("saveto") != "" {
saveTo = filepath.Clean(c.String("saveto"))
}
subArgs := c.Args()
RunImportFiles(parseFamilyId(c), c.Bool("ow"), saveTo, subArgs[0])
return nil
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "ow",
Usage: "overwrite, 覆盖已存在的网盘文件",
},
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
cli.StringFlag{
Name: "saveto",
Usage: "将文件保存到指定的目录",
},
},
}
}
func RunImportFiles(familyId int64, overwrite bool, panSavePath, localFilePath string) {
lfi,_ := os.Stat(localFilePath)
if lfi != nil {

View File

@ -16,79 +16,118 @@ package command
import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-go/cmder/cmdliner"
"github.com/tickstep/library-go/logger"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
_ "github.com/tickstep/library-go/requester"
"github.com/urfave/cli"
)
func RunLogin(username, password string) (usernameStr, passwordStr string, webToken cloudpan.WebLoginToken, appToken cloudpan.AppLoginToken, error error) {
line := cmdliner.NewLiner()
defer line.Close()
if username == "" {
username, error = line.State.Prompt("请输入用户名(手机号/邮箱/别名), 回车键提交 > ")
if error != nil {
return
}
}
func CmdLogin() cli.Command {
return cli.Command{
Name: "login",
Usage: "登录天翼云盘账号",
Description: `
示例:
cloudpan189-go login
cloudpan189-go login -username=tickstep -password=123xxx
if password == "" {
// liner 的 PasswordPrompt 不安全, 拆行之后密码就会显示出来了
fmt.Printf("请输入密码(输入的密码无回显, 确认输入完成, 回车提交即可) > ")
password, error = line.State.PasswordPrompt("")
if error != nil {
return
}
}
// app login
atoken, apperr := cloudpan.AppLogin(username, password)
if apperr != nil {
fmt.Println("APP登录失败", apperr)
return "", "", webToken, appToken, fmt.Errorf("登录失败")
}
// web cookie
wtoken := &cloudpan.WebLoginToken{}
cookieLoginUser := cloudpan.RefreshCookieToken(atoken.SessionKey)
if cookieLoginUser != "" {
logger.Verboseln("get COOKIE_LOGIN_USER by session key")
wtoken.CookieLoginUser = cookieLoginUser
} else {
// try login directly
wtoken, apperr = cloudpan.Login(username, password)
if apperr != nil {
if apperr.Code == apierror.ApiCodeNeedCaptchaCode {
for i := 0; i < 10; i++ {
// 需要认证码
savePath, apiErr := cloudpan.GetCaptchaImage()
if apiErr != nil {
fmt.Errorf("获取认证码错误")
return "", "", webToken, appToken, apiErr
}
fmt.Printf("打开以下路径, 以查看验证码\n%s\n\n", savePath)
vcode, err := line.State.Prompt("请输入验证码 > ")
if err != nil {
return "", "", webToken, appToken, err
}
wtoken, apiErr = cloudpan.LoginWithCaptcha(username, password, vcode)
if apiErr != nil {
return "", "", webToken, appToken, apiErr
} else {
return
}
常规登录:
按提示一步一步来即可.
`,
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc, // 每次进行登录动作的时候需要调用刷新配置
After: cmder.SaveConfigFunc, // 登录完成需要调用保存配置
Action: func(c *cli.Context) error {
appToken := cloudpan.AppLoginToken{}
webToken := cloudpan.WebLoginToken{}
username := ""
passowrd := ""
if c.IsSet("COOKIE_LOGIN_USER") {
webToken.CookieLoginUser = c.String("COOKIE_LOGIN_USER")
} else if c.NArg() == 0 {
var err error
username, passowrd, webToken, appToken, err = RunLogin(c.String("username"), c.String("password"))
if err != nil {
fmt.Println(err)
return err
}
} else {
return "", "", webToken, appToken, fmt.Errorf("登录失败")
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
}
cloudUser, _ := config.SetupUserByCookie(&webToken, &appToken)
// save username / password
cloudUser.LoginUserName = config.EncryptString(username)
cloudUser.LoginUserPassword = config.EncryptString(passowrd)
config.Config.SetActiveUser(cloudUser)
fmt.Println("天翼帐号登录成功: ", cloudUser.Nickname)
return nil
},
// 命令的附加options参数说明使用 help login 命令即可查看
Flags: []cli.Flag{
cli.StringFlag{
Name: "username",
Usage: "登录天翼帐号的用户名(手机号/邮箱/别名)",
},
cli.StringFlag{
Name: "password",
Usage: "登录天翼帐号的用户密码",
},
// 暂不支持
// cloudpan189-go login -COOKIE_LOGIN_USER=8B12CBBCE89CA8DFC3445985B63B511B5E7EC7...
//cli.StringFlag{
// Name: "COOKIE_LOGIN_USER",
// Usage: "使用 COOKIE_LOGIN_USER cookie来登录帐号",
//},
},
}
webToken = *wtoken
appToken = *atoken
usernameStr = username
passwordStr = password
return
}
func CmdLogout() cli.Command {
return cli.Command{
Name: "logout",
Usage: "退出天翼帐号",
Description: "退出当前登录的帐号",
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc,
After: cmder.SaveConfigFunc,
Action: func(c *cli.Context) error {
if config.Config.NumLogins() == 0 {
fmt.Println("未设置任何帐号, 不能退出")
return nil
}
var (
confirm string
activeUser = config.Config.ActiveUser()
)
if !c.Bool("y") {
fmt.Printf("确认退出当前帐号: %s ? (y/n) > ", activeUser.Nickname)
_, err := fmt.Scanln(&confirm)
if err != nil || (confirm != "y" && confirm != "Y") {
return err
}
}
deletedUser, err := config.Config.DeleteUser(activeUser.UID)
if err != nil {
fmt.Printf("退出用户 %s, 失败, 错误: %s\n", activeUser.Nickname, err)
}
fmt.Printf("退出用户成功: %s\n", deletedUser.Nickname)
return nil
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "y",
Usage: "确认退出帐号",
},
},
}
}
func RunLogin(username, password string) (usernameStr, passwordStr string, webToken cloudpan.WebLoginToken, appToken cloudpan.AppLoginToken, error error) {
return cmder.DoLoginHelper(username, password)
}

View File

@ -17,10 +17,12 @@ import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/cmder/cmdtable"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/converter"
"github.com/tickstep/library-go/text"
"github.com/urfave/cli"
"os"
"strconv"
)
@ -43,6 +45,101 @@ const (
opSearch
)
func CmdLs() cli.Command {
return cli.Command{
Name: "ls",
Aliases: []string{"l", "ll"},
Usage: "列出目录",
UsageText: cmder.App().Name + " ls <目录>",
Description: `
列出当前工作目录内的文件和目录, 或指定目录内的文件和目录
示例:
列出 我的资源 内的文件和目录
cloudpan189-go ls 我的资源
绝对路径
cloudpan189-go ls /我的资源
降序排序
cloudpan189-go ls -desc 我的资源
按文件大小降序排序
cloudpan189-go ls -size -desc 我的资源
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
var (
orderBy cloudpan.OrderBy = cloudpan.OrderByName
orderSort cloudpan.OrderSort = cloudpan.OrderAsc
)
switch {
case c.IsSet("asc"):
orderSort = cloudpan.OrderAsc
case c.IsSet("desc"):
orderSort = cloudpan.OrderDesc
default:
orderSort = cloudpan.OrderAsc
}
switch {
case c.IsSet("time"):
orderBy = cloudpan.OrderByTime
case c.IsSet("name"):
orderBy = cloudpan.OrderByName
case c.IsSet("size"):
orderBy = cloudpan.OrderBySize
default:
orderBy = cloudpan.OrderByTime
}
RunLs(parseFamilyId(c), c.Args().Get(0), &LsOptions{
Total: c.Bool("l") || c.Parent().Args().Get(0) == "ll",
}, orderBy, orderSort)
return nil
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "l",
Usage: "详细显示",
},
cli.BoolFlag{
Name: "asc",
Usage: "升序排序",
},
cli.BoolFlag{
Name: "desc",
Usage: "降序排序",
},
cli.BoolFlag{
Name: "time",
Usage: "根据时间排序",
},
cli.BoolFlag{
Name: "name",
Usage: "根据文件名排序",
},
cli.BoolFlag{
Name: "size",
Usage: "根据大小排序",
},
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
func RunLs(familyId int64, targetPath string, lsOptions *LsOptions, orderBy cloudpan.OrderBy, orderSort cloudpan.OrderSort) {
activeUser := config.Config.ActiveUser()
targetPath = activeUser.PathJoin(familyId, targetPath)

View File

@ -17,10 +17,42 @@ import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/urfave/cli"
"path"
"strings"
)
func CmdMkdir() cli.Command {
return cli.Command{
Name: "mkdir",
Usage: "创建目录",
UsageText: cmder.App().Name + " mkdir <目录>",
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
RunMkdir(parseFamilyId(c), c.Args().Get(0))
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
func RunMkdir(familyId int64, name string) {
activeUser := GetActiveUser()
fullpath := activeUser.PathJoin(familyId, name)

View File

@ -13,6 +13,14 @@
// limitations under the License.
package command
import (
"fmt"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/converter"
"github.com/urfave/cli"
)
type QuotaInfo struct {
// 已使用个人空间大小
UsedSize int64
@ -20,6 +28,30 @@ type QuotaInfo struct {
Quota int64
}
func CmdQuota() cli.Command {
return cli.Command{
Name: "quota",
Usage: "获取当前帐号空间配额",
Description: "获取网盘的总储存空间, 和已使用的储存空间",
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
q, err := RunGetQuotaInfo()
if err == nil {
fmt.Printf("账号: %s, uid: %d, 个人空间总额: %s, 个人空间已使用: %s, 比率: %f%%\n",
config.Config.ActiveUser().Nickname, config.Config.ActiveUser().UID,
converter.ConvertFileSize(q.Quota, 2), converter.ConvertFileSize(q.UsedSize, 2),
100*float64(q.UsedSize)/float64(q.Quota))
}
return nil
},
}
}
func RunGetQuotaInfo() (quotaInfo *QuotaInfo, error error) {
user, err := GetActivePanClient().GetUserInfo()
if err != nil {

View File

@ -17,12 +17,104 @@ import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/cmder/cmdtable"
"github.com/tickstep/library-go/converter"
"github.com/urfave/cli"
"os"
"strconv"
)
func CmdRecycle() cli.Command {
return cli.Command{
Name: "recycle",
Usage: "回收站",
Description: `
回收站操作.
示例:
1. 从回收站还原两个文件, 其中的两个文件的 file_id 分别为 1013792297798440 643596340463870
cloudpan189-go recycle restore 1013792297798440 643596340463870
2. 从回收站删除两个文件, 其中的两个文件的 file_id 分别为 1013792297798440 643596340463870
cloudpan189-go recycle delete 1013792297798440 643596340463870
3. 清空回收站, 程序不会进行二次确认, 谨慎操作!!!
cloudpan189-go recycle delete -all
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NumFlags() <= 0 || c.NArg() <= 0 {
cli.ShowCommandHelp(c, c.Command.Name)
}
return nil
},
Subcommands: []cli.Command{
{
Name: "list",
Aliases: []string{"ls", "l"},
Usage: "列出回收站文件列表",
UsageText: cmder.App().Name + " recycle list",
Action: func(c *cli.Context) error {
RunRecycleList(c.Int("page"))
return nil
},
Flags: []cli.Flag{
cli.IntFlag{
Name: "page",
Usage: "回收站文件列表页数",
Value: 1,
},
},
},
{
Name: "restore",
Aliases: []string{"r"},
Usage: "还原回收站文件或目录",
UsageText: cmder.App().Name + " recycle restore <file_id 1> <file_id 2> <file_id 3> ...",
Description: `根据文件/目录的 fs_id, 还原回收站指定的文件或目录`,
Action: func(c *cli.Context) error {
if c.NArg() <= 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
RunRecycleRestore(c.Args()...)
return nil
},
},
{
Name: "delete",
Aliases: []string{"d"},
Usage: "删除回收站文件或目录 / 清空回收站",
UsageText: cmder.App().Name + " recycle delete [-all] <file_id 1> <file_id 2> <file_id 3> ...",
Description: `根据文件/目录的 file_id 或 -all 参数, 删除回收站指定的文件或目录或清空回收站`,
Action: func(c *cli.Context) error {
if c.Bool("all") {
// 清空回收站
RunRecycleClear()
return nil
}
if c.NArg() <= 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
RunRecycleDelete(c.Args()...)
return nil
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "all",
Usage: "清空回收站, 程序不会进行二次确认, 谨慎操作!!!",
},
},
},
},
}
}
// RunRecycleList 执行列出回收站文件列表
func RunRecycleList(page int) {
if page < 1 {

View File

@ -18,10 +18,53 @@ import (
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-api/cloudpan/apiutil"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/urfave/cli"
"path"
"strings"
)
func CmdRename() cli.Command {
return cli.Command{
Name: "rename",
Usage: "重命名文件",
UsageText: `重命名文件:
cloudpan189-go rename <旧文件/目录名> <新文件/目录名>`,
Description: `
示例:
将文件 1.mp4 重命名为 2.mp4
cloudpan189-go rename 1.mp4 2.mp4
将文件 /test/1.mp4 重命名为 /test/2.mp4
要求必须是同一个文件目录内
cloudpan189-go rename /test/1.mp4 /test/2.mp4
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() != 2 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
RunRename(parseFamilyId(c), c.Args().Get(0), c.Args().Get(1))
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
func RunRename(familyId int64, oldName string, newName string) {
if oldName == "" {
fmt.Println("请指定命名文件")

View File

@ -16,14 +16,61 @@ package command
import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/cmder/cmdtable"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/logger"
"github.com/urfave/cli"
"os"
"path"
"strconv"
"time"
)
func CmdRm() cli.Command {
return cli.Command{
Name: "rm",
Usage: "删除文件/目录",
UsageText: cmder.App().Name + " rm <文件/目录的路径1> <文件/目录2> <文件/目录3> ...",
Description: `
注意: 删除多个文件和目录时, 请确保每一个文件和目录都存在, 否则删除操作会失败.
被删除的文件或目录可在网盘文件回收站找回.
示例:
删除 /我的资源/1.mp4
cloudpan189-go rm /我的资源/1.mp4
删除 /我的资源/1.mp4 /我的资源/2.mp4
cloudpan189-go rm /我的资源/1.mp4 /我的资源/2.mp4
删除 /我的资源 整个目录 !!
cloudpan189-go rm /我的资源
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
RunRemove(parseFamilyId(c), c.Args()...)
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
// RunRemove 执行 批量删除文件/目录
func RunRemove(familyId int64, paths ...string) {
if IsFamilyCloud(familyId) {

View File

@ -15,6 +15,10 @@ package command
import (
"fmt"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/tickstep/library-go/converter"
"github.com/urfave/cli"
"os"
"strconv"
"strings"
@ -25,6 +29,138 @@ import (
"github.com/tickstep/library-go/text"
)
func CmdShare() cli.Command {
return cli.Command{
Name: "share",
Usage: "分享文件/目录",
UsageText: cmder.App().Name + " share",
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
},
Subcommands: []cli.Command{
{
Name: "set",
Aliases: []string{"s"},
Usage: "设置分享文件/目录",
UsageText: cmder.App().Name + " share set <文件/目录1> <文件/目录2> ...",
Description: `
示例:
创建文件 1.mp4 的分享链接
cloudpan189-go share set 1.mp4
创建文件 1.mp4 的分享链接并指定有效期为1天
cloudpan189-go share set -time 1 1.mp4
`,
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
if IsFamilyCloud(config.Config.ActiveUser().ActiveFamilyId) {
fmt.Println("家庭云不支持文件分享,请切换到个人云")
return nil
}
et := cloudpan.ShareExpiredTimeForever
if c.IsSet("time") {
op := c.String("time")
if op == "1" {
et = cloudpan.ShareExpiredTime1Day
} else if op == "2" {
et = cloudpan.ShareExpiredTime7Day
} else {
et = cloudpan.ShareExpiredTimeForever
}
}
sm := cloudpan.ShareModePrivate
if c.IsSet("mode") {
op := c.String("mode")
if op == "1" {
sm = cloudpan.ShareModePrivate
} else {
sm = cloudpan.ShareModePublic
}
}
RunShareSet(c.Args(), et, sm)
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "time",
Usage: "有效期0-永久1-1天2-7天",
},
cli.StringFlag{
Name: "mode",
Usage: "有效期1-私密分享2-公开分享",
},
},
},
{
Name: "list",
Aliases: []string{"l"},
Usage: "列出已分享文件/目录",
UsageText: cmder.App().Name + " share list",
Action: func(c *cli.Context) error {
RunShareList(c.Int("page"))
return nil
},
Flags: []cli.Flag{
cli.IntFlag{
Name: "page",
Usage: "分享列表的页数",
Value: 1,
},
},
},
{
Name: "cancel",
Aliases: []string{"c"},
Usage: "取消分享文件/目录",
UsageText: cmder.App().Name + " share cancel <shareid_1> <shareid_2> ...",
Description: `目前只支持通过分享id (shareid) 来取消分享.`,
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
RunShareCancel(converter.SliceStringToInt64(c.Args()))
return nil
},
},
{
Name: "save",
Usage: "转存分享的全部文件到指定文件夹",
UsageText: cmder.App().Name + " share save [save_dir_path] \"[share_url]\"",
Description: `转存分享的全部文件到指定文件夹
示例:
https://cloud.189.cn/t/RzUNre7nq2Uf 分享链接里面的全部文件转存到 /我的文档 这个网盘目录里面
注意转存需要一定的时间才能生效需要等待一会才能完全转存到网盘文件夹里面
cloudpan189-go share save /我的文档 https://cloud.189.cn/t/RzUNre7nq2Uf访问码io7x
`,
Action: func(c *cli.Context) error {
if c.NArg() < 2 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
RunShareSave(c.Args().Get(1), c.Args().Get(0))
return nil
},
},
},
}
}
// RunShareSet 执行分享
func RunShareSet(paths []string, expiredTime cloudpan.ShareExpiredTime, shareMode cloudpan.ShareMode) {
fileList, _, err := GetFileInfoByPaths(paths[:len(paths)]...)

View File

@ -15,6 +15,7 @@ package command
import (
"fmt"
"github.com/tickstep/cloudpan189-go/cmder"
"io/ioutil"
"os"
"path"
@ -98,6 +99,109 @@ var UploadFlags = []cli.Flag{
},
}
func CmdUpload() cli.Command {
return cli.Command{
Name: "upload",
Aliases: []string{"u"},
Usage: "上传文件/目录",
UsageText: cmder.App().Name + " upload <本地文件/目录的路径1> <文件/目录2> <文件/目录3> ... <目标目录>",
Description: `
上传默认采用分片上传的方式, 上传的文件将会保存到, <目标目录>.
示例:
1. 将本地的 C:\Users\Administrator\Desktop\1.mp4 上传到网盘 /视频 目录
注意区别反斜杠 "\" 和 斜杠 "/" !!!
cloudpan189-go upload C:/Users/Administrator/Desktop/1.mp4 /视频
2. 将本地的 C:\Users\Administrator\Desktop\1.mp4 C:\Users\Administrator\Desktop\2.mp4 上传到网盘 /视频 目录
cloudpan189-go upload C:/Users/Administrator/Desktop/1.mp4 C:/Users/Administrator/Desktop/2.mp4 /视频
3. 将本地的 C:\Users\Administrator\Desktop 整个目录上传到网盘 /视频 目录
cloudpan189-go upload C:/Users/Administrator/Desktop /视频
4. 使用相对路径
cloudpan189-go upload 1.mp4 /视频
5. 覆盖上传已存在的同名文件会被移到回收站
cloudpan189-go upload -ow 1.mp4 /视频
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() < 2 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
subArgs := c.Args()
RunUpload(subArgs[:c.NArg()-1], subArgs[c.NArg()-1], &UploadOptions{
AllParallel: c.Int("p"),
Parallel: 1, // 天翼云盘一个文件只支持单线程上传
MaxRetry: c.Int("retry"),
NoRapidUpload: c.Bool("norapid"),
NoSplitFile: true, // 天翼云盘不支持分片并发上传,只支持单线程上传,支持断点续传
ShowProgress: !c.Bool("np"),
IsOverwrite: c.Bool("ow"),
FamilyId: parseFamilyId(c),
ExcludeNames: c.StringSlice("exn"),
})
return nil
},
Flags: UploadFlags,
}
}
func CmdRapidUpload() cli.Command {
return cli.Command{
Name: "rapidupload",
Aliases: []string{"ru"},
Usage: "手动秒传文件",
UsageText: cmder.App().Name + " rapidupload -size=<文件的大小> -md5=<文件的md5值> <保存的网盘路径, 需包含文件名>",
Description: `
使用此功能秒传文件, 前提是知道文件的大小, md5, 且网盘中存在一模一样的文件.
上传的文件将会保存到网盘的目标目录.
示例:
1. 如果秒传成功, 则保存到网盘路径 /test/file.txt
cloudpan189-go rapidupload -size=56276137 -md5=fbe082d80e90f90f0fb1f94adbbcfa7f /test/file.txt
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() <= 0 || !c.IsSet("md5") || !c.IsSet("size") {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
RunRapidUpload(parseFamilyId(c), c.Bool("ow"), c.Args().Get(0), c.String("md5"), c.Int64("size"))
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "md5",
Usage: "文件的 md5 值",
Required: true,
},
cli.Int64Flag{
Name: "size",
Usage: "文件的大小",
Required: true,
},
cli.BoolFlag{
Name: "ow",
Usage: "overwrite, 覆盖已存在的文件",
},
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
},
},
}
}
// RunUpload 执行文件上传
func RunUpload(localPaths []string, savePath string, opt *UploadOptions) {
activeUser := GetActiveUser()

View File

@ -14,9 +14,136 @@
package command
import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/urfave/cli"
"strconv"
)
func CmdLoglist() cli.Command {
return cli.Command{
Name: "loglist",
Usage: "列出帐号列表",
Description: "列出所有已登录的天翼帐号",
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
fmt.Println(config.Config.UserList.String())
return nil
},
}
}
func CmdSu() cli.Command {
return cli.Command{
Name: "su",
Usage: "切换天翼帐号",
Description: `
切换已登录的天翼帐号:
如果运行该条命令没有提供参数, 程序将会列出所有的帐号, 供选择切换.
示例:
cloudpan189-go su
cloudpan189-go su <uid or name>
`,
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc,
After: cmder.SaveConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() >= 2 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
numLogins := config.Config.NumLogins()
if numLogins == 0 {
fmt.Printf("未设置任何帐号, 不能切换\n")
return nil
}
var (
inputData = c.Args().Get(0)
uid uint64
)
if c.NArg() == 1 {
// 直接切换
uid, _ = strconv.ParseUint(inputData, 10, 64)
} else if c.NArg() == 0 {
// 输出所有帐号供选择切换
cli.HandleAction(cmder.App().Command("loglist").Action, c)
// 提示输入 index
var index string
fmt.Printf("输入要切换帐号的 # 值 > ")
_, err := fmt.Scanln(&index)
if err != nil {
return nil
}
if n, err := strconv.Atoi(index); err == nil && n >= 0 && n < numLogins {
uid = config.Config.UserList[n].UID
} else {
fmt.Printf("切换用户失败, 请检查 # 值是否正确\n")
return nil
}
} else {
cli.ShowCommandHelp(c, c.Command.Name)
}
switchedUser, err := config.Config.SwitchUser(uid, inputData)
if err != nil {
fmt.Printf("切换用户失败, %s\n", err)
return nil
}
if switchedUser == nil {
switchedUser = cmder.TryLogin()
}
if switchedUser != nil {
fmt.Printf("切换用户: %s\n", switchedUser.Nickname)
} else {
fmt.Printf("切换用户失败\n")
}
return nil
},
}
}
func CmdWho() cli.Command {
return cli.Command{
Name: "who",
Usage: "获取当前帐号",
Description: "获取当前帐号的信息",
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
activeUser := config.Config.ActiveUser()
gender := "未知"
if activeUser.Sex == "F" {
gender = "女"
} else if activeUser.Sex == "M" {
gender = "男"
}
cloudName := "个人云"
if config.Config.ActiveUser().ActiveFamilyId > 0 {
cloudName = "家庭云(" + config.Config.ActiveUser().ActiveFamilyInfo.RemarkName + ")"
}
fmt.Printf("当前帐号 uid: %d, 昵称: %s, 用户名: %s, 性别: %s, 云:%s\n", activeUser.UID, activeUser.Nickname, activeUser.AccountName, gender, cloudName)
return nil
},
}
}
func RunGetUserInfo() (userInfo *cloudpan.UserInfo, error error) {
return GetActivePanClient().GetUserInfo()
}

View File

@ -16,8 +16,29 @@ package command
import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/urfave/cli"
)
func CmdSign() cli.Command {
return cli.Command{
Name: "sign",
Usage: "用户签到",
Description: "当前帐号进行签到",
Category: "天翼云盘账号",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
RunUserSign()
return nil
},
}
}
func RunUserSign() {
activeUser := GetActiveUser()
result, err := activeUser.PanClient().AppUserSign()

View File

@ -17,6 +17,9 @@ import (
"fmt"
"github.com/tickstep/cloudpan189-api/cloudpan"
"github.com/tickstep/cloudpan189-api/cloudpan/apierror"
"github.com/tickstep/cloudpan189-go/cmder"
"github.com/tickstep/cloudpan189-go/internal/config"
"github.com/urfave/cli"
)
type (
@ -31,6 +34,73 @@ const (
FamilyCloud FileSourceType = "family"
)
func CmdXcp() cli.Command {
return cli.Command{
Name: "xcp",
Usage: "转存拷贝文件/目录,个人云和家庭云之间转存文件",
UsageText: cmder.App().Name + ` xcp <文件/目录>
cloudpan189-go xcp <文件/目录1> <文件/目录2> <文件/目录3>`,
Description: `
注意: 拷贝多个文件和目录时, 请确保每一个文件和目录都存在, 否则拷贝操作会失败. 同样需要保证目标云不存在对应的文件否则也会操作失败
示例:
当前程序工作在个人云模式下 /个人云目录/1.mp4 转存复制到 家庭云根目录中
cloudpan189-go xcp /个人云目录/1.mp4
当前程序工作在家庭云模式下 /家庭云目录/1.mp4 /家庭云目录/2.mp4 转存复制到 个人云 /来自家庭共享 目录中
cloudpan189-go xcp /家庭云目录/1.mp4 /家庭云目录/2.mp4
`,
Category: "天翼云盘",
Before: cmder.ReloadConfigFunc,
Action: func(c *cli.Context) error {
if c.NArg() <= 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
if config.Config.ActiveUser() == nil {
fmt.Println("未登录账号")
return nil
}
familyId := parseFamilyId(c)
fileSource := PersonCloud
if c.IsSet("source") {
sourceStr := c.String("source")
if sourceStr == "person" {
fileSource = PersonCloud
} else if sourceStr == "family" {
fileSource = FamilyCloud
} else {
fmt.Println("不支持的参数")
return nil
}
} else {
if IsFamilyCloud(config.Config.ActiveUser().ActiveFamilyId) {
fileSource = FamilyCloud
} else {
fileSource = PersonCloud
}
}
RunXCopy(fileSource, familyId, c.Args()...)
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "familyId",
Usage: "家庭云ID",
Value: "",
Required: false,
},
cli.StringFlag{
Name: "source",
Usage: "文件源person-个人云family-家庭云",
Value: "",
Required: false,
},
},
}
}
// RunXCopy 执行移动文件/目录
func RunXCopy(source FileSourceType, familyId int64, paths ...string) {
activeUser := GetActiveUser()

1530
main.go

File diff suppressed because it is too large Load Diff