forked from TrueCloudLab/rclone
fs/hash: align hashsum names and update documentation (#5339)
- Unify all hash names as lowercase alphanumerics without punctuation. - Legacy names continue to work but disappear from docs, they can be depreciated or dropped later. - Make rclone hashsum print supported hash list in case of wrong spelling. - Update documentation. Fixes #5071 Fixes #4841
This commit is contained in:
parent
07f2f3a62e
commit
5b6f637461
10 changed files with 122 additions and 82 deletions
|
@ -138,7 +138,7 @@ func getOauthConfig(m configmap.Mapper) *oauth2.Config {
|
||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
func init() {
|
func init() {
|
||||||
DbHashType = hash.RegisterHash("DropboxHash", 64, dbhash.New)
|
DbHashType = hash.RegisterHash("dropbox", "DropboxHash", 64, dbhash.New)
|
||||||
fs.Register(&fs.RegInfo{
|
fs.Register(&fs.RegInfo{
|
||||||
Name: "dropbox",
|
Name: "dropbox",
|
||||||
Description: "Dropbox",
|
Description: "Dropbox",
|
||||||
|
|
|
@ -80,7 +80,7 @@ var oauthConfig = &oauth2.Config{
|
||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
func init() {
|
func init() {
|
||||||
MrHashType = hash.RegisterHash("MailruHash", 40, mrhash.New)
|
MrHashType = hash.RegisterHash("mailru", "MailruHash", 40, mrhash.New)
|
||||||
fs.Register(&fs.RegInfo{
|
fs.Register(&fs.RegInfo{
|
||||||
Name: "mailru",
|
Name: "mailru",
|
||||||
Description: "Mail.ru Cloud",
|
Description: "Mail.ru Cloud",
|
||||||
|
|
|
@ -93,7 +93,7 @@ var (
|
||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
func init() {
|
func init() {
|
||||||
QuickXorHashType = hash.RegisterHash("QuickXorHash", 40, quickxorhash.New)
|
QuickXorHashType = hash.RegisterHash("quickxor", "QuickXorHash", 40, quickxorhash.New)
|
||||||
fs.Register(&fs.RegInfo{
|
fs.Register(&fs.RegInfo{
|
||||||
Name: "onedrive",
|
Name: "onedrive",
|
||||||
Description: "Microsoft OneDrive",
|
Description: "Microsoft OneDrive",
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rclone/rclone/cmd"
|
"github.com/rclone/rclone/cmd"
|
||||||
|
@ -69,23 +70,17 @@ hashed locally enabling any hash for any remote.
|
||||||
Run without a hash to see the list of all supported hashes, e.g.
|
Run without a hash to see the list of all supported hashes, e.g.
|
||||||
|
|
||||||
$ rclone hashsum
|
$ rclone hashsum
|
||||||
Supported hashes are:
|
` + hashListHelp(" ") + `
|
||||||
* MD5
|
|
||||||
* SHA-1
|
|
||||||
* DropboxHash
|
|
||||||
* QuickXorHash
|
|
||||||
|
|
||||||
Then
|
Then
|
||||||
|
|
||||||
$ rclone hashsum MD5 remote:path
|
$ rclone hashsum MD5 remote:path
|
||||||
|
|
||||||
|
Note that hash names are case insensitive.
|
||||||
`,
|
`,
|
||||||
RunE: func(command *cobra.Command, args []string) error {
|
RunE: func(command *cobra.Command, args []string) error {
|
||||||
cmd.CheckArgs(0, 2, command, args)
|
cmd.CheckArgs(0, 2, command, args)
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
fmt.Printf("Supported hashes are:\n")
|
fmt.Print(hashListHelp(""))
|
||||||
for _, ht := range hash.Supported().Array() {
|
|
||||||
fmt.Printf(" * %v\n", ht)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
} else if len(args) == 1 {
|
} else if len(args) == 1 {
|
||||||
return errors.New("need hash type and remote")
|
return errors.New("need hash type and remote")
|
||||||
|
@ -93,6 +88,7 @@ Then
|
||||||
var ht hash.Type
|
var ht hash.Type
|
||||||
err := ht.Set(args[0])
|
err := ht.Set(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(hashListHelp(""))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fsrc := cmd.NewFsSrc(args[1:])
|
fsrc := cmd.NewFsSrc(args[1:])
|
||||||
|
@ -111,3 +107,14 @@ Then
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hashListHelp(indent string) string {
|
||||||
|
var help strings.Builder
|
||||||
|
help.WriteString(indent)
|
||||||
|
help.WriteString("Supported hashes are:\n")
|
||||||
|
for _, ht := range hash.Supported().Array() {
|
||||||
|
help.WriteString(indent)
|
||||||
|
fmt.Fprintf(&help, " * %v\n", ht.String())
|
||||||
|
}
|
||||||
|
return help.String()
|
||||||
|
}
|
||||||
|
|
|
@ -25,15 +25,20 @@ Run without a hash to see the list of all supported hashes, e.g.
|
||||||
|
|
||||||
$ rclone hashsum
|
$ rclone hashsum
|
||||||
Supported hashes are:
|
Supported hashes are:
|
||||||
* MD5
|
* md5
|
||||||
* SHA-1
|
* sha1
|
||||||
* DropboxHash
|
* whirlpool
|
||||||
* QuickXorHash
|
* crc32
|
||||||
|
* dropbox
|
||||||
|
* mailru
|
||||||
|
* quickxor
|
||||||
|
|
||||||
Then
|
Then
|
||||||
|
|
||||||
$ rclone hashsum MD5 remote:path
|
$ rclone hashsum MD5 remote:path
|
||||||
|
|
||||||
|
Note that hash names are case insensitive.
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
rclone hashsum <hash> remote:path [flags]
|
rclone hashsum <hash> remote:path [flags]
|
||||||
|
|
0
docs/content/flags.md
Executable file → Normal file
0
docs/content/flags.md
Executable file → Normal file
103
fs/hash/hash.go
103
fs/hash/hash.go
|
@ -20,25 +20,36 @@ type Type int
|
||||||
type hashDefinition struct {
|
type hashDefinition struct {
|
||||||
width int
|
width int
|
||||||
name string
|
name string
|
||||||
|
alias string
|
||||||
newFunc func() hash.Hash
|
newFunc func() hash.Hash
|
||||||
hashType Type
|
hashType Type
|
||||||
}
|
}
|
||||||
|
|
||||||
var hashes []*hashDefinition
|
var (
|
||||||
var highestType Type = 1
|
type2hash = map[Type]*hashDefinition{}
|
||||||
|
name2hash = map[string]*hashDefinition{}
|
||||||
|
alias2hash = map[string]*hashDefinition{}
|
||||||
|
supported = []Type{}
|
||||||
|
)
|
||||||
|
|
||||||
// RegisterHash adds a new Hash to the list and returns it Type
|
// RegisterHash adds a new Hash to the list and returns it Type
|
||||||
func RegisterHash(name string, width int, newFunc func() hash.Hash) Type {
|
func RegisterHash(name, alias string, width int, newFunc func() hash.Hash) Type {
|
||||||
|
hashType := Type(1 << len(supported))
|
||||||
|
supported = append(supported, hashType)
|
||||||
|
|
||||||
definition := &hashDefinition{
|
definition := &hashDefinition{
|
||||||
name: name,
|
name: name,
|
||||||
|
alias: alias,
|
||||||
width: width,
|
width: width,
|
||||||
newFunc: newFunc,
|
newFunc: newFunc,
|
||||||
hashType: highestType,
|
hashType: hashType,
|
||||||
}
|
}
|
||||||
hashes = append(hashes, definition)
|
|
||||||
highestType = highestType << 1
|
|
||||||
|
|
||||||
return definition.hashType
|
type2hash[hashType] = definition
|
||||||
|
name2hash[name] = definition
|
||||||
|
alias2hash[alias] = definition
|
||||||
|
|
||||||
|
return hashType
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUnsupported should be returned by filesystem,
|
// ErrUnsupported should be returned by filesystem,
|
||||||
|
@ -63,31 +74,23 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
MD5 = RegisterHash("MD5", 32, md5.New)
|
MD5 = RegisterHash("md5", "MD5", 32, md5.New)
|
||||||
SHA1 = RegisterHash("SHA-1", 40, sha1.New)
|
SHA1 = RegisterHash("sha1", "SHA-1", 40, sha1.New)
|
||||||
Whirlpool = RegisterHash("Whirlpool", 128, whirlpool.New)
|
Whirlpool = RegisterHash("whirlpool", "Whirlpool", 128, whirlpool.New)
|
||||||
CRC32 = RegisterHash("CRC-32", 8, func() hash.Hash { return crc32.NewIEEE() })
|
CRC32 = RegisterHash("crc32", "CRC-32", 8, func() hash.Hash { return crc32.NewIEEE() })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supported returns a set of all the supported hashes by
|
// Supported returns a set of all the supported hashes by
|
||||||
// HashStream and MultiHasher.
|
// HashStream and MultiHasher.
|
||||||
func Supported() Set {
|
func Supported() Set {
|
||||||
var types []Type
|
return NewHashSet(supported...)
|
||||||
for _, v := range hashes {
|
|
||||||
types = append(types, v.hashType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewHashSet(types...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns the width in characters for any HashType
|
// Width returns the width in characters for any HashType
|
||||||
func Width(hashType Type) int {
|
func Width(hashType Type) int {
|
||||||
for _, v := range hashes {
|
if hash := type2hash[hashType]; hash != nil {
|
||||||
if v.hashType == hashType {
|
return hash.width
|
||||||
return v.width
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,32 +121,29 @@ func StreamTypes(r io.Reader, set Set) (map[Type]string, error) {
|
||||||
// The function will panic if the hash type is unknown.
|
// The function will panic if the hash type is unknown.
|
||||||
func (h Type) String() string {
|
func (h Type) String() string {
|
||||||
if h == None {
|
if h == None {
|
||||||
return "None"
|
return "none"
|
||||||
|
}
|
||||||
|
if hash := type2hash[h]; hash != nil {
|
||||||
|
return hash.name
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("internal error: unknown hash type: 0x%x", int(h)))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range hashes {
|
// Set a Type from a flag.
|
||||||
if v.hashType == h {
|
// Both name and alias are accepted.
|
||||||
return v.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := fmt.Sprintf("internal error: unknown hash type: 0x%x", int(h))
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a Type from a flag
|
|
||||||
func (h *Type) Set(s string) error {
|
func (h *Type) Set(s string) error {
|
||||||
if s == "None" {
|
if s == "none" || s == "None" {
|
||||||
*h = None
|
*h = None
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range hashes {
|
|
||||||
if v.name == s {
|
|
||||||
*h = v.hashType
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if hash := name2hash[strings.ToLower(s)]; hash != nil {
|
||||||
|
*h = hash.hashType
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if hash := alias2hash[s]; hash != nil {
|
||||||
|
*h = hash.hashType
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Errorf("Unknown hash type %q", s)
|
return errors.Errorf("Unknown hash type %q", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,23 +159,14 @@ func fromTypes(set Set) (map[Type]hash.Hash, error) {
|
||||||
if !set.SubsetOf(Supported()) {
|
if !set.SubsetOf(Supported()) {
|
||||||
return nil, errors.Errorf("requested set %08x contains unknown hash types", int(set))
|
return nil, errors.Errorf("requested set %08x contains unknown hash types", int(set))
|
||||||
}
|
}
|
||||||
var hashers = make(map[Type]hash.Hash)
|
hashers := map[Type]hash.Hash{}
|
||||||
|
|
||||||
types := set.Array()
|
for _, t := range set.Array() {
|
||||||
for _, t := range types {
|
hash := type2hash[t]
|
||||||
for _, v := range hashes {
|
if hash == nil {
|
||||||
if t != v.hashType {
|
panic(fmt.Sprintf("internal error: Unsupported hash type %v", t))
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
hashers[t] = v.newFunc()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if hashers[t] == nil {
|
|
||||||
err := fmt.Sprintf("internal error: Unsupported hash type %v", t)
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
hashers[t] = hash.newFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashers, nil
|
return hashers, nil
|
||||||
|
|
|
@ -156,16 +156,53 @@ func TestHashStreamTypes(t *testing.T) {
|
||||||
|
|
||||||
func TestHashSetStringer(t *testing.T) {
|
func TestHashSetStringer(t *testing.T) {
|
||||||
h := hash.NewHashSet(hash.SHA1, hash.MD5)
|
h := hash.NewHashSet(hash.SHA1, hash.MD5)
|
||||||
assert.Equal(t, h.String(), "[MD5, SHA-1]")
|
assert.Equal(t, "[md5, sha1]", h.String())
|
||||||
h = hash.NewHashSet(hash.SHA1)
|
h = hash.NewHashSet(hash.SHA1)
|
||||||
assert.Equal(t, h.String(), "[SHA-1]")
|
assert.Equal(t, "[sha1]", h.String())
|
||||||
h = hash.NewHashSet()
|
h = hash.NewHashSet()
|
||||||
assert.Equal(t, h.String(), "[]")
|
assert.Equal(t, "[]", h.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHashStringer(t *testing.T) {
|
func TestHashStringer(t *testing.T) {
|
||||||
h := hash.MD5
|
h := hash.MD5
|
||||||
assert.Equal(t, h.String(), "MD5")
|
assert.Equal(t, "md5", h.String())
|
||||||
|
h = hash.SHA1
|
||||||
|
assert.Equal(t, "sha1", h.String())
|
||||||
h = hash.None
|
h = hash.None
|
||||||
assert.Equal(t, h.String(), "None")
|
assert.Equal(t, "none", h.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashSetter(t *testing.T) {
|
||||||
|
var ht hash.Type
|
||||||
|
|
||||||
|
assert.NoError(t, ht.Set("none"))
|
||||||
|
assert.Equal(t, hash.None, ht)
|
||||||
|
assert.NoError(t, ht.Set("None"))
|
||||||
|
assert.Equal(t, hash.None, ht)
|
||||||
|
|
||||||
|
assert.NoError(t, ht.Set("md5"))
|
||||||
|
assert.Equal(t, hash.MD5, ht)
|
||||||
|
assert.NoError(t, ht.Set("MD5"))
|
||||||
|
assert.Equal(t, hash.MD5, ht)
|
||||||
|
|
||||||
|
assert.NoError(t, ht.Set("sha1"))
|
||||||
|
assert.Equal(t, hash.SHA1, ht)
|
||||||
|
assert.NoError(t, ht.Set("SHA-1"))
|
||||||
|
assert.Equal(t, hash.SHA1, ht)
|
||||||
|
|
||||||
|
assert.NoError(t, ht.Set("SHA1"))
|
||||||
|
assert.Equal(t, hash.SHA1, ht)
|
||||||
|
assert.NoError(t, ht.Set("Sha1"))
|
||||||
|
assert.Equal(t, hash.SHA1, ht)
|
||||||
|
assert.Error(t, ht.Set("Sha-1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashTypeStability(t *testing.T) {
|
||||||
|
assert.Equal(t, hash.Type(0), hash.None)
|
||||||
|
assert.Equal(t, hash.Type(1), hash.MD5)
|
||||||
|
assert.Equal(t, hash.Type(2), hash.SHA1)
|
||||||
|
|
||||||
|
assert.True(t, hash.Supported().Contains(hash.MD5))
|
||||||
|
assert.True(t, hash.Supported().Contains(hash.SHA1))
|
||||||
|
assert.False(t, hash.Supported().Contains(hash.None))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1206,10 +1206,10 @@ func TestListFormat(t *testing.T) {
|
||||||
Format: "2006-01-02T15:04:05.000000000Z07:00"},
|
Format: "2006-01-02T15:04:05.000000000Z07:00"},
|
||||||
IsDir: false,
|
IsDir: false,
|
||||||
Hashes: map[string]string{
|
Hashes: map[string]string{
|
||||||
"MD5": "0cc175b9c0f1b6a831c399e269772661",
|
"md5": "0cc175b9c0f1b6a831c399e269772661",
|
||||||
"SHA-1": "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
|
"sha1": "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
|
||||||
"DropboxHash": "bf5d3affb73efd2ec6c36ad3112dd933efed63c4e1cbffcfa88e2759c144f2d8",
|
"dropbox": "bf5d3affb73efd2ec6c36ad3112dd933efed63c4e1cbffcfa88e2759c144f2d8",
|
||||||
"QuickXorHash": "6100000000000000000000000100000000000000"},
|
"quickxor": "6100000000000000000000000100000000000000"},
|
||||||
ID: "fileID",
|
ID: "fileID",
|
||||||
OrigID: "fileOrigID",
|
OrigID: "fileOrigID",
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ func TestHTTPOption(t *testing.T) {
|
||||||
func TestHashesOption(t *testing.T) {
|
func TestHashesOption(t *testing.T) {
|
||||||
opt := &HashesOption{hash.Set(hash.MD5 | hash.SHA1)}
|
opt := &HashesOption{hash.Set(hash.MD5 | hash.SHA1)}
|
||||||
var _ OpenOption = opt // check interface
|
var _ OpenOption = opt // check interface
|
||||||
assert.Equal(t, `HashesOption([MD5, SHA-1])`, opt.String())
|
assert.Equal(t, `HashesOption([md5, sha1])`, opt.String())
|
||||||
key, value := opt.Header()
|
key, value := opt.Header()
|
||||||
assert.Equal(t, "", key)
|
assert.Equal(t, "", key)
|
||||||
assert.Equal(t, "", value)
|
assert.Equal(t, "", value)
|
||||||
|
|
Loading…
Reference in a new issue