mirror of
https://github.com/rclone/rclone.git
synced 2024-12-04 22:34:48 +01:00
149 lines
3.1 KiB
Go
149 lines
3.1 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package fpath
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// FPath is an OS independent path handling structure.
|
|
type FPath struct {
|
|
original string // the original URL or local path
|
|
local bool // if local path
|
|
bucket string // only for Storj URL
|
|
path string // only for Storj URL - the path within the bucket, cleaned from duplicated slashes
|
|
}
|
|
|
|
var parseSchemeRegex = regexp.MustCompile(`^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$`)
|
|
|
|
func parseScheme(o string) (scheme, rest string) {
|
|
found := parseSchemeRegex.FindStringSubmatch(o)
|
|
|
|
switch len(found) {
|
|
case 2:
|
|
return strings.ToLower(found[1]), ""
|
|
case 3:
|
|
return strings.ToLower(found[1]), found[2]
|
|
}
|
|
|
|
return "", o
|
|
}
|
|
|
|
var parseBucketRegex = regexp.MustCompile(`^/{1,4}([^/]+)(/.*)?$`)
|
|
|
|
func parseBucket(o string) (bucket, rest string) {
|
|
found := parseBucketRegex.FindStringSubmatch(o)
|
|
|
|
switch len(found) {
|
|
case 2:
|
|
return found[1], ""
|
|
case 3:
|
|
return found[1], found[2]
|
|
}
|
|
|
|
return "", o
|
|
}
|
|
|
|
// New creates new FPath from the given URL
|
|
func New(p string) (FPath, error) {
|
|
fp := FPath{original: p}
|
|
|
|
// Skip processing further if we can determine this is an absolute
|
|
// path to a local file.
|
|
if filepath.IsAbs(p) {
|
|
fp.local = true
|
|
|
|
return fp, nil
|
|
}
|
|
|
|
// Does the path have a scheme? If not then we treat it as a local
|
|
// path. Otherwise we validate that the scheme is a supported one.
|
|
scheme, rest := parseScheme(p)
|
|
if scheme == "" {
|
|
// Forbid the use of an empty scheme.
|
|
if strings.HasPrefix(rest, ":") {
|
|
return fp, errors.New("malformed URL: missing scheme, use format sj://bucket/")
|
|
}
|
|
|
|
fp.local = true
|
|
|
|
return fp, nil
|
|
}
|
|
|
|
switch scheme {
|
|
case "s3":
|
|
case "sj":
|
|
default:
|
|
return fp, fmt.Errorf("unsupported URL scheme: %s, use format sj://bucket/", scheme)
|
|
}
|
|
|
|
// The remaining portion of the path must begin with a bucket.
|
|
bucket, rest := parseBucket(rest)
|
|
if bucket == "" {
|
|
return fp, errors.New("no bucket specified, use format sj://bucket/")
|
|
}
|
|
|
|
fp.bucket = bucket
|
|
|
|
// We only want to clean the path if it is non-empty. This is because
|
|
// path. Clean will turn an empty path into ".".
|
|
rest = strings.TrimLeft(rest, "/")
|
|
if rest != "" {
|
|
fp.path = path.Clean(rest)
|
|
}
|
|
|
|
return fp, nil
|
|
}
|
|
|
|
// Join is appends the given segment to the path
|
|
func (p FPath) Join(segment string) FPath {
|
|
if p.local {
|
|
p.original = filepath.Join(p.original, segment)
|
|
return p
|
|
}
|
|
|
|
p.original += "/" + segment
|
|
p.path = path.Join(p.path, segment)
|
|
return p
|
|
}
|
|
|
|
// Base returns the last segment of the path
|
|
func (p FPath) Base() string {
|
|
if p.local {
|
|
return filepath.Base(p.original)
|
|
}
|
|
if p.path == "" {
|
|
return ""
|
|
}
|
|
return path.Base(p.path)
|
|
}
|
|
|
|
// Bucket returns the first segment of path
|
|
func (p FPath) Bucket() string {
|
|
return p.bucket
|
|
}
|
|
|
|
// Path returns the URL path without the scheme
|
|
func (p FPath) Path() string {
|
|
if p.local {
|
|
return p.original
|
|
}
|
|
return p.path
|
|
}
|
|
|
|
// IsLocal returns whether the path refers to local or remote location
|
|
func (p FPath) IsLocal() bool {
|
|
return p.local
|
|
}
|
|
|
|
// String returns the entire URL (untouched)
|
|
func (p FPath) String() string {
|
|
return p.original
|
|
}
|