link: allow creating public link to files and folders - closes #1562
This commit is contained in:
parent
9df266a6b4
commit
a8267d1628
31 changed files with 275 additions and 41 deletions
|
@ -71,6 +71,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -71,6 +71,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
1
backend/cache/cache_test.go
vendored
1
backend/cache/cache_test.go
vendored
|
@ -72,6 +72,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -69,6 +69,7 @@ func TestObjectUpdate2(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable2(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile2(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound2(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink2(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove2(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream2(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge2(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -69,6 +69,7 @@ func TestObjectUpdate3(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable3(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile3(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound3(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink3(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove3(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream3(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge3(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -69,6 +69,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -1095,6 +1095,41 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
|
|||
return dstObj, nil
|
||||
}
|
||||
|
||||
// PublicLink adds a "readable by anyone with link" permission on the given file or folder.
|
||||
func (f *Fs) PublicLink(remote string) (link string, err error) {
|
||||
id, err := f.dirCache.FindDir(remote, false)
|
||||
if err == nil {
|
||||
fs.Debugf(f, "attempting to share directory '%s'", remote)
|
||||
} else {
|
||||
fs.Debugf(f, "attempting to share single file '%s'", remote)
|
||||
o := &Object{
|
||||
fs: f,
|
||||
remote: remote,
|
||||
}
|
||||
if err = o.readMetaData(); err != nil {
|
||||
return
|
||||
}
|
||||
id = o.id
|
||||
}
|
||||
|
||||
permission := &drive.Permission{
|
||||
AllowFileDiscovery: false,
|
||||
Role: "reader",
|
||||
Type: "anyone",
|
||||
}
|
||||
|
||||
err = f.pacer.Call(func() (bool, error) {
|
||||
// TODO: On TeamDrives this might fail if lacking permissions to change ACLs.
|
||||
// Need to either check `canShare` attribute on the object or see if a sufficient permission is already present.
|
||||
_, err = f.svc.Permissions.Create(id, permission).Fields(googleapi.Field("id")).SupportsTeamDrives(f.isTeamDrive).Do()
|
||||
return shouldRetry(err)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("https://drive.google.com/open?id=%s", id), nil
|
||||
}
|
||||
|
||||
// DirMove moves src, srcRemote to this remote at dstRemote
|
||||
// using server side move operations.
|
||||
//
|
||||
|
@ -1597,6 +1632,7 @@ var (
|
|||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||
_ fs.ChangeNotifier = (*Fs)(nil)
|
||||
_ fs.PutUncheckeder = (*Fs)(nil)
|
||||
_ fs.PublicLinker = (*Fs)(nil)
|
||||
_ fs.MergeDirser = (*Fs)(nil)
|
||||
_ fs.Object = (*Object)(nil)
|
||||
_ fs.MimeTyper = &Object{}
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
|
||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
|
||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
|
||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing"
|
||||
"github.com/ncw/rclone/fs"
|
||||
"github.com/ncw/rclone/fs/config"
|
||||
"github.com/ncw/rclone/fs/config/flags"
|
||||
|
@ -126,13 +127,14 @@ func init() {
|
|||
|
||||
// Fs represents a remote dropbox server
|
||||
type Fs struct {
|
||||
name string // name of this remote
|
||||
root string // the path we are working on
|
||||
features *fs.Features // optional features
|
||||
srv files.Client // the connection to the dropbox server
|
||||
slashRoot string // root with "/" prefix, lowercase
|
||||
slashRootSlash string // root with "/" prefix and postfix, lowercase
|
||||
pacer *pacer.Pacer // To pace the API calls
|
||||
name string // name of this remote
|
||||
root string // the path we are working on
|
||||
features *fs.Features // optional features
|
||||
srv files.Client // the connection to the dropbox server
|
||||
sharingClient sharing.Client // as above, but for generating sharing links
|
||||
slashRoot string // root with "/" prefix, lowercase
|
||||
slashRootSlash string // root with "/" prefix and postfix, lowercase
|
||||
pacer *pacer.Pacer // To pace the API calls
|
||||
}
|
||||
|
||||
// Object describes a dropbox object
|
||||
|
@ -210,11 +212,13 @@ func NewFs(name, root string) (fs.Fs, error) {
|
|||
Client: oAuthClient, // maybe???
|
||||
}
|
||||
srv := files.New(config)
|
||||
sharingClient := sharing.New(config)
|
||||
|
||||
f := &Fs{
|
||||
name: name,
|
||||
srv: srv,
|
||||
pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant),
|
||||
name: name,
|
||||
srv: srv,
|
||||
sharingClient: sharingClient,
|
||||
pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant),
|
||||
}
|
||||
f.features = (&fs.Features{
|
||||
CaseInsensitive: true,
|
||||
|
@ -640,6 +644,52 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
|
|||
return dstObj, nil
|
||||
}
|
||||
|
||||
// PublicLink adds a "readable by anyone with link" permission on the given file or folder.
|
||||
func (f *Fs) PublicLink(remote string) (link string, err error) {
|
||||
absPath := "/" + path.Join(f.Root(), remote)
|
||||
fs.Debugf(f, "attempting to share '%s' (absolute path: %s)", remote, absPath)
|
||||
createArg := sharing.CreateSharedLinkWithSettingsArg{
|
||||
Path: absPath,
|
||||
}
|
||||
var linkRes sharing.IsSharedLinkMetadata
|
||||
err = f.pacer.Call(func() (bool, error) {
|
||||
linkRes, err = f.sharingClient.CreateSharedLinkWithSettings(&createArg)
|
||||
return shouldRetry(err)
|
||||
})
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), sharing.CreateSharedLinkWithSettingsErrorSharedLinkAlreadyExists) {
|
||||
fs.Debugf(absPath, "has a public link already, attempting to retrieve it")
|
||||
listArg := sharing.ListSharedLinksArg{
|
||||
Path: absPath,
|
||||
DirectOnly: true,
|
||||
}
|
||||
var listRes *sharing.ListSharedLinksResult
|
||||
err = f.pacer.Call(func() (bool, error) {
|
||||
listRes, err = f.sharingClient.ListSharedLinks(&listArg)
|
||||
return shouldRetry(err)
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(listRes.Links) == 0 {
|
||||
err = errors.New("Dropbox says the sharing link already exists, but list came back empty")
|
||||
return
|
||||
}
|
||||
linkRes = listRes.Links[0]
|
||||
}
|
||||
if err == nil {
|
||||
switch res := linkRes.(type) {
|
||||
case *sharing.FileLinkMetadata:
|
||||
link = res.Url
|
||||
case *sharing.FolderLinkMetadata:
|
||||
link = res.Url
|
||||
default:
|
||||
err = fmt.Errorf("Don't know how to extract link, response has unknown format: %T", res)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DirMove moves src, srcRemote to this remote at dstRemote
|
||||
// using server side move operations.
|
||||
//
|
||||
|
@ -975,11 +1025,12 @@ func (o *Object) Remove() (err error) {
|
|||
|
||||
// Check the interfaces are satisfied
|
||||
var (
|
||||
_ fs.Fs = (*Fs)(nil)
|
||||
_ fs.Copier = (*Fs)(nil)
|
||||
_ fs.Purger = (*Fs)(nil)
|
||||
_ fs.PutStreamer = (*Fs)(nil)
|
||||
_ fs.Mover = (*Fs)(nil)
|
||||
_ fs.DirMover = (*Fs)(nil)
|
||||
_ fs.Object = (*Object)(nil)
|
||||
_ fs.Fs = (*Fs)(nil)
|
||||
_ fs.Copier = (*Fs)(nil)
|
||||
_ fs.Purger = (*Fs)(nil)
|
||||
_ fs.PutStreamer = (*Fs)(nil)
|
||||
_ fs.Mover = (*Fs)(nil)
|
||||
_ fs.PublicLinker = (*Fs)(nil)
|
||||
_ fs.DirMover = (*Fs)(nil)
|
||||
_ fs.Object = (*Object)(nil)
|
||||
)
|
||||
|
|
|
@ -71,6 +71,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -71,6 +71,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -71,6 +71,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestObjectUpdate(t *testing.T) { fstests.TestObjectUpdate(t) }
|
|||
func TestObjectStorable(t *testing.T) { fstests.TestObjectStorable(t) }
|
||||
func TestFsIsFile(t *testing.T) { fstests.TestFsIsFile(t) }
|
||||
func TestFsIsFileNotFound(t *testing.T) { fstests.TestFsIsFileNotFound(t) }
|
||||
func TestPublicLink(t *testing.T) { fstests.TestPublicLink(t) }
|
||||
func TestObjectRemove(t *testing.T) { fstests.TestObjectRemove(t) }
|
||||
func TestFsPutStream(t *testing.T) { fstests.TestFsPutStream(t) }
|
||||
func TestObjectPurge(t *testing.T) { fstests.TestObjectPurge(t) }
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
_ "github.com/ncw/rclone/cmd/genautocomplete"
|
||||
_ "github.com/ncw/rclone/cmd/gendocs"
|
||||
_ "github.com/ncw/rclone/cmd/info"
|
||||
_ "github.com/ncw/rclone/cmd/link"
|
||||
_ "github.com/ncw/rclone/cmd/listremotes"
|
||||
_ "github.com/ncw/rclone/cmd/ls"
|
||||
_ "github.com/ncw/rclone/cmd/lsd"
|
||||
|
|
|
@ -141,10 +141,10 @@ func ShowVersion() {
|
|||
fmt.Printf("- go version: %s\n", runtime.Version())
|
||||
}
|
||||
|
||||
// newFsFile creates a dst Fs from a name but may point to a file.
|
||||
// NewFsFile creates a dst Fs from a name but may point to a file.
|
||||
//
|
||||
// It returns a string with the file name if points to a file
|
||||
func newFsFile(remote string) (fs.Fs, string) {
|
||||
func NewFsFile(remote string) (fs.Fs, string) {
|
||||
fsInfo, configName, fsPath, err := fs.ParseRemote(remote)
|
||||
if err != nil {
|
||||
fs.CountError(err)
|
||||
|
@ -169,7 +169,7 @@ func newFsFile(remote string) (fs.Fs, string) {
|
|||
//
|
||||
// This can point to a file
|
||||
func newFsSrc(remote string) (fs.Fs, string) {
|
||||
f, fileName := newFsFile(remote)
|
||||
f, fileName := NewFsFile(remote)
|
||||
if fileName != "" {
|
||||
if !filter.Active.InActive() {
|
||||
err := errors.Errorf("Can't limit to single files when using filters: %v", remote)
|
||||
|
|
41
cmd/link/link.go
Normal file
41
cmd/link/link.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package link
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ncw/rclone/cmd"
|
||||
"github.com/ncw/rclone/fs/operations"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd.Root.AddCommand(commandDefintion)
|
||||
}
|
||||
|
||||
var commandDefintion = &cobra.Command{
|
||||
Use: "link remote:path",
|
||||
Short: `Generate public link to file/folder.`,
|
||||
Long: `
|
||||
rclone link will create or retrieve a public link to the given file or folder.
|
||||
|
||||
rclone link remote:path/to/file
|
||||
rclone link remote:path/to/folder/
|
||||
|
||||
If successful, the last line of the output will contain the link. Exact
|
||||
capabilities depend on the remote, but the link will always be created with
|
||||
the least constraints – e.g. no expiry, no password protection, accessible
|
||||
without account.
|
||||
`,
|
||||
Run: func(command *cobra.Command, args []string) {
|
||||
cmd.CheckArgs(1, 1, command, args)
|
||||
fsrc, remote := cmd.NewFsFile(args[0])
|
||||
cmd.Run(false, false, command, func() error {
|
||||
link, err := operations.PublicLink(fsrc, remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(link)
|
||||
return nil
|
||||
})
|
||||
},
|
||||
}
|
|
@ -119,27 +119,27 @@ All the remotes support a basic set of features, but there are some
|
|||
optional features supported by some remotes used to make some
|
||||
operations more efficient.
|
||||
|
||||
| Name | Purge | Copy | Move | DirMove | CleanUp | ListR | StreamUpload |
|
||||
| ---------------------------- |:-----:|:----:|:----:|:-------:|:-------:|:-----:|:------------:|
|
||||
| Amazon Drive | Yes | No | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | No |
|
||||
| Amazon S3 | No | Yes | No | No | No | Yes | Yes |
|
||||
| Backblaze B2 | No | No | No | No | Yes | Yes | Yes |
|
||||
| Box | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes |
|
||||
| Dropbox | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes |
|
||||
| FTP | No | No | Yes | Yes | No | No | Yes |
|
||||
| Google Cloud Storage | Yes | Yes | No | No | No | Yes | Yes |
|
||||
| Google Drive | Yes | Yes | Yes | Yes | Yes | No | Yes |
|
||||
| HTTP | No | No | No | No | No | No | No |
|
||||
| Hubic | Yes † | Yes | No | No | No | Yes | Yes |
|
||||
| Microsoft Azure Blob Storage | Yes | Yes | No | No | No | Yes | No |
|
||||
| Microsoft OneDrive | Yes | Yes | Yes | No [#197](https://github.com/ncw/rclone/issues/197) | No [#575](https://github.com/ncw/rclone/issues/575) | No | No |
|
||||
| Openstack Swift | Yes † | Yes | No | No | No | Yes | Yes |
|
||||
| pCloud | Yes | Yes | Yes | Yes | Yes | No | No |
|
||||
| QingStor | No | Yes | No | No | No | Yes | No |
|
||||
| SFTP | No | No | Yes | Yes | No | No | Yes |
|
||||
| WebDAV | Yes | Yes | Yes | Yes | No | No | Yes ‡ |
|
||||
| Yandex Disk | Yes | No | No | No | Yes | Yes | Yes |
|
||||
| The local filesystem | Yes | No | Yes | Yes | No | No | Yes |
|
||||
| Name | Purge | Copy | Move | DirMove | CleanUp | ListR | StreamUpload | LinkSharing |
|
||||
| ---------------------------- |:-----:|:----:|:----:|:-------:|:-------:|:-----:|:------------:|:------------:|
|
||||
| Amazon Drive | Yes | No | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Amazon S3 | No | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Backblaze B2 | No | No | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Box | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Dropbox | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes |
|
||||
| FTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Google Cloud Storage | Yes | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Google Drive | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes |
|
||||
| HTTP | No | No | No | No | No | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Hubic | Yes † | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Microsoft Azure Blob Storage | Yes | Yes | No | No | No | Yes | No | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Microsoft OneDrive | Yes | Yes | Yes | No [#197](https://github.com/ncw/rclone/issues/197) | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Openstack Swift | Yes † | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| pCloud | Yes | Yes | Yes | Yes | Yes | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| QingStor | No | Yes | No | No | No | Yes | No | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| SFTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| WebDAV | Yes | Yes | Yes | Yes | No | No | Yes ‡ | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| Yandex Disk | Yes | No | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) |
|
||||
| The local filesystem | Yes | No | Yes | Yes | No | No | Yes | No |
|
||||
|
||||
### Purge ###
|
||||
|
||||
|
@ -196,3 +196,9 @@ See the [rclone docs](/docs/#fast-list) for more details.
|
|||
Some remotes allow files to be uploaded without knowing the file size
|
||||
in advance. This allows certain operations to work without spooling the
|
||||
file to local disk first, e.g. `rclone rcat`.
|
||||
|
||||
### LinkSharing ###
|
||||
|
||||
Sets the necessary permissions on a file or folder and prints a link
|
||||
that allows others to access them, even if they don't have an account
|
||||
on the particular cloud provider.
|
||||
|
|
12
fs/fs.go
12
fs/fs.go
|
@ -328,6 +328,9 @@ type Features struct {
|
|||
// as an optional interface
|
||||
DirCacheFlush func()
|
||||
|
||||
// PublicLink generates a public link to the remote path (usually readable by anyone)
|
||||
PublicLink func(remote string) (string, error)
|
||||
|
||||
// Put in to the remote path with the modTime given of the given size
|
||||
//
|
||||
// May create the object even if it returns an error - if so
|
||||
|
@ -443,6 +446,9 @@ func (ft *Features) Fill(f Fs) *Features {
|
|||
if do, ok := f.(DirCacheFlusher); ok {
|
||||
ft.DirCacheFlush = do.DirCacheFlush
|
||||
}
|
||||
if do, ok := f.(PublicLinker); ok {
|
||||
ft.PublicLink = do.PublicLink
|
||||
}
|
||||
if do, ok := f.(PutUncheckeder); ok {
|
||||
ft.PutUnchecked = do.PutUnchecked
|
||||
}
|
||||
|
@ -642,6 +648,12 @@ type PutStreamer interface {
|
|||
PutStream(in io.Reader, src ObjectInfo, options ...OpenOption) (Object, error)
|
||||
}
|
||||
|
||||
// PublicLinker is an optional interface for Fs
|
||||
type PublicLinker interface {
|
||||
// PublicLink generates a public link to the remote path (usually readable by anyone)
|
||||
PublicLink(remote string) (string, error)
|
||||
}
|
||||
|
||||
// MergeDirser is an option interface for Fs
|
||||
type MergeDirser interface {
|
||||
// MergeDirs merges the contents of all the directories passed
|
||||
|
|
|
@ -1470,6 +1470,15 @@ func Rcat(fdst fs.Fs, dstFileName string, in io.ReadCloser, modTime time.Time) (
|
|||
return dst, nil
|
||||
}
|
||||
|
||||
// PublicLink adds a "readable by anyone with link" permission on the given file or folder.
|
||||
func PublicLink(f fs.Fs, remote string) (string, error) {
|
||||
doPublicLink := f.Features().PublicLink
|
||||
if doPublicLink == nil {
|
||||
return "", errors.Errorf("%v doesn't support public links", f)
|
||||
}
|
||||
return doPublicLink(remote)
|
||||
}
|
||||
|
||||
// Rmdirs removes any empty directories (or directories only
|
||||
// containing empty directories) under f, including f.
|
||||
func Rmdirs(f fs.Fs, dir string, leaveRoot bool) error {
|
||||
|
|
|
@ -948,6 +948,62 @@ func TestFsIsFileNotFound(t *testing.T) {
|
|||
fstest.CheckListing(t, fileRemote, []fstest.Item{})
|
||||
}
|
||||
|
||||
// TestPublicLink tests creation of sharable, public links
|
||||
func TestPublicLink(t *testing.T) {
|
||||
skipIfNotOk(t)
|
||||
|
||||
doPublicLink := remote.Features().PublicLink
|
||||
if doPublicLink == nil {
|
||||
t.Skip("FS has no PublicLinker interface")
|
||||
}
|
||||
|
||||
// if object not found
|
||||
link, err := doPublicLink(file1.Path + "_does_not_exist")
|
||||
require.Error(t, err, "Expected to get error when file doesn't exist")
|
||||
require.Equal(t, "", link, "Expected link to be empty on error")
|
||||
|
||||
// sharing file for the first time
|
||||
link1, err := doPublicLink(file1.Path)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, "", link1, "Link should not be empty")
|
||||
|
||||
link2, err := doPublicLink(file2.Path)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, "", link2, "Link should not be empty")
|
||||
|
||||
require.NotEqual(t, link1, link2, "Links to different files should differ")
|
||||
|
||||
// sharing file for the 2nd time
|
||||
link1, err = doPublicLink(file1.Path)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, "", link1, "Link should not be empty")
|
||||
|
||||
// sharing directory for the first time
|
||||
path := path.Dir(file2.Path)
|
||||
link3, err := doPublicLink(path)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, "", link3, "Link should not be empty")
|
||||
|
||||
// sharing directory for the second time
|
||||
link3, err = doPublicLink(path)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, "", link3, "Link should not be empty")
|
||||
|
||||
// sharing the "root" directory in a subremote
|
||||
subRemote, _, removeSubRemote, err := fstest.RandomRemote(RemoteName, false)
|
||||
require.NoError(t, err)
|
||||
defer removeSubRemote()
|
||||
// ensure sub remote isn't empty
|
||||
buf := bytes.NewBufferString("somecontent")
|
||||
obji := object.NewStaticObjectInfo("somefile", time.Now(), int64(buf.Len()), true, nil, nil)
|
||||
_, err = subRemote.Put(buf, obji)
|
||||
require.NoError(t, err)
|
||||
|
||||
link4, err := subRemote.Features().PublicLink("")
|
||||
require.NoError(t, err, "Sharing root in a sub-remote should work")
|
||||
require.NotEqual(t, "", link4, "Link should not be empty")
|
||||
}
|
||||
|
||||
// TestObjectRemove tests Remove
|
||||
func TestObjectRemove(t *testing.T) {
|
||||
skipIfNotOk(t)
|
||||
|
|
Loading…
Reference in a new issue