fs: implement MetadataInfo to show info about metadata in help and rc

Info about this will appear in operations/fsinfo and in the backend
help (`rclone help backend s3`).
This commit is contained in:
Nick Craig-Wood 2022-06-22 15:56:41 +01:00
parent 6a0e021dac
commit 0652ec95db
6 changed files with 160 additions and 48 deletions

View File

@ -6,6 +6,7 @@ import (
"log" "log"
"os" "os"
"regexp" "regexp"
"sort"
"strings" "strings"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
@ -362,4 +363,28 @@ func showBackend(name string) {
fmt.Printf("\n") fmt.Printf("\n")
} }
} }
if backend.MetadataInfo != nil {
fmt.Printf("### Metadata\n\n")
fmt.Printf("%s\n\n", strings.TrimSpace(backend.MetadataInfo.Help))
if len(backend.MetadataInfo.System) > 0 {
fmt.Printf("Here are the possible system metadata items for the %s backend.\n\n", backend.Name)
keys := []string{}
for k := range backend.MetadataInfo.System {
keys = append(keys, k)
}
sort.Strings(keys)
fmt.Printf("| Name | Help | Type | Example | Read Only |\n")
fmt.Printf("|------|------|------|---------|-----------|\n")
for _, k := range keys {
v := backend.MetadataInfo.System[k]
ro := "N"
if v.ReadOnly {
ro = "**Y**"
}
fmt.Printf("| %s | %s | %s | %s | %s |\n", k, v.Help, v.Type, v.Example, ro)
}
fmt.Printf("\n")
}
fmt.Printf("See the [metadata](/docs/#metadata) docs for more info.\n\n")
}
} }

View File

@ -7,6 +7,20 @@ import "context"
// See docs/content/metadata.md for the interpretation of the keys // See docs/content/metadata.md for the interpretation of the keys
type Metadata map[string]string type Metadata map[string]string
// MetadataHelp represents help for a bit of system metadata
type MetadataHelp struct {
Help string
Type string
Example string
ReadOnly bool
}
// MetadataInfo is help for the whole metadata for this backend.
type MetadataInfo struct {
System map[string]MetadataHelp
Help string
}
// Set k to v on m // Set k to v on m
// //
// If m is nil, then it will get made // If m is nil, then it will get made

View File

@ -2278,21 +2278,30 @@ type FsInfo struct {
// Features returns the optional features of this Fs // Features returns the optional features of this Fs
Features map[string]bool Features map[string]bool
// MetadataInfo returns info about the metadata for this backend
MetadataInfo *fs.MetadataInfo
} }
// GetFsInfo gets the information (FsInfo) about a given Fs // GetFsInfo gets the information (FsInfo) about a given Fs
func GetFsInfo(f fs.Fs) *FsInfo { func GetFsInfo(f fs.Fs) *FsInfo {
features := f.Features()
info := &FsInfo{ info := &FsInfo{
Name: f.Name(), Name: f.Name(),
Root: f.Root(), Root: f.Root(),
String: f.String(), String: f.String(),
Precision: f.Precision(), Precision: f.Precision(),
Hashes: make([]string, 0, 4), Hashes: make([]string, 0, 4),
Features: f.Features().Enabled(), Features: features.Enabled(),
MetadataInfo: nil,
} }
for _, hashType := range f.Hashes().Array() { for _, hashType := range f.Hashes().Array() {
info.Hashes = append(info.Hashes, hashType.String()) info.Hashes = append(info.Hashes, hashType.String())
} }
fsInfo, _, _, _, err := fs.ParseRemote(fs.ConfigString(f))
if err == nil && fsInfo != nil && fsInfo.MetadataInfo != nil {
info.MetadataInfo = fsInfo.MetadataInfo
}
return info return info
} }

View File

@ -413,46 +413,103 @@ This returns info about the remote passed in;
` + "```" + ` ` + "```" + `
{ {
// optional features and whether they are available or not // optional features and whether they are available or not
"Features": { "Features": {
"About": true, "About": true,
"BucketBased": false, "BucketBased": false,
"CanHaveEmptyDirectories": true, "BucketBasedRootOK": false,
"CaseInsensitive": false, "CanHaveEmptyDirectories": true,
"ChangeNotify": false, "CaseInsensitive": false,
"CleanUp": false, "ChangeNotify": false,
"Copy": false, "CleanUp": false,
"DirCacheFlush": false, "Command": true,
"DirMove": true, "Copy": false,
"DuplicateFiles": false, "DirCacheFlush": false,
"GetTier": false, "DirMove": true,
"ListR": false, "Disconnect": false,
"MergeDirs": false, "DuplicateFiles": false,
"Move": true, "GetTier": false,
"OpenWriterAt": true, "IsLocal": true,
"PublicLink": false, "ListR": false,
"Purge": true, "MergeDirs": false,
"PutStream": true, "MetadataInfo": true,
"PutUnchecked": false, "Move": true,
"ReadMimeType": false, "OpenWriterAt": true,
"ServerSideAcrossConfigs": false, "PublicLink": false,
"SetTier": false, "Purge": true,
"SetWrapper": false, "PutStream": true,
"UnWrap": false, "PutUnchecked": false,
"WrapFs": false, "ReadMetadata": true,
"WriteMimeType": false "ReadMimeType": false,
}, "ServerSideAcrossConfigs": false,
// Names of hashes available "SetTier": false,
"Hashes": [ "SetWrapper": false,
"MD5", "Shutdown": false,
"SHA-1", "SlowHash": true,
"DropboxHash", "SlowModTime": false,
"QuickXorHash" "UnWrap": false,
], "UserInfo": false,
"Name": "local", // Name as created "UserMetadata": true,
"Precision": 1, // Precision of timestamps in ns "WrapFs": false,
"Root": "/", // Path as created "WriteMetadata": true,
"String": "Local file system at /" // how the remote will appear in logs "WriteMimeType": false
},
// Names of hashes available
"Hashes": [
"md5",
"sha1",
"whirlpool",
"crc32",
"sha256",
"dropbox",
"mailru",
"quickxor"
],
"Name": "local", // Name as created
"Precision": 1, // Precision of timestamps in ns
"Root": "/", // Path as created
"String": "Local file system at /", // how the remote will appear in logs
// Information about the system metadata for this backend
"MetadataInfo": {
"System": {
"atime": {
"Help": "Time of last access",
"Type": "RFC 3339",
"Example": "2006-01-02T15:04:05.999999999Z07:00"
},
"btime": {
"Help": "Time of file birth (creation)",
"Type": "RFC 3339",
"Example": "2006-01-02T15:04:05.999999999Z07:00"
},
"gid": {
"Help": "Group ID of owner",
"Type": "decimal number",
"Example": "500"
},
"mode": {
"Help": "File type and mode",
"Type": "octal, unix style",
"Example": "0100664"
},
"mtime": {
"Help": "Time of last modification",
"Type": "RFC 3339",
"Example": "2006-01-02T15:04:05.999999999Z07:00"
},
"rdev": {
"Help": "Device ID (if special file)",
"Type": "hexadecimal",
"Example": "1abc"
},
"uid": {
"Help": "User ID of owner",
"Type": "decimal number",
"Example": "500"
}
},
"Help": "Textual help string\n"
}
} }
` + "```" + ` ` + "```" + `

View File

@ -40,6 +40,8 @@ type RegInfo struct {
Aliases []string Aliases []string
// Hide - if set don't show in the configurator // Hide - if set don't show in the configurator
Hide bool Hide bool
// MetadataInfo help about the metadata in use in this backend
MetadataInfo *MetadataInfo
} }
// FileName returns the on disk file name for this backend // FileName returns the on disk file name for this backend

View File

@ -1384,8 +1384,13 @@ func Run(t *testing.T, opt *Opt) {
skipIfNotOk(t) skipIfNotOk(t)
features := f.Features() features := f.Features()
obj := findObject(ctx, t, f, file1.Path) obj := findObject(ctx, t, f, file1.Path)
do, ok := obj.(fs.Metadataer) do, objectHasMetadata := obj.(fs.Metadataer)
if !ok { if objectHasMetadata || features.ReadMetadata || features.WriteMetadata || features.UserMetadata {
fsInfo, _, _, _, err := fs.ParseRemote(fs.ConfigString(f))
require.NoError(t, err)
require.NotNil(t, fsInfo.MetadataInfo, "Object declares metadata support but no MetadataInfo in RegInfo")
}
if !objectHasMetadata {
require.False(t, features.ReadMetadata, "Features.ReadMetadata is set but Object.Metadata method not found") require.False(t, features.ReadMetadata, "Features.ReadMetadata is set but Object.Metadata method not found")
t.Skip("Metadata method not supported") t.Skip("Metadata method not supported")
} }