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:
Ivan Andreev 2021-05-21 17:32:33 +03:00 committed by GitHub
parent 07f2f3a62e
commit 5b6f637461
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 122 additions and 82 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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()
}

View file

@ -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
View file

View file

@ -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

View file

@ -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))
} }

View file

@ -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",
} }

View file

@ -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)