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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 2.3.4
1. listbucket, listbucket2增加捕捉interrupt信号(CTR-C), 打印marker
2. account在本地记录账号,默认不覆盖, 加了-w强制覆盖选项
3. listbucket2 增加append 模式(-a)开启, 修复列举几亿空间的时候,列举一半左右程序中断问题
4. 修复dircache 列表没有输出到文件使用-o选项的时候
5. 修复qupload, qupload2使用多线程上传导致的部分文件上传失败问题
6. 加了-L 选项到qshell, 使用当前工作路径作为qshell的配置目录

# 2.3.3
1. 修复qdownload配置cdn_domain使用了测试域名作为HOST 引起超过10G流量限制的问题
2. listbucket2 max-retry选项只限制出错下载次数,不限制接口返回空的次数
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ qshell是利用[七牛文档上公开的API](http://developer.qiniu.com)实现

|版本 |支持平台|链接|
|--------|---------|----|
|qshell-v2.3.3 |Mac OSX, Linux, Windows|[下载](http://devtools.qiniu.com/qshell-v2.3.3.zip)|
|qshell-v2.3.4 |Mac OSX, Linux, Windows|[下载](http://devtools.qiniu.com/qshell-v2.3.4.zip)|

## 安装

Expand Down Expand Up @@ -69,7 +69,8 @@ $ qshell account <Your AccessKey> <Your SecretKey> <Your Name>
$ qshell account -- <Your AccessKey> <Your SecretKey> <Your Name>
```

可以连续使用qshell account 添加账号ak, sk, name信息,qshell会保存这些账号的信息, 可以使用qshell user命令列举账号信息,在各个账号之间切换, 删除账号等
可以连续使用qshell account 添加账号ak, sk, name信息,qshell会保存这些账号的信息, 可以使用qshell user命令列举账号信息,在各个账号之间切换, 删除账号等。
如果使用的2.3.0之前的版本account命令记录的账户信息,需要先使用qshell user clean清楚保存的账户信息,然后使用qshell account命令重新记录账户信息。

2. 添加完账户后,就可以使用qshell上传,下载文件了

Expand Down Expand Up @@ -123,6 +124,7 @@ fi
|-h|打印命令列表帮助信息,遇到参数忘记的情况下,可以使用该命令|
|-v|打印工具版本,反馈问题的时候,请提前告知工具对应版本号|
|-C|qshell配置文件, 其配置格式请看下一节|
|-L|使用当前工作路径作为qshell的配置目录|

## 配置文件

Expand Down
7 changes: 6 additions & 1 deletion cmd/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import (
"os"
)

var (
accountOver bool
)

func init() {
cmdAccount.Flags().BoolVarP(&accountOver, "overwrite", "w", false, "overwrite account or not when account exists in local db, by default not overwrite")
RootCmd.AddCommand(cmdAccount)
}

Expand Down Expand Up @@ -37,7 +42,7 @@ func Account(cmd *cobra.Command, params []string) {
name := params[2]

pt, oldPath := iqshell.AccPath(), iqshell.OldAccPath()
sErr := iqshell.SetAccount2(accessKey, secretKey, name, pt, oldPath)
sErr := iqshell.SetAccount2(accessKey, secretKey, name, pt, oldPath, accountOver)
if sErr != nil {
fmt.Println(sErr)
os.Exit(iqshell.STATUS_ERROR)
Expand Down
30 changes: 30 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import (
"github.com/spf13/viper"
"os"
"os/user"
"path/filepath"
"runtime"
)

var (
DebugFlag bool
VersionFlag bool
cfgFile string
local bool
)

const (
Expand Down Expand Up @@ -61,8 +63,10 @@ func init() {
RootCmd.PersistentFlags().BoolVarP(&DebugFlag, "debug", "d", false, "debug mode")
RootCmd.PersistentFlags().BoolVarP(&VersionFlag, "version", "v", false, "show version")
RootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "C", "", "config file (default is $HOME/.qshell.json)")
RootCmd.PersistentFlags().BoolVarP(&local, "local", "L", false, "use current directory as config file path")

viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config"))
viper.BindPFlag("local", RootCmd.PersistentFlags().Lookup("local"))
}

func initConfig() {
Expand All @@ -86,6 +90,32 @@ func initConfig() {
viper.AddConfigPath(curUser.HomeDir)
viper.SetConfigName(".qshell")
}

if local {
dir, gErr := os.Getwd()
if gErr != nil {
fmt.Fprintf(os.Stderr, "get current directory: %v\n", gErr)
os.Exit(1)
}
viper.Set("path.root_path", dir+"/.qshell")
} else {
curUser, gErr := user.Current()
if gErr != nil {
fmt.Fprintf(os.Stderr, "Error: get current user error: %v\n", gErr)
os.Exit(1)
}
viper.Set("path.root_path", curUser.HomeDir+"/.qshell")
}
rootPath := viper.GetString("path.root_path")

viper.SetDefault("path.acc_db_path", filepath.Join(rootPath, "account.db"))
viper.SetDefault("path.acc_path", filepath.Join(rootPath, "account.json"))
viper.SetDefault("hosts.up_host", "upload.qiniup.com")
viper.SetDefault("hosts.rs_host", storage.DefaultRsHost)
viper.SetDefault("hosts.rsf_host", storage.DefaultRsfHost)
viper.SetDefault("hosts.io_host", "iovip.qbox.me")
viper.SetDefault("hosts.api_host", storage.DefaultAPIHost)

if rErr := viper.ReadInConfig(); rErr != nil {
if _, ok := rErr.(viper.ConfigFileNotFoundError); !ok {
fmt.Fprintf(os.Stderr, "read config file: %v\n", rErr)
Expand Down
15 changes: 6 additions & 9 deletions cmd/rs.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ var (
endDate string
maxRetry int
finalKey string
appendMode bool
)

func init() {
Expand All @@ -144,10 +145,11 @@ func init() {
lsBucketCmd2.Flags().StringVarP(&listMarker, "marker", "m", "", "list marker")
lsBucketCmd2.Flags().StringVarP(&prefix, "prefix", "p", "", "list by prefix")
lsBucketCmd2.Flags().StringVarP(&suffixes, "suffixes", "q", "", "list by key suffixes, separated by comma")
lsBucketCmd2.Flags().IntVarP(&maxRetry, "max-retry", "x", 20, "max retries when error occurred")
lsBucketCmd2.Flags().IntVarP(&maxRetry, "max-retry", "x", -1, "max retries when error occurred")
lsBucketCmd2.Flags().StringVarP(&outFile, "out", "o", "", "output file")
lsBucketCmd2.Flags().StringVarP(&startDate, "start", "s", "", "start date with format yyyy-mm-dd-hh-MM-ss")
lsBucketCmd2.Flags().StringVarP(&endDate, "end", "e", "", "end date with format yyyy-mm-dd-hh-MM-ss")
lsBucketCmd2.Flags().BoolVarP(&appendMode, "append", "a", false, "append to file")

moveCmd.Flags().BoolVarP(&mOverwrite, "overwrite", "w", false, "overwrite mode")
moveCmd.Flags().StringVarP(&finalKey, "key", "k", "", "filename saved in bucket")
Expand All @@ -163,9 +165,8 @@ func init() {
func DirCache(cmd *cobra.Command, params []string) {
var cacheResultFile string
cacheRootPath := params[0]
if len(params) == 2 {
cacheResultFile = params[1]
}

cacheResultFile = outFile
if cacheResultFile == "" {
cacheResultFile = "stdout"
}
Expand All @@ -176,10 +177,6 @@ func DirCache(cmd *cobra.Command, params []string) {
}

func ListBucket2(cmd *cobra.Command, params []string) {
if maxRetry <= 0 {
fmt.Fprintf(os.Stderr, "maxRetry must be greater than 0\n")
os.Exit(1)
}
bucket := params[0]

var dateParser = func(datestr string) (time.Time, error) {
Expand Down Expand Up @@ -221,7 +218,7 @@ func ListBucket2(cmd *cobra.Command, params []string) {
}
}
bm := iqshell.GetBucketManager()
retErr := bm.ListBucket2(bucket, prefix, listMarker, outFile, "", start, end, sf, maxRetry)
retErr := bm.ListBucket2(bucket, prefix, listMarker, outFile, "", start, end, sf, maxRetry, appendMode)
if retErr != nil {
os.Exit(iqshell.STATUS_ERROR)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"runtime"
)

var version = "v2.3.3"
var version = "v2.3.4"

var versionCmd = &cobra.Command{
Use: "version",
Expand Down
12 changes: 9 additions & 3 deletions docs/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
`account`命令用来设置当前用户的`AccessKey`和`SecretKey`,这对Key主要用在其他的需要授权的命令中,比如`stat`,`delete`,`listbucket`命令中。
该命令设置的信息,经过加密保存在命令执行的目录下的`.qshell/account.json`文件中。

本地数据库会记录`account`注册的所有<AccessKey>, <SecretKey> 和<Name>的信息, 所以当用`account`注册账户信息时,如果qshell发现本地数据库有同样的名字为
<Name>的账户, 那么默认qshell会返回错误信息报告该名字的账户已经存在,如果要覆盖注册,需要使用强制覆盖选项--overwrite 或者 -w

# 格式

```
Expand All @@ -12,18 +15,21 @@ qshell account
打印当前设置的`AccessKey`, `SecretKey`和`Name`

```
qshell account <Your AccessKey> <Your SecretKey> <Your Account Name>
qshell account [--overwrite | -w]<Your AccessKey> <Your SecretKey> <Your Account Name>
```

设置当前用户的`AccessKey`, `SecretKey`和`Name`
设置当前用户的`AccessKey`, `SecretKey`和`Name`, Name是用户可以任意取的名字,表示当前在本地记录的账户的名称,和在七牛注册的邮箱信息没有关系

# 选项
-w --overwrite 强制覆盖已经存在的账户

# 参数

|参数名|描述|
|--------|--------|
|AccessKey|七牛账号对应的AccessKey [获取](https://portal.qiniu.com/user/key)|
|SecretKey|七牛账号对应的SecretKey [获取](https://portal.qiniu.com/user/key)|
|Name|账户的名字|
|Name|账户的名字, 可以任意取,和在七牛注册的邮箱信息没有关系, 只是qshell本地用来标示<ak, sk>对 |

# 示例

Expand Down
2 changes: 1 addition & 1 deletion docs/cdnrefresh.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ qshell cdnrefresh [-i <UrlListFile>]
刷新目录的命令格式:

```
qshell cdnrefresh --dirs <DirListFile>
qshell cdnrefresh --dirs -i <DirListFile>
```

注意需要刷新的目录,必须以`/`结尾。如果没有制定输入文件<UrlListFile>默认从终端读取输入内容
Expand Down
23 changes: 15 additions & 8 deletions docs/listbucket2.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Key\tSize\tHash\tPutTime\tMimeType\tFileType\tEndUser
# 格式

```
qshell listbucket2 [--prefix <Prefix> | --suffixes <suffixes1,suffixes2>] [--start <StartDate>] [--max-retry <RetryCount>][--end <EndDate>] <Bucket> [-o <ListBucketResultFile>]
qshell listbucket2 [--prefix <Prefix> | --suffixes <suffixes1,suffixes2>] [--start <StartDate>] [--max-retry <RetryCount>][--end <EndDate>] <Bucket> [ [-a] -o <ListBucketResultFile>]
```

# 鉴权
Expand All @@ -32,8 +32,9 @@ qshell listbucket2 [--prefix <Prefix> | --suffixes <suffixes1,suffixes2>] [--sta
| ListBucketResultFile | 获取的文件列表保存在本地的文件名,如果不指定该参数,则会把结果输出到终端,一般可用于获取小规模文件列表测试使用 | Y |
| StartDate | 列举整个空间,然后从中筛选出文件上传日期在<StartDate>之后的文件 | Y |
| EndDate | 列举整个空间, 然后从中筛选出文件上传日期在<EndDate>之前的文件 | Y |
| RetryCount | 列举整个空间文件出错以后,最大的尝试次数;超过最大尝试次数以后,程序退出,打印出marker | Y |
| suffixes | 列举整个空间文件, 然后从中筛选出文件后缀为在[suffixes1, suffixes2, ...]中的文件 |Y|
| RetryCount | 列举整个空间文件出错以后,最大的尝试次数;超过最大尝试次数以后,程序退出,打印出marker | Y |
| suffixes | 列举整个空间文件, 然后从中筛选出文件后缀为在[suffixes1, suffixes2, ...]中的文件 | Y |
| a | 开启选项o 的append模式, 如果本地保存文件列表的文件已经存在,如果希望像该文件添加内容,使用该选项, 必须和-o选项一起使用 | Y |


# 常用使用场景介绍
Expand All @@ -43,31 +44,37 @@ qshell listbucket2 [--prefix <Prefix> | --suffixes <suffixes1,suffixes2>] [--sta
```
qshell listbucket2 <Bucket> -o <ListBucketResultFile>
```

(2) 如果本地文件`ListBucketResultFile`已经存在,有上一次列举的内容,如果希望把新的列表添加到该文件中,需要使用选项-a开启-o选项的append 模式

```
qshell listbucket2 <Bucket> -a -o <ListBucketResultFile>
```

(2) 获取空间所有文件,输出到屏幕上(标准输出)
(3) 获取空间所有文件,输出到屏幕上(标准输出)

```
qshell listbucket2 <Bucket>
```

3)获取空间中指定前缀的文件列表
4)获取空间中指定前缀的文件列表

```
qshell listbucket2 [--prefix <Prefix>] <Bucket> -o <ListBucketResultFile>
```

(4) 获取空间中指定前缀的文件列表, 输出到屏幕上
(5) 获取空间中指定前缀的文件列表, 输出到屏幕上

```
qshell listbucket2 [--prefix <Prefix>] <Bucket>
```

(5) 获取2018-10-30到2018-11-02上传的文件
(6) 获取2018-10-30到2018-11-02上传的文件
```
qshell listbucket2 --start 2018-10-30 --end 2018-11-02 <Bucket>
```

(6) 获取后缀为mp4, html的文件
(7) 获取后缀为mp4, html的文件

```
qshell listbucket2 --suffixes mp4,html <Bucket>
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/onsi/gomega v1.4.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/qiniu/api.v7 v7.2.5-0.20181112070011-bc6998c1186a+incompatible
github.com/qiniu/api.v7 v7.2.6-0.20181128092015-8c3e1ca2eb33+incompatible
github.com/qiniu/x v7.0.8+incompatible // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/spf13/cobra v0.0.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qiniu/api.v7 v7.2.5-0.20181112070011-bc6998c1186a+incompatible h1:FnBHGjkt7JJ2ebnQU0cRWrjk14ShzTSfvnLqjin2AWI=
github.com/qiniu/api.v7 v7.2.5-0.20181112070011-bc6998c1186a+incompatible/go.mod h1:V8/EzlTgLN6q0s0CJmg/I81ytsvldSF22F7h6MI02+c=
github.com/qiniu/api.v7 v7.2.6-0.20181128092015-8c3e1ca2eb33+incompatible h1:L3WEV1XhimkIAI0u1b2KlUIJyURecprw0EW42gsvYTA=
github.com/qiniu/api.v7 v7.2.6-0.20181128092015-8c3e1ca2eb33+incompatible/go.mod h1:V8/EzlTgLN6q0s0CJmg/I81ytsvldSF22F7h6MI02+c=
github.com/qiniu/x v7.0.8+incompatible h1:P4LASsfwJY7SoZ13dwqBwGhZh7HKU8cdFVCUkmz0gZ8=
github.com/qiniu/x v7.0.8+incompatible/go.mod h1:KpRKWYG/GaidPQVpoQ2Cvuvtts3gYnoo2PftgdmAiU4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
Expand Down
25 changes: 19 additions & 6 deletions iqshell/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func DecryptSecretKey(accessKey, encryptedKey string) (string, error) {
return secretKey, nil
}

func setdb(acc Account) (err error) {
func setdb(acc Account, accountOver bool) (err error) {
accDbPath := AccDBPath()
if accDbPath == "" {
return fmt.Errorf("empty account db path")
Expand All @@ -111,6 +111,19 @@ func setdb(acc Account) (err error) {
}
defer ldb.Close()

if !accountOver {

exists, hErr := ldb.Has([]byte(acc.Name), nil)
if hErr != nil {
err = hErr
return
}
if exists {
err = fmt.Errorf("Account Name: %s already exist in local db", acc.Name)
return
}
}

ldbWOpt := opt.WriteOptions{
Sync: true,
}
Expand All @@ -127,7 +140,7 @@ func setdb(acc Account) (err error) {
return
}

func SetAccount2(accessKey, secretKey, name, accPath, oldPath string) (err error) {
func SetAccount2(accessKey, secretKey, name, accPath, oldPath string, accountOver bool) (err error) {
acc := Account{
Name: name,
AccessKey: accessKey,
Expand All @@ -139,7 +152,7 @@ func SetAccount2(accessKey, secretKey, name, accPath, oldPath string) (err error
return
}

err = setdb(acc)
err = setdb(acc, accountOver)

return
}
Expand All @@ -151,21 +164,21 @@ func SetAccount(acc Account, accPath, oldPath string) (err error) {
}
if _, sErr := os.Stat(QShellRootPath); sErr != nil {
if mErr := os.MkdirAll(QShellRootPath, 0755); mErr != nil {
err = fmt.Errorf("Mkdir `%s` error, %s", QShellRootPath, mErr)
err = fmt.Errorf("Mkdir `%s` error: %s", QShellRootPath, mErr)
return
}
}

accountFh, openErr := os.OpenFile(accPath, os.O_CREATE|os.O_RDWR, 0600)
if openErr != nil {
err = fmt.Errorf("Open account file error, %s", openErr)
err = fmt.Errorf("Open account file error: %s", openErr)
return
}
defer accountFh.Close()

oldAccountFh, openErr := os.OpenFile(oldPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if openErr != nil {
err = fmt.Errorf("Open account file error, %s", openErr)
err = fmt.Errorf("Open account file error: %s", openErr)
return
}
defer oldAccountFh.Close()
Expand Down
Loading