Преглед на файлове

Several changes, Dropbox support

changed Default config files to yaml from json
Restructured basefs to be common to several others
cleaned gdrivefs  (most code went to basefs)
added dropboxfs (still lacks delta api)
added Dropbox to vendor folder (since its beta and might change in
future)
luis преди 7 години
родител
ревизия
806e41ccc3
променени са 88 файла, в които са добавени 49702 реда и са изтрити 1988 реда
  1. 0 6
      README.md
  2. 1 1
      flags.go
  3. 1 1
      internal/core/core.go
  4. 0 689
      internal/fs/bakfs/basefs.go
  5. 0 4
      internal/fs/bakfs/driver.go
  6. 0 171
      internal/fs/bakfs/file_entry.go
  7. 0 14
      internal/fs/bakfs/handle.go
  8. 56 47
      internal/fs/basefs/basefs.go
  9. 6 0
      internal/fs/basefs/changes.go
  10. 29 2
      internal/fs/basefs/file.go
  11. 84 84
      internal/fs/basefs/file_container.go
  12. 39 43
      internal/fs/basefs/file_entry.go
  13. 18 0
      internal/fs/basefs/file_wrapper.go
  14. 0 47
      internal/fs/basefs/gfile.go
  15. 7 3
      internal/fs/basefs/service.go
  16. 61 0
      internal/fs/dropboxfs/client.go
  17. 13 0
      internal/fs/dropboxfs/config.go
  18. 18 626
      internal/fs/dropboxfs/dropboxfs.go
  19. 0 68
      internal/fs/dropboxfs/research/main.go
  20. 205 0
      internal/fs/dropboxfs/service.go
  21. 27 119
      internal/fs/gdrivefs/gdrivefs.go
  22. 242 0
      internal/fs/gdrivefs/service.go
  23. 0 62
      internal/fs/gdrivefs/serviceimpl.go
  24. 2 1
      main.go
  25. 6 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/.gitignore
  26. 3 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/.gitmodules
  27. 15 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/.travis.yml
  28. 20 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/LICENSE
  29. 95 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/README.md
  30. 132 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/async/types.go
  31. 193 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/auth/client.go
  32. 187 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/auth/types.go
  33. 132 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/common/types.go
  34. 3611 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files/client.go
  35. 75 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files/metadata.go
  36. 2981 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files/types.go
  37. 1199 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/paper/client.go
  38. 729 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/paper/types.go
  39. 213 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/properties/types.go
  40. 170 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sdk.go
  41. 3586 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing/client.go
  42. 49 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing/metadata.go
  43. 4066 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing/types.go
  44. 4417 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team/client.go
  45. 3336 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team/types.go
  46. 91 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_common/types.go
  47. 202 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_log/client.go
  48. 8836 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_log/types.go
  49. 118 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_policies/types.go
  50. 353 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users/client.go
  51. 357 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users/types.go
  52. 37 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users_common/types.go
  53. 237 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/README.md
  54. 5 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/README.md
  55. 96 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/async.stone
  56. 91 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/auth.stone
  57. 60 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/common.stone
  58. 1560 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/files.stone
  59. 193 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/files_properties.stone
  60. 549 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/paper.stone
  61. 120 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/properties.stone
  62. 114 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/shared_content_links.stone
  63. 548 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/shared_links.stone
  64. 7 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/sharing.stone
  65. 659 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/sharing_files.stone
  66. 1186 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/sharing_folders.stone
  67. 32 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/stone_cfg.stone
  68. 243 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team.stone
  69. 56 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_common.stone
  70. 316 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_devices.stone
  71. 292 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_folders.stone
  72. 558 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_groups.stone
  73. 215 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_linked_apps.stone
  74. 90 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_log.stone
  75. 4341 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_log_generated.stone
  76. 625 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_members.stone
  77. 73 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_policies.stone
  78. 86 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_property_templates.stone
  79. 184 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_reports.stone
  80. 277 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/users.stone
  81. 20 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/users_common.stone
  82. 25 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/generate-sdk.sh
  83. 202 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_client.stoneg.py
  84. 134 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_helpers.py
  85. 75 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_rsrc/files/metadata.go
  86. 170 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_rsrc/sdk.go
  87. 49 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_rsrc/sharing/metadata.go
  88. 196 0
      vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_types.stoneg.py

+ 0 - 6
README.md

@@ -101,11 +101,5 @@ HUP    | Perform a GC and shows memory usage <small>Works when its not running i
 * Consider using github.com/codegangsta/cli
 * Create test suit to implement new packages
 * GDrive: long term caching, maintain most used files locally until flush/change
-* File_container can be common for all FS?
-* Define what should be common per FS and create an interface for implementations
 
 
-#### Plan:
-
-Create a common structure for driver
-// Driver needs populate list somehow

+ 1 - 1
flags.go

@@ -39,7 +39,7 @@ func parseFlags(config *core.Config) (err error) {
 		return errors.New("Missing parameter")
 	}
 	if flag.NArg() == 1 {
-		config.Source = filepath.Join(config.HomeDir, config.Type+".json")
+		config.Source = filepath.Join(config.HomeDir, config.Type+".yaml")
 		config.Target = flag.Arg(0)
 	} else {
 		config.Source = flag.Arg(0)

+ 1 - 1
internal/core/core.go

@@ -56,7 +56,7 @@ func New() *Core {
 			VerboseLog:  false,
 			RefreshTime: 5 * time.Second,
 			HomeDir:     filepath.Join(usr.HomeDir, ".cloudmount"),
-			Source:      filepath.Join(usr.HomeDir, ".cloudmount", "gdrive.json"),
+			Source:      filepath.Join(usr.HomeDir, ".cloudmount", "gdrive.yaml"),
 			UID:         uint32(uid),
 			GID:         uint32(gid),
 		},

+ 0 - 689
internal/fs/bakfs/basefs.go

@@ -1,689 +0,0 @@
-package basefs
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"strings"
-	"sync"
-	"syscall"
-	"time"
-
-	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
-	"dev.hexasoftware.com/hxs/prettylog"
-
-	"golang.org/x/net/context"
-
-	drive "google.golang.org/api/drive/v3"
-	"google.golang.org/api/googleapi"
-
-	"github.com/jacobsa/fuse"
-	"github.com/jacobsa/fuse/fuseops"
-	"github.com/jacobsa/fuse/fuseutil"
-)
-
-var (
-	log = prettylog.New("commonfs")
-)
-
-// BaseFS struct
-type BaseFS struct {
-	fuseutil.NotImplementedFileSystem // Defaults
-
-	Config *core.Config //core   *core.Core // Core Config instead?
-	//serviceConfig                     *Config
-	Client *drive.Service
-
-	fileHandles map[fuseops.HandleID]*Handle
-	fileEntries map[fuseops.InodeID]*FileEntry
-
-	//nextRefresh time.Time
-	handleMU *sync.Mutex
-	inodeMU  *sync.Mutex
-	//fileMap map[string]
-	// Map IDS with FileEntries
-}
-
-func New(core *core.Core) *BaseFS {
-
-	fs := &BaseFS{
-		Config: &core.Config,
-		//serviceConfig: &Config{}, // This is on service Driver
-		fileHandles: map[fuseops.HandleID]*Handle{},
-		handleMU:    &sync.Mutex{},
-	}
-	// Temporary entry
-	entry := fs.fileEntry("Loading...", nil, 9999)
-	entry.Attr.Mode = os.FileMode(0)
-
-	return fs
-}
-
-// Async
-func (fs *BaseFS) Start() {
-	go func() {
-		//fs.Refresh() // First load
-
-		// Change reader loop
-		/*startPageTokenRes, err := fs.root.client.Changes.GetStartPageToken().Do()
-		if err != nil {
-			log.Println("GDrive err", err)
-		}
-		savedStartPageToken := startPageTokenRes.StartPageToken
-		for {
-			pageToken := savedStartPageToken
-			for pageToken != "" {
-				changesRes, err := fs.root.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
-				if err != nil {
-					log.Println("Err fetching changes", err)
-					break
-				}
-				//log.Println("Changes:", len(changesRes.Changes))
-				for _, c := range changesRes.Changes {
-					entry := fs.root.FindByGID(c.FileId)
-					if c.Removed {
-						if entry == nil {
-							continue
-						} else {
-							fs.root.RemoveEntry(entry)
-						}
-						continue
-					}
-
-					if entry != nil {
-						entry.SetGFile(c.File)
-					} else {
-						//Create new one
-						fs.root.FileEntry(c.File) // Creating new one
-					}
-				}
-				if changesRes.NewStartPageToken != "" {
-					savedStartPageToken = changesRes.NewStartPageToken
-				}
-				pageToken = changesRes.NextPageToken
-			}
-
-			time.Sleep(fs.config.RefreshTime)
-		}*/
-	}()
-}
-
-////////////////////////////////////////////////////////
-// TOOLS & HELPERS
-////////////////////////////////////////////////////////
-
-// COMMON
-func (fs *BaseFS) createHandle() *Handle {
-	// Lock here instead
-	fs.handleMU.Lock()
-	defer fs.handleMU.Unlock()
-
-	var handleID fuseops.HandleID
-
-	for handleID = 1; handleID < 99999; handleID++ {
-		_, ok := fs.fileHandles[handleID]
-		if !ok {
-			break
-		}
-	}
-
-	handle := &Handle{ID: handleID}
-	fs.fileHandles[handleID] = handle
-
-	return handle
-}
-
-const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
-const gdFields = googleapi.Field("files(" + fileFields + ")")
-
-// FULL Refresh service files
-
-///////////////////////////////
-// Fuse operations
-////////////
-
-// OpenDir return nil error allows open dir
-// COMMON for drivers
-func (fs *BaseFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
-
-	entry := fs.findByInode(op.Inode)
-	if entry == nil {
-		return fuse.ENOENT
-	}
-
-	handle := fs.createHandle()
-	handle.entry = entry
-	op.Handle = handle.ID
-
-	return // No error allow, dir open
-}
-
-// ReadDir lists files into readdirop
-// Common for drivers
-func (fs *BaseFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
-	fh, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		log.Fatal("Handle does not exists")
-	}
-
-	if op.Offset == 0 { // Rebuild/rewind dir list
-		fh.entries = []fuseutil.Dirent{}
-
-		children := fs.listByParent(fh.entry)
-
-		for i, v := range children {
-			fusetype := fuseutil.DT_File
-			if v.IsDir() {
-				fusetype = fuseutil.DT_Directory
-			}
-			dirEnt := fuseutil.Dirent{
-				Inode:  v.Inode,
-				Name:   v.Name,
-				Type:   fusetype,
-				Offset: fuseops.DirOffset(i) + 1,
-			}
-			//	written += fuseutil.WriteDirent(fh.buf[written:], dirEnt)
-			fh.entries = append(fh.entries, dirEnt)
-		}
-	}
-
-	index := int(op.Offset)
-	if index > len(fh.entries) {
-		return fuse.EINVAL
-	}
-	if index > 0 {
-		index++
-	}
-	for i := index; i < len(fh.entries); i++ {
-		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
-		if n == 0 {
-			break
-		}
-		op.BytesRead += n
-	}
-	return
-}
-
-// SPECIFIC code
-func (fs *BaseFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
-
-	// Hack to truncate file?
-
-	if op.Size != nil {
-		entry := fs.Root.FindByInode(op.Inode)
-
-		if *op.Size != 0 { // We only allow truncate to 0
-			return fuse.ENOSYS
-		}
-		err = entry.Truncate()
-	}
-
-	return
-}
-
-//GetInodeAttributes return attributes
-// COMMON
-func (fs *BaseFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
-
-	f := fs.findByInode(op.Inode)
-	if f == nil {
-		return fuse.ENOENT
-	}
-	op.Attributes = f.Attr
-	op.AttributesExpiration = time.Now().Add(time.Minute)
-
-	return
-}
-
-// ReleaseDirHandle deletes file handle entry
-// COMMON
-func (fs *BaseFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
-	delete(fs.fileHandles, op.Handle)
-	return
-}
-
-// LookUpInode based on Parent and Name we return a self cached inode
-// Cloud be COMMON but has specific ID
-func (fs *BaseFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
-
-	parentFile := fs.findByInode(op.Parent) // true means transverse all
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-
-	entry := fs.lookupByParent(parentFile, op.Name)
-
-	if entry == nil {
-		return fuse.ENOENT
-	}
-
-	// Transverse only local
-
-	now := time.Now()
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: now.Add(time.Second),
-		EntryExpiration:      now.Add(time.Second),
-	}
-	return
-}
-
-// ForgetInode allows to forgetInode
-// COMMON
-func (fs *BaseFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
-	return
-}
-
-// GetXAttr special attributes
-// COMMON
-func (fs *BaseFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
-	return
-}
-
-//////////////////////////////////////////////////////////////////////////
-// File OPS
-//////////////////////////////////////////////////////////////////////////
-
-// OpenFile creates a temporary handle to be handled on read or write
-// COMMON
-func (fs *BaseFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
-	f := fs.findByInode(op.Inode) // might not exists
-
-	// Generate new handle
-	handle := fs.createHandle()
-	handle.entry = f
-
-	op.Handle = handle.ID
-	op.UseDirectIO = true
-
-	return
-}
-
-// COMMON but specific in cache
-func (fs *BaseFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
-	handle := fs.fileHandles[op.Handle]
-
-	localFile := handle.entry.Cache()
-	op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
-	if err == io.EOF { // fuse does not expect a EOF
-		err = nil
-	}
-
-	return
-}
-
-// CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
-// Cloud SPECIFIC
-func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
-
-	parentFile := fs.findByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	// Only write on child folders
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	existsFile := fs.lookupByParent(parentFile, op.Name)
-	//existsFile := parentFile.FindByName(op.Name, false)
-	if existsFile != nil {
-		return fuse.EEXIST
-	}
-
-	// Parent entry/Name
-	entry, err := fs.createFile(parentFile, op.Name, false)
-	if err != nil {
-		return err
-	}
-	// Associate a temp file to a new handle
-	// Local copy
-	// Lock
-	handle := fs.createHandle()
-	handle.entry = entry
-	handle.uploadOnDone = true
-	//
-	op.Handle = handle.ID
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: time.Now().Add(time.Minute),
-		EntryExpiration:      time.Now().Add(time.Minute),
-	}
-	op.Mode = entry.Attr.Mode
-
-	return
-}
-
-// WriteFile as ReadFile it creates a temporary file on first read
-// Maybe the ReadFile should be called here aswell to cache current contents since we are using writeAt
-// CLOUD SPECIFIC
-func (fs *BaseFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
-	handle, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-
-	localFile := handle.entry.Cache()
-	if localFile == nil {
-		return fuse.EINVAL
-	}
-
-	_, err = localFile.WriteAt(op.Data, op.Offset)
-	if err != nil {
-		err = fuse.EIO
-		return
-	}
-	handle.uploadOnDone = true
-
-	return
-}
-
-// FlushFile just returns no error, maybe upload should be handled here
-// COMMON
-func (fs *BaseFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
-	handle, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-	if handle.entry.tempFile == nil {
-		return
-	}
-	if handle.uploadOnDone { // or if content changed basically
-		err = handle.entry.Sync()
-		if err != nil {
-			return fuse.EINVAL
-		}
-	}
-	return
-}
-
-// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
-// COMMON
-func (fs *BaseFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
-	handle := fs.fileHandles[op.Handle]
-
-	handle.entry.ClearCache()
-
-	delete(fs.fileHandles, op.Handle)
-
-	return
-}
-
-// Unlink remove file and remove from local cache entry
-// SPECIFIC
-func (fs *BaseFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
-	parentEntry := fs.findByInode(op.Parent)
-	if parentEntry == nil {
-		return fuse.ENOENT
-	}
-	if parentEntry.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	fileEntry := fs.lookupByParent(parentEntry, op.Name)
-	//fileEntry := parentEntry.FindByName(op.Name, false)
-	if fileEntry == nil {
-		return fuse.ENOATTR
-	}
-	return fs.deleteFile(fileEntry)
-}
-
-// MkDir creates a directory on a parent dir
-func (fs *BaseFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
-
-	parentFile := fs.findByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	entry, err := fs.createFile(parentFile, op.Name, true)
-	if err != nil {
-		return err
-	}
-	//entry = parentFile.AppendGFile(fi, entry.Inode)
-	//if entry == nil {
-	//		return fuse.EINVAL
-	//	}
-
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: time.Now().Add(time.Minute),
-		EntryExpiration:      time.Now().Add(time.Microsecond),
-	}
-
-	return
-}
-
-// RmDir fuse implementation
-func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
-
-	parentFile := fs.findByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	theFile := fs.lookupByParent(parentFile, op.Name)
-	//theFile := parentFile.FindByName(op.Name, false)
-
-	err = fs.deleteFile(theFile)
-	//err = fs.Client.Files.Delete(theFile.GFile.Id).Do()
-	if err != nil {
-		return fuse.ENOTEMPTY
-	}
-
-	//parentFile.RemoveChild(theFile)
-
-	// Remove from entry somehow
-
-	return
-}
-
-// Rename fuse implementation
-func (fs *BaseFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
-	oldParentFile := fs.findByInode(op.OldParent)
-	if oldParentFile == nil {
-		return fuse.ENOENT
-	}
-	newParentFile := fs.findByInode(op.NewParent)
-	if newParentFile == nil {
-		return fuse.ENOENT
-	}
-
-	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	//oldFile := oldParentFile.FindByName(op.OldName, false)
-	oldEntry := fs.lookupByParent(oldParentFile, op.OldName)
-
-	// Although GDrive allows duplicate names, there is some issue with inode caching
-	// So we prevent a rename to a file with same name
-	//existsFile := newParentFile.FindByName(op.NewName, false)
-	existsEntry := fs.lookupByGID(newParentFile.GID, op.NewName)
-	if existsEntry != nil {
-		return fuse.EEXIST
-	}
-
-	// Rename somehow
-	ngFile := &drive.File{
-		Name: op.NewName,
-	}
-
-	updateCall := fs.Client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
-	if oldParentFile != newParentFile {
-		updateCall.RemoveParents(oldParentFile.GID)
-		updateCall.AddParents(newParentFile.GID)
-	}
-	updatedFile, err := updateCall.Do()
-
-	oldEntry.SetGFile(updatedFile)
-
-	//oldParentFile.RemoveChild(oldFile)
-	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
-
-	return
-}
-
-func (fs *BaseFS) findByInode(inode fuseops.InodeID) *FileEntry {
-	return fs.fileEntries[inode]
-}
-
-// GID specific functions
-func (fs *BaseFS) findByGID(gid string) *FileEntry {
-	for _, v := range fs.fileEntries {
-		if v.GFile != nil && v.GFile.Id == gid {
-			return v
-		}
-	}
-	return nil
-}
-func (fs *BaseFS) lookupByGID(parentGID string, name string) *FileEntry {
-	for _, entry := range fs.fileEntries {
-		if entry.HasParentGID(parentGID) && entry.Name == name {
-			return entry
-		}
-	}
-	return nil
-}
-
-func (fs *BaseFS) listByParent(parent *FileEntry) []*FileEntry {
-	ret := []*FileEntry{}
-	for _, entry := range fs.fileEntries {
-		if entry.HasParentGID(parent.GID) {
-			ret = append(ret, entry)
-		}
-	}
-	return ret
-}
-func (fs *BaseFS) lookupByParent(parent *FileEntry, name string) *FileEntry {
-	for _, entry := range fs.fileEntries {
-		if entry.HasParentGID(parent.GID) && entry.Name == name {
-			return entry
-		}
-	}
-	return nil
-
-}
-
-func (fs *BaseFS) createFile(parentFile *FileEntry, name string, isDir bool) (*FileEntry, error) {
-
-	newGFile := &drive.File{
-		Parents: []string{parentFile.GFile.Id},
-		Name:    name,
-	}
-	if isDir {
-		newGFile.MimeType = "application/vnd.google-apps.folder"
-	}
-	// Could be transformed to CreateFile in continer
-	// InDriver
-	createdGFile, err := fs.Client.Files.Create(newGFile).Fields(fileFields).Do()
-	if err != nil {
-		return nil, fuse.EINVAL
-	}
-	entry := fs.fileEntry(createdGFile.Name, createdGFile) // New Entry added // Or Return same?
-
-	return entry, nil
-}
-
-func (fs *BaseFS) deleteFile(entry *FileEntry) error {
-	err := fs.Client.Files.Delete(entry.GFile.Id).Do()
-	if err != nil {
-		return fuse.EIO
-	}
-
-	fs.removeEntry(entry)
-	return nil
-}
-
-//////////////
-
-//Return or create inode // Pass name maybe?
-func (fs *BaseFS) fileEntry(aname string, gfile *drive.File, inodeOps ...fuseops.InodeID) *FileEntry {
-
-	fs.inodeMU.Lock()
-	defer fs.inodeMU.Unlock()
-
-	var inode fuseops.InodeID
-	if len(inodeOps) > 0 {
-		inode = inodeOps[0]
-		if fe, ok := fs.fileEntries[inode]; ok {
-			return fe
-		}
-	} else { // generate new inode
-		// Max Inode Number
-		for inode = 2; inode < 99999; inode++ {
-			_, ok := fs.fileEntries[inode]
-			if !ok {
-				break
-			}
-		}
-	}
-
-	name := ""
-	if gfile != nil {
-		name = aname
-		count := 1
-		nameParts := strings.Split(name, ".")
-		for {
-			// We find if we have a GFile in same parent with same name
-			var entry *FileEntry
-			for _, p := range gfile.Parents {
-				entry = fs.lookupByGID(p, name)
-				if entry != nil {
-					break
-				}
-			}
-			if entry == nil { // Not found return
-				break
-			}
-			count++
-			if len(nameParts) > 1 {
-				name = fmt.Sprintf("%s(%d).%s", nameParts[0], count, strings.Join(nameParts[1:], "."))
-			} else {
-				name = fmt.Sprintf("%s(%d)", nameParts[0], count)
-			}
-			log.Printf("Conflicting name generated new '%s' as '%s'", gfile.Name, name)
-		}
-	}
-
-	fe := &FileEntry{
-		GFile: gfile,
-		Inode: inode,
-		fs:    fs,
-		Name:  name,
-		//children:  []*FileEntry{},
-		Attr: fuseops.InodeAttributes{
-			Uid: fs.Config.UID,
-			Gid: fs.Config.GID,
-		},
-	}
-	fe.SetGFile(gfile)
-
-	fs.fileEntries[inode] = fe
-
-	return fe
-}
-
-/*func (fs *BaseFS) addEntry(entry *FileEntry) {
-	fc.fileEntries[entry.Inode] = entry
-}*/
-
-// RemoveEntry remove file entry
-func (fs *BaseFS) removeEntry(entry *FileEntry) {
-	var inode fuseops.InodeID
-	for k, e := range fs.fileEntries {
-		if e == entry {
-			inode = k
-		}
-	}
-	delete(fs.fileEntries, inode)
-}

+ 0 - 4
internal/fs/bakfs/driver.go

@@ -1,4 +0,0 @@
-package basefs
-
-type Driver interface {
-}

+ 0 - 171
internal/fs/bakfs/file_entry.go

@@ -1,171 +0,0 @@
-package basefs
-
-import (
-	"io"
-	"io/ioutil"
-	"net/http"
-	"os"
-	"time"
-
-	"github.com/jacobsa/fuse"
-	"github.com/jacobsa/fuse/fuseops"
-	drive "google.golang.org/api/drive/v3"
-)
-
-//FileEntry entry to handle files
-type FileEntry struct {
-	//parent *FileEntry
-	fs    *BaseFS
-	GID   string      // google driveID
-	GFile *drive.File // GDrive file // Interface maybe?
-	Name  string      // local name
-	// fuseops
-	Inode fuseops.InodeID
-	Attr  fuseops.InodeAttributes // Cached attributes
-
-	// cache file
-	tempFile *os.File // Cached file
-	// childs
-	//children []*FileEntry // children
-}
-
-// Why?
-func (fe *FileEntry) HasParentGID(gid string) bool {
-
-	// Exceptional case
-	if fe.Inode == fuseops.RootInodeID {
-		return false
-	}
-	if gid == "" { // We are looking in root
-		if fe.GFile == nil {
-			return true
-		}
-		if len(fe.GFile.Parents) == 0 {
-			return true
-		}
-	}
-
-	if fe.GFile == nil { // Case gid is not empty and GFile is null
-		return false
-	}
-	for _, pgid := range fe.GFile.Parents {
-		if pgid == gid {
-			return true
-		}
-	}
-	return false
-}
-
-// SetGFile update attributes and set drive.File
-func (fe *FileEntry) SetGFile(gfile *drive.File) { // Should remove from here maybe?
-	if gfile == nil {
-		fe.GFile = nil
-	} else {
-		fe.GFile = gfile
-	}
-
-	// GetAttribute from GFile somehow
-	// Create Attribute
-	attr := fuseops.InodeAttributes{}
-	attr.Nlink = 1
-	attr.Uid = fe.fs.Config.UID
-	attr.Gid = fe.fs.Config.GID
-
-	attr.Mode = os.FileMode(0644) // default
-	if gfile != nil {
-		attr.Size = uint64(gfile.Size)
-		attr.Crtime, _ = time.Parse(time.RFC3339, gfile.CreatedTime)
-		attr.Ctime = attr.Crtime // Set CTime to created, although it is change inode metadata
-		attr.Mtime, _ = time.Parse(time.RFC3339, gfile.ModifiedTime)
-		attr.Atime = attr.Mtime // Set access time to modified, not sure if gdrive has access time
-		if gfile.MimeType == "application/vnd.google-apps.folder" {
-			attr.Mode = os.FileMode(0755) | os.ModeDir
-		}
-		fe.GID = gfile.Id
-	}
-	fe.Attr = attr
-}
-
-// Sync cached , upload to gdrive
-
-func (fe *FileEntry) Sync() (err error) {
-	if fe.tempFile == nil {
-		return
-	}
-	fe.tempFile.Sync()
-	fe.tempFile.Seek(0, io.SeekStart)
-
-	ngFile := &drive.File{}
-	up := fe.fs.Client.Files.Update(fe.GFile.Id, ngFile)
-	upFile, err := up.Media(fe.tempFile).Fields(fileFields).Do()
-
-	fe.SetGFile(upFile) // update local GFile entry
-	return
-
-}
-
-//ClearCache remove local file
-func (fe *FileEntry) ClearCache() (err error) {
-	if fe.tempFile == nil {
-		return
-	}
-	fe.tempFile.Close()
-	os.Remove(fe.tempFile.Name())
-	fe.tempFile = nil
-	return
-}
-
-// Cache download GDrive file to a temporary local file or return already created file
-func (fe *FileEntry) Cache() *os.File {
-	if fe.tempFile != nil {
-		return fe.tempFile
-	}
-	var res *http.Response
-	var err error
-	// Export GDocs (Special google doc documents needs to be exported make a config somewhere for this)
-	switch fe.GFile.MimeType { // Make this somewhat optional special case
-	case "application/vnd.google-apps.document":
-		log.Println("Exporting as: text/markdown")
-		res, err = fe.fs.Client.Files.Export(fe.GFile.Id, "text/plain").Download()
-	case "application/vnd.google-apps.spreadsheet":
-		log.Println("Exporting as: text/csv")
-		res, err = fe.fs.Client.Files.Export(fe.GFile.Id, "text/csv").Download()
-	default:
-		res, err = fe.fs.Client.Files.Get(fe.GFile.Id).Download()
-	}
-
-	if err != nil {
-		log.Println("Error from GDrive API", err)
-		return nil
-	}
-	defer res.Body.Close()
-
-	// Local copy
-	fe.tempFile, err = ioutil.TempFile(os.TempDir(), "gdfs") // TODO: const this elsewhere
-	if err != nil {
-		log.Println("Error creating temp file")
-		return nil
-	}
-	io.Copy(fe.tempFile, res.Body)
-
-	fe.tempFile.Seek(0, io.SeekStart)
-	return fe.tempFile
-
-}
-
-func (fe *FileEntry) Truncate() (err error) { // DriverTruncate
-	// Delete and create another on truncate 0
-	err = fe.fs.Client.Files.Delete(fe.GFile.Id).Do() // XXX: Careful on this
-	createdFile, err := fe.fs.Client.Files.Create(&drive.File{Parents: fe.GFile.Parents, Name: fe.GFile.Name}).Fields(fileFields).Do()
-	if err != nil {
-		return fuse.EINVAL // ??
-	}
-	fe.SetGFile(createdFile) // Set new file
-
-	return
-}
-
-// IsDir returns true if entry is a directory:w
-func (fe *FileEntry) IsDir() bool {
-	return fe.Attr.Mode&os.ModeDir == os.ModeDir
-}

+ 0 - 14
internal/fs/bakfs/handle.go

@@ -1,14 +0,0 @@
-package basefs
-
-import (
-	"github.com/jacobsa/fuse/fuseops"
-	"github.com/jacobsa/fuse/fuseutil"
-)
-
-type Handle struct {
-	ID           fuseops.HandleID
-	entry        *FileEntry
-	uploadOnDone bool
-	// Handling for dir
-	entries []fuseutil.Dirent
-}

+ 56 - 47
internal/fs/basefs/basefs.go

@@ -2,7 +2,9 @@
 package basefs
 
 import (
+	"errors"
 	"io"
+	"math"
 	"os"
 	"sync"
 	"syscall"
@@ -13,7 +15,6 @@ import (
 
 	"golang.org/x/net/context"
 
-	drive "google.golang.org/api/drive/v3"
 	"google.golang.org/api/googleapi"
 
 	"github.com/jacobsa/fuse"
@@ -21,11 +22,15 @@ import (
 	"github.com/jacobsa/fuse/fuseutil"
 )
 
+const maxInodes = math.MaxUint64
+
 var (
 	log = prettylog.New("basefs")
+	// ErrNotImplemented basic Not implemented error
+	ErrNotImplemented = errors.New("Not implemented")
 )
 
-type Handle struct {
+type handle struct {
 	ID           fuseops.HandleID
 	entry        *FileEntry
 	uploadOnDone bool
@@ -37,18 +42,11 @@ type Handle struct {
 type BaseFS struct {
 	fuseutil.NotImplementedFileSystem // Defaults
 
-	Config *core.Config //core   *core.Core // Core Config instead?
-	Root   *FileContainer
-
-	fileHandles map[fuseops.HandleID]*Handle
+	Config      *core.Config //core   *core.Core // Core Config instead?
+	Root        *FileContainer
+	fileHandles map[fuseops.HandleID]*handle
 	handleMU    *sync.Mutex
-	//serviceConfig *Config
-	Client  *drive.Service
-	Service Service
-	//root   *FileEntry // hiearchy reference
-
-	//fileMap map[string]
-	// Map IDS with FileEntries
+	Service     Service
 }
 
 // New Creates a new BaseFS with config based on core
@@ -56,42 +54,58 @@ func New(core *core.Core) *BaseFS {
 
 	fs := &BaseFS{
 		Config:      &core.Config,
-		fileHandles: map[fuseops.HandleID]*Handle{},
+		fileHandles: map[fuseops.HandleID]*handle{},
 		handleMU:    &sync.Mutex{},
 	}
+
 	fs.Root = NewFileContainer(fs)
 	fs.Root.uid = core.Config.UID
 	fs.Root.gid = core.Config.GID
 
-	_, entry := fs.Root.FileEntry(&drive.File{Id: "0", Name: "Loading..."}, 9999)
+	loadingFile := File{Name: "Loading...", ID: "0"}
+	entry := fs.Root.FileEntry(&loadingFile, maxInodes) // Last inode
 	entry.Attr.Mode = os.FileMode(0)
 
 	return fs
 }
 
+// Refresh should be renamed to Load or something
+func (fs *BaseFS) Refresh() {
+	// Try
+	files, err := fs.Service.ListAll()
+	if err == nil { // Do something repeat maybe?
+
+	}
+	root := NewFileContainer(fs)
+	for _, file := range files {
+		root.FileEntry(file) // Try to find in previous root
+	}
+	log.Println("File count:", root.Count())
+	fs.Root = root
+}
+
 ////////////////////////////////////////////////////////
 // TOOLS & HELPERS
 ////////////////////////////////////////////////////////
 
 // COMMON
-func (fs *BaseFS) createHandle() *Handle {
+func (fs *BaseFS) createHandle() *handle {
 	// Lock here instead
 	fs.handleMU.Lock()
 	defer fs.handleMU.Unlock()
 
 	var handleID fuseops.HandleID
-
-	for handleID = 1; handleID < 99999; handleID++ {
+	for handleID = 1; handleID < math.MaxUint64; handleID++ {
 		_, ok := fs.fileHandles[handleID]
 		if !ok {
 			break
 		}
 	}
 
-	handle := &Handle{ID: handleID}
-	fs.fileHandles[handleID] = handle
+	h := &handle{ID: handleID}
+	fs.fileHandles[handleID] = h
 
-	return handle
+	return h
 }
 
 // Client is still here
@@ -130,21 +144,19 @@ func (fs *BaseFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error
 		fh.entries = []fuseutil.Dirent{}
 		children := fs.Root.ListByParent(fh.entry)
 
-		i := 0
-		for inode, v := range children {
+		for i, v := range children {
 			fusetype := fuseutil.DT_File
 			if v.IsDir() {
 				fusetype = fuseutil.DT_Directory
 			}
 			dirEnt := fuseutil.Dirent{
-				Inode:  inode,
+				Inode:  v.Inode,
 				Name:   v.Name,
 				Type:   fusetype,
 				Offset: fuseops.DirOffset(i) + 1,
 			}
 			//	written += fuseutil.WriteDirent(fh.buf[written:], dirEnt)
 			fh.entries = append(fh.entries, dirEnt)
-			i++
 		}
 	}
 
@@ -213,7 +225,7 @@ func (fs *BaseFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (e
 		return fuse.ENOENT
 	}
 
-	inode, entry := fs.Root.Lookup(parentFile, op.Name)
+	entry := fs.Root.Lookup(parentFile, op.Name)
 
 	if entry == nil {
 		return fuse.ENOENT
@@ -224,7 +236,7 @@ func (fs *BaseFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (e
 	now := time.Now()
 	op.Entry = fuseops.ChildInodeEntry{
 		Attributes:           entry.Attr,
-		Child:                inode,
+		Child:                entry.Inode,
 		AttributesExpiration: now.Add(time.Second),
 		EntryExpiration:      now.Add(time.Second),
 	}
@@ -290,18 +302,18 @@ func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err
 		return fuse.ENOENT
 	}
 	// Only write on child folders
-	if parentFile == fs.Root.fileEntries[fuseops.RootInodeID] {
+	if parentFile == fs.Root.FileEntries[fuseops.RootInodeID] {
 		return syscall.EPERM
 	}
 
-	_, existsFile := fs.Root.Lookup(parentFile, op.Name)
+	existsFile := fs.Root.Lookup(parentFile, op.Name)
 	//existsFile := parentFile.FindByName(op.Name, false)
 	if existsFile != nil {
 		return fuse.EEXIST
 	}
 
 	// Parent entry/Name
-	inode, entry, err := fs.Root.CreateFile(parentFile, op.Name, false)
+	entry, err := fs.Root.CreateFile(parentFile, op.Name, false)
 	if err != nil {
 		return err
 	}
@@ -315,7 +327,7 @@ func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err
 	op.Handle = handle.ID
 	op.Entry = fuseops.ChildInodeEntry{
 		Attributes:           entry.Attr,
-		Child:                inode,
+		Child:                entry.Inode,
 		AttributesExpiration: time.Now().Add(time.Minute),
 		EntryExpiration:      time.Now().Add(time.Minute),
 	}
@@ -337,7 +349,6 @@ func (fs *BaseFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err e
 	if localFile == nil {
 		return fuse.EINVAL
 	}
-
 	_, err = localFile.WriteAt(op.Data, op.Offset)
 	if err != nil {
 		err = fuse.EIO
@@ -390,7 +401,7 @@ func (fs *BaseFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error)
 		return fuse.ENOENT
 	}
 
-	_, fileEntry := fs.Root.Lookup(parentEntry, op.Name)
+	fileEntry := fs.Root.Lookup(parentEntry, op.Name)
 	//fileEntry := parentEntry.FindByName(op.Name, false)
 	if fileEntry == nil {
 		return fuse.ENOATTR
@@ -409,14 +420,14 @@ func (fs *BaseFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
 		return fuse.ENOENT
 	}
 
-	inode, entry, err := fs.Root.CreateFile(parentFile, op.Name, true)
+	entry, err := fs.Root.CreateFile(parentFile, op.Name, true)
 	if err != nil {
 		return err
 	}
 
 	op.Entry = fuseops.ChildInodeEntry{
 		Attributes:           entry.Attr,
-		Child:                inode,
+		Child:                entry.Inode,
 		AttributesExpiration: time.Now().Add(time.Minute),
 		EntryExpiration:      time.Now().Add(time.Microsecond),
 	}
@@ -435,7 +446,7 @@ func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
 		return fuse.ENOENT
 	}
 
-	_, theFile := fs.Root.Lookup(parentFile, op.Name)
+	theFile := fs.Root.Lookup(parentFile, op.Name)
 
 	err = fs.Root.DeleteFile(theFile)
 	if err != nil {
@@ -460,28 +471,26 @@ func (fs *BaseFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error)
 	}
 
 	//oldFile := oldParentFile.FindByName(op.OldName, false)
-	_, oldEntry := fs.Root.Lookup(oldParentFile, op.OldName)
+	oldEntry := fs.Root.Lookup(oldParentFile, op.OldName)
 
 	// Although GDrive allows duplicate names, there is some issue with inode caching
 	// So we prevent a rename to a file with same name
 	//existsFile := newParentFile.FindByName(op.NewName, false)
-	_, existsEntry := fs.Root.Lookup(newParentFile, op.NewName)
+	existsEntry := fs.Root.Lookup(newParentFile, op.NewName)
 	if existsEntry != nil {
 		return fuse.EEXIST
 	}
 
-	ngFile := &drive.File{
-		Name: op.NewName,
+	nFile, err := fs.Service.Move(oldEntry.File, newParentFile.File, op.NewName)
+	if err != nil {
+		return fuse.EINVAL
 	}
 
-	updateCall := fs.Client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
-	if oldParentFile != newParentFile {
-		updateCall.RemoveParents(oldParentFile.GID)
-		updateCall.AddParents(newParentFile.GID)
-	}
-	updatedFile, err := updateCall.Do()
+	//oldEntry.SetFile(nFile, fs.Config.UID, fs.Config.GID)
 
-	oldEntry.SetFile(&GFile{updatedFile}, fs.Config.UID, fs.Config.GID)
+	// Why remove and add instead of setting file, is just in case we have an existing name FileEntry solves the name adding duplicates helpers
+	fs.Root.RemoveEntry(oldEntry)
+	fs.Root.FileEntry(nFile, oldEntry.Inode) // Use this same inode
 
 	//oldParentFile.RemoveChild(oldFile)
 	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)

+ 6 - 0
internal/fs/basefs/changes.go

@@ -0,0 +1,6 @@
+package basefs
+
+type Change struct {
+	File    *File
+	deleted bool
+}

+ 29 - 2
internal/fs/basefs/file.go

@@ -1,10 +1,37 @@
 package basefs
 
-import "github.com/jacobsa/fuse/fuseops"
+import (
+	"os"
+	"time"
+)
 
-type File interface {
+// This could be a struct
+// And service would be creating these
+/*type File interface {
 	ID() string
 	Name() string
 	Attr() fuseops.InodeAttributes
 	Parents() []string
+	HasParent(file File) bool
+}*/
+type File struct {
+	ID   string
+	Name string
+	// Build Attr from this
+	Size         uint64
+	CreatedTime  time.Time
+	ModifiedTime time.Time
+	AccessedTime time.Time
+	Mode         os.FileMode
+	Parents      []string
+	Data         interface{} // Any thing
+}
+
+func (f *File) HasParent(parent *File) bool {
+	for _, p := range f.Parents {
+		if p == parent.ID {
+			return true
+		}
+	}
+	return false
 }

+ 84 - 84
internal/fs/basefs/file_container.go

@@ -4,18 +4,17 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"math"
 	"os"
 	"strings"
 	"sync"
 
-	drive "google.golang.org/api/drive/v3"
-
 	"github.com/jacobsa/fuse"
 	"github.com/jacobsa/fuse/fuseops"
 )
 
 type FileContainer struct {
-	fileEntries map[fuseops.InodeID]*FileEntry
+	FileEntries map[fuseops.InodeID]*FileEntry
 	///	tree        *FileEntry
 	fs *BaseFS
 	//client *drive.Service // Wrong should be common
@@ -27,92 +26,75 @@ type FileContainer struct {
 
 func NewFileContainer(fs *BaseFS) *FileContainer {
 	fc := &FileContainer{
-		fileEntries: map[fuseops.InodeID]*FileEntry{},
+		FileEntries: map[fuseops.InodeID]*FileEntry{},
 		fs:          fs,
 		//client:  fs.Client,
 		inodeMU: &sync.Mutex{},
 		uid:     fs.Config.UID,
 		gid:     fs.Config.GID,
 	}
-	_, rootEntry := fc.FileEntry(nil, fuseops.RootInodeID)
+	rootEntry := fc.FileEntry(nil, fuseops.RootInodeID)
 	rootEntry.Attr.Mode = os.FileMode(0755) | os.ModeDir
 
 	return fc
 }
 
 func (fc *FileContainer) Count() int {
-	return len(fc.fileEntries)
+	return len(fc.FileEntries)
 }
 
 func (fc *FileContainer) FindByInode(inode fuseops.InodeID) *FileEntry {
-	return fc.fileEntries[inode]
+	return fc.FileEntries[inode]
 }
-
-// GID specific functions
-func (fc *FileContainer) FindByGID(gid string) (fuseops.InodeID, *FileEntry) {
-	for inode, v := range fc.fileEntries {
-		if v.File != nil && v.File.ID() == gid {
-			return inode, v
+func (fc *FileContainer) FindByID(id string) *FileEntry {
+	for _, v := range fc.FileEntries {
+		if v.File == nil && id == "" {
+			return v
 		}
-	}
-	return 0, nil
-}
-
-func (fc *FileContainer) LookupByGID(parentGID string, name string) (fuseops.InodeID, *FileEntry) {
-
-	for inode, entry := range fc.fileEntries {
-		if entry.HasParentGID(parentGID) && entry.Name == name {
-			return inode, entry
+		if v.File != nil && v.File.ID == id {
+			return v
 		}
 	}
-	return 0, nil
-
+	return nil
 }
-func (fc *FileContainer) Lookup(parent *FileEntry, name string) (fuseops.InodeID, *FileEntry) {
-	for inode, entry := range fc.fileEntries {
+
+// Try not to use this
+func (fc *FileContainer) Lookup(parent *FileEntry, name string) *FileEntry {
+	for _, entry := range fc.FileEntries {
 		if entry.HasParent(parent) && entry.Name == name {
-			return inode, entry
+			return entry
 		}
 	}
-	return 0, nil
+	return nil
 }
 
-func (fc *FileContainer) ListByParent(parent *FileEntry) map[fuseops.InodeID]*FileEntry {
-	ret := map[fuseops.InodeID]*FileEntry{}
-	for inode, entry := range fc.fileEntries {
+func (fc *FileContainer) ListByParent(parent *FileEntry) []*FileEntry {
+	ret := []*FileEntry{}
+	for _, entry := range fc.FileEntries {
 		if entry.HasParent(parent) {
-			ret[inode] = entry
+			ret = append(ret, entry)
 		}
 	}
 	return ret
 
 }
 
-func (fc *FileContainer) CreateFile(parentFile *FileEntry, name string, isDir bool) (fuseops.InodeID, *FileEntry, error) {
+func (fc *FileContainer) CreateFile(parentFile *FileEntry, name string, isDir bool) (*FileEntry, error) {
 
-	newGFile := &drive.File{
-		Parents: []string{parentFile.File.ID()},
-		Name:    name,
-	}
-	if isDir {
-		newGFile.MimeType = "application/vnd.google-apps.folder"
-	}
-	// Could be transformed to CreateFile in continer
-	createdGFile, err := fc.fs.Client.Files.Create(newGFile).Fields(fileFields).Do()
+	createdFile, err := fc.fs.Service.Create(parentFile.File, name, isDir)
 	if err != nil {
-		return 0, nil, fuse.EINVAL
+		return nil, err
 	}
-	inode, entry := fc.FileEntry(createdGFile) // New Entry added // Or Return same?
+	entry := fc.FileEntry(createdFile) // New Entry added // Or Return same?
 
-	return inode, entry, nil
+	return entry, nil
 }
 
 func (fc *FileContainer) DeleteFile(entry *FileEntry) error {
-	err := fc.fs.Client.Files.Delete(entry.File.ID()).Do()
+	err := fc.fs.Service.Delete(entry.File)
 	if err != nil {
-		return fuse.EIO
+		return fuse.EINVAL
 	}
-
 	fc.RemoveEntry(entry)
 	return nil
 }
@@ -120,7 +102,7 @@ func (fc *FileContainer) DeleteFile(entry *FileEntry) error {
 //////////////
 
 //Return or create inode // Pass name maybe?
-func (fc *FileContainer) FileEntry(gfile *drive.File, inodeOps ...fuseops.InodeID) (fuseops.InodeID, *FileEntry) {
+func (fc *FileContainer) FileEntry(file *File, inodeOps ...fuseops.InodeID) *FileEntry {
 
 	fc.inodeMU.Lock()
 	defer fc.inodeMU.Unlock()
@@ -128,30 +110,31 @@ func (fc *FileContainer) FileEntry(gfile *drive.File, inodeOps ...fuseops.InodeI
 	var inode fuseops.InodeID
 	if len(inodeOps) > 0 {
 		inode = inodeOps[0]
-		if fe, ok := fc.fileEntries[inode]; ok {
-			return inode, fe
+		if fe, ok := fc.FileEntries[inode]; ok {
+			return fe
 		}
 	} else { // generate new inode
 		// Max Inode Number
-		for inode = 2; inode < 99999; inode++ {
-			_, ok := fc.fileEntries[inode]
+		for inode = 2; inode < math.MaxUint64; inode++ {
+			_, ok := fc.FileEntries[inode]
 			if !ok {
 				break
 			}
 		}
 	}
 
+	// Name solver
 	name := ""
-	if gfile != nil {
-		name = gfile.Name
+	if file != nil {
+		name = file.Name
 		count := 1
 		nameParts := strings.Split(name, ".")
 		for {
 			// We find if we have a GFile in same parent with same name
 			var entry *FileEntry
 			// Only Place requireing a GID
-			for _, p := range gfile.Parents {
-				_, entry = fc.LookupByGID(p, name)
+			for _, p := range file.Parents {
+				entry = fc.LookupByID(p, name)
 				if entry != nil {
 					break
 				}
@@ -165,39 +148,36 @@ func (fc *FileContainer) FileEntry(gfile *drive.File, inodeOps ...fuseops.InodeI
 			} else {
 				name = fmt.Sprintf("%s(%d)", nameParts[0], count)
 			}
-			log.Printf("Conflicting name generated new '%s' as '%s'", gfile.Name, name)
+			log.Printf("Conflicting name generated new '%s' as '%s'", file.Name, name)
 		}
 	}
 
 	fe := &FileEntry{
-		//Inode: inode,
-		//container: fc,
-		Name: name,
-		//children:  []*FileEntry{},
-		Attr: fuseops.InodeAttributes{
-			Uid: fc.uid,
-			Gid: fc.gid,
-		},
+		Inode: inode,
+		Name:  name,
 	}
-	fe.SetFile(&GFile{gfile}, fc.uid, fc.gid)
-	fc.fileEntries[inode] = fe
+	// Temp gfile?
+	if file != nil {
+		fe.SetFile(file, fc.uid, fc.gid)
+	}
+	fc.FileEntries[inode] = fe
 
-	return inode, fe
+	return fe
 }
 
 func (fc *FileContainer) SetEntry(inode fuseops.InodeID, entry *FileEntry) {
-	fc.fileEntries[inode] = entry
+	fc.FileEntries[inode] = entry
 }
 
 // RemoveEntry remove file entry
 func (fc *FileContainer) RemoveEntry(entry *FileEntry) {
 	var inode fuseops.InodeID
-	for k, e := range fc.fileEntries {
+	for k, e := range fc.FileEntries {
 		if e == entry {
 			inode = k
 		}
 	}
-	delete(fc.fileEntries, inode)
+	delete(fc.FileEntries, inode)
 }
 
 func (fc *FileContainer) Sync(fe *FileEntry) (err error) {
@@ -205,12 +185,13 @@ func (fc *FileContainer) Sync(fe *FileEntry) (err error) {
 		return
 	}
 	fe.tempFile.Sync()
-	fe.tempFile.Seek(0, io.SeekStart)
+	fe.tempFile.Seek(0, io.SeekStart) // Depends??, for reading?
 
 	upFile, err := fc.fs.Service.Upload(fe.tempFile, fe.File)
 	if err != nil {
-		return
+		return err
 	}
+	log.Println("Uploaded file size:", upFile.Size)
 	fe.SetFile(upFile, fc.uid, fc.gid) // update local GFile entry
 	return
 
@@ -221,41 +202,60 @@ func (fc *FileContainer) ClearCache(fe *FileEntry) (err error) {
 	if fe.tempFile == nil {
 		return
 	}
-	fe.tempFile.Close()
+	fe.tempFile.RealClose()
 	os.Remove(fe.tempFile.Name())
 	fe.tempFile = nil
 	return
 }
 
 // Cache download GDrive file to a temporary local file or return already created file
-func (fc *FileContainer) Cache(fe *FileEntry) *os.File {
+func (fc *FileContainer) Cache(fe *FileEntry) *fileWrapper {
 	if fe.tempFile != nil {
 		return fe.tempFile
 	}
 	var err error
 
 	// Local copy
-	fe.tempFile, err = ioutil.TempFile(os.TempDir(), "gdfs") // TODO: const this elsewhere
+	localFile, err := ioutil.TempFile(os.TempDir(), "gdfs") // TODO: const this elsewhere
 	if err != nil {
-		log.Println("Error creating temp file")
 		return nil
 	}
+	fe.tempFile = &fileWrapper{localFile}
+
 	err = fc.fs.Service.DownloadTo(fe.tempFile, fe.File)
-	if err != nil {
-		return nil
-	}
+	// ignore download since can be a bogus file, for certain file systems
+	//if err != nil { // Ignore this error
+	//    return nil
+	//}
 	fe.tempFile.Seek(0, io.SeekStart)
 	return fe.tempFile
 
 }
 
+// Truncate truncates localFile to 0 bytes
 func (fc *FileContainer) Truncate(fe *FileEntry) (err error) { // DriverTruncate
 	// Delete and create another on truncate 0
-	newFile, err := fc.fs.Service.Truncate(fe.File)
 
+	//   newFile, err := fc.fs.Service.Truncate(fe.File)
+	//	if err != nil {
+	//		return fuse.EINVAL
+	//	}
+	localFile, err := ioutil.TempFile(os.TempDir(), "gdfs") // TODO: const this elsewhere
 	if err != nil {
-		return fuse.EINVAL
+		return err
 	}
-	fe.SetFile(newFile, fc.uid, fc.gid) // Set new file
+	fe.tempFile = &fileWrapper{localFile}
+	//fc.Sync(fe) // Basically upload empty file
+
 	return
 }
+
+// LookupByID lookup by remote ID
+func (fc *FileContainer) LookupByID(parentID string, name string) *FileEntry {
+	for _, entry := range fc.FileEntries {
+		if entry.HasParentID(parentID) && entry.Name == name {
+			return entry
+		}
+	}
+	return nil
+}

+ 39 - 43
internal/fs/basefs/file_entry.go

@@ -8,68 +8,64 @@ import (
 
 //FileEntry entry to handle files
 type FileEntry struct {
-	//Inode fuseops.InodeID
-	GID      string // google driveID
-	File     File
+	Inode    fuseops.InodeID         // Inode
+	File     *File                   // Remote file information
 	Name     string                  // local name
 	Attr     fuseops.InodeAttributes // Cached attributes
-	tempFile *os.File                // Cached file
+	tempFile *fileWrapper            // Cached file
 }
 
-// Why?
-func (fe *FileEntry) HasParent(parent *FileEntry) bool {
-
-	// Exceptional case
-	/*if fe.Inode == fuseops.RootInodeID {
-		return false
-	}*/
-	if parent.GID == "" && fe.File == nil && len(fe.File.Parents()) == 0 { // We are looking in root
-		return true
+// SetFile update attributes and set drive.File
+func (fe *FileEntry) SetFile(file *File, uid, gid uint32) { // Should remove from here maybe?
+	fe.File = file
+	fe.Attr = fuseops.InodeAttributes{
+		Size:   fe.File.Size,
+		Crtime: file.CreatedTime,
+		Ctime:  file.CreatedTime,
+		Mtime:  file.ModifiedTime,
+		Atime:  file.AccessedTime,
+		Mode:   file.Mode,
+		Uid:    uid,
+		Gid:    gid,
 	}
+}
 
-	if fe.File == nil { // Case gid is not empty and GFile is nil
+// IsDir returns true if entry is a directory:w
+func (fe *FileEntry) IsDir() bool {
+	return fe.Attr.Mode&os.ModeDir == os.ModeDir
+}
+
+// HasParentID check parent by cloud ID
+func (fe *FileEntry) HasParentID(parentID string) bool {
+	// Exceptional case
+	if fe.Inode == fuseops.RootInodeID {
 		return false
 	}
-	for _, pgid := range fe.File.Parents() {
-		if pgid == parent.GID {
+	if parentID == "" {
+		if fe.File == nil || len(fe.File.Parents) == 0 { // We are looking in root
 			return true
 		}
-	}
-	return false
-}
-func (fe *FileEntry) HasParentGID(parentGID string) bool {
-
-	// Exceptional case
-	/*if fe.Inode == fuseops.RootInodeID {
 		return false
-	}*/
-	if parentGID == "" && fe.File == nil && len(fe.File.Parents()) == 0 { // We are looking in root
-		return true
 	}
 	if fe.File == nil { // Case gid is not empty and GFile is null
 		return false
 	}
-	for _, pgid := range fe.File.Parents() {
-		if pgid == parentGID {
+	for _, pgid := range fe.File.Parents {
+		if pgid == parentID {
 			return true
 		}
 	}
 	return false
 }
 
-// SetGFile update attributes and set drive.File
-func (fe *FileEntry) SetFile(file File, uid, gid uint32) { // Should remove from here maybe?
-	fe.File = file
-
-	// GetAttribute from GFile somehow
-	// Create Attribute
-	fe.Attr = file.Attr()
-	//fe.Attr.Uid = fe.container.uid
-	//fe.Attr.Gid = fe.container.gid
-}
-
-// Sync cached , upload to gdrive
-// IsDir returns true if entry is a directory:w
-func (fe *FileEntry) IsDir() bool {
-	return fe.Attr.Mode&os.ModeDir == os.ModeDir
+// HasParent check Parent by entry
+func (fe *FileEntry) HasParent(parent *FileEntry) bool {
+	// Exceptional case
+	if fe.Inode == fuseops.RootInodeID {
+		return false
+	}
+	if parent.File == nil {
+		return fe.HasParentID("")
+	}
+	return fe.HasParentID(parent.File.ID)
 }

+ 18 - 0
internal/fs/basefs/file_wrapper.go

@@ -0,0 +1,18 @@
+package basefs
+
+// Unfortunately Dropbox/net/http closes the file after sending (even if it is io.Reader only) and in this case we still need it locally open
+
+import "os"
+
+type fileWrapper struct {
+	*os.File
+}
+
+func (f *fileWrapper) Close() error {
+	//panic("I don't want anyone to close this file")
+	// Ignore closers
+	return nil
+}
+func (f *fileWrapper) RealClose() error {
+	return f.File.Close()
+}

+ 0 - 47
internal/fs/basefs/gfile.go

@@ -1,47 +0,0 @@
-// Tempoarry in basefs since we dont have the service yet
-package basefs
-
-import (
-	"os"
-	"time"
-
-	"github.com/jacobsa/fuse/fuseops"
-	drive "google.golang.org/api/drive/v3"
-)
-
-type GFile struct {
-	*drive.File
-}
-
-func (gf *GFile) ID() string {
-	return gf.Id
-}
-
-func (gf *GFile) Name() string {
-	return gf.File.Name
-}
-
-func (gf *GFile) Parents() []string {
-
-	return gf.File.Parents
-}
-
-func (gf *GFile) Attr() fuseops.InodeAttributes {
-
-	attr := fuseops.InodeAttributes{}
-	attr.Nlink = 1
-	//attr.Uid = fe.container.uid
-	//attr.Gid = fe.container.gid
-
-	attr.Mode = os.FileMode(0644) // default
-	//if gfile != nil {
-	attr.Size = uint64(gf.File.Size)
-	attr.Crtime, _ = time.Parse(time.RFC3339, gf.File.CreatedTime)
-	attr.Ctime = attr.Crtime // Set CTime to created, although it is change inode metadata
-	attr.Mtime, _ = time.Parse(time.RFC3339, gf.File.ModifiedTime)
-	attr.Atime = attr.Mtime // Set access time to modified, not sure if gdrive has access time
-	if gf.MimeType == "application/vnd.google-apps.folder" {
-		attr.Mode = os.FileMode(0755) | os.ModeDir
-	}
-	return attr
-}

+ 7 - 3
internal/fs/basefs/service.go

@@ -3,7 +3,11 @@ package basefs
 import "io"
 
 type Service interface {
-	Truncate(file File) (File, error)
-	Upload(reader io.Reader, file File) (File, error)
-	DownloadTo(w io.Writer, file File) error
+	ListAll() ([]*File, error)
+	Create(parent *File, name string, isDir bool) (*File, error)
+	//Truncate(file *File) (*File, error)
+	Upload(reader io.Reader, file *File) (*File, error)
+	DownloadTo(w io.Writer, file *File) error
+	Move(file *File, newParent *File, name string) (*File, error)
+	Delete(file *File) error
 }

+ 61 - 0
internal/fs/dropboxfs/client.go

@@ -0,0 +1,61 @@
+package dropboxfs
+
+import (
+	"fmt"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+	"golang.org/x/oauth2"
+)
+
+func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
+	authURL := config.AuthCodeURL("state-token")
+
+	fmt.Printf(
+		`Go to the following link in your browser: 
+----------------------------------------------------------------------------------------------
+%v
+----------------------------------------------------------------------------------------------
+
+type the authorization code: `, authURL)
+
+	var code string
+	if _, err := fmt.Scan(&code); err != nil {
+		log.Fatalf("Unable to read authorization code %v", err)
+	}
+
+	tok, err := config.Exchange(oauth2.NoContext, code)
+	if err != nil {
+		log.Fatalf("Unable to retrieve token from web: %v", err)
+	}
+
+	return tok
+}
+
+func (fs *DropboxFS) initClient() dropbox.Config {
+
+	err := core.ParseConfig(fs.Config.Source, fs.serviceConfig)
+	if err != nil {
+		log.Fatalf("Unable to read <source>: %v", err)
+	}
+
+	config := &oauth2.Config{
+		ClientID:     fs.serviceConfig.ClientSecret.ClientID,
+		ClientSecret: fs.serviceConfig.ClientSecret.ClientSecret,
+		RedirectURL:  "",
+		Scopes:       []string{},
+		Endpoint: oauth2.Endpoint{
+			AuthURL:  "https://www.dropbox.com/1/oauth2/authorize",
+			TokenURL: "https://api.dropbox.com/1/oauth2/token",
+		},
+	}
+	if fs.serviceConfig.Auth == nil {
+		tok := getTokenFromWeb(config)
+		fs.serviceConfig.Auth = tok
+		core.SaveConfig(fs.Config.Source, fs.serviceConfig)
+	}
+	dbconfig := dropbox.Config{Token: fs.serviceConfig.Auth.AccessToken}
+
+	return dbconfig
+}

+ 13 - 0
internal/fs/dropboxfs/config.go

@@ -0,0 +1,13 @@
+package dropboxfs
+
+import "golang.org/x/oauth2"
+
+//Silimar to gdrive
+type Config struct {
+	ClientSecret struct {
+		ClientID     string `json:"client_id" yaml:"client_id"`
+		ClientSecret string `json:"client_secret" yaml:"client_secret"`
+	} `json:"client_secret" yaml:"client_secret"`
+
+	Auth *oauth2.Token `json:"auth" yaml:"auth"`
+}

+ 18 - 626
internal/fs/dropboxfs/dropboxfs.go

@@ -1,651 +1,43 @@
-// gdrivemount implements a google drive fuse driver
 package dropboxfs
 
 import (
-	"io"
-	"os"
-	"sync"
-	"syscall"
 	"time"
 
 	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
 	"dev.hexasoftware.com/hxs/prettylog"
-
-	"golang.org/x/net/context"
-
-	drive "google.golang.org/api/drive/v3"
-	"google.golang.org/api/googleapi"
-
-	"github.com/jacobsa/fuse"
-	"github.com/jacobsa/fuse/fuseops"
-	"github.com/jacobsa/fuse/fuseutil"
-	"github.com/stacktic/dropbox"
 )
 
 var (
-	log = prettylog.New("gdrivefs")
+	log = prettylog.New("dropboxfs")
 )
 
-type Handle struct {
-	ID fuseops.HandleID
-	//entry        *FileEntry
-	uploadOnDone bool
-	// Handling for dir
-	entries []fuseutil.Dirent
-}
-
-// GDriveFS
-type GDriveFS struct {
-	fuseutil.NotImplementedFileSystem // Defaults
-
-	config *core.Config //core   *core.Core // Core Config instead?
-	//serviceConfig *Config
-	client *dropbox.Dropbox
-	//root   *FileEntry // hiearchy reference
-	root *FileContainer
-
-	fileHandles map[fuseops.HandleID]*Handle
-	//fileEntries map[fuseops.InodeID]*FileEntry
-
-	nextRefresh time.Time
-
-	handleMU *sync.Mutex
-	//fileMap map[string]
-	// Map IDS with FileEntries
+type DropboxFS struct {
+	*basefs.BaseFS
+	serviceConfig *Config
+	nextRefresh   time.Time
 }
 
-func New(core *core.Core) core.Driver {
+func New(core *core.Core) core.DriverFS {
 
-	fs := &GDriveFS{
-		config:        &core.Config,
-		serviceConfig: &Config{},
-		fileHandles:   map[fuseops.HandleID]*Handle{},
-		handleMU:      &sync.Mutex{},
-	}
-	fs.initClient() // Init Oauth2 client
-	fs.root = NewFileContainer(fs)
-	fs.root.uid = core.Config.UID
-	fs.root.gid = core.Config.GID
+	fs := &DropboxFS{basefs.New(core), &Config{}, time.Now()}
+	client := fs.initClient() // Init Oauth2 client
+	//client.Verbose = true
 
-	//fs.root = rootEntry
-
-	// Temporary entry
-	entry := fs.root.FileEntry(&drive.File{Id: "0", Name: "Loading..."}, 9999)
-	entry.Attr.Mode = os.FileMode(0)
+	// Set necesary service
+	fs.BaseFS.Service = &Service{client}
 
 	return fs
 }
 
-// Async
-func (fs *GDriveFS) Start() {
+func (fs *DropboxFS) Start() {
+	Service := fs.Service.(*Service)
+	// Fill root container and do changes
 	go func() {
-		fs.Refresh() // First load
-
-		// Change reader loop
-		startPageTokenRes, err := fs.client.Changes.GetStartPageToken().Do()
-		if err != nil {
-			log.Println("GDrive err", err)
-		}
-		savedStartPageToken := startPageTokenRes.StartPageToken
+		fs.Refresh()
 		for {
-			pageToken := savedStartPageToken
-			for pageToken != "" {
-				changesRes, err := fs.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
-				if err != nil {
-					log.Println("Err fetching changes", err)
-					break
-				}
-				//log.Println("Changes:", len(changesRes.Changes))
-				for _, c := range changesRes.Changes {
-					entry := fs.root.FindByGID(c.FileId)
-					if c.Removed {
-						if entry == nil {
-							continue
-						} else {
-							fs.root.RemoveEntry(entry)
-						}
-						continue
-					}
-
-					if entry != nil {
-						entry.SetGFile(c.File)
-					} else {
-						//Create new one
-						fs.root.FileEntry(c.File) // Creating new one
-					}
-				}
-				if changesRes.NewStartPageToken != "" {
-					savedStartPageToken = changesRes.NewStartPageToken
-				}
-				pageToken = changesRes.NextPageToken
-			}
-
-			time.Sleep(fs.config.RefreshTime)
+			Service.Changes()
+			time.Sleep(fs.Config.RefreshTime)
 		}
 	}()
 }
-
-////////////////////////////////////////////////////////
-// TOOLS & HELPERS
-////////////////////////////////////////////////////////
-
-func (fs *GDriveFS) createHandle() *Handle {
-	// Lock here instead
-	fs.handleMU.Lock()
-	defer fs.handleMU.Unlock()
-
-	var handleID fuseops.HandleID
-
-	for handleID = 1; handleID < 99999; handleID++ {
-		_, ok := fs.fileHandles[handleID]
-		if !ok {
-			break
-		}
-	}
-
-	handle := &Handle{ID: handleID}
-	fs.fileHandles[handleID] = handle
-
-	return handle
-}
-
-const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
-const gdFields = googleapi.Field("files(" + fileFields + ")")
-
-// FULL Refresh service files
-func (fs *GDriveFS) Refresh() {
-	fs.nextRefresh = time.Now().Add(1 * time.Minute)
-
-	fileList := []*drive.File{}
-	fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
-
-	r, err := fs.client.Files.List().
-		OrderBy("createdTime").
-		PageSize(1000).
-		SupportsTeamDrives(true).
-		IncludeTeamDriveItems(true).
-		Fields(googleapi.Field("nextPageToken"), gdFields).
-		Do()
-	if err != nil {
-		// Sometimes gdrive returns error 500 randomly
-		log.Println("GDrive ERR:", err)
-		fs.Refresh() // retry
-		return
-	}
-	fileList = append(fileList, r.Files...)
-
-	// Rest of the pages
-	for r.NextPageToken != "" {
-		r, err = fs.client.Files.List().
-			OrderBy("createdTime").
-			PageToken(r.NextPageToken).
-			Fields(googleapi.Field("nextPageToken"), gdFields).
-			Do()
-		if err != nil {
-			log.Println("GDrive ERR:", err)
-			fs.Refresh() // retry // Same as above
-			return
-		}
-		fileList = append(fileList, r.Files...)
-	}
-	log.Println("Total entries:", len(fileList))
-
-	// Cache ID for faster retrieval, might not be necessary
-	for _, f := range fileList {
-		fileMap[f.Id] = f
-	}
-
-	if err != nil || r == nil {
-		log.Println("Unable to retrieve files", err)
-		return
-	}
-
-	// Create clean fileList
-	root := NewFileContainer(fs)
-	var appendFile func(gfile *drive.File)
-	appendFile = func(gfile *drive.File) {
-		for _, pID := range gfile.Parents {
-			parentFile, ok := fileMap[pID]
-			if !ok {
-				parentFile, err = fs.client.Files.Get(pID).Do()
-				if err != nil {
-					log.Println("Error fetching single file:", err)
-				}
-				fileMap[parentFile.Id] = parentFile
-			}
-			appendFile(parentFile) // Recurse
-		}
-
-		// Find existing entry
-		entry := fs.root.FindByGID(gfile.Id)
-		// Store for later add
-		if entry == nil {
-			entry = fs.root.FileEntry(gfile) // Add New and retrieve
-		}
-		root.AddEntry(entry)
-		// add File
-	}
-
-	for _, f := range fileList { // Ordered
-		appendFile(f) // Check parent first
-	}
-
-	log.Println("Refresh done, update root")
-	fs.root = root
-	//fs.root.children = root.children
-
-	log.Println("File count:", len(root.fileEntries))
-}
-
-///////////////////////////////
-// Fuse operations
-////////////
-
-// OpenDir return nil error allows open dir
-func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
-
-	entry := fs.root.FindByInode(op.Inode)
-	if entry == nil {
-		return fuse.ENOENT
-	}
-
-	handle := fs.createHandle()
-	handle.entry = entry
-	op.Handle = handle.ID
-
-	return // No error allow, dir open
-}
-
-// ReadDir lists files into readdirop
-func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
-	fh, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		log.Fatal("Handle does not exists")
-	}
-
-	if op.Offset == 0 { // Rebuild/rewind dir list
-		fh.entries = []fuseutil.Dirent{}
-		children := fs.root.ListByParentGID(fh.entry.GID)
-
-		for i, v := range children {
-			fusetype := fuseutil.DT_File
-			if v.IsDir() {
-				fusetype = fuseutil.DT_Directory
-			}
-			dirEnt := fuseutil.Dirent{
-				Inode:  v.Inode,
-				Name:   v.Name,
-				Type:   fusetype,
-				Offset: fuseops.DirOffset(i) + 1,
-			}
-			//	written += fuseutil.WriteDirent(fh.buf[written:], dirEnt)
-			fh.entries = append(fh.entries, dirEnt)
-		}
-	}
-
-	index := int(op.Offset)
-	if index > len(fh.entries) {
-		return fuse.EINVAL
-	}
-	if index > 0 {
-		index++
-	}
-	for i := index; i < len(fh.entries); i++ {
-		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
-		if n == 0 {
-			break
-		}
-		op.BytesRead += n
-	}
-	return
-}
-
-// SetInodeAttributes Not sure what attributes gdrive support we just leave this blank for now
-func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
-
-	// Hack to truncate file?
-
-	if op.Size != nil {
-		f := fs.root.FindByInode(op.Inode)
-
-		if *op.Size != 0 { // We only allow truncate to 0
-			return fuse.ENOSYS
-		}
-		// Delete and create another on truncate 0
-		err = fs.client.Files.Delete(f.GFile.Id).Do() // XXX: Careful on this
-		createdFile, err := fs.client.Files.Create(&drive.File{Parents: f.GFile.Parents, Name: f.GFile.Name}).Fields(fileFields).Do()
-		if err != nil {
-			return fuse.EINVAL
-		}
-		f.SetGFile(createdFile) // Set new file
-	}
-
-	return
-}
-
-//GetInodeAttributes return attributes
-func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
-
-	f := fs.root.FindByInode(op.Inode)
-	if f == nil {
-		return fuse.ENOENT
-	}
-	op.Attributes = f.Attr
-	op.AttributesExpiration = time.Now().Add(time.Minute)
-
-	return
-}
-
-// ReleaseDirHandle deletes file handle entry
-func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
-	delete(fs.fileHandles, op.Handle)
-	return
-}
-
-// LookUpInode based on Parent and Name we return a self cached inode
-func (fs *GDriveFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent) // true means transverse all
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-
-	entry := fs.root.LookupByGID(parentFile.GID, op.Name)
-
-	if entry == nil {
-		return fuse.ENOENT
-	}
-
-	// Transverse only local
-
-	now := time.Now()
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: now.Add(time.Second),
-		EntryExpiration:      now.Add(time.Second),
-	}
-	return
-}
-
-// StatFS basically allows StatFS to run
-/*func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
-	return
-}*/
-
-// ForgetInode allows to forgetInode
-func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
-	return
-}
-
-// GetXAttr special attributes
-func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
-	return
-}
-
-//////////////////////////////////////////////////////////////////////////
-// File OPS
-//////////////////////////////////////////////////////////////////////////
-
-// OpenFile creates a temporary handle to be handled on read or write
-func (fs *GDriveFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
-	f := fs.root.FindByInode(op.Inode) // might not exists
-
-	// Generate new handle
-	handle := fs.createHandle()
-	handle.entry = f
-
-	op.Handle = handle.ID
-	op.UseDirectIO = true
-
-	return
-}
-
-// ReadFile  if the first time we download the google drive file into a local temporary file
-func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
-	handle := fs.fileHandles[op.Handle]
-
-	localFile := handle.entry.Cache()
-	op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
-	if err == io.EOF { // fuse does not expect a EOF
-		err = nil
-	}
-
-	return
-}
-
-// CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
-func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	// Only write on child folders
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	existsFile := fs.root.LookupByGID(parentFile.GID, op.Name)
-	//existsFile := parentFile.FindByName(op.Name, false)
-	if existsFile != nil {
-		return fuse.EEXIST
-	}
-
-	newGFile := &drive.File{
-		Parents: []string{parentFile.GFile.Id},
-		Name:    op.Name,
-	}
-
-	createdGFile, err := fs.client.Files.Create(newGFile).Fields(fileFields).Do()
-	if err != nil {
-		err = fuse.EINVAL
-		return
-	}
-	entry := fs.root.FileEntry(createdGFile) // New Entry added // Or Return same?
-
-	// Associate a temp file to a new handle
-	// Local copy
-	// Lock
-	handle := fs.createHandle()
-	handle.entry = entry
-	handle.uploadOnDone = true
-	//
-	op.Handle = handle.ID
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: time.Now().Add(time.Minute),
-		EntryExpiration:      time.Now().Add(time.Minute),
-	}
-	op.Mode = entry.Attr.Mode
-
-	return
-}
-
-// WriteFile as ReadFile it creates a temporary file on first read
-// Maybe the ReadFile should be called here aswell to cache current contents since we are using writeAt
-func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
-	handle, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-
-	localFile := handle.entry.Cache()
-	if localFile == nil {
-		return fuse.EINVAL
-	}
-
-	_, err = localFile.WriteAt(op.Data, op.Offset)
-	if err != nil {
-		err = fuse.EIO
-		return
-	}
-	handle.uploadOnDone = true
-
-	return
-}
-
-// FlushFile just returns no error, maybe upload should be handled here
-func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
-	handle, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-	if handle.entry.tempFile == nil {
-		return
-	}
-	if handle.uploadOnDone { // or if content changed basically
-		err = handle.entry.Sync()
-		if err != nil {
-			return fuse.EINVAL
-		}
-	}
-	return
-}
-
-// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
-func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
-	handle := fs.fileHandles[op.Handle]
-
-	handle.entry.ClearCache()
-
-	delete(fs.fileHandles, op.Handle)
-
-	return
-}
-
-// Unlink remove file and remove from local cache entry
-func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
-	parentEntry := fs.root.FindByInode(op.Parent)
-	if parentEntry == nil {
-		return fuse.ENOENT
-	}
-	if parentEntry.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	fileEntry := fs.root.LookupByGID(parentEntry.GID, op.Name)
-	//fileEntry := parentEntry.FindByName(op.Name, false)
-	if fileEntry == nil {
-		return fuse.ENOATTR
-	}
-	err = fs.client.Files.Delete(fileEntry.GFile.Id).Do()
-	if err != nil {
-		return fuse.EIO
-	}
-
-	fs.root.RemoveEntry(fileEntry)
-	//parentEntry.RemoveChild(fileEntry)
-
-	return
-}
-
-// MkDir creates a directory on a parent dir
-func (fs *GDriveFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	// Should check existent first too
-	createdGFile, err := fs.client.Files.Create(&drive.File{
-		Parents:  []string{parentFile.GFile.Id},
-		MimeType: "application/vnd.google-apps.folder",
-		Name:     op.Name,
-	}).Fields(fileFields).Do()
-	if err != nil {
-		return fuse.ENOATTR
-	}
-	entry := fs.root.FileEntry(createdGFile)
-	//entry = parentFile.AppendGFile(fi, entry.Inode)
-	//if entry == nil {
-	//		return fuse.EINVAL
-	//	}
-
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: time.Now().Add(time.Minute),
-		EntryExpiration:      time.Now().Add(time.Microsecond),
-	}
-
-	return
-}
-
-// RmDir fuse implementation
-func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	theFile := fs.root.LookupByGID(parentFile.GID, op.Name)
-	//theFile := parentFile.FindByName(op.Name, false)
-
-	err = fs.client.Files.Delete(theFile.GFile.Id).Do()
-	if err != nil {
-		return fuse.ENOTEMPTY
-	}
-	fs.root.RemoveEntry(theFile)
-
-	//parentFile.RemoveChild(theFile)
-
-	// Remove from entry somehow
-
-	return
-}
-
-// Rename fuse implementation
-func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
-	oldParentFile := fs.root.FindByInode(op.OldParent)
-	if oldParentFile == nil {
-		return fuse.ENOENT
-	}
-	newParentFile := fs.root.FindByInode(op.NewParent)
-	if newParentFile == nil {
-		return fuse.ENOENT
-	}
-
-	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	//oldFile := oldParentFile.FindByName(op.OldName, false)
-	oldEntry := fs.root.LookupByGID(oldParentFile.GID, op.OldName)
-
-	// Although GDrive allows duplicate names, there is some issue with inode caching
-	// So we prevent a rename to a file with same name
-	//existsFile := newParentFile.FindByName(op.NewName, false)
-	existsEntry := fs.root.LookupByGID(newParentFile.GID, op.NewName)
-	if existsEntry != nil {
-		return fuse.EEXIST
-	}
-
-	ngFile := &drive.File{
-		Name: op.NewName,
-	}
-
-	updateCall := fs.client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
-	if oldParentFile != newParentFile {
-		updateCall.RemoveParents(oldParentFile.GID)
-		updateCall.AddParents(newParentFile.GID)
-	}
-	updatedFile, err := updateCall.Do()
-
-	oldEntry.SetGFile(updatedFile)
-
-	//oldParentFile.RemoveChild(oldFile)
-	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
-
-	return
-
-}

+ 0 - 68
internal/fs/dropboxfs/research/main.go

@@ -1,68 +0,0 @@
-package main
-
-import (
-	"log"
-
-	"github.com/stacktic/dropbox"
-)
-
-func main() {
-	var err error
-	var db *dropbox.Dropbox
-
-	clientid := "ycsj9tcm7bqdutw"
-	clientsecret := "rtlg6f5jfgau80s"
-	//token := "Iw62s3Vg5sAAAAAAAAAuPeAaIstFQmi3iJ659RMTlaL_xcV7FPnYMMwtNdMNEII5"
-	token := "Iw62s3Vg5sAAAAAAAAAuP-725bDtvizWhH-OhxyvxjSgeNXYxIrL44siRqpw4ZNJ"
-
-	// 1. Create a new dropbox object.
-	db = dropbox.NewDropbox()
-
-	// 2. Provide your clientid and clientsecret (see prerequisite).
-	db.SetAppInfo(clientid, clientsecret)
-
-	// 3. Provide the user token.
-	// This method will ask the user to visit an URL and paste the generated code.
-	/*if err = db.Auth(); err != nil {
-		fmt.Println(err)
-		return
-	}
-	token := db.AccessToken() // You can now retrieve the token if you want.*/
-	log.Println("Tok:", token)
-	db.SetAccessToken(token)
-
-	// 4. Send your commands.
-	// In this example, you will create a new folder named "demo".
-	log.Println("Commands")
-	acct, err := db.GetAccountInfo()
-	errCheck(err)
-	log.Println(acct.Country)
-	log.Println(acct.DisplayName)
-	log.Println(acct.QuotaInfo.Normal)
-
-	entry, err := db.Metadata("/", true, false, "", "", 0)
-	errCheck(err)
-	log.Println("Entries:", len(entry.Contents))
-
-	for _, e := range entry.Contents {
-		log.Println("Entry:", e.Hash, e.Path, e.Bytes)
-	}
-
-	dm := db.NewDatastoreManager()
-	ds, err := dm.ListDatastores()
-	errCheck(err)
-	log.Println("Data stores:", ds)
-
-	//	folder := "demo"
-	/*if _, err = db.CreateFolder(folder); err != nil {
-		fmt.Printf("Error creating folder %s: %s\n", folder, err)
-	} else {
-		fmt.Printf("Folder %s successfully created\n", folder)
-	}*/
-}
-
-func errCheck(err error) {
-	if err != nil {
-		log.Fatal(err)
-	}
-}

+ 205 - 0
internal/fs/dropboxfs/service.go

@@ -0,0 +1,205 @@
+package dropboxfs
+
+import (
+	"io"
+	"os"
+	"strings"
+	"time"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+	dbfiles "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
+)
+
+// Service basefs Service implementation
+type Service struct {
+	dbconfig dropbox.Config
+}
+
+func (s *Service) Changes() error {
+	log.Println("Checking changes")
+	fileService := dbfiles.New(s.dbconfig)
+	if fileService == nil {
+		log.Println("File service is nill")
+		return nil
+	}
+
+	res, err := fileService.ListFolderLongpoll(dbfiles.NewListFolderLongpollArg(""))
+	if err != nil {
+		log.Println("Err in longpoll", err)
+		return nil
+	}
+	log.Println("Has Changes:", res.Changes)
+	log.Println("Backoff:", res.Backoff)
+
+	return nil
+}
+
+// ListAll implementation
+func (s *Service) ListAll() ([]*basefs.File, error) {
+	fileService := dbfiles.New(s.dbconfig)
+	// Some how list all files from Dropbox
+	log.Println("Loading meta data")
+	ret := []*basefs.File{}
+	var err error
+	var res *dbfiles.ListFolderResult
+
+	res, err = fileService.ListFolder(&dbfiles.ListFolderArg{Recursive: true, Path: "/test", IncludeDeleted: false, IncludeMediaInfo: false})
+	if err != nil {
+		log.Println("Error listing:", err)
+		return nil, err
+	}
+	log.Println("Loaded: res.Entries", len(res.Entries))
+	for _, e := range res.Entries {
+		ret = append(ret, File(e))
+	}
+
+	for res.HasMore {
+		res, err = fileService.ListFolderContinue(&dbfiles.ListFolderContinueArg{Cursor: res.Cursor})
+		log.Println("Loaded: res.Entries", len(res.Entries))
+		for _, e := range res.Entries {
+			ret = append(ret, File(e))
+		}
+	}
+
+	return ret, nil
+}
+
+// Create file implementation
+func (s *Service) Create(parent *basefs.File, name string, isDir bool) (*basefs.File, error) {
+	fileService := dbfiles.New(s.dbconfig)
+
+	if isDir {
+		data, err := fileService.CreateFolder(&dbfiles.CreateFolderArg{
+			Autorename: false,
+			Path:       parent.ID + "/" + name,
+		})
+		if err != nil {
+			return nil, err
+		}
+		return File(data), nil
+	}
+	newPath := parent.ID + "/" + name
+	// For file we create Local entry but do nothing, since dropbox does not upload local empty files
+	metadata := &dbfiles.FileMetadata{}
+
+	metadata.Id = newPath
+	metadata.Name = name
+	metadata.ServerModified = time.Now().UTC()
+	metadata.PathLower = strings.ToLower(newPath)
+
+	return File(metadata), nil
+}
+
+// Upload file implementation
+func (s *Service) Upload(reader io.Reader, file *basefs.File) (*basefs.File, error) {
+	fileService := dbfiles.New(s.dbconfig)
+
+	data, err := fileService.Upload(&dbfiles.CommitInfo{
+		Path:       file.ID, // ???
+		Autorename: false,
+		Mode:       &dbfiles.WriteMode{Tagged: dropbox.Tagged{Tag: dbfiles.WriteModeOverwrite}},
+		//ClientModified: time.Now().UTC(),
+	}, reader.(io.Reader))
+	if err != nil {
+		log.Println("Upload Error:", err)
+		return nil, err
+	}
+
+	return File(data), nil
+}
+
+// DownloadTo implementation
+func (s *Service) DownloadTo(w io.Writer, file *basefs.File) error {
+	fileService := dbfiles.New(s.dbconfig)
+
+	_, content, err := fileService.Download(&dbfiles.DownloadArg{Path: file.ID})
+	if err != nil {
+		return err
+	}
+
+	defer content.Close()
+	io.Copy(w, content)
+
+	return nil
+}
+
+// Move and Rename file implementation
+func (s *Service) Move(file *basefs.File, newParent *basefs.File, name string) (*basefs.File, error) {
+	fileService := dbfiles.New(s.dbconfig)
+
+	res, err := fileService.Move(&dbfiles.RelocationArg{
+		RelocationPath: dbfiles.RelocationPath{
+			FromPath: file.ID,
+			ToPath:   newParent.ID + "/" + name,
+		},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return File(res), nil
+}
+
+// Delete deletes a file entry (including Dir)
+func (s *Service) Delete(file *basefs.File) error {
+	fileService := dbfiles.New(s.dbconfig)
+
+	_, err := fileService.Delete(&dbfiles.DeleteArg{Path: file.ID})
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// File Metadata to File Converter
+func File(metadata dbfiles.IsMetadata) *basefs.File {
+
+	var ID string
+	var name string
+	var modifiedTime time.Time
+	var mode = os.FileMode(0644)
+	var size uint64
+
+	//var parentID string
+
+	switch t := metadata.(type) {
+	case *dbfiles.FileMetadata:
+		ID = t.PathLower
+		name = t.Name
+		modifiedTime = t.ServerModified
+		size = t.Size
+	case *dbfiles.FolderMetadata:
+		ID = t.PathLower
+		name = t.Name
+		mode = os.FileMode(0755) | os.ModeDir
+		modifiedTime = time.Now()
+		//parentID = t.SharedFolderId
+	}
+
+	createdTime := modifiedTime
+
+	//log.Println("ParentID:", parentID)
+
+	pathParts := strings.Split(ID, "/")
+	parentID := strings.Join(pathParts[:len(pathParts)-1], "/")
+	parents := []string{}
+	if parentID != "" {
+		parents = []string{parentID}
+	}
+
+	file := &basefs.File{
+		ID:           ID,
+		Name:         name,
+		Parents:      parents,
+		Size:         size,
+		CreatedTime:  createdTime,
+		ModifiedTime: modifiedTime,
+		AccessedTime: modifiedTime,
+		Mode:         mode,
+	}
+	//log.Println("FileID is:", file.ID)
+
+	return file
+}

+ 27 - 119
internal/fs/gdrivefs/gdrivefs.go

@@ -3,9 +3,6 @@ package gdrivefs
 import (
 	"time"
 
-	drive "google.golang.org/api/drive/v3"
-	"google.golang.org/api/googleapi"
-
 	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
 	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
 	"dev.hexasoftware.com/hxs/prettylog"
@@ -19,149 +16,60 @@ type GDriveFS struct {
 	*basefs.BaseFS
 	serviceConfig *Config
 	nextRefresh   time.Time
+	//client        *drive.Service
 }
 
 func New(core *core.Core) core.DriverFS {
 
-	fs := &GDriveFS{basefs.New(core), &Config{}, time.Now()}
+	fs := &GDriveFS{
+		BaseFS:        basefs.New(core),
+		serviceConfig: &Config{},
+		nextRefresh:   time.Now(),
+	}
 	client := fs.initClient() // Init Oauth2 client
 
-	fs.BaseFS.Client = client // This will be removed
-	fs.BaseFS.Service = &gdriveService{client}
+	//fs.BaseFS.Client = client // This will be removed
+	fs.BaseFS.Service = &Service{client: client}
 
 	return fs
 }
 
+// Start will loop to update File entries
 func (fs *GDriveFS) Start() {
 	go func() {
 		fs.Refresh() // First load
 
-		// Change reader loop
-		startPageTokenRes, err := fs.Client.Changes.GetStartPageToken().Do()
-		if err != nil {
-			log.Println("GDrive err", err)
-		}
-		savedStartPageToken := startPageTokenRes.StartPageToken
 		for {
-			pageToken := savedStartPageToken
-			for pageToken != "" {
-				changesRes, err := fs.Client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
-				if err != nil {
-					log.Println("Err fetching changes", err)
-					break
-				}
-				//log.Println("Changes:", len(changesRes.Changes))
-				for _, c := range changesRes.Changes {
-					_, entry := fs.Root.FindByGID(c.FileId)
-					if c.Removed {
-						if entry == nil {
-							continue
-						} else {
-							fs.Root.RemoveEntry(entry)
-						}
-						continue
-					}
-
-					if entry != nil {
-						entry.SetFile(&basefs.GFile{c.File}, fs.Config.UID, fs.Config.GID)
-					} else {
-						//Create new one
-						fs.Root.FileEntry(c.File) // Creating new one
-					}
-				}
-				if changesRes.NewStartPageToken != "" {
-					savedStartPageToken = changesRes.NewStartPageToken
-				}
-				pageToken = changesRes.NextPageToken
-			}
-
+			fs.CheckForChanges() // Loop
 			time.Sleep(fs.Config.RefreshTime)
 		}
+		// Change reader loop
 	}()
 }
 
-const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
-const gdFields = googleapi.Field("files(" + fileFields + ")")
-
-func (fs *GDriveFS) Refresh() {
+func (fs *GDriveFS) CheckForChanges() {
 
-	fileList := []*drive.File{}
-	fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
+	Service := fs.Service.(*Service) // Our Service
 
-	r, err := fs.Client.Files.List().
-		OrderBy("createdTime").
-		PageSize(1000).
-		SupportsTeamDrives(true).
-		IncludeTeamDriveItems(true).
-		Fields(googleapi.Field("nextPageToken"), gdFields).
-		Do()
+	changes, err := Service.Changes()
 	if err != nil {
-		// Sometimes gdrive returns error 500 randomly
-		log.Println("GDrive ERR:", err)
-		fs.Refresh() // retry
 		return
 	}
-	fileList = append(fileList, r.Files...)
-
-	// Rest of the pages
-	for r.NextPageToken != "" {
-		r, err = fs.Client.Files.List().
-			OrderBy("createdTime").
-			PageToken(r.NextPageToken).
-			Fields(googleapi.Field("nextPageToken"), gdFields).
-			Do()
-		if err != nil {
-			log.Println("GDrive ERR:", err)
-			fs.Refresh() // retry // Same as above
-			return
-		}
-		fileList = append(fileList, r.Files...)
-	}
-	log.Println("Total entries:", len(fileList))
-
-	// Cache ID for faster retrieval, might not be necessary
-	for _, f := range fileList {
-		fileMap[f.Id] = f
-	}
-
-	if err != nil || r == nil {
-		log.Println("Unable to retrieve files", err)
-		return
-	}
-
-	// Create clean fileList
-	root := basefs.NewFileContainer(fs.BaseFS)
-	var appendFile func(gfile *drive.File)
-	appendFile = func(gfile *drive.File) {
-		for _, pID := range gfile.Parents {
-			parentFile, ok := fileMap[pID]
-			if !ok {
-				parentFile, err = fs.Client.Files.Get(pID).Do()
-				if err != nil {
-					log.Println("Error fetching single file:", err)
-				}
-				fileMap[parentFile.Id] = parentFile
+	for _, c := range changes {
+		entry := fs.Root.FindByID(c.FileId)
+		if c.Removed {
+			if entry == nil {
+				continue
+			} else {
+				fs.Root.RemoveEntry(entry)
 			}
-			appendFile(parentFile) // Recurse
+			continue
 		}
-
-		// Find existing entry
-		inode, entry := fs.Root.FindByGID(gfile.Id)
-		// Store for later add
-		if entry == nil {
-			inode, entry = fs.Root.FileEntry(gfile) // Add New and retrieve
+		if entry != nil {
+			entry.SetFile(File(c.File), fs.Config.UID, fs.Config.GID)
+		} else {
+			//Create new one
+			fs.Root.FileEntry(File(c.File)) // Creating new one
 		}
-		root.SetEntry(inode, entry)
-		// add File
-	}
-
-	for _, f := range fileList { // Ordered
-		appendFile(f) // Check parent first
 	}
-
-	log.Println("Refresh done, update root")
-	fs.Root = root
-	//fs.root.children = root.children
-
-	log.Println("File count:", root.Count())
 }

+ 242 - 0
internal/fs/gdrivefs/service.go

@@ -0,0 +1,242 @@
+package gdrivefs
+
+import (
+	"io"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/jacobsa/fuse"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
+
+	drive "google.golang.org/api/drive/v3"
+	"google.golang.org/api/googleapi"
+)
+
+const (
+	fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
+	gdFields   = googleapi.Field("files(" + fileFields + ")")
+)
+
+type Service struct {
+	client              *drive.Service
+	savedStartPageToken string
+}
+
+func (s *Service) Changes() ([]*drive.Change, error) { // Return a list of New file entries
+	if s.savedStartPageToken == "" {
+		startPageTokenRes, err := s.client.Changes.GetStartPageToken().Do()
+		if err != nil {
+			log.Println("GDrive err", err)
+		}
+		s.savedStartPageToken = startPageTokenRes.StartPageToken
+	}
+
+	ret := []*drive.Change{}
+	pageToken := s.savedStartPageToken
+	for pageToken != "" {
+		changesRes, err := s.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
+		if err != nil {
+			log.Println("Err fetching changes", err)
+			break
+		}
+		//log.Println("Changes:", len(changesRes.Changes))
+		for _, c := range changesRes.Changes {
+			ret = append(ret, c) // Convert to our changes
+		}
+		if changesRes.NewStartPageToken != "" {
+			s.savedStartPageToken = changesRes.NewStartPageToken
+		}
+		pageToken = changesRes.NextPageToken
+	}
+	return ret, nil
+}
+
+func (s *Service) ListAll() ([]*basefs.File, error) {
+	fileList := []*drive.File{}
+	// Service list ALL ???
+	fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
+
+	r, err := s.client.Files.List().
+		OrderBy("createdTime").
+		PageSize(1000).
+		SupportsTeamDrives(true).
+		IncludeTeamDriveItems(true).
+		Fields(googleapi.Field("nextPageToken"), gdFields).
+		Do()
+	if err != nil {
+		// Sometimes gdrive returns error 500 randomly
+		log.Println("GDrive ERR:", err)
+		return s.ListAll() // retry
+		//return nil, err
+	}
+
+	fileList = append(fileList, r.Files...)
+
+	// Rest of the pages
+	for r.NextPageToken != "" {
+		r, err = s.client.Files.List().
+			OrderBy("createdTime").
+			PageToken(r.NextPageToken).
+			Fields(googleapi.Field("nextPageToken"), gdFields).
+			Do()
+		if err != nil {
+			log.Println("GDrive ERR:", err)
+			return s.ListAll() // retry
+			//return nil, err
+		}
+		fileList = append(fileList, r.Files...)
+	}
+	log.Println("Total entries:", len(fileList))
+
+	// Cache ID for faster retrieval, might not be necessary
+	for _, f := range fileList { // Temporary lookup Cache
+		fileMap[f.Id] = f
+	}
+
+	// All fetched
+
+	files := []*basefs.File{}
+	// Create clean fileList
+	var appendFile func(gfile *drive.File)
+	appendFile = func(gfile *drive.File) {
+		for _, pID := range gfile.Parents {
+			parentFile, ok := fileMap[pID]
+			if !ok {
+				parentFile, err = s.client.Files.Get(pID).Do()
+				if err != nil {
+					log.Println("Error fetching single file:", err)
+				}
+				fileMap[parentFile.Id] = parentFile
+				appendFile(parentFile) // Recurse
+			}
+		}
+		// Do not append directly
+		files = append(files, File(gfile)) // Add converted file
+	}
+
+	for _, f := range fileList { // Ordered
+		appendFile(f) // Check parent first
+	}
+
+	log.Println("File count:", len(files))
+
+	return files, nil
+
+}
+
+func (s *Service) Create(parent *basefs.File, name string, isDir bool) (*basefs.File, error) {
+	newGFile := &drive.File{
+		Parents: []string{parent.ID},
+		Name:    name,
+	}
+	if isDir {
+		newGFile.MimeType = "application/vnd.google-apps.folder"
+	}
+	// Could be transformed to CreateFile in continer
+	createdGFile, err := s.client.Files.Create(newGFile).Fields(fileFields).Do()
+	if err != nil {
+		return nil, fuse.EINVAL
+	}
+
+	return File(createdGFile), nil
+
+}
+
+func (s *Service) Upload(reader io.Reader, file *basefs.File) (*basefs.File, error) {
+	ngFile := &drive.File{}
+	up := s.client.Files.Update(file.ID, ngFile)
+	upFile, err := up.Media(reader).Fields(fileFields).Do()
+	if err != nil {
+		return nil, err
+	}
+
+	return File(upFile), nil
+}
+
+func (s *Service) DownloadTo(w io.Writer, file *basefs.File) error {
+
+	var res *http.Response
+	var err error
+	// TODO :Place this in service Download
+	gfile := file.Data.(*drive.File)
+	// Export GDocs (Special google doc documents needs to be exported make a config somewhere for this)
+	switch gfile.MimeType { // Make this somewhat optional special case
+	case "application/vnd.google-apps.document":
+		res, err = s.client.Files.Export(gfile.Id, "text/plain").Download()
+	case "application/vnd.google-apps.spreadsheet":
+		res, err = s.client.Files.Export(gfile.Id, "text/csv").Download()
+	default:
+		res, err = s.client.Files.Get(gfile.Id).Download()
+	}
+
+	if err != nil {
+		log.Println("Error from GDrive API", err)
+		return err
+	}
+	defer res.Body.Close()
+	io.Copy(w, res.Body)
+
+	return nil
+}
+
+func (s *Service) Move(file *basefs.File, newParent *basefs.File, name string) (*basefs.File, error) {
+
+	ngFile := &drive.File{
+		Name: name,
+	}
+
+	updateCall := s.client.Files.Update(file.ID, ngFile).Fields(fileFields)
+
+	if !file.HasParent(newParent) {
+		for _, pgid := range file.Parents {
+			updateCall.RemoveParents(pgid) // Remove all parents??
+		}
+		updateCall.AddParents(newParent.ID)
+	}
+	//	}
+	/*if oldParentFile != newParentFile {
+		updateCall.RemoveParents(oldParentFile.GID)
+		updateCall.AddParents(newParentFile.GID)
+	}*/
+	updatedFile, err := updateCall.Do()
+
+	return File(updatedFile), err
+}
+
+func (s *Service) Delete(file *basefs.File) error {
+	err := s.client.Files.Delete(file.ID).Do()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func File(gfile *drive.File) *basefs.File {
+	if gfile == nil {
+		return nil
+	}
+
+	createdTime, _ := time.Parse(time.RFC3339, gfile.CreatedTime)
+	modifiedTime, _ := time.Parse(time.RFC3339, gfile.ModifiedTime)
+
+	mode := os.FileMode(0644)
+	if gfile.MimeType == "application/vnd.google-apps.folder" {
+		mode = os.FileMode(0755) | os.ModeDir
+	}
+
+	file := &basefs.File{
+		ID:           gfile.Id,
+		Name:         gfile.Name,
+		Size:         uint64(gfile.Size),
+		CreatedTime:  createdTime,
+		ModifiedTime: modifiedTime,
+		AccessedTime: modifiedTime,
+		Mode:         mode,
+
+		Parents: gfile.Parents,
+		Data:    gfile, // Extra gfile
+	}
+	return file
+}

+ 0 - 62
internal/fs/gdrivefs/serviceimpl.go

@@ -1,62 +0,0 @@
-package gdrivefs
-
-import (
-	"io"
-	"net/http"
-
-	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
-
-	"google.golang.org/api/drive/v3"
-)
-
-type gdriveService struct {
-	client *drive.Service
-}
-
-func (s *gdriveService) Truncate(file basefs.File) (basefs.File, error) {
-	err := s.client.Files.Delete(file.ID()).Do() // XXX: Careful on this
-	createdFile, err := s.client.Files.Create(&drive.File{Parents: file.Parents(), Name: file.Name()}).Fields(fileFields).Do()
-	if err != nil {
-		return nil, err
-	}
-
-	return &basefs.GFile{createdFile}, nil
-
-}
-
-func (s *gdriveService) Upload(reader io.Reader, file basefs.File) (basefs.File, error) {
-	ngFile := &drive.File{}
-	up := s.client.Files.Update(file.ID(), ngFile)
-	upFile, err := up.Media(reader).Fields(fileFields).Do()
-	if err != nil {
-		return nil, err
-	}
-
-	return &basefs.GFile{upFile}, nil
-}
-
-func (s *gdriveService) DownloadTo(w io.Writer, file basefs.File) error {
-
-	var res *http.Response
-	var err error
-	// TODO :Place this in service Download
-	gfile := file.(*basefs.GFile).File
-	// Export GDocs (Special google doc documents needs to be exported make a config somewhere for this)
-	switch gfile.MimeType { // Make this somewhat optional special case
-	case "application/vnd.google-apps.document":
-		res, err = s.client.Files.Export(gfile.Id, "text/plain").Download()
-	case "application/vnd.google-apps.spreadsheet":
-		res, err = s.client.Files.Export(gfile.Id, "text/csv").Download()
-	default:
-		res, err = s.client.Files.Get(gfile.Id).Download()
-	}
-
-	if err != nil {
-		log.Println("Error from GDrive API", err)
-		return err
-	}
-	defer res.Body.Close()
-	io.Copy(w, res.Body)
-
-	return nil
-}

+ 2 - 1
main.go

@@ -12,6 +12,7 @@ import (
 	"os/exec"
 
 	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/dropboxfs"
 	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/gdrivefs"
 	"dev.hexasoftware.com/hxs/prettylog"
 )
@@ -31,7 +32,7 @@ func main() {
 
 	// More will be added later
 	core.Drivers["gdrive"] = gdrivefs.New
-	//core.Drivers["dummy"] = basefs.New
+	core.Drivers["dropbox"] = dropboxfs.New
 
 	if err := parseFlags(&core.Config); err != nil {
 		log.Fatalln(err)

+ 6 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/.gitignore

@@ -0,0 +1,6 @@
+# swap
+[._]*.s[a-w][a-z]
+[._]s[a-w][a-z]
+
+.pyc
+__pycache__

+ 3 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "generator/dropbox-api-spec"]
+	path = generator/dropbox-api-spec
+	url = https://github.com/dropbox/dropbox-api-spec

+ 15 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/.travis.yml

@@ -0,0 +1,15 @@
+language: go
+
+go:
+  - 1.6.4
+  - 1.7.5
+  - 1.8
+
+install:
+  - go get -u golang.org/x/oauth2
+
+before_script:
+  - go get -u github.com/mitchellh/gox
+
+script:
+  - gox -osarch="darwin/amd64 linux/amd64 windows/amd64" ./dropbox/...

+ 20 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/LICENSE

@@ -0,0 +1,20 @@
+Copyright (c) 2009-2016 Dropbox Inc., http://www.dropbox.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 95 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/README.md

@@ -0,0 +1,95 @@
+# Dropbox SDK for Go [UNOFFICIAL] [![GoDoc](https://godoc.org/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox?status.svg)](https://godoc.org/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox) [![Build Status](https://travis-ci.org/dropbox/dropbox-sdk-go-unofficial.svg?branch=master)](https://travis-ci.org/dropbox/dropbox-sdk-go-unofficial)
+
+An **UNOFFICIAL** Go SDK for integrating with the Dropbox API v2. Tested with Go 1.5+
+
+:warning: WARNING: This SDK is **NOT yet official**. What does this mean?
+
+  * There is no formal Dropbox [support](https://www.dropbox.com/developers/support) for this SDK at this point
+  * Bugs may or may not get fixed
+  * Not all SDK features may be implemented and implemented features may be buggy or incorrect
+
+
+### Uh OK, so why are you releasing this?
+
+  * the SDK, while unofficial, _is_ usable. See [dbxcli](https://github.com/dropbox/dbxcli) for an example application built using the SDK
+  * we would like to get feedback from the community and evaluate the level of interest/enthusiasm before investing into official supporting one more SDK
+
+## Installation
+
+```sh
+$ go get github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/...
+```
+
+For most applications, you should just import the relevant namespace(s) only. The SDK exports the following sub-packages:
+
+* `github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/auth`
+* `github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files`
+* `github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing`
+* `github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team`
+* `github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users`
+
+Additionally, the base `github.com/dropbox/dropbox-sdk-go-unofficial/dropbox` package exports some configuration and helper methods.
+
+## Usage
+
+First, you need to [register a new "app"](https://dropbox.com/developers/apps) to start making API requests. Once you have created an app, you can either use the SDK via an access token (useful for testing) or via the regular OAuth2 flow (recommended for production).
+
+### Using OAuth token
+
+Once you've created an app, you can get an access token from the app's console. Note that this token will only work for the Dropbox account the token is associated with.
+
+```go
+import "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+import "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users"
+
+func main() {
+  config := dropbox.Config{Token: token, Verbose: true} // second arg enables verbose logging in the SDK
+  dbx := users.New(config)
+  // start making API calls
+}
+```
+
+### Using OAuth2 flow
+
+For this, you will need your `APP_KEY` and `APP_SECRET` from the developers console. Your app will then have to take users though the oauth flow, as part of which users will explicitly grant permissions to your app. At the end of this process, users will get a token that the app can then use for subsequent authentication. See [this](https://godoc.org/golang.org/x/oauth2#example-Config) for an example of oauth2 flow in Go.
+
+Once you have the token, usage is same as above.
+
+### Making API calls
+
+Each Dropbox API takes in a request type and returns a response type. For instance, [/users/get_account](https://www.dropbox.com/developers/documentation/http/documentation#users-get_account) takes as input a `GetAccountArg` and returns a `BasicAccount`. The typical pattern for making API calls is:
+
+  * Instantiate the argument via the `New*` convenience functions in the SDK
+  * Invoke the API
+  * Process the response (or handle error, as below)
+
+Here's an example:
+
+```go
+  arg := users.NewGetAccountArg(accountId)
+  if resp, err := dbx.GetAccount(arg); err != nil {
+    return err
+  }
+  fmt.Printf("Name: %v", resp.Name)
+```
+
+### Error Handling
+
+As described in the [API docs](https://www.dropbox.com/developers/documentation/http/documentation#error-handling), all HTTP errors _except_ 409 are returned as-is to the client (with a helpful text message where possible). In case of a 409, the SDK will return an endpoint-specific error as described in the API. This will be made available as `EndpointError` member in the error.
+
+## Note on using the Teams API
+
+To use the Team API, you will need to create a Dropbox Business App. The OAuth token from this app will _only_ work for the Team API.
+
+Please read the [API docs](https://www.dropbox.com/developers/documentation/http/teams) carefully to appropriate secure your apps and tokens when using the Team API.
+
+## Code Generation
+
+This SDK is automatically generated using the public [Dropbox API spec](https://github.com/dropbox/dropbox-api-spec) and [Stone](https://github.com/dropbox/stone). See this [README](https://github.com/dropbox/dropbox-sdk-go-unofficial/blob/master/generator/README.md)
+for more details on how code is generated. 
+
+## Caveats
+
+  * To re-iterate, this is an **UNOFFICIAL** SDK and thus has no official support from Dropbox
+  * Only supports the v2 API. Parts of the v2 API are still in beta, and thus subject to change
+  * This SDK itself is in beta, and so interfaces may change at any point

+ 132 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/async/types.go

@@ -0,0 +1,132 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package async : has no documentation (yet)
+package async
+
+import (
+	"encoding/json"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// LaunchResultBase : Result returned by methods that launch an asynchronous
+// job. A method who may either launch an asynchronous job, or complete the
+// request synchronously, can use this union by extending it, and adding a
+// 'complete' field with the type of the synchronous response. See
+// `LaunchEmptyResult` for an example.
+type LaunchResultBase struct {
+	dropbox.Tagged
+	// AsyncJobId : This response indicates that the processing is asynchronous.
+	// The string is an id that can be used to obtain the status of the
+	// asynchronous job.
+	AsyncJobId string `json:"async_job_id,omitempty"`
+}
+
+// Valid tag values for LaunchResultBase
+const (
+	LaunchResultBaseAsyncJobId = "async_job_id"
+)
+
+// UnmarshalJSON deserializes into a LaunchResultBase instance
+func (u *LaunchResultBase) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "async_job_id":
+		err = json.Unmarshal(body, &u.AsyncJobId)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// LaunchEmptyResult : Result returned by methods that may either launch an
+// asynchronous job or complete synchronously. Upon synchronous completion of
+// the job, no additional information is returned.
+type LaunchEmptyResult struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for LaunchEmptyResult
+const (
+	LaunchEmptyResultComplete = "complete"
+)
+
+// PollArg : Arguments for methods that poll the status of an asynchronous job.
+type PollArg struct {
+	// AsyncJobId : Id of the asynchronous job. This is the value of a response
+	// returned from the method that launched the job.
+	AsyncJobId string `json:"async_job_id"`
+}
+
+// NewPollArg returns a new PollArg instance
+func NewPollArg(AsyncJobId string) *PollArg {
+	s := new(PollArg)
+	s.AsyncJobId = AsyncJobId
+	return s
+}
+
+// PollResultBase : Result returned by methods that poll for the status of an
+// asynchronous job. Unions that extend this union should add a 'complete' field
+// with a type of the information returned upon job completion. See
+// `PollEmptyResult` for an example.
+type PollResultBase struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PollResultBase
+const (
+	PollResultBaseInProgress = "in_progress"
+)
+
+// PollEmptyResult : Result returned by methods that poll for the status of an
+// asynchronous job. Upon completion of the job, no additional information is
+// returned.
+type PollEmptyResult struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PollEmptyResult
+const (
+	PollEmptyResultComplete = "complete"
+)
+
+// PollError : Error returned by methods for polling the status of asynchronous
+// job.
+type PollError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PollError
+const (
+	PollErrorInvalidAsyncJobId = "invalid_async_job_id"
+	PollErrorInternalError     = "internal_error"
+	PollErrorOther             = "other"
+)

+ 193 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/auth/client.go

@@ -0,0 +1,193 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package auth
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"log"
+	"net/http"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// Client interface describes all routes in this namespace
+type Client interface {
+	// TokenFromOauth1 : Creates an OAuth 2.0 access token from the supplied
+	// OAuth 1.0 access token.
+	TokenFromOauth1(arg *TokenFromOAuth1Arg) (res *TokenFromOAuth1Result, err error)
+	// TokenRevoke : Disables the access token used to authenticate the call.
+	TokenRevoke() (err error)
+}
+
+type apiImpl dropbox.Context
+
+//TokenFromOauth1APIError is an error-wrapper for the token/from_oauth1 route
+type TokenFromOauth1APIError struct {
+	dropbox.APIError
+	EndpointError *TokenFromOAuth1Error `json:"error"`
+}
+
+func (dbx *apiImpl) TokenFromOauth1(arg *TokenFromOAuth1Arg) (res *TokenFromOAuth1Result, err error) {
+	cli := dbx.Client
+
+	if dbx.Config.Verbose {
+		log.Printf("arg: %v", arg)
+	}
+	b, err := json.Marshal(arg)
+	if err != nil {
+		return
+	}
+
+	headers := map[string]string{
+		"Content-Type": "application/json",
+	}
+	if dbx.Config.AsMemberID != "" {
+		headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "auth", "token/from_oauth1", headers, bytes.NewReader(b))
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		err = json.Unmarshal(body, &res)
+		if err != nil {
+			return
+		}
+
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError TokenFromOauth1APIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+//TokenRevokeAPIError is an error-wrapper for the token/revoke route
+type TokenRevokeAPIError struct {
+	dropbox.APIError
+	EndpointError struct{} `json:"error"`
+}
+
+func (dbx *apiImpl) TokenRevoke() (err error) {
+	cli := dbx.Client
+
+	headers := map[string]string{}
+	if dbx.Config.AsMemberID != "" {
+		headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "auth", "token/revoke", headers, nil)
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError TokenRevokeAPIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+// New returns a Client implementation for this namespace
+func New(c dropbox.Config) *apiImpl {
+	ctx := apiImpl(dropbox.NewContext(c))
+	return &ctx
+}

+ 187 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/auth/types.go

@@ -0,0 +1,187 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package auth : has no documentation (yet)
+package auth
+
+import (
+	"encoding/json"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// AccessError : Error occurred because the account doesn't have permission to
+// access the resource.
+type AccessError struct {
+	dropbox.Tagged
+	// InvalidAccountType : Current account type cannot access the resource.
+	InvalidAccountType *InvalidAccountTypeError `json:"invalid_account_type,omitempty"`
+	// PaperAccessDenied : Current account cannot access Paper.
+	PaperAccessDenied *PaperAccessError `json:"paper_access_denied,omitempty"`
+}
+
+// Valid tag values for AccessError
+const (
+	AccessErrorInvalidAccountType = "invalid_account_type"
+	AccessErrorPaperAccessDenied  = "paper_access_denied"
+	AccessErrorOther              = "other"
+)
+
+// UnmarshalJSON deserializes into a AccessError instance
+func (u *AccessError) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+		// InvalidAccountType : Current account type cannot access the resource.
+		InvalidAccountType json.RawMessage `json:"invalid_account_type,omitempty"`
+		// PaperAccessDenied : Current account cannot access Paper.
+		PaperAccessDenied json.RawMessage `json:"paper_access_denied,omitempty"`
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "invalid_account_type":
+		err = json.Unmarshal(w.InvalidAccountType, &u.InvalidAccountType)
+
+		if err != nil {
+			return err
+		}
+	case "paper_access_denied":
+		err = json.Unmarshal(w.PaperAccessDenied, &u.PaperAccessDenied)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// AuthError : Errors occurred during authentication.
+type AuthError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for AuthError
+const (
+	AuthErrorInvalidAccessToken = "invalid_access_token"
+	AuthErrorInvalidSelectUser  = "invalid_select_user"
+	AuthErrorInvalidSelectAdmin = "invalid_select_admin"
+	AuthErrorUserSuspended      = "user_suspended"
+	AuthErrorOther              = "other"
+)
+
+// InvalidAccountTypeError : has no documentation (yet)
+type InvalidAccountTypeError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for InvalidAccountTypeError
+const (
+	InvalidAccountTypeErrorEndpoint = "endpoint"
+	InvalidAccountTypeErrorFeature  = "feature"
+	InvalidAccountTypeErrorOther    = "other"
+)
+
+// PaperAccessError : has no documentation (yet)
+type PaperAccessError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PaperAccessError
+const (
+	PaperAccessErrorPaperDisabled = "paper_disabled"
+	PaperAccessErrorNotPaperUser  = "not_paper_user"
+	PaperAccessErrorOther         = "other"
+)
+
+// RateLimitError : Error occurred because the app is being rate limited.
+type RateLimitError struct {
+	// Reason : The reason why the app is being rate limited.
+	Reason *RateLimitReason `json:"reason"`
+	// RetryAfter : The number of seconds that the app should wait before making
+	// another request.
+	RetryAfter uint64 `json:"retry_after"`
+}
+
+// NewRateLimitError returns a new RateLimitError instance
+func NewRateLimitError(Reason *RateLimitReason) *RateLimitError {
+	s := new(RateLimitError)
+	s.Reason = Reason
+	s.RetryAfter = 1
+	return s
+}
+
+// RateLimitReason : has no documentation (yet)
+type RateLimitReason struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for RateLimitReason
+const (
+	RateLimitReasonTooManyRequests        = "too_many_requests"
+	RateLimitReasonTooManyWriteOperations = "too_many_write_operations"
+	RateLimitReasonOther                  = "other"
+)
+
+// TokenFromOAuth1Arg : has no documentation (yet)
+type TokenFromOAuth1Arg struct {
+	// Oauth1Token : The supplied OAuth 1.0 access token.
+	Oauth1Token string `json:"oauth1_token"`
+	// Oauth1TokenSecret : The token secret associated with the supplied access
+	// token.
+	Oauth1TokenSecret string `json:"oauth1_token_secret"`
+}
+
+// NewTokenFromOAuth1Arg returns a new TokenFromOAuth1Arg instance
+func NewTokenFromOAuth1Arg(Oauth1Token string, Oauth1TokenSecret string) *TokenFromOAuth1Arg {
+	s := new(TokenFromOAuth1Arg)
+	s.Oauth1Token = Oauth1Token
+	s.Oauth1TokenSecret = Oauth1TokenSecret
+	return s
+}
+
+// TokenFromOAuth1Error : has no documentation (yet)
+type TokenFromOAuth1Error struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for TokenFromOAuth1Error
+const (
+	TokenFromOAuth1ErrorInvalidOauth1TokenInfo = "invalid_oauth1_token_info"
+	TokenFromOAuth1ErrorAppIdMismatch          = "app_id_mismatch"
+	TokenFromOAuth1ErrorOther                  = "other"
+)
+
+// TokenFromOAuth1Result : has no documentation (yet)
+type TokenFromOAuth1Result struct {
+	// Oauth2Token : The OAuth 2.0 token generated from the supplied OAuth 1.0
+	// token.
+	Oauth2Token string `json:"oauth2_token"`
+}
+
+// NewTokenFromOAuth1Result returns a new TokenFromOAuth1Result instance
+func NewTokenFromOAuth1Result(Oauth2Token string) *TokenFromOAuth1Result {
+	s := new(TokenFromOAuth1Result)
+	s.Oauth2Token = Oauth2Token
+	return s
+}

+ 132 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/common/types.go

@@ -0,0 +1,132 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package common : has no documentation (yet)
+package common
+
+import (
+	"encoding/json"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// InvalidPathRootError : has no documentation (yet)
+type InvalidPathRootError struct {
+	// PathRoot : The latest path root id for user's team if the user is still
+	// in a team.
+	PathRoot string `json:"path_root,omitempty"`
+}
+
+// NewInvalidPathRootError returns a new InvalidPathRootError instance
+func NewInvalidPathRootError() *InvalidPathRootError {
+	s := new(InvalidPathRootError)
+	return s
+}
+
+// PathRoot : has no documentation (yet)
+type PathRoot struct {
+	dropbox.Tagged
+	// Team : Paths are relative to the given team directory. (This results in
+	// `PathRootError.invalid` if the user is not a member of the team
+	// associated with that path root id.)
+	Team string `json:"team,omitempty"`
+	// SharedFolder : Paths are relative to given shared folder id (This results
+	// in `PathRootError.no_permission` if you don't have access to  this shared
+	// folder.)
+	SharedFolder string `json:"shared_folder,omitempty"`
+}
+
+// Valid tag values for PathRoot
+const (
+	PathRootHome         = "home"
+	PathRootMemberHome   = "member_home"
+	PathRootTeam         = "team"
+	PathRootUserHome     = "user_home"
+	PathRootSharedFolder = "shared_folder"
+	PathRootOther        = "other"
+)
+
+// UnmarshalJSON deserializes into a PathRoot instance
+func (u *PathRoot) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "team":
+		err = json.Unmarshal(body, &u.Team)
+
+		if err != nil {
+			return err
+		}
+	case "shared_folder":
+		err = json.Unmarshal(body, &u.SharedFolder)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// PathRootError : has no documentation (yet)
+type PathRootError struct {
+	dropbox.Tagged
+	// Invalid : The path root id value in Dropbox-API-Path-Root header is no
+	// longer valid.
+	Invalid *InvalidPathRootError `json:"invalid,omitempty"`
+}
+
+// Valid tag values for PathRootError
+const (
+	PathRootErrorInvalid      = "invalid"
+	PathRootErrorNoPermission = "no_permission"
+	PathRootErrorOther        = "other"
+)
+
+// UnmarshalJSON deserializes into a PathRootError instance
+func (u *PathRootError) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+		// Invalid : The path root id value in Dropbox-API-Path-Root header is
+		// no longer valid.
+		Invalid json.RawMessage `json:"invalid,omitempty"`
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "invalid":
+		err = json.Unmarshal(body, &u.Invalid)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

Файловите разлики са ограничени, защото са твърде много
+ 3611 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files/client.go


+ 75 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files/metadata.go

@@ -0,0 +1,75 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package files
+
+import "encoding/json"
+
+type listFolderResult struct {
+	Entries []metadataUnion `json:"entries"`
+	Cursor  string          `json:"cursor"`
+	HasMore bool            `json:"has_more"`
+}
+
+// UnmarshalJSON deserializes into a ListFolderResult instance
+func (r *ListFolderResult) UnmarshalJSON(b []byte) error {
+	var l listFolderResult
+	if err := json.Unmarshal(b, &l); err != nil {
+		return err
+	}
+	r.Cursor = l.Cursor
+	r.HasMore = l.HasMore
+	r.Entries = make([]IsMetadata, len(l.Entries))
+	for i, e := range l.Entries {
+		switch e.Tag {
+		case "file":
+			r.Entries[i] = e.File
+		case "folder":
+			r.Entries[i] = e.Folder
+		case "deleted":
+			r.Entries[i] = e.Deleted
+		}
+	}
+	return nil
+}
+
+type searchMatch struct {
+	MatchType *SearchMatchType `json:"match_type"`
+	Metadata  metadataUnion    `json:"metadata"`
+}
+
+// UnmarshalJSON deserializes into a SearchMatch instance
+func (s *SearchMatch) UnmarshalJSON(b []byte) error {
+	var m searchMatch
+	if err := json.Unmarshal(b, &m); err != nil {
+		return err
+	}
+	s.MatchType = m.MatchType
+	e := m.Metadata
+	switch e.Tag {
+	case "file":
+		s.Metadata = e.File
+	case "folder":
+		s.Metadata = e.Folder
+	case "deleted":
+		s.Metadata = e.Deleted
+	}
+	return nil
+}

Файловите разлики са ограничени, защото са твърде много
+ 2981 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files/types.go


Файловите разлики са ограничени, защото са твърде много
+ 1199 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/paper/client.go


+ 729 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/paper/types.go

@@ -0,0 +1,729 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package paper : This namespace contains endpoints and data types for managing
+// docs and folders in Dropbox Paper.
+package paper
+
+import (
+	"encoding/json"
+	"time"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing"
+)
+
+// AddMember : has no documentation (yet)
+type AddMember struct {
+	// PermissionLevel : Permission for the user.
+	PermissionLevel *PaperDocPermissionLevel `json:"permission_level"`
+	// Member : User which should be added to the Paper doc. Specify only email
+	// address or Dropbox account ID.
+	Member *sharing.MemberSelector `json:"member"`
+}
+
+// NewAddMember returns a new AddMember instance
+func NewAddMember(Member *sharing.MemberSelector) *AddMember {
+	s := new(AddMember)
+	s.Member = Member
+	s.PermissionLevel = &PaperDocPermissionLevel{Tagged: dropbox.Tagged{"edit"}}
+	return s
+}
+
+// RefPaperDoc : has no documentation (yet)
+type RefPaperDoc struct {
+	// DocId : The Paper doc ID.
+	DocId string `json:"doc_id"`
+}
+
+// NewRefPaperDoc returns a new RefPaperDoc instance
+func NewRefPaperDoc(DocId string) *RefPaperDoc {
+	s := new(RefPaperDoc)
+	s.DocId = DocId
+	return s
+}
+
+// AddPaperDocUser : has no documentation (yet)
+type AddPaperDocUser struct {
+	RefPaperDoc
+	// Members : User which should be added to the Paper doc. Specify only email
+	// address or Dropbox account ID.
+	Members []*AddMember `json:"members"`
+	// CustomMessage : A personal message that will be emailed to each
+	// successfully added member.
+	CustomMessage string `json:"custom_message,omitempty"`
+	// Quiet : Clients should set this to true if no email message shall be sent
+	// to added users.
+	Quiet bool `json:"quiet"`
+}
+
+// NewAddPaperDocUser returns a new AddPaperDocUser instance
+func NewAddPaperDocUser(DocId string, Members []*AddMember) *AddPaperDocUser {
+	s := new(AddPaperDocUser)
+	s.DocId = DocId
+	s.Members = Members
+	s.Quiet = false
+	return s
+}
+
+// AddPaperDocUserMemberResult : Per-member result for `docsUsersAdd`.
+type AddPaperDocUserMemberResult struct {
+	// Member : One of specified input members.
+	Member *sharing.MemberSelector `json:"member"`
+	// Result : The outcome of the action on this member.
+	Result *AddPaperDocUserResult `json:"result"`
+}
+
+// NewAddPaperDocUserMemberResult returns a new AddPaperDocUserMemberResult instance
+func NewAddPaperDocUserMemberResult(Member *sharing.MemberSelector, Result *AddPaperDocUserResult) *AddPaperDocUserMemberResult {
+	s := new(AddPaperDocUserMemberResult)
+	s.Member = Member
+	s.Result = Result
+	return s
+}
+
+// AddPaperDocUserResult : has no documentation (yet)
+type AddPaperDocUserResult struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for AddPaperDocUserResult
+const (
+	AddPaperDocUserResultSuccess                    = "success"
+	AddPaperDocUserResultUnknownError               = "unknown_error"
+	AddPaperDocUserResultSharingOutsideTeamDisabled = "sharing_outside_team_disabled"
+	AddPaperDocUserResultDailyLimitReached          = "daily_limit_reached"
+	AddPaperDocUserResultUserIsOwner                = "user_is_owner"
+	AddPaperDocUserResultFailedUserDataRetrieval    = "failed_user_data_retrieval"
+	AddPaperDocUserResultPermissionAlreadyGranted   = "permission_already_granted"
+	AddPaperDocUserResultOther                      = "other"
+)
+
+// Cursor : has no documentation (yet)
+type Cursor struct {
+	// Value : The actual cursor value.
+	Value string `json:"value"`
+	// Expiration : Expiration time of `value`. Some cursors might have
+	// expiration time assigned. This is a UTC value after which the cursor is
+	// no longer valid and the API starts returning an error. If cursor expires
+	// a new one needs to be obtained and pagination needs to be restarted. Some
+	// cursors might be short-lived some cursors might be long-lived. This
+	// really depends on the sorting type and order, e.g.: 1. on one hand,
+	// listing docs created by the user, sorted by the created time ascending
+	// will have undefinite expiration because the results cannot change while
+	// the iteration is happening. This cursor would be suitable for long term
+	// polling. 2. on the other hand, listing docs sorted by the last modified
+	// time will have a very short expiration as docs do get modified very often
+	// and the modified time can be changed while the iteration is happening
+	// thus altering the results.
+	Expiration time.Time `json:"expiration,omitempty"`
+}
+
+// NewCursor returns a new Cursor instance
+func NewCursor(Value string) *Cursor {
+	s := new(Cursor)
+	s.Value = Value
+	return s
+}
+
+// PaperApiBaseError : has no documentation (yet)
+type PaperApiBaseError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PaperApiBaseError
+const (
+	PaperApiBaseErrorInsufficientPermissions = "insufficient_permissions"
+	PaperApiBaseErrorOther                   = "other"
+)
+
+// DocLookupError : has no documentation (yet)
+type DocLookupError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for DocLookupError
+const (
+	DocLookupErrorDocNotFound = "doc_not_found"
+)
+
+// DocSubscriptionLevel : The subscription level of a Paper doc.
+type DocSubscriptionLevel struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for DocSubscriptionLevel
+const (
+	DocSubscriptionLevelDefault = "default"
+	DocSubscriptionLevelIgnore  = "ignore"
+	DocSubscriptionLevelEvery   = "every"
+	DocSubscriptionLevelNoEmail = "no_email"
+)
+
+// ExportFormat : The desired export format of the Paper doc.
+type ExportFormat struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for ExportFormat
+const (
+	ExportFormatHtml     = "html"
+	ExportFormatMarkdown = "markdown"
+	ExportFormatOther    = "other"
+)
+
+// Folder : Data structure representing a Paper folder.
+type Folder struct {
+	// Id : Paper folder ID. This ID uniquely identifies the folder.
+	Id string `json:"id"`
+	// Name : Paper folder name.
+	Name string `json:"name"`
+}
+
+// NewFolder returns a new Folder instance
+func NewFolder(Id string, Name string) *Folder {
+	s := new(Folder)
+	s.Id = Id
+	s.Name = Name
+	return s
+}
+
+// FolderSharingPolicyType : The sharing policy of a Paper folder.  Note: The
+// sharing policy of subfolders is inherited from the root folder.
+type FolderSharingPolicyType struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for FolderSharingPolicyType
+const (
+	FolderSharingPolicyTypeTeam       = "team"
+	FolderSharingPolicyTypeInviteOnly = "invite_only"
+)
+
+// FolderSubscriptionLevel : The subscription level of a Paper folder.
+type FolderSubscriptionLevel struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for FolderSubscriptionLevel
+const (
+	FolderSubscriptionLevelNone         = "none"
+	FolderSubscriptionLevelActivityOnly = "activity_only"
+	FolderSubscriptionLevelDailyEmails  = "daily_emails"
+	FolderSubscriptionLevelWeeklyEmails = "weekly_emails"
+)
+
+// FoldersContainingPaperDoc : Metadata about Paper folders containing the
+// specififed Paper doc.
+type FoldersContainingPaperDoc struct {
+	// FolderSharingPolicyType : The sharing policy of the folder containing the
+	// Paper doc.
+	FolderSharingPolicyType *FolderSharingPolicyType `json:"folder_sharing_policy_type,omitempty"`
+	// Folders : The folder path. If present the first folder is the root
+	// folder.
+	Folders []*Folder `json:"folders,omitempty"`
+}
+
+// NewFoldersContainingPaperDoc returns a new FoldersContainingPaperDoc instance
+func NewFoldersContainingPaperDoc() *FoldersContainingPaperDoc {
+	s := new(FoldersContainingPaperDoc)
+	return s
+}
+
+// InviteeInfoWithPermissionLevel : has no documentation (yet)
+type InviteeInfoWithPermissionLevel struct {
+	// Invitee : Email address invited to the Paper doc.
+	Invitee *sharing.InviteeInfo `json:"invitee"`
+	// PermissionLevel : Permission level for the invitee.
+	PermissionLevel *PaperDocPermissionLevel `json:"permission_level"`
+}
+
+// NewInviteeInfoWithPermissionLevel returns a new InviteeInfoWithPermissionLevel instance
+func NewInviteeInfoWithPermissionLevel(Invitee *sharing.InviteeInfo, PermissionLevel *PaperDocPermissionLevel) *InviteeInfoWithPermissionLevel {
+	s := new(InviteeInfoWithPermissionLevel)
+	s.Invitee = Invitee
+	s.PermissionLevel = PermissionLevel
+	return s
+}
+
+// ListDocsCursorError : has no documentation (yet)
+type ListDocsCursorError struct {
+	dropbox.Tagged
+	// CursorError : has no documentation (yet)
+	CursorError *PaperApiCursorError `json:"cursor_error,omitempty"`
+}
+
+// Valid tag values for ListDocsCursorError
+const (
+	ListDocsCursorErrorCursorError = "cursor_error"
+	ListDocsCursorErrorOther       = "other"
+)
+
+// UnmarshalJSON deserializes into a ListDocsCursorError instance
+func (u *ListDocsCursorError) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+		// CursorError : has no documentation (yet)
+		CursorError json.RawMessage `json:"cursor_error,omitempty"`
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "cursor_error":
+		err = json.Unmarshal(w.CursorError, &u.CursorError)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// ListPaperDocsArgs : has no documentation (yet)
+type ListPaperDocsArgs struct {
+	// FilterBy : Allows user to specify how the Paper docs should be filtered.
+	FilterBy *ListPaperDocsFilterBy `json:"filter_by"`
+	// SortBy : Allows user to specify how the Paper docs should be sorted.
+	SortBy *ListPaperDocsSortBy `json:"sort_by"`
+	// SortOrder : Allows user to specify the sort order of the result.
+	SortOrder *ListPaperDocsSortOrder `json:"sort_order"`
+	// Limit : Size limit per batch. The maximum number of docs that can be
+	// retrieved per batch is 1000. Higher value results in invalid arguments
+	// error.
+	Limit int32 `json:"limit"`
+}
+
+// NewListPaperDocsArgs returns a new ListPaperDocsArgs instance
+func NewListPaperDocsArgs() *ListPaperDocsArgs {
+	s := new(ListPaperDocsArgs)
+	s.FilterBy = &ListPaperDocsFilterBy{Tagged: dropbox.Tagged{"docs_accessed"}}
+	s.SortBy = &ListPaperDocsSortBy{Tagged: dropbox.Tagged{"accessed"}}
+	s.SortOrder = &ListPaperDocsSortOrder{Tagged: dropbox.Tagged{"ascending"}}
+	s.Limit = 1000
+	return s
+}
+
+// ListPaperDocsContinueArgs : has no documentation (yet)
+type ListPaperDocsContinueArgs struct {
+	// Cursor : The cursor obtained from `docsList` or `docsListContinue`.
+	// Allows for pagination.
+	Cursor string `json:"cursor"`
+}
+
+// NewListPaperDocsContinueArgs returns a new ListPaperDocsContinueArgs instance
+func NewListPaperDocsContinueArgs(Cursor string) *ListPaperDocsContinueArgs {
+	s := new(ListPaperDocsContinueArgs)
+	s.Cursor = Cursor
+	return s
+}
+
+// ListPaperDocsFilterBy : has no documentation (yet)
+type ListPaperDocsFilterBy struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for ListPaperDocsFilterBy
+const (
+	ListPaperDocsFilterByDocsAccessed = "docs_accessed"
+	ListPaperDocsFilterByDocsCreated  = "docs_created"
+	ListPaperDocsFilterByOther        = "other"
+)
+
+// ListPaperDocsResponse : has no documentation (yet)
+type ListPaperDocsResponse struct {
+	// DocIds : The list of Paper doc IDs that can be used to access the given
+	// Paper docs or supplied to other API methods. The list is sorted in the
+	// order specified by the initial call to `docsList`.
+	DocIds []string `json:"doc_ids"`
+	// Cursor : Pass the cursor into `docsListContinue` to paginate through all
+	// files. The cursor preserves all properties as specified in the original
+	// call to `docsList`.
+	Cursor *Cursor `json:"cursor"`
+	// HasMore : Will be set to True if a subsequent call with the provided
+	// cursor to `docsListContinue` returns immediately with some results. If
+	// set to False please allow some delay before making another call to
+	// `docsListContinue`.
+	HasMore bool `json:"has_more"`
+}
+
+// NewListPaperDocsResponse returns a new ListPaperDocsResponse instance
+func NewListPaperDocsResponse(DocIds []string, Cursor *Cursor, HasMore bool) *ListPaperDocsResponse {
+	s := new(ListPaperDocsResponse)
+	s.DocIds = DocIds
+	s.Cursor = Cursor
+	s.HasMore = HasMore
+	return s
+}
+
+// ListPaperDocsSortBy : has no documentation (yet)
+type ListPaperDocsSortBy struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for ListPaperDocsSortBy
+const (
+	ListPaperDocsSortByAccessed = "accessed"
+	ListPaperDocsSortByModified = "modified"
+	ListPaperDocsSortByCreated  = "created"
+	ListPaperDocsSortByOther    = "other"
+)
+
+// ListPaperDocsSortOrder : has no documentation (yet)
+type ListPaperDocsSortOrder struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for ListPaperDocsSortOrder
+const (
+	ListPaperDocsSortOrderAscending  = "ascending"
+	ListPaperDocsSortOrderDescending = "descending"
+	ListPaperDocsSortOrderOther      = "other"
+)
+
+// ListUsersCursorError : has no documentation (yet)
+type ListUsersCursorError struct {
+	dropbox.Tagged
+	// CursorError : has no documentation (yet)
+	CursorError *PaperApiCursorError `json:"cursor_error,omitempty"`
+}
+
+// Valid tag values for ListUsersCursorError
+const (
+	ListUsersCursorErrorDocNotFound = "doc_not_found"
+	ListUsersCursorErrorCursorError = "cursor_error"
+)
+
+// UnmarshalJSON deserializes into a ListUsersCursorError instance
+func (u *ListUsersCursorError) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+		// CursorError : has no documentation (yet)
+		CursorError json.RawMessage `json:"cursor_error,omitempty"`
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "cursor_error":
+		err = json.Unmarshal(w.CursorError, &u.CursorError)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// ListUsersOnFolderArgs : has no documentation (yet)
+type ListUsersOnFolderArgs struct {
+	RefPaperDoc
+	// Limit : Size limit per batch. The maximum number of users that can be
+	// retrieved per batch is 1000. Higher value results in invalid arguments
+	// error.
+	Limit int32 `json:"limit"`
+}
+
+// NewListUsersOnFolderArgs returns a new ListUsersOnFolderArgs instance
+func NewListUsersOnFolderArgs(DocId string) *ListUsersOnFolderArgs {
+	s := new(ListUsersOnFolderArgs)
+	s.DocId = DocId
+	s.Limit = 1000
+	return s
+}
+
+// ListUsersOnFolderContinueArgs : has no documentation (yet)
+type ListUsersOnFolderContinueArgs struct {
+	RefPaperDoc
+	// Cursor : The cursor obtained from `docsFolderUsersList` or
+	// `docsFolderUsersListContinue`. Allows for pagination.
+	Cursor string `json:"cursor"`
+}
+
+// NewListUsersOnFolderContinueArgs returns a new ListUsersOnFolderContinueArgs instance
+func NewListUsersOnFolderContinueArgs(DocId string, Cursor string) *ListUsersOnFolderContinueArgs {
+	s := new(ListUsersOnFolderContinueArgs)
+	s.DocId = DocId
+	s.Cursor = Cursor
+	return s
+}
+
+// ListUsersOnFolderResponse : has no documentation (yet)
+type ListUsersOnFolderResponse struct {
+	// Invitees : List of email addresses that are invited on the Paper folder.
+	Invitees []*sharing.InviteeInfo `json:"invitees"`
+	// Users : List of users that are invited on the Paper folder.
+	Users []*sharing.UserInfo `json:"users"`
+	// Cursor : Pass the cursor into `docsFolderUsersListContinue` to paginate
+	// through all users. The cursor preserves all properties as specified in
+	// the original call to `docsFolderUsersList`.
+	Cursor *Cursor `json:"cursor"`
+	// HasMore : Will be set to True if a subsequent call with the provided
+	// cursor to `docsFolderUsersListContinue` returns immediately with some
+	// results. If set to False please allow some delay before making another
+	// call to `docsFolderUsersListContinue`.
+	HasMore bool `json:"has_more"`
+}
+
+// NewListUsersOnFolderResponse returns a new ListUsersOnFolderResponse instance
+func NewListUsersOnFolderResponse(Invitees []*sharing.InviteeInfo, Users []*sharing.UserInfo, Cursor *Cursor, HasMore bool) *ListUsersOnFolderResponse {
+	s := new(ListUsersOnFolderResponse)
+	s.Invitees = Invitees
+	s.Users = Users
+	s.Cursor = Cursor
+	s.HasMore = HasMore
+	return s
+}
+
+// ListUsersOnPaperDocArgs : has no documentation (yet)
+type ListUsersOnPaperDocArgs struct {
+	RefPaperDoc
+	// Limit : Size limit per batch. The maximum number of users that can be
+	// retrieved per batch is 1000. Higher value results in invalid arguments
+	// error.
+	Limit int32 `json:"limit"`
+	// FilterBy : Specify this attribute if you want to obtain users that have
+	// already accessed the Paper doc.
+	FilterBy *UserOnPaperDocFilter `json:"filter_by"`
+}
+
+// NewListUsersOnPaperDocArgs returns a new ListUsersOnPaperDocArgs instance
+func NewListUsersOnPaperDocArgs(DocId string) *ListUsersOnPaperDocArgs {
+	s := new(ListUsersOnPaperDocArgs)
+	s.DocId = DocId
+	s.Limit = 1000
+	s.FilterBy = &UserOnPaperDocFilter{Tagged: dropbox.Tagged{"shared"}}
+	return s
+}
+
+// ListUsersOnPaperDocContinueArgs : has no documentation (yet)
+type ListUsersOnPaperDocContinueArgs struct {
+	RefPaperDoc
+	// Cursor : The cursor obtained from `docsUsersList` or
+	// `docsUsersListContinue`. Allows for pagination.
+	Cursor string `json:"cursor"`
+}
+
+// NewListUsersOnPaperDocContinueArgs returns a new ListUsersOnPaperDocContinueArgs instance
+func NewListUsersOnPaperDocContinueArgs(DocId string, Cursor string) *ListUsersOnPaperDocContinueArgs {
+	s := new(ListUsersOnPaperDocContinueArgs)
+	s.DocId = DocId
+	s.Cursor = Cursor
+	return s
+}
+
+// ListUsersOnPaperDocResponse : has no documentation (yet)
+type ListUsersOnPaperDocResponse struct {
+	// Invitees : List of email addresses with their respective permission
+	// levels that are invited on the Paper doc.
+	Invitees []*InviteeInfoWithPermissionLevel `json:"invitees"`
+	// Users : List of users with their respective permission levels that are
+	// invited on the Paper folder.
+	Users []*UserInfoWithPermissionLevel `json:"users"`
+	// DocOwner : The Paper doc owner. This field is populated on every single
+	// response.
+	DocOwner *sharing.UserInfo `json:"doc_owner"`
+	// Cursor : Pass the cursor into `docsUsersListContinue` to paginate through
+	// all users. The cursor preserves all properties as specified in the
+	// original call to `docsUsersList`.
+	Cursor *Cursor `json:"cursor"`
+	// HasMore : Will be set to True if a subsequent call with the provided
+	// cursor to `docsUsersListContinue` returns immediately with some results.
+	// If set to False please allow some delay before making another call to
+	// `docsUsersListContinue`.
+	HasMore bool `json:"has_more"`
+}
+
+// NewListUsersOnPaperDocResponse returns a new ListUsersOnPaperDocResponse instance
+func NewListUsersOnPaperDocResponse(Invitees []*InviteeInfoWithPermissionLevel, Users []*UserInfoWithPermissionLevel, DocOwner *sharing.UserInfo, Cursor *Cursor, HasMore bool) *ListUsersOnPaperDocResponse {
+	s := new(ListUsersOnPaperDocResponse)
+	s.Invitees = Invitees
+	s.Users = Users
+	s.DocOwner = DocOwner
+	s.Cursor = Cursor
+	s.HasMore = HasMore
+	return s
+}
+
+// PaperApiCursorError : has no documentation (yet)
+type PaperApiCursorError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PaperApiCursorError
+const (
+	PaperApiCursorErrorExpiredCursor     = "expired_cursor"
+	PaperApiCursorErrorInvalidCursor     = "invalid_cursor"
+	PaperApiCursorErrorWrongUserInCursor = "wrong_user_in_cursor"
+	PaperApiCursorErrorReset             = "reset"
+	PaperApiCursorErrorOther             = "other"
+)
+
+// PaperDocExport : has no documentation (yet)
+type PaperDocExport struct {
+	RefPaperDoc
+	// ExportFormat : has no documentation (yet)
+	ExportFormat *ExportFormat `json:"export_format"`
+}
+
+// NewPaperDocExport returns a new PaperDocExport instance
+func NewPaperDocExport(DocId string, ExportFormat *ExportFormat) *PaperDocExport {
+	s := new(PaperDocExport)
+	s.DocId = DocId
+	s.ExportFormat = ExportFormat
+	return s
+}
+
+// PaperDocExportResult : has no documentation (yet)
+type PaperDocExportResult struct {
+	// Owner : The Paper doc owner's email address.
+	Owner string `json:"owner"`
+	// Title : The Paper doc title.
+	Title string `json:"title"`
+	// Revision : The Paper doc revision. Simply an ever increasing number.
+	Revision int64 `json:"revision"`
+	// MimeType : MIME type of the export. This corresponds to `ExportFormat`
+	// specified in the request.
+	MimeType string `json:"mime_type"`
+}
+
+// NewPaperDocExportResult returns a new PaperDocExportResult instance
+func NewPaperDocExportResult(Owner string, Title string, Revision int64, MimeType string) *PaperDocExportResult {
+	s := new(PaperDocExportResult)
+	s.Owner = Owner
+	s.Title = Title
+	s.Revision = Revision
+	s.MimeType = MimeType
+	return s
+}
+
+// PaperDocPermissionLevel : has no documentation (yet)
+type PaperDocPermissionLevel struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PaperDocPermissionLevel
+const (
+	PaperDocPermissionLevelEdit           = "edit"
+	PaperDocPermissionLevelViewAndComment = "view_and_comment"
+	PaperDocPermissionLevelOther          = "other"
+)
+
+// PaperDocSharingPolicy : has no documentation (yet)
+type PaperDocSharingPolicy struct {
+	RefPaperDoc
+	// SharingPolicy : The default sharing policy to be set for the Paper doc.
+	SharingPolicy *SharingPolicy `json:"sharing_policy"`
+}
+
+// NewPaperDocSharingPolicy returns a new PaperDocSharingPolicy instance
+func NewPaperDocSharingPolicy(DocId string, SharingPolicy *SharingPolicy) *PaperDocSharingPolicy {
+	s := new(PaperDocSharingPolicy)
+	s.DocId = DocId
+	s.SharingPolicy = SharingPolicy
+	return s
+}
+
+// RemovePaperDocUser : has no documentation (yet)
+type RemovePaperDocUser struct {
+	RefPaperDoc
+	// Member : User which should be removed from the Paper doc. Specify only
+	// email address or Dropbox account ID.
+	Member *sharing.MemberSelector `json:"member"`
+}
+
+// NewRemovePaperDocUser returns a new RemovePaperDocUser instance
+func NewRemovePaperDocUser(DocId string, Member *sharing.MemberSelector) *RemovePaperDocUser {
+	s := new(RemovePaperDocUser)
+	s.DocId = DocId
+	s.Member = Member
+	return s
+}
+
+// SharingPolicy : Sharing policy of Paper doc.
+type SharingPolicy struct {
+	// PublicSharingPolicy : This value applies to the non-team members.
+	PublicSharingPolicy *SharingPublicPolicyType `json:"public_sharing_policy,omitempty"`
+	// TeamSharingPolicy : This value applies to the team members only. The
+	// value is null for all personal accounts.
+	TeamSharingPolicy *SharingTeamPolicyType `json:"team_sharing_policy,omitempty"`
+}
+
+// NewSharingPolicy returns a new SharingPolicy instance
+func NewSharingPolicy() *SharingPolicy {
+	s := new(SharingPolicy)
+	return s
+}
+
+// SharingTeamPolicyType : The sharing policy type of the Paper doc.
+type SharingTeamPolicyType struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for SharingTeamPolicyType
+const (
+	SharingTeamPolicyTypePeopleWithLinkCanEdit           = "people_with_link_can_edit"
+	SharingTeamPolicyTypePeopleWithLinkCanViewAndComment = "people_with_link_can_view_and_comment"
+	SharingTeamPolicyTypeInviteOnly                      = "invite_only"
+)
+
+// SharingPublicPolicyType : has no documentation (yet)
+type SharingPublicPolicyType struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for SharingPublicPolicyType
+const (
+	SharingPublicPolicyTypeDisabled = "disabled"
+)
+
+// UserInfoWithPermissionLevel : has no documentation (yet)
+type UserInfoWithPermissionLevel struct {
+	// User : User shared on the Paper doc.
+	User *sharing.UserInfo `json:"user"`
+	// PermissionLevel : Permission level for the user.
+	PermissionLevel *PaperDocPermissionLevel `json:"permission_level"`
+}
+
+// NewUserInfoWithPermissionLevel returns a new UserInfoWithPermissionLevel instance
+func NewUserInfoWithPermissionLevel(User *sharing.UserInfo, PermissionLevel *PaperDocPermissionLevel) *UserInfoWithPermissionLevel {
+	s := new(UserInfoWithPermissionLevel)
+	s.User = User
+	s.PermissionLevel = PermissionLevel
+	return s
+}
+
+// UserOnPaperDocFilter : has no documentation (yet)
+type UserOnPaperDocFilter struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for UserOnPaperDocFilter
+const (
+	UserOnPaperDocFilterVisited = "visited"
+	UserOnPaperDocFilterShared  = "shared"
+	UserOnPaperDocFilterOther   = "other"
+)

+ 213 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/properties/types.go

@@ -0,0 +1,213 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package properties : This namespace contains helper entities for property and
+// property/template endpoints.
+package properties
+
+import (
+	"encoding/json"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// GetPropertyTemplateArg : has no documentation (yet)
+type GetPropertyTemplateArg struct {
+	// TemplateId : An identifier for property template added by route
+	// properties/template/add.
+	TemplateId string `json:"template_id"`
+}
+
+// NewGetPropertyTemplateArg returns a new GetPropertyTemplateArg instance
+func NewGetPropertyTemplateArg(TemplateId string) *GetPropertyTemplateArg {
+	s := new(GetPropertyTemplateArg)
+	s.TemplateId = TemplateId
+	return s
+}
+
+// PropertyGroupTemplate : Describes property templates that can be filled and
+// associated with a file.
+type PropertyGroupTemplate struct {
+	// Name : A display name for the property template. Property template names
+	// can be up to 256 bytes.
+	Name string `json:"name"`
+	// Description : Description for new property template. Property template
+	// descriptions can be up to 1024 bytes.
+	Description string `json:"description"`
+	// Fields : This is a list of custom properties associated with a property
+	// template. There can be up to 64 properties in a single property template.
+	Fields []*PropertyFieldTemplate `json:"fields"`
+}
+
+// NewPropertyGroupTemplate returns a new PropertyGroupTemplate instance
+func NewPropertyGroupTemplate(Name string, Description string, Fields []*PropertyFieldTemplate) *PropertyGroupTemplate {
+	s := new(PropertyGroupTemplate)
+	s.Name = Name
+	s.Description = Description
+	s.Fields = Fields
+	return s
+}
+
+// GetPropertyTemplateResult : The Property template for the specified template.
+type GetPropertyTemplateResult struct {
+	PropertyGroupTemplate
+}
+
+// NewGetPropertyTemplateResult returns a new GetPropertyTemplateResult instance
+func NewGetPropertyTemplateResult(Name string, Description string, Fields []*PropertyFieldTemplate) *GetPropertyTemplateResult {
+	s := new(GetPropertyTemplateResult)
+	s.Name = Name
+	s.Description = Description
+	s.Fields = Fields
+	return s
+}
+
+// ListPropertyTemplateIds : has no documentation (yet)
+type ListPropertyTemplateIds struct {
+	// TemplateIds : List of identifiers for templates added by route
+	// properties/template/add.
+	TemplateIds []string `json:"template_ids"`
+}
+
+// NewListPropertyTemplateIds returns a new ListPropertyTemplateIds instance
+func NewListPropertyTemplateIds(TemplateIds []string) *ListPropertyTemplateIds {
+	s := new(ListPropertyTemplateIds)
+	s.TemplateIds = TemplateIds
+	return s
+}
+
+// PropertyTemplateError : has no documentation (yet)
+type PropertyTemplateError struct {
+	dropbox.Tagged
+	// TemplateNotFound : Property template does not exist for given identifier.
+	TemplateNotFound string `json:"template_not_found,omitempty"`
+}
+
+// Valid tag values for PropertyTemplateError
+const (
+	PropertyTemplateErrorTemplateNotFound  = "template_not_found"
+	PropertyTemplateErrorRestrictedContent = "restricted_content"
+	PropertyTemplateErrorOther             = "other"
+)
+
+// UnmarshalJSON deserializes into a PropertyTemplateError instance
+func (u *PropertyTemplateError) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "template_not_found":
+		err = json.Unmarshal(body, &u.TemplateNotFound)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// ModifyPropertyTemplateError : has no documentation (yet)
+type ModifyPropertyTemplateError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for ModifyPropertyTemplateError
+const (
+	ModifyPropertyTemplateErrorConflictingPropertyNames  = "conflicting_property_names"
+	ModifyPropertyTemplateErrorTooManyProperties         = "too_many_properties"
+	ModifyPropertyTemplateErrorTooManyTemplates          = "too_many_templates"
+	ModifyPropertyTemplateErrorTemplateAttributeTooLarge = "template_attribute_too_large"
+)
+
+// PropertyField : has no documentation (yet)
+type PropertyField struct {
+	// Name : This is the name or key of a custom property in a property
+	// template. File property names can be up to 256 bytes.
+	Name string `json:"name"`
+	// Value : Value of a custom property attached to a file. Values can be up
+	// to 1024 bytes.
+	Value string `json:"value"`
+}
+
+// NewPropertyField returns a new PropertyField instance
+func NewPropertyField(Name string, Value string) *PropertyField {
+	s := new(PropertyField)
+	s.Name = Name
+	s.Value = Value
+	return s
+}
+
+// PropertyFieldTemplate : Describe a single property field type which that can
+// be part of a property template.
+type PropertyFieldTemplate struct {
+	// Name : This is the name or key of a custom property in a property
+	// template. File property names can be up to 256 bytes.
+	Name string `json:"name"`
+	// Description : This is the description for a custom property in a property
+	// template. File property description can be up to 1024 bytes.
+	Description string `json:"description"`
+	// Type : This is the data type of the value of this property. This type
+	// will be enforced upon property creation and modifications.
+	Type *PropertyType `json:"type"`
+}
+
+// NewPropertyFieldTemplate returns a new PropertyFieldTemplate instance
+func NewPropertyFieldTemplate(Name string, Description string, Type *PropertyType) *PropertyFieldTemplate {
+	s := new(PropertyFieldTemplate)
+	s.Name = Name
+	s.Description = Description
+	s.Type = Type
+	return s
+}
+
+// PropertyGroup : Collection of custom properties in filled property templates.
+type PropertyGroup struct {
+	// TemplateId : A unique identifier for a property template type.
+	TemplateId string `json:"template_id"`
+	// Fields : This is a list of custom properties associated with a file.
+	// There can be up to 32 properties for a template.
+	Fields []*PropertyField `json:"fields"`
+}
+
+// NewPropertyGroup returns a new PropertyGroup instance
+func NewPropertyGroup(TemplateId string, Fields []*PropertyField) *PropertyGroup {
+	s := new(PropertyGroup)
+	s.TemplateId = TemplateId
+	s.Fields = Fields
+	return s
+}
+
+// PropertyType : Data type of the given property added. This endpoint is in
+// beta and  only properties of type strings is supported.
+type PropertyType struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for PropertyType
+const (
+	PropertyTypeString = "string"
+	PropertyTypeOther  = "other"
+)

+ 170 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sdk.go

@@ -0,0 +1,170 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package dropbox
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+
+	"golang.org/x/oauth2"
+)
+
+const (
+	apiVersion    = 2
+	defaultDomain = ".dropboxapi.com"
+	hostAPI       = "api"
+	hostContent   = "content"
+	hostNotify    = "notify"
+	sdkVersion    = "1.0.0-beta"
+	specVersion   = "8c790b1"
+)
+
+// Version returns the current SDK version and API Spec version
+func Version() (string, string) {
+	return sdkVersion, specVersion
+}
+
+// Config contains parameters for configuring the SDK.
+type Config struct {
+	// OAuth2 access token
+	Token string
+	// Enable verbose logging in SDK
+	Verbose bool
+	// Used with APIs that support operations as another user
+	AsMemberID string
+	// No need to set -- for testing only
+	Domain string
+	// No need to set -- for testing only
+	Client *http.Client
+	// No need to set -- for testing only
+	HeaderGenerator func(hostType string, style string, namespace string, route string) map[string]string
+	// No need to set -- for testing only
+	URLGenerator func(hostType string, style string, namespace string, route string) string
+}
+
+// Context is the base client context used to implement per-namespace clients.
+type Context struct {
+	Config          Config
+	Client          *http.Client
+	HeaderGenerator func(hostType string, style string, namespace string, route string) map[string]string
+	URLGenerator    func(hostType string, style string, namespace string, route string) string
+}
+
+// NewRequest returns an appropriate Request object for the given namespace/route.
+func (c *Context) NewRequest(
+	hostType string,
+	style string,
+	authed bool,
+	namespace string,
+	route string,
+	headers map[string]string,
+	body io.Reader,
+) (*http.Request, error) {
+	url := c.URLGenerator(hostType, style, namespace, route)
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range headers {
+		req.Header.Add(k, v)
+	}
+	for k, v := range c.HeaderGenerator(hostType, style, namespace, route) {
+		req.Header.Add(k, v)
+	}
+	if req.Header.Get("Host") != "" {
+		req.Host = req.Header.Get("Host")
+	}
+	if !authed {
+		req.Header.Del("Authorization")
+	}
+	return req, nil
+}
+
+// NewContext returns a new Context with the given Config.
+func NewContext(c Config) Context {
+	domain := c.Domain
+	if domain == "" {
+		domain = defaultDomain
+	}
+
+	client := c.Client
+	if client == nil {
+		var conf = &oauth2.Config{Endpoint: OAuthEndpoint(domain)}
+		tok := &oauth2.Token{AccessToken: c.Token}
+		client = conf.Client(oauth2.NoContext, tok)
+	}
+
+	headerGenerator := c.HeaderGenerator
+	if headerGenerator == nil {
+		headerGenerator = func(hostType string, style string, namespace string, route string) map[string]string {
+			return map[string]string{}
+		}
+	}
+
+	urlGenerator := c.URLGenerator
+	if urlGenerator == nil {
+		hostMap := map[string]string{
+			hostAPI:     hostAPI + domain,
+			hostContent: hostContent + domain,
+			hostNotify:  hostNotify + domain,
+		}
+		urlGenerator = func(hostType string, style string, namespace string, route string) string {
+			fqHost := hostMap[hostType]
+			return fmt.Sprintf("https://%s/%d/%s/%s", fqHost, apiVersion, namespace, route)
+		}
+	}
+
+	return Context{c, client, headerGenerator, urlGenerator}
+}
+
+// OAuthEndpoint constructs an `oauth2.Endpoint` for the given domain
+func OAuthEndpoint(domain string) oauth2.Endpoint {
+	if domain == "" {
+		domain = defaultDomain
+	}
+	authURL := fmt.Sprintf("https://meta%s/1/oauth2/authorize", domain)
+	tokenURL := fmt.Sprintf("https://api%s/1/oauth2/token", domain)
+	if domain == defaultDomain {
+		authURL = "https://www.dropbox.com/1/oauth2/authorize"
+	}
+	return oauth2.Endpoint{AuthURL: authURL, TokenURL: tokenURL}
+}
+
+// Tagged is used for tagged unions.
+type Tagged struct {
+	Tag string `json:".tag"`
+}
+
+// APIError is the base type for endpoint-specific errors.
+type APIError struct {
+	ErrorSummary string `json:"error_summary"`
+}
+
+func (e APIError) Error() string {
+	return e.ErrorSummary
+}
+
+func init() {
+	// These are not registered in the oauth library by default
+	oauth2.RegisterBrokenAuthHeaderProvider("https://api.dropboxapi.com")
+	oauth2.RegisterBrokenAuthHeaderProvider("https://api-dbdev.dev.corp.dropbox.com")
+}

Файловите разлики са ограничени, защото са твърде много
+ 3586 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing/client.go


+ 49 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing/metadata.go

@@ -0,0 +1,49 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package sharing
+
+import "encoding/json"
+
+type listSharedLinksResult struct {
+	Links   []sharedLinkMetadataUnion `json:"links"`
+	HasMore bool                      `json:"has_more"`
+	Cursor  string                    `json:"cursor,omitempty"`
+}
+
+// UnmarshalJSON deserializes into a ListSharedLinksResult instance
+func (r *ListSharedLinksResult) UnmarshalJSON(b []byte) error {
+	var l listSharedLinksResult
+	if err := json.Unmarshal(b, &l); err != nil {
+		return err
+	}
+	r.Cursor = l.Cursor
+	r.HasMore = l.HasMore
+	r.Links = make([]IsSharedLinkMetadata, len(l.Links))
+	for i, e := range l.Links {
+		switch e.Tag {
+		case "file":
+			r.Links[i] = e.File
+		case "folder":
+			r.Links[i] = e.Folder
+		}
+	}
+	return nil
+}

Файловите разлики са ограничени, защото са твърде много
+ 4066 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing/types.go


Файловите разлики са ограничени, защото са твърде много
+ 4417 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team/client.go


Файловите разлики са ограничени, защото са твърде много
+ 3336 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team/types.go


+ 91 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_common/types.go

@@ -0,0 +1,91 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package team_common : has no documentation (yet)
+package team_common
+
+import (
+	"time"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// GroupManagementType : The group type determines how a group is managed.
+type GroupManagementType struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for GroupManagementType
+const (
+	GroupManagementTypeUserManaged    = "user_managed"
+	GroupManagementTypeCompanyManaged = "company_managed"
+	GroupManagementTypeSystemManaged  = "system_managed"
+	GroupManagementTypeOther          = "other"
+)
+
+// GroupSummary : Information about a group.
+type GroupSummary struct {
+	// GroupName : has no documentation (yet)
+	GroupName string `json:"group_name"`
+	// GroupId : has no documentation (yet)
+	GroupId string `json:"group_id"`
+	// GroupExternalId : External ID of group. This is an arbitrary ID that an
+	// admin can attach to a group.
+	GroupExternalId string `json:"group_external_id,omitempty"`
+	// MemberCount : The number of members in the group.
+	MemberCount uint32 `json:"member_count,omitempty"`
+	// GroupManagementType : Who is allowed to manage the group.
+	GroupManagementType *GroupManagementType `json:"group_management_type"`
+}
+
+// NewGroupSummary returns a new GroupSummary instance
+func NewGroupSummary(GroupName string, GroupId string, GroupManagementType *GroupManagementType) *GroupSummary {
+	s := new(GroupSummary)
+	s.GroupName = GroupName
+	s.GroupId = GroupId
+	s.GroupManagementType = GroupManagementType
+	return s
+}
+
+// GroupType : The group type determines how a group is created and managed.
+type GroupType struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for GroupType
+const (
+	GroupTypeTeam        = "team"
+	GroupTypeUserManaged = "user_managed"
+	GroupTypeOther       = "other"
+)
+
+// TimeRange : Time range.
+type TimeRange struct {
+	// StartTime : Optional starting time (inclusive).
+	StartTime time.Time `json:"start_time,omitempty"`
+	// EndTime : Optional ending time (exclusive).
+	EndTime time.Time `json:"end_time,omitempty"`
+}
+
+// NewTimeRange returns a new TimeRange instance
+func NewTimeRange() *TimeRange {
+	s := new(TimeRange)
+	return s
+}

+ 202 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_log/client.go

@@ -0,0 +1,202 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package team_log
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"log"
+	"net/http"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// Client interface describes all routes in this namespace
+type Client interface {
+	// GetEvents : Retrieves team events. Permission : Team Auditing.
+	GetEvents(arg *GetTeamEventsArg) (res *GetTeamEventsResult, err error)
+	// GetEventsContinue : Once a cursor has been retrieved from `getEvents`,
+	// use this to paginate through all events. Permission : Team Auditing.
+	GetEventsContinue(arg *GetTeamEventsContinueArg) (res *GetTeamEventsResult, err error)
+}
+
+type apiImpl dropbox.Context
+
+//GetEventsAPIError is an error-wrapper for the get_events route
+type GetEventsAPIError struct {
+	dropbox.APIError
+	EndpointError *GetTeamEventsError `json:"error"`
+}
+
+func (dbx *apiImpl) GetEvents(arg *GetTeamEventsArg) (res *GetTeamEventsResult, err error) {
+	cli := dbx.Client
+
+	if dbx.Config.Verbose {
+		log.Printf("arg: %v", arg)
+	}
+	b, err := json.Marshal(arg)
+	if err != nil {
+		return
+	}
+
+	headers := map[string]string{
+		"Content-Type": "application/json",
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "team_log", "get_events", headers, bytes.NewReader(b))
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		err = json.Unmarshal(body, &res)
+		if err != nil {
+			return
+		}
+
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError GetEventsAPIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+//GetEventsContinueAPIError is an error-wrapper for the get_events/continue route
+type GetEventsContinueAPIError struct {
+	dropbox.APIError
+	EndpointError *GetTeamEventsContinueError `json:"error"`
+}
+
+func (dbx *apiImpl) GetEventsContinue(arg *GetTeamEventsContinueArg) (res *GetTeamEventsResult, err error) {
+	cli := dbx.Client
+
+	if dbx.Config.Verbose {
+		log.Printf("arg: %v", arg)
+	}
+	b, err := json.Marshal(arg)
+	if err != nil {
+		return
+	}
+
+	headers := map[string]string{
+		"Content-Type": "application/json",
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "team_log", "get_events/continue", headers, bytes.NewReader(b))
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		err = json.Unmarshal(body, &res)
+		if err != nil {
+			return
+		}
+
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError GetEventsContinueAPIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+// New returns a Client implementation for this namespace
+func New(c dropbox.Config) *apiImpl {
+	ctx := apiImpl(dropbox.NewContext(c))
+	return &ctx
+}

Файловите разлики са ограничени, защото са твърде много
+ 8836 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_log/types.go


+ 118 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_policies/types.go

@@ -0,0 +1,118 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package team_policies : has no documentation (yet)
+package team_policies
+
+import "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+
+// EmmState : has no documentation (yet)
+type EmmState struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for EmmState
+const (
+	EmmStateDisabled = "disabled"
+	EmmStateOptional = "optional"
+	EmmStateRequired = "required"
+	EmmStateOther    = "other"
+)
+
+// SharedFolderJoinPolicy : Policy governing which shared folders a team member
+// can join.
+type SharedFolderJoinPolicy struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for SharedFolderJoinPolicy
+const (
+	SharedFolderJoinPolicyFromTeamOnly = "from_team_only"
+	SharedFolderJoinPolicyFromAnyone   = "from_anyone"
+	SharedFolderJoinPolicyOther        = "other"
+)
+
+// SharedFolderMemberPolicy : Policy governing who can be a member of a folder
+// shared by a team member.
+type SharedFolderMemberPolicy struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for SharedFolderMemberPolicy
+const (
+	SharedFolderMemberPolicyTeam   = "team"
+	SharedFolderMemberPolicyAnyone = "anyone"
+	SharedFolderMemberPolicyOther  = "other"
+)
+
+// SharedLinkCreatePolicy : Policy governing the visibility of shared links.
+// This policy can apply to newly created shared links, or all shared links.
+type SharedLinkCreatePolicy struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for SharedLinkCreatePolicy
+const (
+	SharedLinkCreatePolicyDefaultPublic   = "default_public"
+	SharedLinkCreatePolicyDefaultTeamOnly = "default_team_only"
+	SharedLinkCreatePolicyTeamOnly        = "team_only"
+	SharedLinkCreatePolicyOther           = "other"
+)
+
+// TeamMemberPolicies : Policies governing team members.
+type TeamMemberPolicies struct {
+	// Sharing : Policies governing sharing.
+	Sharing *TeamSharingPolicies `json:"sharing"`
+	// EmmState : This describes the Enterprise Mobility Management (EMM) state
+	// for this team. This information can be used to understand if an
+	// organization is integrating with a third-party EMM vendor to further
+	// manage and apply restrictions upon the team's Dropbox usage on mobile
+	// devices. This is a new feature and in the future we'll be adding more new
+	// fields and additional documentation.
+	EmmState *EmmState `json:"emm_state"`
+}
+
+// NewTeamMemberPolicies returns a new TeamMemberPolicies instance
+func NewTeamMemberPolicies(Sharing *TeamSharingPolicies, EmmState *EmmState) *TeamMemberPolicies {
+	s := new(TeamMemberPolicies)
+	s.Sharing = Sharing
+	s.EmmState = EmmState
+	return s
+}
+
+// TeamSharingPolicies : Policies governing sharing within and outside of the
+// team.
+type TeamSharingPolicies struct {
+	// SharedFolderMemberPolicy : Who can join folders shared by team members.
+	SharedFolderMemberPolicy *SharedFolderMemberPolicy `json:"shared_folder_member_policy"`
+	// SharedFolderJoinPolicy : Which shared folders team members can join.
+	SharedFolderJoinPolicy *SharedFolderJoinPolicy `json:"shared_folder_join_policy"`
+	// SharedLinkCreatePolicy : Who can view shared links owned by team members.
+	SharedLinkCreatePolicy *SharedLinkCreatePolicy `json:"shared_link_create_policy"`
+}
+
+// NewTeamSharingPolicies returns a new TeamSharingPolicies instance
+func NewTeamSharingPolicies(SharedFolderMemberPolicy *SharedFolderMemberPolicy, SharedFolderJoinPolicy *SharedFolderJoinPolicy, SharedLinkCreatePolicy *SharedLinkCreatePolicy) *TeamSharingPolicies {
+	s := new(TeamSharingPolicies)
+	s.SharedFolderMemberPolicy = SharedFolderMemberPolicy
+	s.SharedFolderJoinPolicy = SharedFolderJoinPolicy
+	s.SharedLinkCreatePolicy = SharedLinkCreatePolicy
+	return s
+}

+ 353 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users/client.go

@@ -0,0 +1,353 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package users
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"log"
+	"net/http"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+)
+
+// Client interface describes all routes in this namespace
+type Client interface {
+	// GetAccount : Get information about a user's account.
+	GetAccount(arg *GetAccountArg) (res *BasicAccount, err error)
+	// GetAccountBatch : Get information about multiple user accounts.  At most
+	// 300 accounts may be queried per request.
+	GetAccountBatch(arg *GetAccountBatchArg) (res []*BasicAccount, err error)
+	// GetCurrentAccount : Get information about the current user's account.
+	GetCurrentAccount() (res *FullAccount, err error)
+	// GetSpaceUsage : Get the space usage information for the current user's
+	// account.
+	GetSpaceUsage() (res *SpaceUsage, err error)
+}
+
+type apiImpl dropbox.Context
+
+//GetAccountAPIError is an error-wrapper for the get_account route
+type GetAccountAPIError struct {
+	dropbox.APIError
+	EndpointError *GetAccountError `json:"error"`
+}
+
+func (dbx *apiImpl) GetAccount(arg *GetAccountArg) (res *BasicAccount, err error) {
+	cli := dbx.Client
+
+	if dbx.Config.Verbose {
+		log.Printf("arg: %v", arg)
+	}
+	b, err := json.Marshal(arg)
+	if err != nil {
+		return
+	}
+
+	headers := map[string]string{
+		"Content-Type": "application/json",
+	}
+	if dbx.Config.AsMemberID != "" {
+		headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "users", "get_account", headers, bytes.NewReader(b))
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		err = json.Unmarshal(body, &res)
+		if err != nil {
+			return
+		}
+
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError GetAccountAPIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+//GetAccountBatchAPIError is an error-wrapper for the get_account_batch route
+type GetAccountBatchAPIError struct {
+	dropbox.APIError
+	EndpointError *GetAccountBatchError `json:"error"`
+}
+
+func (dbx *apiImpl) GetAccountBatch(arg *GetAccountBatchArg) (res []*BasicAccount, err error) {
+	cli := dbx.Client
+
+	if dbx.Config.Verbose {
+		log.Printf("arg: %v", arg)
+	}
+	b, err := json.Marshal(arg)
+	if err != nil {
+		return
+	}
+
+	headers := map[string]string{
+		"Content-Type": "application/json",
+	}
+	if dbx.Config.AsMemberID != "" {
+		headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "users", "get_account_batch", headers, bytes.NewReader(b))
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		err = json.Unmarshal(body, &res)
+		if err != nil {
+			return
+		}
+
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError GetAccountBatchAPIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+//GetCurrentAccountAPIError is an error-wrapper for the get_current_account route
+type GetCurrentAccountAPIError struct {
+	dropbox.APIError
+	EndpointError struct{} `json:"error"`
+}
+
+func (dbx *apiImpl) GetCurrentAccount() (res *FullAccount, err error) {
+	cli := dbx.Client
+
+	headers := map[string]string{}
+	if dbx.Config.AsMemberID != "" {
+		headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "users", "get_current_account", headers, nil)
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		err = json.Unmarshal(body, &res)
+		if err != nil {
+			return
+		}
+
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError GetCurrentAccountAPIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+//GetSpaceUsageAPIError is an error-wrapper for the get_space_usage route
+type GetSpaceUsageAPIError struct {
+	dropbox.APIError
+	EndpointError struct{} `json:"error"`
+}
+
+func (dbx *apiImpl) GetSpaceUsage() (res *SpaceUsage, err error) {
+	cli := dbx.Client
+
+	headers := map[string]string{}
+	if dbx.Config.AsMemberID != "" {
+		headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID
+	}
+
+	req, err := (*dropbox.Context)(dbx).NewRequest("api", "rpc", true, "users", "get_space_usage", headers, nil)
+	if err != nil {
+		return
+	}
+	if dbx.Config.Verbose {
+		log.Printf("req: %v", req)
+	}
+
+	resp, err := cli.Do(req)
+	if dbx.Config.Verbose {
+		log.Printf("resp: %v", resp)
+	}
+	if err != nil {
+		return
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+
+	if dbx.Config.Verbose {
+		log.Printf("body: %s", body)
+	}
+	if resp.StatusCode == http.StatusOK {
+		err = json.Unmarshal(body, &res)
+		if err != nil {
+			return
+		}
+
+		return
+	}
+	if resp.StatusCode == http.StatusConflict {
+		var apiError GetSpaceUsageAPIError
+		err = json.Unmarshal(body, &apiError)
+		if err != nil {
+			return
+		}
+		err = apiError
+		return
+	}
+	var apiError dropbox.APIError
+	if resp.StatusCode == http.StatusBadRequest {
+		apiError.ErrorSummary = string(body)
+		err = apiError
+		return
+	}
+	err = json.Unmarshal(body, &apiError)
+	if err != nil {
+		return
+	}
+	err = apiError
+	return
+}
+
+// New returns a Client implementation for this namespace
+func New(c dropbox.Config) *apiImpl {
+	ctx := apiImpl(dropbox.NewContext(c))
+	return &ctx
+}

+ 357 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users/types.go

@@ -0,0 +1,357 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package users : This namespace contains endpoints and data types for user
+// management.
+package users
+
+import (
+	"encoding/json"
+
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_policies"
+	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users_common"
+)
+
+// Account : The amount of detail revealed about an account depends on the user
+// being queried and the user making the query.
+type Account struct {
+	// AccountId : The user's unique Dropbox ID.
+	AccountId string `json:"account_id"`
+	// Name : Details of a user's name.
+	Name *Name `json:"name"`
+	// Email : The user's e-mail address. Do not rely on this without checking
+	// the `email_verified` field. Even then, it's possible that the user has
+	// since lost access to their e-mail.
+	Email string `json:"email"`
+	// EmailVerified : Whether the user has verified their e-mail address.
+	EmailVerified bool `json:"email_verified"`
+	// ProfilePhotoUrl : URL for the photo representing the user, if one is set.
+	ProfilePhotoUrl string `json:"profile_photo_url,omitempty"`
+	// Disabled : Whether the user has been disabled.
+	Disabled bool `json:"disabled"`
+}
+
+// NewAccount returns a new Account instance
+func NewAccount(AccountId string, Name *Name, Email string, EmailVerified bool, Disabled bool) *Account {
+	s := new(Account)
+	s.AccountId = AccountId
+	s.Name = Name
+	s.Email = Email
+	s.EmailVerified = EmailVerified
+	s.Disabled = Disabled
+	return s
+}
+
+// BasicAccount : Basic information about any account.
+type BasicAccount struct {
+	Account
+	// IsTeammate : Whether this user is a teammate of the current user. If this
+	// account is the current user's account, then this will be true.
+	IsTeammate bool `json:"is_teammate"`
+	// TeamMemberId : The user's unique team member id. This field will only be
+	// present if the user is part of a team and `is_teammate` is true.
+	TeamMemberId string `json:"team_member_id,omitempty"`
+}
+
+// NewBasicAccount returns a new BasicAccount instance
+func NewBasicAccount(AccountId string, Name *Name, Email string, EmailVerified bool, Disabled bool, IsTeammate bool) *BasicAccount {
+	s := new(BasicAccount)
+	s.AccountId = AccountId
+	s.Name = Name
+	s.Email = Email
+	s.EmailVerified = EmailVerified
+	s.Disabled = Disabled
+	s.IsTeammate = IsTeammate
+	return s
+}
+
+// FullAccount : Detailed information about the current user's account.
+type FullAccount struct {
+	Account
+	// Country : The user's two-letter country code, if available. Country codes
+	// are based on `ISO 3166-1` <http://en.wikipedia.org/wiki/ISO_3166-1>.
+	Country string `json:"country,omitempty"`
+	// Locale : The language that the user specified. Locale tags will be `IETF
+	// language tags` <http://en.wikipedia.org/wiki/IETF_language_tag>.
+	Locale string `json:"locale"`
+	// ReferralLink : The user's `referral link`
+	// <https://www.dropbox.com/referrals>.
+	ReferralLink string `json:"referral_link"`
+	// Team : If this account is a member of a team, information about that
+	// team.
+	Team *FullTeam `json:"team,omitempty"`
+	// TeamMemberId : This account's unique team member id. This field will only
+	// be present if `team` is present.
+	TeamMemberId string `json:"team_member_id,omitempty"`
+	// IsPaired : Whether the user has a personal and work account. If the
+	// current account is personal, then `team` will always be nil, but
+	// `is_paired` will indicate if a work account is linked.
+	IsPaired bool `json:"is_paired"`
+	// AccountType : What type of account this user has.
+	AccountType *users_common.AccountType `json:"account_type"`
+}
+
+// NewFullAccount returns a new FullAccount instance
+func NewFullAccount(AccountId string, Name *Name, Email string, EmailVerified bool, Disabled bool, Locale string, ReferralLink string, IsPaired bool, AccountType *users_common.AccountType) *FullAccount {
+	s := new(FullAccount)
+	s.AccountId = AccountId
+	s.Name = Name
+	s.Email = Email
+	s.EmailVerified = EmailVerified
+	s.Disabled = Disabled
+	s.Locale = Locale
+	s.ReferralLink = ReferralLink
+	s.IsPaired = IsPaired
+	s.AccountType = AccountType
+	return s
+}
+
+// Team : Information about a team.
+type Team struct {
+	// Id : The team's unique ID.
+	Id string `json:"id"`
+	// Name : The name of the team.
+	Name string `json:"name"`
+}
+
+// NewTeam returns a new Team instance
+func NewTeam(Id string, Name string) *Team {
+	s := new(Team)
+	s.Id = Id
+	s.Name = Name
+	return s
+}
+
+// FullTeam : Detailed information about a team.
+type FullTeam struct {
+	Team
+	// SharingPolicies : Team policies governing sharing.
+	SharingPolicies *team_policies.TeamSharingPolicies `json:"sharing_policies"`
+}
+
+// NewFullTeam returns a new FullTeam instance
+func NewFullTeam(Id string, Name string, SharingPolicies *team_policies.TeamSharingPolicies) *FullTeam {
+	s := new(FullTeam)
+	s.Id = Id
+	s.Name = Name
+	s.SharingPolicies = SharingPolicies
+	return s
+}
+
+// GetAccountArg : has no documentation (yet)
+type GetAccountArg struct {
+	// AccountId : A user's account identifier.
+	AccountId string `json:"account_id"`
+}
+
+// NewGetAccountArg returns a new GetAccountArg instance
+func NewGetAccountArg(AccountId string) *GetAccountArg {
+	s := new(GetAccountArg)
+	s.AccountId = AccountId
+	return s
+}
+
+// GetAccountBatchArg : has no documentation (yet)
+type GetAccountBatchArg struct {
+	// AccountIds : List of user account identifiers.  Should not contain any
+	// duplicate account IDs.
+	AccountIds []string `json:"account_ids"`
+}
+
+// NewGetAccountBatchArg returns a new GetAccountBatchArg instance
+func NewGetAccountBatchArg(AccountIds []string) *GetAccountBatchArg {
+	s := new(GetAccountBatchArg)
+	s.AccountIds = AccountIds
+	return s
+}
+
+// GetAccountBatchError : has no documentation (yet)
+type GetAccountBatchError struct {
+	dropbox.Tagged
+	// NoAccount : The value is an account ID specified in
+	// `GetAccountBatchArg.account_ids` that does not exist.
+	NoAccount string `json:"no_account,omitempty"`
+}
+
+// Valid tag values for GetAccountBatchError
+const (
+	GetAccountBatchErrorNoAccount = "no_account"
+	GetAccountBatchErrorOther     = "other"
+)
+
+// UnmarshalJSON deserializes into a GetAccountBatchError instance
+func (u *GetAccountBatchError) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "no_account":
+		err = json.Unmarshal(body, &u.NoAccount)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// GetAccountError : has no documentation (yet)
+type GetAccountError struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for GetAccountError
+const (
+	GetAccountErrorNoAccount = "no_account"
+	GetAccountErrorOther     = "other"
+)
+
+// IndividualSpaceAllocation : has no documentation (yet)
+type IndividualSpaceAllocation struct {
+	// Allocated : The total space allocated to the user's account (bytes).
+	Allocated uint64 `json:"allocated"`
+}
+
+// NewIndividualSpaceAllocation returns a new IndividualSpaceAllocation instance
+func NewIndividualSpaceAllocation(Allocated uint64) *IndividualSpaceAllocation {
+	s := new(IndividualSpaceAllocation)
+	s.Allocated = Allocated
+	return s
+}
+
+// Name : Representations for a person's name to assist with
+// internationalization.
+type Name struct {
+	// GivenName : Also known as a first name.
+	GivenName string `json:"given_name"`
+	// Surname : Also known as a last name or family name.
+	Surname string `json:"surname"`
+	// FamiliarName : Locale-dependent name. In the US, a person's familiar name
+	// is their `given_name`, but elsewhere, it could be any combination of a
+	// person's `given_name` and `surname`.
+	FamiliarName string `json:"familiar_name"`
+	// DisplayName : A name that can be used directly to represent the name of a
+	// user's Dropbox account.
+	DisplayName string `json:"display_name"`
+	// AbbreviatedName : An abbreviated form of the person's name. Their
+	// initials in most locales.
+	AbbreviatedName string `json:"abbreviated_name"`
+}
+
+// NewName returns a new Name instance
+func NewName(GivenName string, Surname string, FamiliarName string, DisplayName string, AbbreviatedName string) *Name {
+	s := new(Name)
+	s.GivenName = GivenName
+	s.Surname = Surname
+	s.FamiliarName = FamiliarName
+	s.DisplayName = DisplayName
+	s.AbbreviatedName = AbbreviatedName
+	return s
+}
+
+// SpaceAllocation : Space is allocated differently based on the type of
+// account.
+type SpaceAllocation struct {
+	dropbox.Tagged
+	// Individual : The user's space allocation applies only to their individual
+	// account.
+	Individual *IndividualSpaceAllocation `json:"individual,omitempty"`
+	// Team : The user shares space with other members of their team.
+	Team *TeamSpaceAllocation `json:"team,omitempty"`
+}
+
+// Valid tag values for SpaceAllocation
+const (
+	SpaceAllocationIndividual = "individual"
+	SpaceAllocationTeam       = "team"
+	SpaceAllocationOther      = "other"
+)
+
+// UnmarshalJSON deserializes into a SpaceAllocation instance
+func (u *SpaceAllocation) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+		// Individual : The user's space allocation applies only to their
+		// individual account.
+		Individual json.RawMessage `json:"individual,omitempty"`
+		// Team : The user shares space with other members of their team.
+		Team json.RawMessage `json:"team,omitempty"`
+	}
+	var w wrap
+	var err error
+	if err = json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "individual":
+		err = json.Unmarshal(body, &u.Individual)
+
+		if err != nil {
+			return err
+		}
+	case "team":
+		err = json.Unmarshal(body, &u.Team)
+
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// SpaceUsage : Information about a user's space usage and quota.
+type SpaceUsage struct {
+	// Used : The user's total space usage (bytes).
+	Used uint64 `json:"used"`
+	// Allocation : The user's space allocation.
+	Allocation *SpaceAllocation `json:"allocation"`
+}
+
+// NewSpaceUsage returns a new SpaceUsage instance
+func NewSpaceUsage(Used uint64, Allocation *SpaceAllocation) *SpaceUsage {
+	s := new(SpaceUsage)
+	s.Used = Used
+	s.Allocation = Allocation
+	return s
+}
+
+// TeamSpaceAllocation : has no documentation (yet)
+type TeamSpaceAllocation struct {
+	// Used : The total space currently used by the user's team (bytes).
+	Used uint64 `json:"used"`
+	// Allocated : The total space allocated to the user's team (bytes).
+	Allocated uint64 `json:"allocated"`
+}
+
+// NewTeamSpaceAllocation returns a new TeamSpaceAllocation instance
+func NewTeamSpaceAllocation(Used uint64, Allocated uint64) *TeamSpaceAllocation {
+	s := new(TeamSpaceAllocation)
+	s.Used = Used
+	s.Allocated = Allocated
+	return s
+}

+ 37 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users_common/types.go

@@ -0,0 +1,37 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package users_common : This namespace contains common data types used within
+// the users namespace
+package users_common
+
+import "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
+
+// AccountType : What type of account this user has.
+type AccountType struct {
+	dropbox.Tagged
+}
+
+// Valid tag values for AccountType
+const (
+	AccountTypeBasic    = "basic"
+	AccountTypePro      = "pro"
+	AccountTypeBusiness = "business"
+)

+ 237 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/README.md

@@ -0,0 +1,237 @@
+# Dropbox Go SDK Generator
+
+This directory contains the [Stone](https://github.com/dropbox/stone) code generators
+used to programmatically generate the [Dropbox Go SDK](https://github.com/dropbox/dropbox-sdk-go).
+
+## Requirements
+
+  * While not a hard requirement, this repo currently assumes `python3` in the path.
+  * Assumes you have already installed [Stone](https://github.com/dropbox/stone)
+  * Requires [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) to fix up imports in the auto-generated code
+
+## Basic Setup
+
+  . Clone this repo
+  . Run `git submodule init` followed by `git submodule update`
+  . Run `./generate-sdk.sh` to generate code under `../dropbox`
+
+## Generated Code
+
+### Basic Types
+
+Here is how Stone [basic types](https://github.com/dropbox/stone/blob/master/doc/lang_ref.rst#basic-types) map to Go types:
+
+Stone Type | Go Type
+---------- | -------
+Int32/Int64/UInt32/UInt64 | int32/int64/uint32/uint64
+Float32/Float64 | float32/float64
+Boolean | bool
+String | string
+Timestamp | time.Time
+Void | struct{}
+
+### Structs
+
+Stone [structs](https://github.com/dropbox/stone/blob/master/doc/lang_ref.rst#struct) are represented as Go [structs](https://gobyexample.com/structs) in a relatively straight-forward manner. Each struct member is exported and also gets assigned the correct json tag. The latter is used for serializing requests and deserializing responses. Non-primitive types are represented as pointers to the corresponding type.
+
+```
+struct Account
+    "The amount of detail revealed about an account depends on the user
+    being queried and the user making the query."
+
+    account_id AccountId
+        "The user's unique Dropbox ID."
+    name Name
+        "Details of a user's name."
+```
+
+```go
+// The amount of detail revealed about an account depends on the user being
+// queried and the user making the query.
+type Account struct {
+	// The user's unique Dropbox ID.
+	AccountId string `json:"account_id"`
+	// Details of a user's name.
+	Name *Name `json:"name"`
+}
+```
+
+#### Inheritance
+
+Stone supports [struct inheritance](https://github.com/dropbox/stone/blob/master/doc/lang_ref.rst#inheritance). In Go, we support this via [embedding](https://golang.org/doc/effective_go.html#embedding)
+
+```
+struct BasicAccount extends Account
+    "Basic information about any account."
+
+    is_teammate Boolean
+        "Whether this user is a teammate of the current user. If this account
+        is the current user's account, then this will be :val:`true`."
+```
+
+```go
+// Basic information about any account.
+type BasicAccount struct {
+	Account
+	// Whether this user is a teammate of the current user. If this account is
+	// the current user's account, then this will be `True`.
+	IsTeammate bool `json:"is_teammate"`
+```
+
+### Unions
+
+Stone https://github.com/dropbox/stone/blob/master/doc/lang_ref.rst#union[unions] are bit more complex as Go doesn't have native support for union types (tagged or otherwise). We declare a union as a Go struct with all the possible fields as pointer types, and then use the tag value to populate the correct field during deserialization. This necessitates the use of an intermediate wrapper struct for the deserialization to work correctly, see below for a concrete example.
+
+```
+union SpaceAllocation
+    "Space is allocated differently based on the type of account."
+
+    individual IndividualSpaceAllocation
+        "The user's space allocation applies only to their individual account."
+    team TeamSpaceAllocation
+        "The user shares space with other members of their team."
+```
+
+```go
+// Space is allocated differently based on the type of account.
+type SpaceAllocation struct {
+	dropbox.Tagged
+	// The user's space allocation applies only to their individual account.
+	Individual *IndividualSpaceAllocation `json:"individual,omitempty"`
+	// The user shares space with other members of their team.
+	Team *TeamSpaceAllocation `json:"team,omitempty"`
+}
+
+// Valid tag values for `SpaceAllocation`
+const (
+	SpaceAllocation_Individual = "individual"
+	SpaceAllocation_Team       = "team"
+	SpaceAllocation_Other      = "other"
+)
+
+func (u *SpaceAllocation) UnmarshalJSON(body []byte) error {
+	type wrap struct {
+		dropbox.Tagged
+		// The user's space allocation applies only to their individual account.
+		Individual json.RawMessage `json:"individual,omitempty"`
+		// The user shares space with other members of their team.
+		Team json.RawMessage `json:"team,omitempty"`
+	}
+	var w wrap
+	if err := json.Unmarshal(body, &w); err != nil {
+		return err
+	}
+	u.Tag = w.Tag
+	switch u.Tag {
+	case "individual":
+		if err := json.Unmarshal(body, &u.Individual); err != nil {
+			return err
+		}
+
+	case "team":
+		if err := json.Unmarshal(body, &u.Team); err != nil {
+			return err
+		}
+
+	}
+	return nil
+}
+```
+
+### Struct with Enumerated Subtypes
+
+Per the https://github.com/dropbox/stone/blob/master/doc/lang_ref.rst#struct-polymorphism[spec], structs with enumerated subtypes are a mechanism of inheritance:
+
+> If a struct enumerates its subtypes, an instance of any subtype will satisfy the type constraint. This is useful when wanting to discriminate amongst types that are part of the same hierarchy while simultaneously being able to avoid discriminating when accessing common fields.
+
+To represent structs with enumerated subtypes in Go, we use a combination of Go interface types and unions as implemented above. Considering the following:
+
+```
+struct Metadata
+    union
+        file FileMetadata
+        folder FolderMetadata
+        deleted DeletedMetadata  # Used by list_folder* and search
+
+    name String
+    path_lower String?
+    path_display String?
+    parent_shared_folder_id common.SharedFolderId?
+    
+struct FileMetadata extends Metadata
+    id Id
+    client_modified common.DropboxTimestamp
+    ...
+```
+
+In this case, `FileMetadata`, `FolderMetadata` etc are subtypes of `Metadata`. Specifically, any subtype can be used where a parent type is expected. Thus, if `list_folder` returns a list of `Metadata`s, we should be able to parse and "upcast" to one of the enumerated subtypes.
+
+First, we define structs to represent the base and enumerated types as we did for inherited structs above:
+
+```go
+type Metadata struct {
+	Name string `json:"name"`
+	PathLower string `json:"path_lower,omitempty"`
+	PathDisplay string `json:"path_display,omitempty"`
+	ParentSharedFolderId string `json:"parent_shared_folder_id,omitempty"`
+}
+
+type FileMetadata struct {
+	Metadata
+	Id string `json:"id"`
+	ClientModified time.Time `json:"client_modified"`
+	...
+}
+```
+
+Next, we define an interface type with a dummy method and ensure that both the base and the subtypes implement the interface:
+
+```go
+type IsMetadata interface {
+	IsMetadata()
+}
+
+func (u *Metadata) IsMetadata() {} // Subtypes get this for free due to embedding
+```
+
+At this point, types or methods that accept/return a struct with enumerated subtypes can use the interface type instead. For instance:
+
+```go
+func GetMetadata(arg *GetMetadataArg) (res IsMetadata, err error) {...}
+
+type ListFolderResult struct {
+	// The files and (direct) subfolders in the folder.
+	Entries []IsMetadata `json:"entries"`
+	...
+}
+```
+
+Finally, to actually deserialize a bag of bytes into the appropriate type or subtype, we use a trick similar to how we handle unions above.
+
+```go
+type metadataUnion struct {
+	dropbox.Tagged
+	File    *FileMetadata    `json:"file,omitempty"`
+	Folder  *FolderMetadata  `json:"folder,omitempty"`
+	Deleted *DeletedMetadata `json:"deleted,omitempty"`
+}
+
+func (u *metadataUnion) UnmarshalJSON(body []byte) error {...}
+
+func (dbx *apiImpl) GetMetadata(arg *GetMetadataArg) (res IsMetadata, err error) {
+   	...
+   	var tmp metadataUnion
+	err = json.Unmarshal(body, &tmp)
+	if err != nil {
+		return
+	}
+	switch tmp.Tag {
+	case "file":
+		res = tmp.File
+	case "folder":
+		res = tmp.Folder
+	case "deleted":
+		res = tmp.Deleted
+	}
+}
+```

+ 5 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/README.md

@@ -0,0 +1,5 @@
+Dropbox API Spec
+================
+
+The Stone API specification that describes the Dropbox v2 API.
+

+ 96 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/async.stone

@@ -0,0 +1,96 @@
+namespace async
+
+#
+# Types for writing asynchronous API methods.
+#
+# There are two calls for each asynchronous method:
+#  1. A "Launch" method that (optionally) launches the asynchronous job
+#  2. A "Polling" method that polls for the status of the job that was launched by the first call.
+#
+# The following definitions are prefixed by "Launch" or "Poll", according to their intended use.
+
+
+alias AsyncJobId = String(min_length=1)
+
+
+#
+# Launch
+#
+
+union_closed LaunchResultBase
+    "Result returned by methods that launch an asynchronous job.
+
+    A method who may either launch an asynchronous job, or complete the request
+    synchronously, can use this union by extending it, and adding a 'complete' field
+    with the type of the synchronous response.
+
+    See :type:`LaunchEmptyResult` for an example."
+
+    async_job_id AsyncJobId
+        "This response indicates that the processing is asynchronous.
+        The string is an id that can be used to obtain the status of the asynchronous job."
+
+
+union_closed LaunchEmptyResult extends LaunchResultBase
+    "Result returned by methods that may either launch an asynchronous job or complete synchronously.
+    Upon synchronous completion of the job, no additional information is returned."
+
+    complete
+        "The job finished synchronously and successfully."
+
+    example complete
+        complete = null
+
+    example async_job_id
+        async_job_id = "34g93hh34h04y384084"
+
+#
+# Poll
+#
+
+struct PollArg
+    "Arguments for methods that poll the status of an asynchronous job."
+
+    async_job_id AsyncJobId
+        "Id of the asynchronous job.
+        This is the value of a response returned from the method that launched the job."
+
+    example default
+        async_job_id = "34g93hh34h04y384084"
+
+# TODO(kelkabany): Remove `error_msg` since others might want to return it
+# differently.
+union_closed PollResultBase
+    "Result returned by methods that poll for the status of an asynchronous job.
+    Unions that extend this union should add a 'complete' field with a type of
+    the information returned upon job completion.
+
+    See :type:`PollEmptyResult` for an example."
+
+    in_progress
+        "The asynchronous job is still in progress."
+
+
+union_closed PollEmptyResult extends PollResultBase
+    "Result returned by methods that poll for the status of an asynchronous job.
+    Upon completion of the job, no additional information is returned."
+
+    complete
+        "The asynchronous job has completed successfully."
+
+    example complete
+        complete = null
+
+    example in_progress
+        in_progress = null
+
+
+union PollError
+    "Error returned by methods for polling the status of asynchronous job."
+
+    invalid_async_job_id
+        "The job ID is invalid."
+    internal_error
+        "Something went wrong with the job on Dropbox's end. You'll need to
+        verify that the action you were taking succeeded, and if not, try
+        again. This should happen very rarely."

+ 91 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/auth.stone

@@ -0,0 +1,91 @@
+namespace auth
+
+union AuthError
+    "Errors occurred during authentication."
+
+    invalid_access_token
+        "The access token is invalid."
+    invalid_select_user
+        "The user specified in 'Dropbox-API-Select-User' is no longer on the team."
+    invalid_select_admin
+        "The user specified in 'Dropbox-API-Select-Admin' is not a Dropbox Business team admin."
+    user_suspended
+        "The user has been suspended."
+
+route token/revoke(Void, Void, Void)
+    "Disables the access token used to authenticate the call."
+
+    attrs
+        owner = "dev-plat"
+        allow_app_folder_app = true
+
+union RateLimitReason
+    too_many_requests
+        "You are making too many requests in the past few minutes."
+    too_many_write_operations
+        "There are currently too many write operations happening in the user's Dropbox."
+
+struct RateLimitError
+    "Error occurred because the app is being rate limited."
+
+    reason RateLimitReason
+        "The reason why the app is being rate limited."
+
+    retry_after UInt64 = 1
+        "The number of seconds that the app should wait
+        before making another request."
+
+#
+# OAuth 1.0 token conversion
+#
+
+struct TokenFromOAuth1Arg
+    oauth1_token String(min_length=1)
+        "The supplied OAuth 1.0 access token."
+    oauth1_token_secret String(min_length=1)
+        "The token secret associated with the supplied access token."
+
+    example default
+        oauth1_token = "qievr8hamyg6ndck"
+        oauth1_token_secret = "qomoftv0472git7"
+
+struct TokenFromOAuth1Result
+    oauth2_token String(min_length=1)
+        "The OAuth 2.0 token generated from the supplied OAuth 1.0 token."
+
+    example default
+        oauth2_token = "9mCrkS7BIdAAAAAAAAAAHHS0TsSnpYvKQVtKdBnN5IuzhYOGblSgTcHgBFKFMmFn"
+
+union TokenFromOAuth1Error
+    invalid_oauth1_token_info
+        "Part or all of the OAuth 1.0 access token info is invalid."
+    app_id_mismatch
+        "The authorized app does not match the app associated with the supplied access token."
+
+route token/from_oauth1(TokenFromOAuth1Arg, TokenFromOAuth1Result, TokenFromOAuth1Error)
+    "Creates an OAuth 2.0 access token from the supplied OAuth 1.0 access token."
+    attrs
+        auth = "app"
+        owner = "dev-plat"
+        allow_app_folder_app = true
+
+union AccessError
+    "Error occurred because the account doesn't have permission to access the resource."
+
+    invalid_account_type InvalidAccountTypeError
+        "Current account type cannot access the resource."
+
+    paper_access_denied PaperAccessError
+        "Current account cannot access Paper."
+
+union PaperAccessError
+    paper_disabled
+        "Paper is disabled."
+    not_paper_user
+        "The provided user has not used Paper yet."
+
+union InvalidAccountTypeError
+    endpoint
+        "Current account type doesn't have permission to access this route endpoint."
+    feature
+        "Current account type doesn't have permission to access this feature."

+ 60 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/common.stone

@@ -0,0 +1,60 @@
+namespace common
+
+alias DropboxTimestamp = Timestamp("%Y-%m-%dT%H:%M:%SZ")
+
+alias Date = Timestamp("%Y-%m-%d")
+
+# Note - "\\." is needed in order to translate to "\."
+alias EmailAddress = String(pattern="^['&A-Za-z0-9._%+-]+@[A-Za-z0-9-][A-Za-z0-9.-]*.[A-Za-z]{2,15}$", max_length=255)
+
+# First name or Last name. NOTE: max_length should be synced with USER_NAME_MAX_LEN
+alias NamePart = String(pattern="[^\/:?*<>\"|]*", min_length=1, max_length=100)
+
+# Display name. We don't limit the length because it's always generated from the first & last names.
+alias DisplayName = String(pattern="[^\/:?*<>\"|]*", min_length=1)
+
+alias NamespaceId = String(pattern="[-_0-9a-zA-Z:]+")
+alias SharedFolderId = NamespaceId
+
+alias SessionId = String
+
+alias PathRootId = NamespaceId
+
+union PathRoot
+    home
+        "Paths are relative to the authenticating user's home directory,
+        whether or not that user belongs to a team."
+
+    member_home
+        "Paths are relative to the authenticating team member's home
+        directory. (This results in :field:`PathRootError.invalid' if the
+        user does not belong to a team.)"
+
+    team PathRootId
+        "Paths are relative to the given team directory. (This results in
+        :field:`PathRootError.invalid` if the user is not a member of
+        the team associated with that path root id.)"
+
+    user_home
+        "Paths are relative to the user's home directory. (This results in
+        :field:`PathRootError.invalid` if the belongs to a team.)"
+
+    shared_folder PathRootId
+        "Paths are relative to given shared folder id (This results in
+        :field:`PathRootError.no_permission` if you don't have access
+        to  this shared folder.)"
+
+
+struct InvalidPathRootError
+    path_root PathRootId?
+        "The latest path root id for user's team if the user is still in
+        a team."
+
+
+union PathRootError
+    invalid InvalidPathRootError
+        "The path root id value in Dropbox-API-Path-Root header is no longer
+        valid."
+    no_permission
+        "You don't have permission to access the path root id in Dropbox-API-Path-Root
+         header."

Файловите разлики са ограничени, защото са твърде много
+ 1560 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/files.stone


+ 193 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/files_properties.stone

@@ -0,0 +1,193 @@
+namespace files
+
+
+#
+# Property Group routes
+#
+
+route properties/add(PropertyGroupWithPath, Void, AddPropertiesError)
+    "Add custom properties to a file using a filled property template.
+    See properties/template/add to create new property templates."
+
+    attrs
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+route properties/overwrite(PropertyGroupWithPath, Void, InvalidPropertyGroupError)
+    "Overwrite custom properties from a specified template
+    associated with a file."
+
+    attrs
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+route properties/update(UpdatePropertyGroupArg, Void, UpdatePropertiesError)
+    "Add, update or remove custom properties from a specified template
+    associated with a file. Fields that already exist and not described in
+    the request will not be modified."
+
+    attrs
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+route properties/remove(RemovePropertiesArg, Void, RemovePropertiesError)
+    "Remove all custom properties from a specified template associated with a file.
+    To remove specific property key value pairs, see :route:`properties/update`.
+    To update a property template, see properties/template/update.
+    Property templates can't be removed once created."
+
+    attrs
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+route properties/template/get(properties.GetPropertyTemplateArg, properties.GetPropertyTemplateResult, properties.PropertyTemplateError)
+    "Get the schema for a specified template."
+
+    attrs
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+route properties/template/list(Void, properties.ListPropertyTemplateIds, properties.PropertyTemplateError)
+    "Get the property template identifiers for a user. To get the schema of
+    each template use :route:`properties/template/get`."
+
+    attrs
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+struct RemovePropertiesArg
+    path PathOrId
+        "A unique identifier for the file."
+    property_template_ids List(properties.TemplateId)
+        "A list of identifiers for a property template created by route properties/template/add."
+
+    example default
+        path = "/my_awesome/word.docx"
+        property_template_ids = ["ptid:1a5n2i6d3OYEAAAAAAAAAYa"]
+
+struct PropertyGroupWithPath
+    path PathOrId
+        "A unique identifier for the file."
+    property_groups List(properties.PropertyGroup)
+        "Filled custom property templates associated with a file."
+
+    example default
+        path = "/my_awesome/word.docx"
+        property_groups = [default]
+
+struct UpdatePropertyGroupArg
+    path PathOrId
+        "A unique identifier for the file."
+    update_property_groups List(PropertyGroupUpdate)
+        "Filled custom property templates associated with a file."
+
+    example default
+        path = "/my_awesome/word.docx"
+        update_property_groups = [default]
+
+struct PropertyGroupUpdate
+    template_id properties.TemplateId
+        "A unique identifier for a property template."
+    add_or_update_fields List(properties.PropertyField)?
+        "List of property fields to update if the field already exists. If the field doesn't exist,
+        add the field to the property group."
+    remove_fields List(String)?
+        "List of property field names to remove from property group if the field exists."
+
+    example default
+        template_id = "ptid:1a5n2i6d3OYEAAAAAAAAAYa"
+        add_or_update_fields = [default]
+        remove_fields = []
+
+union AddPropertiesError extends InvalidPropertyGroupError
+    property_group_already_exists
+        "This property group already exists for this file."
+
+union UpdatePropertiesError extends InvalidPropertyGroupError
+    property_group_lookup LookUpPropertiesError
+
+union RemovePropertiesError extends PropertiesError
+    property_group_lookup LookUpPropertiesError
+
+union_closed LookUpPropertiesError
+    property_group_not_found
+        "This property group does not exist for this file."
+
+union InvalidPropertyGroupError extends PropertiesError
+    property_field_too_large
+        "A field value in this property group is too large."
+    does_not_fit_template
+        "The property group specified does not conform to the property template."
+
+union PropertiesError extends properties.PropertyTemplateError
+    path LookupError
+
+#
+# Patched /get_metadata that can return properties
+#
+
+route alpha/get_metadata (AlphaGetMetadataArg, Metadata, AlphaGetMetadataError)
+    "Returns the metadata for a file or folder. This is an alpha endpoint
+    compatible with the properties API.
+
+    Note: Metadata for the root folder is unsupported."
+
+    attrs
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+struct AlphaGetMetadataArg extends GetMetadataArg
+    include_property_templates List(properties.TemplateId)?
+        "If set to a valid list of template IDs,
+        :field:`FileMetadata.property_groups` is set for files with custom
+        properties."
+
+    example default
+        path = "/Homework/math"
+
+    example id
+        path = "id:a4ayc_80_OEAAAAAAAAAYa"
+
+    example rev
+        path = "rev:a1c10ce0dd78"
+
+union_closed AlphaGetMetadataError extends GetMetadataError
+    properties_error LookUpPropertiesError
+
+#
+# Patched /upload that accepts properties
+#
+
+route alpha/upload (CommitInfoWithProperties, FileMetadata, UploadErrorWithProperties)
+    "Create a new file with the contents provided in the request. Note that this
+    endpoint is part of the properties API alpha and is slightly different from
+    :route:`upload`.
+
+    Do not use this to upload a file larger than 150 MB. Instead, create an
+    upload session with :route:`upload_session/start`."
+
+    attrs
+        host="content"
+        style="upload"
+        api_group="properties"
+        is_preview=true
+        allow_app_folder_app = true
+
+struct CommitInfoWithProperties extends CommitInfo
+    property_groups List(properties.PropertyGroup)?
+        "List of custom properties to add to file."
+
+    example default
+        path = "/Homework/math/Matrices.txt"
+        autorename = true
+        property_groups = [default]
+
+union UploadErrorWithProperties extends UploadError
+    properties_error InvalidPropertyGroupError

+ 549 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/paper.stone

@@ -0,0 +1,549 @@
+namespace paper
+    "This namespace contains endpoints and data types for managing docs and folders in Dropbox Paper."
+
+import common
+import sharing
+
+alias PaperDocId = String
+    "Paper doc ID."
+
+struct RefPaperDoc
+    doc_id PaperDocId
+        "The Paper doc ID."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+
+union ListPaperDocsFilterBy
+    docs_accessed
+        "Fetches all Paper doc IDs that the user has ever accessed."
+    docs_created
+        "Fetches only the Paper doc IDs that the user has created."
+
+union ListPaperDocsSortBy
+    accessed
+        "Sorts the Paper docs by the time they were last accessed."
+    modified
+        "Sorts the Paper docs by the time they were last modified."
+    created
+        "Sorts the Paper docs by the creation time."
+
+union ListPaperDocsSortOrder
+    ascending
+        "Sorts the search result in ascending order."
+    descending
+        "Sorts the search result in descending order."
+
+struct ListPaperDocsArgs
+    filter_by ListPaperDocsFilterBy = docs_accessed
+        "Allows user to specify how the Paper docs should be filtered."
+    sort_by ListPaperDocsSortBy = accessed
+        "Allows user to specify how the Paper docs should be sorted."
+    sort_order ListPaperDocsSortOrder = ascending
+        "Allows user to specify the sort order of the result."
+    limit Int32(min_value=1, max_value=1000) = 1000
+        "Size limit per batch. The maximum number of docs that
+        can be retrieved per batch is 1000. Higher value results in invalid arguments error."
+    example default
+        filter_by = docs_created
+        sort_by = modified
+        sort_order = descending
+        limit = 100
+
+struct ListPaperDocsContinueArgs
+    cursor String
+        "The cursor obtained from :route:`docs/list` or :route:`docs/list/continue`.
+        Allows for pagination."
+    example default
+        cursor = "U60b6BxT43ySd5sAVQbbIvoteSnWLjUdLU7aR25hbt3ySd5sAVQbbIvoteSnWLjUd"
+
+struct Cursor
+    value String
+        "The actual cursor value."
+    expiration common.DropboxTimestamp?
+        "Expiration time of :field:`value`.
+
+        Some cursors might have expiration time assigned. This is a UTC value after which
+        the cursor is no longer valid and the API starts returning an error.
+        If cursor expires a new one needs to be obtained and pagination needs to be restarted.
+        Some cursors might be short-lived some cursors might be long-lived.
+
+        This really depends on the sorting type and order, e.g.:
+
+        1. on one hand, listing docs created by the user, sorted by the created time
+        ascending will have undefinite expiration because the results cannot change while
+        the iteration is happening. This cursor would be suitable for long term polling.
+
+        2. on the other hand, listing docs sorted by the last modified time will have
+        a very short expiration as docs do get modified very often and the modified time
+        can be changed while the iteration is happening thus altering the results.
+        "
+    example default
+        value = "zHZvTPBnXilGgm1AmDgVyZ10zf7qb0qznd5sAVQbbIvoteSnWLjUdLU7aR25hb"
+        expiration = "2016-08-07T14:56:15Z"
+
+struct ListPaperDocsResponse
+    doc_ids List(String)
+        "The list of Paper doc IDs that can be used to access the given Paper docs or
+        supplied to other API methods.
+        The list is sorted in the order specified by the initial call to :route:`docs/list`."
+    cursor Cursor
+        "Pass the cursor into :route:`docs/list/continue` to paginate through all files.
+        The cursor preserves all properties as specified in the original call
+        to :route:`docs/list`.
+        "
+    has_more Boolean
+        "Will be set to True if a subsequent call with the provided cursor
+        to :route:`docs/list/continue` returns immediately with some results. If set to False
+        please allow some delay before making another call to :route:`docs/list/continue`."
+
+    example default
+        doc_ids = ["zO1E7coc54sE8IuMdUoxz", "mm1AmDgVyZ10zf7qb0qzn", "dByYHZvTPBnXilGgyc5mm"]
+        cursor = default
+        has_more = true
+
+union ExportFormat
+    "The desired export format of the Paper doc."
+
+    html
+        "The HTML export format."
+    markdown
+        "The markdown export format."
+
+struct PaperDocExport extends RefPaperDoc
+    export_format ExportFormat
+
+    example default
+        export_format = markdown
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+
+struct PaperDocExportResult
+    owner String
+        "The Paper doc owner's email address."
+    title String
+        "The Paper doc title."
+    revision Int64
+        "The Paper doc revision. Simply an ever increasing number."
+    mime_type String
+        "MIME type of the export. This corresponds to :type:`ExportFormat` specified
+        in the request."
+
+    example default
+        owner = "james@example.com"
+        title = "Week one retention"
+        revision = 456736745
+        mime_type = "text/x-markdown"
+
+union_closed DocSubscriptionLevel
+    "The subscription level of a Paper doc."
+
+    default
+        "No change email messages unless you're the creator."
+    ignore
+        "Ignored: Not shown in pad lists or activity and no email message is sent."
+    every
+        "Subscribed: Shown in pad lists and activity and change email messages are sent."
+    no_email
+        "Unsubscribed: Shown in pad lists, but not in activity and no change email messages are sent."
+
+union_closed FolderSubscriptionLevel
+    "The subscription level of a Paper folder."
+
+    none
+        "Not shown in activity, no email messages."
+    activity_only
+        "Shown in activity, no email messages."
+    daily_emails
+        "Shown in activity, daily email messages."
+    weekly_emails
+        "Shown in activity, weekly email messages."
+
+union_closed FolderSharingPolicyType
+    "The sharing policy of a Paper folder.
+
+
+    Note: The sharing policy of subfolders is inherited from the root folder."
+    team
+        "Everyone in your team and anyone directly invited can access this folder."
+    invite_only
+        "Only people directly invited can access this folder."
+
+union_closed SharingTeamPolicyType
+    "The sharing policy type of the Paper doc."
+
+    people_with_link_can_edit
+        "Users who have a link to this doc can edit it."
+    people_with_link_can_view_and_comment
+        "Users who have a link to this doc can view and comment on it."
+    invite_only
+        "Users must be explicitly invited to this doc."
+
+union_closed SharingPublicPolicyType extends SharingTeamPolicyType
+    disabled
+        "Value used to indicate that doc sharing is enabled only within team."
+
+struct SharingPolicy
+    "Sharing policy of Paper doc."
+
+    public_sharing_policy SharingPublicPolicyType?
+        "This value applies to the non-team members."
+    team_sharing_policy SharingTeamPolicyType?
+        "This value applies to the team members only. The value is null for all personal accounts."
+
+    example default
+        public_sharing_policy = people_with_link_can_edit
+        team_sharing_policy = people_with_link_can_edit
+
+struct Folder
+    "Data structure representing a Paper folder."
+
+    id String
+        "Paper folder ID. This ID uniquely identifies the folder."
+    name String
+        "Paper folder name."
+
+    example default
+        name = "Design docs"
+        id = "e.gGYT6HSafpMej9bUv306oGm60vrHiCHgEFnzziioPGCvHf"
+
+struct FoldersContainingPaperDoc
+    "Metadata about Paper folders containing the specififed Paper doc."
+
+    folder_sharing_policy_type FolderSharingPolicyType?
+        "The sharing policy of the folder containing the Paper doc."
+
+    folders List(Folder)?
+        "The folder path. If present the first folder is the root folder."
+
+    example default
+        folder_sharing_policy_type = team
+        folders = [default]
+
+struct PaperDocSharingPolicy extends RefPaperDoc
+    sharing_policy SharingPolicy
+        "The default sharing policy to be set for the Paper doc."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+        sharing_policy = default
+
+struct RemovePaperDocUser extends RefPaperDoc
+    member sharing.MemberSelector
+        "User which should be removed from the Paper doc. Specify only email address or
+        Dropbox account ID."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+        member = default
+
+union PaperDocPermissionLevel
+    edit
+        "User will be granted edit permissions."
+    view_and_comment
+        "User will be granted view and comment permissions."
+
+struct AddMember
+    permission_level PaperDocPermissionLevel = edit
+        "Permission for the user."
+    member sharing.MemberSelector
+        "User which should be added to the Paper doc. Specify only email address or
+        Dropbox account ID."
+
+    example default
+        member = default
+        permission_level = view_and_comment
+
+union UserOnPaperDocFilter
+    visited
+        "all users who have visited the Paper doc."
+    shared
+        "All uses who are shared on the Paper doc. This includes all users who have visited
+        the Paper doc as well as those who have not."
+
+struct ListUsersOnPaperDocArgs extends RefPaperDoc
+    limit Int32(min_value=1, max_value=1000) = 1000
+        "Size limit per batch. The maximum number of users that can be retrieved per batch
+        is 1000. Higher value results in invalid arguments error."
+
+    filter_by UserOnPaperDocFilter = shared
+        "Specify this attribute if you want to obtain users that have already accessed
+        the Paper doc."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+        limit = 100
+        filter_by = shared
+
+struct ListUsersOnPaperDocContinueArgs extends RefPaperDoc
+    cursor String
+        "The cursor obtained from :route:`docs/users/list` or
+        :route:`docs/users/list/continue`. Allows for pagination."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+        cursor = "U60b6BxT43ySd5sAVQbbIvoteSnWLjUdLU7aR25hbt3ySd5sAVQbbIvoteSnWLjUd"
+
+struct InviteeInfoWithPermissionLevel
+    invitee sharing.InviteeInfo
+        "Email address invited to the Paper doc."
+
+    permission_level PaperDocPermissionLevel
+        "Permission level for the invitee."
+
+    example default
+        invitee = default
+        permission_level = edit
+
+
+struct UserInfoWithPermissionLevel
+    user sharing.UserInfo
+        "User shared on the Paper doc."
+
+    permission_level PaperDocPermissionLevel
+        "Permission level for the user."
+
+    example default
+        user = default
+        permission_level = view_and_comment
+
+struct ListUsersOnPaperDocResponse
+    invitees List(InviteeInfoWithPermissionLevel)
+        "List of email addresses with their respective permission levels
+        that are invited on the Paper doc."
+    users List(UserInfoWithPermissionLevel)
+        "List of users with their respective permission levels
+        that are invited on the Paper folder."
+    doc_owner sharing.UserInfo
+        "The Paper doc owner. This field is populated on every single response."
+    cursor Cursor
+        "Pass the cursor into :route:`docs/users/list/continue` to paginate
+        through all users.
+        The cursor preserves all properties as specified in the original call
+        to :route:`docs/users/list`.
+        "
+    has_more Boolean
+        "Will be set to True if a subsequent call with the provided cursor
+        to :route:`docs/users/list/continue` returns immediately with some results.
+        If set to False please allow some delay before making another call to
+        :route:`docs/users/list/continue`."
+
+    example default
+        users = [default]
+        invitees = [default]
+        doc_owner = default
+        cursor = default
+        has_more = false
+
+struct AddPaperDocUser extends RefPaperDoc
+    members List(AddMember, max_items = 20)
+        "User which should be added to the Paper doc. Specify only email address or
+        Dropbox account ID."
+    custom_message String?
+        "A personal message that will be emailed to each successfully added member."
+    quiet Boolean = false
+        "Clients should set this to true if no email message shall be sent to added users."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+        members = [default]
+        custom_message = "Welcome to Paper."
+
+union AddPaperDocUserResult
+    success
+        "User was successfully added to the Paper doc."
+    unknown_error
+        "Something unexpected happened when trying to add the user to the Paper doc."
+    sharing_outside_team_disabled
+        "The Paper doc can be shared only with team members."
+    daily_limit_reached
+        "The daily limit of how many users can be added to the Paper doc was reached."
+    user_is_owner
+        "Owner's permissions cannot be changed."
+    failed_user_data_retrieval
+        "User data could not be retrieved. Clients should retry."
+    permission_already_granted
+        "This user already has the correct permission to the Paper doc."
+
+struct AddPaperDocUserMemberResult
+    "Per-member result for :route:`docs/users/add`."
+    member sharing.MemberSelector
+        "One of specified input members."
+    result AddPaperDocUserResult
+        "The outcome of the action on this member."
+
+union PaperApiBaseError
+    insufficient_permissions
+        "Your account does not have permissions to perform this action."
+
+union PaperApiCursorError
+    expired_cursor
+        "The provided cursor is expired."
+    invalid_cursor
+        "The provided cursor is invalid."
+    wrong_user_in_cursor
+        "The provided cursor contains invalid user."
+    reset
+        "Indicates that the cursor has been invalidated. Call
+        the corresponding non-continue endpoint to obtain a new cursor."
+
+union DocLookupError extends PaperApiBaseError
+    doc_not_found
+        "The required doc was not found."
+
+union ListDocsCursorError
+    cursor_error PaperApiCursorError
+
+union ListUsersCursorError extends PaperApiBaseError
+    doc_not_found
+        "The required doc was not found."
+    cursor_error PaperApiCursorError
+
+struct ListUsersOnFolderResponse
+    invitees List(sharing.InviteeInfo)
+        "List of email addresses that are invited on the Paper folder."
+    users List(sharing.UserInfo)
+        "List of users that are invited on the Paper folder."
+    cursor Cursor
+        "Pass the cursor into :route:`docs/folder_users/list/continue` to paginate
+        through all users.
+        The cursor preserves all properties as specified in the original call
+        to :route:`docs/folder_users/list`.
+        "
+    has_more Boolean
+        "Will be set to True if a subsequent call with the provided cursor
+        to :route:`docs/folder_users/list/continue` returns immediately with some results.
+        If set to False please allow some delay before making another call to
+        :route:`docs/folder_users/list/continue`."
+
+    example default
+        users = [default]
+        invitees = [default]
+        cursor = default
+        has_more = false
+
+struct ListUsersOnFolderArgs extends RefPaperDoc
+    limit Int32(min_value=1, max_value=1000) = 1000
+        "Size limit per batch. The maximum number of users that can be retrieved per batch
+        is 1000. Higher value results in invalid arguments error."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+        limit = 100
+
+struct ListUsersOnFolderContinueArgs extends RefPaperDoc
+    cursor String
+        "The cursor obtained from :route:`docs/folder_users/list` or
+        :route:`docs/folder_users/list/continue`. Allows for pagination."
+
+    example default
+        doc_id = "uaSvRuxvnkFa12PTkBv5q"
+        cursor = "U60b6BxT43ySd5sAVQbbIvoteSnWLjUdLU7aR25hbt3ySd5sAVQbbIvoteSnWLjUd"
+
+route docs/folder_users/list (ListUsersOnFolderArgs, ListUsersOnFolderResponse, DocLookupError)
+    "Lists the users who are explicitly invited to the Paper folder in which the Paper doc
+    is contained. For private folders all users (including owner) shared on the folder
+    are listed and for team folders all non-team users shared on the folder are returned."
+    attrs
+        owner="paper-eng"
+
+route docs/folder_users/list/continue (ListUsersOnFolderContinueArgs, ListUsersOnFolderResponse, ListUsersCursorError)
+    "Once a cursor has been retrieved from :route:`docs/folder_users/list`, use this to
+    paginate through all users on the Paper folder."
+    attrs
+        owner="paper-eng"
+
+route docs/sharing_policy/get (RefPaperDoc, SharingPolicy, DocLookupError)
+    "Gets the default sharing policy for the given Paper doc."
+    attrs
+        owner="paper-eng"
+
+route docs/sharing_policy/set (PaperDocSharingPolicy, Void, DocLookupError)
+    "Sets the default sharing policy for the given Paper doc. The default 'team_sharing_policy'
+    can be changed only by teams, omit this field for personal accounts.
+
+
+    Note: 'public_sharing_policy' cannot be set to the value 'disabled' because this setting
+    can be changed only via the team admin console."
+    attrs
+        owner="paper-eng"
+
+route docs/archive (RefPaperDoc, Void, DocLookupError)
+    "Marks the given Paper doc as archived.
+
+    Note: This action can be performed or undone by anyone with edit permissions to the doc."
+    attrs
+        owner="paper-eng"
+
+route docs/permanently_delete (RefPaperDoc, Void, DocLookupError)
+    "Permanently deletes the given Paper doc. This operation is final as the doc
+    cannot be recovered.
+
+
+    Note: This action can be performed only by the doc owner."
+    attrs
+        owner="paper-eng"
+
+route docs/download (PaperDocExport, PaperDocExportResult, DocLookupError)
+    "Exports and downloads Paper doc either as HTML or markdown."
+    attrs
+        style="download"
+        owner="paper-eng"
+
+route docs/get_folder_info (RefPaperDoc, FoldersContainingPaperDoc, DocLookupError)
+    "Retrieves folder information for the given Paper doc. This includes:
+
+      - folder sharing policy; permissions for subfolders are set by the top-level folder.
+
+      - full 'filepath', i.e. the list of folders (both folderId and folderName) from
+        the root folder to the folder directly containing the Paper doc.
+
+
+    Note: If the Paper doc is not in any folder (aka unfiled) the response will be empty."
+    attrs
+        owner="paper-eng"
+
+route docs/users/add (AddPaperDocUser, List(AddPaperDocUserMemberResult), DocLookupError)
+    "Allows an owner or editor to add users to a Paper doc or change their permissions
+    using their email address or Dropbox account ID.
+
+
+    Note: The Doc owner's permissions cannot be changed."
+    attrs
+        owner="paper-eng"
+
+route docs/users/remove (RemovePaperDocUser, Void, DocLookupError)
+    "Allows an owner or editor to remove users from a Paper doc using their email address or
+    Dropbox account ID.
+
+
+    Note: Doc owner cannot be removed."
+    attrs
+        owner="paper-eng"
+
+route docs/users/list (ListUsersOnPaperDocArgs, ListUsersOnPaperDocResponse, DocLookupError)
+    "Lists all users who visited the Paper doc or users with explicit access. This call
+    excludes users who have been removed. The list is sorted by the date of the visit or
+    the share date.
+
+    The list will include both users, the explicitly shared ones as well as those
+    who came in using the Paper url link."
+    attrs
+        owner="paper-eng"
+
+route docs/users/list/continue (ListUsersOnPaperDocContinueArgs, ListUsersOnPaperDocResponse, ListUsersCursorError)
+    "Once a cursor has been retrieved from :route:`docs/users/list`, use this to
+    paginate through all users on the Paper doc."
+    attrs
+        owner="paper-eng"
+
+route docs/list (ListPaperDocsArgs, ListPaperDocsResponse, Void)
+    "Return the list of all Paper docs according to the argument specifications. To iterate
+    over through the full pagination, pass the cursor to :route:`docs/list/continue`."
+    attrs
+        owner="paper-eng"
+
+route docs/list/continue (ListPaperDocsContinueArgs, ListPaperDocsResponse, ListDocsCursorError)
+    "Once a cursor has been retrieved from :route:`docs/list`, use this to
+    paginate through all Paper doc."
+    attrs
+        owner="paper-eng"

+ 120 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/properties.stone

@@ -0,0 +1,120 @@
+namespace properties
+    "This namespace contains helper entities for property and property/template endpoints."
+
+alias TemplateId = String(min_length=1,pattern="(/|ptid:).*")
+
+struct PropertyGroupTemplate
+    "Describes property templates that can be filled and associated with a file."
+
+    name String
+        "A display name for the property template. Property template names can
+        be up to 256 bytes."
+    description String
+        "Description for new property template. Property template descriptions
+        can be up to 1024 bytes."
+    fields List(PropertyFieldTemplate)
+        "This is a list of custom properties associated with a property template.
+        There can be up to 64 properties in a single property template."
+
+    example default
+        name = "Security"
+        description = "These properties describe how confidential this file is."
+        fields = [default]
+
+struct PropertyFieldTemplate
+    "Describe a single property field type which that can be part of a property template."
+
+    name String
+        "This is the name or key of a custom property in a property template.
+        File property names can be up to 256 bytes."
+    description String
+        "This is the description for a custom property in a property template.
+        File property description can be up to 1024 bytes."
+    type PropertyType
+        "This is the data type of the value of this property. This type
+        will be enforced upon property creation and modifications."
+
+    example default
+        name = "Security Policy"
+        description = "This is the security policy of the file or folder described.
+        Policies can be Confidential, Public or Internal."
+        type = default
+
+union ModifyPropertyTemplateError extends PropertyTemplateError
+    conflicting_property_names
+        "A property field name already exists in the template."
+    too_many_properties
+        "There are too many properties in the changed template.
+        The maximum number of properties per template is 32."
+    too_many_templates
+        "There are too many templates for the team."
+    template_attribute_too_large
+        "The template name, description or field names is too large."
+
+union PropertyTemplateError
+    template_not_found TemplateId
+        "Property template does not exist for given identifier."
+    restricted_content
+        "You do not have the permissions to modify this property template."
+
+struct PropertyGroup
+    "Collection of custom properties in filled property templates."
+
+    template_id TemplateId
+        "A unique identifier for a property template type."
+    fields List(PropertyField)
+        "This is a list of custom properties associated with a file.
+        There can be up to 32 properties for a template."
+
+    example default
+        template_id = "ptid:1a5n2i6d3OYEAAAAAAAAAYa"
+        fields = [default]
+
+struct PropertyField
+    name String
+        "This is the name or key of a custom property in a property template.
+        File property names can be up to 256 bytes."
+    value String
+        "Value of a custom property attached to a file. Values can be up to 1024
+        bytes."
+
+    example default
+        name = "Security Policy"
+        value = "Confidential"
+
+union PropertyType
+    "Data type of the given property added. This endpoint is in beta and
+     only properties of type strings is supported."
+
+    string
+        "The associated property will be of type string. Unicode is supported."
+
+    example default
+        string = null
+
+#
+# Shared struct used for /template/list and /template/get
+#
+
+struct GetPropertyTemplateArg
+    template_id TemplateId
+        "An identifier for property template added by route properties/template/add."
+
+    example default
+        template_id = "ptid:1a5n2i6d3OYEAAAAAAAAAYa"
+
+struct GetPropertyTemplateResult extends PropertyGroupTemplate
+    "The Property template for the specified template."
+
+    example default
+        name = "Security"
+        description = "These properties describe how confidential this file is."
+        fields = [default]
+
+struct ListPropertyTemplateIds
+    template_ids List(TemplateId)
+        "List of identifiers for templates added by route properties/template/add."
+
+    example default
+        template_ids = ["ptid:1a5n2i6d3OYEAAAAAAAAAYa"]
+

+ 114 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/shared_content_links.stone

@@ -0,0 +1,114 @@
+namespace sharing
+
+import common
+
+
+union LinkExpiry
+    remove_expiry
+        "Remove the currently set expiry for the link."
+    set_expiry common.DropboxTimestamp
+        "Set a new expiry or change an existing expiry."
+
+union LinkPassword
+    remove_password
+        "Remove the currently set password for the link."
+    set_password String
+        "Set a new password or change an existing password."
+
+struct LinkSettings
+    "Settings that apply to a link."
+    access_level AccessLevel?
+        "The access level on the link for this file. Currently,
+        it only accepts 'viewer' and 'viewer_no_comment'."
+    audience LinkAudience?
+        "The type of audience on the link for this file."
+    expiry LinkExpiry?
+        "An expiry timestamp to set on a link."
+    password LinkPassword?
+        "The password for the link."
+
+union LinkAudience
+    public
+        "Link is accessible by anyone."
+    team
+        "Link is accessible only by team members."
+    members
+        "Link is accessible only by members of the content."
+
+struct LinkPermission
+    "Permissions for actions that can be performed on a link."
+    action LinkAction
+    allow Boolean
+    reason PermissionDeniedReason?
+
+    example default
+        action = change_audience
+        allow = true
+
+union LinkAction
+    "Actions that can be performed on a link."
+    change_access_level
+        "Change the access level of the link."
+    change_audience
+        "Change the audience of the link."
+    remove_expiry
+        "Remove the expiry date of the link."
+    remove_password
+        "Remove the password of the link."
+    set_expiry
+        "Create or modify the expiry date of the link."
+    set_password
+        "Create or modify the password of the link."
+
+struct SharedContentLinkMetadataBase
+    access_level AccessLevel?
+        "The access level on the link for this file."
+    audience_options List(LinkAudience)
+        "The audience options that are available for the content. Some audience options may be
+        unavailable. For example, team_only may be unavailable if the content is not owned by a
+        user on a team. The 'default' audience option is always available if the user can modify
+        link settings."
+    audience_restricting_shared_folder AudienceRestrictingSharedFolder?
+        "The shared folder that prevents the link audience for this link from being more
+        restrictive."
+    current_audience LinkAudience
+        "The current audience of the link."
+    expiry common.DropboxTimestamp?
+        "Whether the link has an expiry set on it. A link with an expiry will have its
+         audience changed to members when the expiry is reached."
+    link_permissions List(LinkPermission)
+        "A list of permissions for actions you can perform on the link."
+    password_protected Boolean
+        "Whether the link is protected by a password."
+
+struct SharedContentLinkMetadata extends SharedContentLinkMetadataBase
+    "Metadata of a shared link for a file or folder."
+    url String
+        "The URL of the link."
+
+    example default
+        audience_options = [public, team, members]
+        current_audience = public
+        link_permissions = [default]
+        password_protected = false
+        url = ""
+
+struct ExpectedSharedContentLinkMetadata extends SharedContentLinkMetadataBase
+    "The expected metadata of a shared link for a file or folder when a link is first created for
+     the content. Absent if the link already exists."
+
+    example default
+        audience_options = [public, team, members]
+        current_audience = public
+        link_permissions = [default]
+        password_protected = false
+
+struct AudienceRestrictingSharedFolder
+    "Information about the shared folder that prevents the link audience for this link from being
+    more restrictive."
+    shared_folder_id common.SharedFolderId
+        "The ID of the shared folder."
+    name String
+        "The name of the shared folder."
+    audience LinkAudience
+        "The link audience of the shared folder."

+ 548 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/shared_links.stone

@@ -0,0 +1,548 @@
+namespace sharing
+
+import common
+import files
+import users
+
+alias Id = files.Id
+alias Path = files.Path
+alias Rev = files.Rev
+alias TeamInfo = users.Team
+alias ReadPath = files.ReadPath
+
+#
+# Link Metadata definitions and route
+#
+
+struct GetSharedLinkMetadataArg
+    url String
+        "URL of the shared link."
+    path Path?
+        "If the shared link is to a folder, this parameter can be used to retrieve the metadata for
+        a specific file or sub-folder in this folder. A relative path should be used."
+    link_password String?
+        "If the shared link has a password, this parameter can be used."
+
+    example default
+        url = "https://www.dropbox.com/s/2sn712vy1ovegw8/Prime_Numbers.txt?dl=0"
+        path = "/Prime_Numbers.txt"
+
+union_closed RequestedVisibility
+    "The access permission that can be requested by the caller for the shared link.
+    Note that the final resolved visibility of the shared link takes into account other aspects,
+    such as team and shared folder settings.
+    Check the :type:`ResolvedVisibility` for more info on the possible resolved visibility values
+    of shared links. "
+
+    public
+        "Anyone who has received the link can access it. No login required."
+    team_only
+        "Only members of the same team can
+        access the link. Login is required."
+    password
+        "A link-specific password is required to access the
+        link. Login is not required."
+
+union ResolvedVisibility extends RequestedVisibility
+    "The actual access permissions values of shared links after taking into account user
+    preferences and the team and shared folder settings.
+    Check the :type:`RequestedVisibility` for more info on the possible visibility values
+    that can be set by the shared link's owner. "
+
+    team_and_password
+        "Only members of the same team who
+        have the link-specific password can access the link. Login is required."
+    shared_folder_only
+        "Only members of the shared folder containing the linked file
+        can access the link. Login is required."
+
+union SharedLinkAccessFailureReason
+    login_required
+        "User is not logged in."
+    email_verify_required
+        "User's email is not verified."
+    password_required
+        "The link is password protected."
+    team_only
+        "Access is allowed for team members only."
+    owner_only
+        "Access is allowed for the shared link's owner only."
+
+struct LinkPermissions
+    resolved_visibility ResolvedVisibility?
+        "The current visibility of the link after considering the shared links policies of the
+        the team (in case the link's owner is part of a team) and the shared folder (in case the
+        linked file is part of a shared folder). This field is shown only if the caller has access
+        to this info (the link's owner always has access to this data)."
+
+    requested_visibility RequestedVisibility?
+        "The shared link's requested visibility. This can be overridden by the team and shared
+        folder policies. The final visibility, after considering these policies, can be found in
+        :field:`resolved_visibility`. This is shown only if the caller is the link's
+        owner."
+
+    can_revoke Boolean
+        "Whether the caller can revoke the shared link"
+    revoke_failure_reason SharedLinkAccessFailureReason?
+        "The failure reason for revoking the link. This field will only be present if the
+        :field:`can_revoke` is :val:`false`."
+
+    example default
+        resolved_visibility = public
+        can_revoke = false
+        revoke_failure_reason = owner_only
+
+struct TeamMemberInfo
+    "Information about a team member."
+
+    team_info TeamInfo
+        "Information about the member's team"
+    display_name String
+        "The display name of the user."
+    member_id String?
+        "ID of user as a member of a team. This field will only be present if the member is in the
+        same team as current user."
+
+    example default
+        team_info = default
+        display_name = "Roger Rabbit"
+        member_id = "dbmid:abcd1234"
+
+struct SharedLinkMetadata
+    "The metadata of a shared link "
+
+    union
+        file FileLinkMetadata
+        folder FolderLinkMetadata
+
+    url String
+        "URL of the shared link."
+    id Id?
+        "A unique identifier for the linked file."
+    name String
+        "The linked file name (including extension).
+        This never contains a slash."
+    expires common.DropboxTimestamp?
+        "Expiration time, if set. By default the link won't expire."
+    path_lower String?
+        "The lowercased full path in the user's Dropbox. This always starts with a slash.
+        This field will only be present only if the linked file is in the authenticated user's
+         dropbox."
+    link_permissions LinkPermissions
+        "The link's access permissions."
+    team_member_info TeamMemberInfo?
+        "The team membership information of the link's owner.  This field will only be present
+         if the link's owner is a team member."
+    content_owner_team_info TeamInfo?
+        "The team information of the content's owner. This field will only be present if
+        the content's owner is a team member and the content's owner team is different from the
+        link's owner team."
+
+    example default
+        file = default
+
+    example folder_link_metadata
+        folder = default
+
+struct FileLinkMetadata extends SharedLinkMetadata
+    "The metadata of a file shared link "
+
+    client_modified common.DropboxTimestamp
+        "The modification time set by the desktop client
+        when the file was added to Dropbox. Since this time is not verified
+        (the Dropbox server stores whatever the desktop client sends up), this
+        should only be used for display purposes (such as sorting) and not,
+        for example, to determine if a file has changed or not."
+    server_modified common.DropboxTimestamp
+        "The last time the file was modified on Dropbox."
+    rev Rev
+        "A unique identifier for the current revision of a file. This field is
+        the same rev as elsewhere in the API and can be used to detect changes
+        and avoid conflicts."
+    size UInt64
+        "The file size in bytes."
+
+    example default
+        url = "https://www.dropbox.com/s/2sn712vy1ovegw8/Prime_Numbers.txt?dl=0"
+        id = "id:a4ayc_80_OEAAAAAAAAAXw"
+        name = "Prime_Numbers.txt"
+        path_lower = "/homework/math/prime_numbers.txt"
+        link_permissions = default
+        team_member_info = default
+        client_modified = "2015-05-12T15:50:38Z"
+        server_modified = "2015-05-12T15:50:38Z"
+        rev = "a1c10ce0dd78"
+        size = 7212
+
+struct FolderLinkMetadata extends SharedLinkMetadata
+    "The metadata of a folder shared link "
+
+    example default
+        url = "https://www.dropbox.com/sh/s6fvw6ol7rmqo1x/AAAgWRSbjmYDvPpDB30Sykjfa?dl=0"
+        id = "id:a4ayc_80_OEAAAAAAAAAXw"
+        name = "Math"
+        path_lower = "/homework/math"
+        team_member_info = default
+        link_permissions = default
+
+union SharedLinkError
+    shared_link_not_found
+        "The shared link wasn't found."
+    shared_link_access_denied
+        "The caller is not allowed to access this shared link."
+    unsupported_link_type
+        "This type of link is not supported."
+
+route get_shared_link_metadata(GetSharedLinkMetadataArg, SharedLinkMetadata, SharedLinkError)
+    "Get the shared link's metadata."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+#
+# List Shared links definitions and route
+#
+
+struct ListSharedLinksArg
+    path ReadPath?
+        "See :route:`list_shared_links` description."
+    cursor String?
+        "The cursor returned by your last call to :route:`list_shared_links`."
+    direct_only Boolean?
+        "See :route:`list_shared_links` description."
+
+    example default
+        "List all links"
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+
+    example path
+        path = "/Homework/Math"
+
+    example id
+        path = "id:a4ayc_80_OEAAAAAAAAAYa"
+
+    example rev
+        path = "rev:a1c10ce0dd78"
+
+    example id_no_parent_links
+        path = "id:a4ayc_80_OEAAAAAAAAAYa"
+        direct_only = true
+
+struct ListSharedLinksResult
+    links List(SharedLinkMetadata)
+        "Shared links applicable to the path argument."
+    has_more Boolean
+        "Is true if there are additional shared links that have not been returned
+        yet. Pass the cursor into :route:`list_shared_links` to retrieve them."
+    cursor String?
+        "Pass the cursor into :route:`list_shared_links` to obtain the additional links. Cursor is
+        returned only if no path is given."
+
+    example default
+        links = [default]
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+        has_more = true
+
+union ListSharedLinksError
+    path files.LookupError
+    reset
+        "Indicates that the cursor has been invalidated. Call
+        :route:`list_shared_links` to obtain a new cursor."
+
+route list_shared_links(ListSharedLinksArg, ListSharedLinksResult, ListSharedLinksError)
+    "List shared links of this user.
+
+    If no path is given, returns a list of all shared links for the current user.
+
+    If a non-empty path is given, returns a list of all shared links
+    that allow access to the given path - direct links to the given path and links to parent folders
+    of the given path. Links to parent folders can be suppressed by setting
+    direct_only to true."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+#
+# Modify shared link settings definitions and route
+#
+
+struct SharedLinkSettings
+    requested_visibility RequestedVisibility?
+        "The requested access for this shared link."
+    link_password String?
+        "If :field:`requested_visibility` is :field:`RequestedVisibility.password` this is needed
+        to specify the password to access the link. "
+    expires common.DropboxTimestamp?
+        "Expiration time of the shared link. By default the link won't expire."
+
+    example default
+        requested_visibility = public
+
+struct ModifySharedLinkSettingsArgs
+    url String
+        "URL of the shared link to change its settings"
+    settings SharedLinkSettings
+        "Set of settings for the shared link."
+    remove_expiration Boolean = false
+        "If set to true, removes the expiration of the shared link."
+
+    example default
+        url = "https://www.dropbox.com/s/2sn712vy1ovegw8/Prime_Numbers.txt?dl=0"
+        settings = default
+
+union_closed SharedLinkSettingsError
+    invalid_settings
+        "The given settings are invalid
+        (for example, all attributes of the :type:`SharedLinkSettings` are empty,
+        the requested visibility is :field:`RequestedVisibility.password` but the
+        :field:`SharedLinkSettings.link_password` is missing, :field:`SharedLinkSettings.expires`
+        is set to the past, etc.)"
+
+    not_authorized
+        "User is not allowed to modify the settings of this link. Note that basic
+        users can only set :field:`RequestedVisibility.public`
+        as the :field:`SharedLinkSettings.requested_visibility` and cannot
+        set :field:`SharedLinkSettings.expires`"
+
+
+union ModifySharedLinkSettingsError extends SharedLinkError
+    settings_error SharedLinkSettingsError
+        "There is an error with the given settings"
+    email_not_verified
+        "The caller's email should be verified"
+
+route modify_shared_link_settings(ModifySharedLinkSettingsArgs, SharedLinkMetadata, ModifySharedLinkSettingsError)
+    "Modify the shared link's settings.
+
+    If the requested visibility conflict with the shared links policy of the team or the
+    shared folder (in case the linked file is part of a shared folder) then the
+    :field:`LinkPermissions.resolved_visibility` of the returned :type:`SharedLinkMetadata` will
+    reflect the actual visibility of the shared link and the
+    :field:`LinkPermissions.requested_visibility` will reflect the requested visibility."
+
+    attrs
+        owner = "adminx"
+        allow_app_folder_app = true
+
+#
+# Create shared link with settings definitions and route
+#
+
+struct CreateSharedLinkWithSettingsArg
+    path ReadPath
+        "The path to be shared by the shared link"
+    settings SharedLinkSettings?
+        "The requested settings for the newly created shared link"
+
+    example default
+        path = "/Prime_Numbers.txt"
+        settings = default
+
+union_closed CreateSharedLinkWithSettingsError
+    path files.LookupError
+    email_not_verified
+        "User's email should be verified"
+    shared_link_already_exists
+        "The shared link already exists"
+    settings_error SharedLinkSettingsError
+        "There is an error with the given settings"
+    access_denied
+        "Access to the requested path is forbidden"
+
+route create_shared_link_with_settings(CreateSharedLinkWithSettingsArg, SharedLinkMetadata, CreateSharedLinkWithSettingsError)
+    "Create a shared link with custom settings.
+    If no settings are given then the default visibility is :field:`RequestedVisibility.public`
+    (The resolved visibility, though, may depend on other aspects such as team and shared folder
+    settings). "
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+
+#
+# Revoke shared link
+#
+
+struct RevokeSharedLinkArg
+    url String
+        "URL of the shared link."
+
+    example default
+        url = "https://www.dropbox.com/s/2sn712vy1ovegw8/Prime_Numbers.txt?dl=0"
+
+union RevokeSharedLinkError extends SharedLinkError
+    shared_link_malformed
+        "Shared link is malformed."
+
+route revoke_shared_link(RevokeSharedLinkArg, Void, RevokeSharedLinkError)
+    "Revoke a shared link.
+
+    Note that even after revoking a shared link to a file, the file may be accessible if there are
+    shared links leading to any of the file parent folders. To list all shared links that enable
+    access to a specific file, you can use the :route:`list_shared_links` with the file as the
+    :field:`ListSharedLinksArg.path` argument. "
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+#
+# NSLR endpoints
+#
+
+union GetSharedLinkFileError extends SharedLinkError
+    shared_link_is_directory
+        "Directories cannot be retrieved by this endpoint."
+
+alias GetSharedLinkFileArg = GetSharedLinkMetadataArg
+
+route get_shared_link_file(GetSharedLinkFileArg, SharedLinkMetadata, GetSharedLinkFileError)
+    "Download the shared link's file from a user's Dropbox."
+
+    attrs
+        host="content"
+        style="download"
+        owner = "sharing"
+        allow_app_folder_app = true
+
+#
+# Depracated endpoints
+#
+
+
+union Visibility
+    "Who can access a shared link.
+    The most open visibility is :field:`public`.
+    The default depends on many aspects, such as team and user
+    preferences and shared folder settings."
+
+    public
+        "Anyone who has received the link can access it. No login required."
+    team_only
+        "Only members of the same team can
+        access the link. Login is required."
+    password
+        "A link-specific password is required to access the
+        link. Login is not required."
+    team_and_password
+        "Only members of the same team who
+        have the link-specific password can access the link."
+    shared_folder_only
+        "Only members of the shared folder containing the linked file
+        can access the link. Login is required."
+
+struct LinkMetadata
+    "Metadata for a shared link. This can be either a
+    :type:`PathLinkMetadata` or :type:`CollectionLinkMetadata`."
+
+    union
+        path PathLinkMetadata
+        collection CollectionLinkMetadata
+
+    url String
+        "URL of the shared link."
+    visibility Visibility
+        "Who can access the link."
+    expires common.DropboxTimestamp?
+        "Expiration time, if set. By default the link won't expire."
+
+struct PathLinkMetadata extends LinkMetadata
+    "Metadata for a path-based shared link."
+
+    path String
+        "Path in user's Dropbox."
+
+    example default
+        url = "https://www.dropbox.com/s/2sn712vy1ovegw8/Prime_Numbers.txt?dl=0"
+        path = "/Homework/Math/Prime_Numbers.txt"
+        expires = null
+        visibility = public
+
+struct CollectionLinkMetadata extends LinkMetadata
+    "Metadata for a collection-based shared link."
+
+    example default
+        url = "https://www.dropbox.com/sh/s6fvw6ol7rmqo1x/AAAgWRSbjmYDvPpDB30Sykjfa?dl=0"
+        expires = null
+        visibility = public
+
+struct GetSharedLinksArg
+    path String?
+        "See :route:`get_shared_links` description."
+
+    example default
+        "Get all links, including collection links"
+        path = ""
+
+    example math_homework_links
+        "Get links giving access to /Homework/Math"
+        path = "/Homework/Math"
+
+struct GetSharedLinksResult
+    links List(LinkMetadata)
+        "Shared links applicable to the path argument."
+
+union GetSharedLinksError
+    path files.MalformedPathError
+
+route get_shared_links(GetSharedLinksArg, GetSharedLinksResult, GetSharedLinksError) deprecated by list_shared_links
+    "Returns a list of :type:`LinkMetadata` objects for this user,
+    including collection links.
+
+    If no path is given, returns a list of all shared links for the current
+    user, including collection links.
+
+    If a non-empty path is given, returns a list of all shared links
+    that allow access to the given path.  Collection links are never
+    returned in this case.
+
+    Note that the url field in the response is never the shortened URL."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+union_closed PendingUploadMode
+    "Flag to indicate pending upload default (for linking to not-yet-existing paths)."
+
+    file
+        "Assume pending uploads are files."
+    folder
+        "Assume pending uploads are folders."
+
+struct CreateSharedLinkArg
+    path String
+        "The path to share."
+    short_url Boolean = false
+        "Whether to return a shortened URL."
+    pending_upload PendingUploadMode?
+        "If it's okay to share a path that does not yet exist, set this to
+        either :field:`PendingUploadMode.file` or :field:`PendingUploadMode.folder`
+        to indicate whether to assume it's a file or folder."
+
+    example default
+        path = "/Homework/Math/Prime_Numbers.txt"
+
+union CreateSharedLinkError
+    path files.LookupError
+
+route create_shared_link(CreateSharedLinkArg, PathLinkMetadata, CreateSharedLinkError) deprecated by create_shared_link_with_settings
+    "Create a shared link.
+
+    If a shared link already exists for the given path, that link is returned.
+
+    Note that in the returned :type:`PathLinkMetadata`, the
+    :field:`PathLinkMetadata.url` field is the shortened URL if
+    :field:`CreateSharedLinkArg.short_url` argument is set to :val:`true`.
+
+    Previously, it was technically possible to break a shared link by moving or
+    renaming the corresponding file or folder. In the future, this will no
+    longer be the case, so your app shouldn't rely on this behavior. Instead, if
+    your app needs to revoke a shared link, use :route:`revoke_shared_link`."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true

+ 7 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/sharing.stone

@@ -0,0 +1,7 @@
+namespace sharing
+    "This namespace contains endpoints and data types for creating and managing shared links and
+    shared folders."
+
+import common
+import files
+import users

+ 659 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/sharing_files.stone

@@ -0,0 +1,659 @@
+namespace sharing
+
+import common
+import files
+import users
+
+alias PathOrId = String(pattern="((\/|id:).*|nspath:[0-9]+:.*)|ns:[0-9]+(/.*)?", min_length=1)
+alias FileId = String(pattern="id:.*", min_length=1)
+
+###########################################
+# Generic route error related definitions #
+###########################################
+
+union SharingUserError
+    "User account had a problem preventing this action."
+    email_unverified
+        "The current user must verify the account e-mail address before performing this action."
+
+union SharingFileAccessError
+    "User could not access this file."
+
+    no_permission
+        "Current user does not have sufficient privileges to perform the desired action."
+    invalid_file
+        "File specified was not found."
+    is_folder
+        "A folder can't be shared this way. Use folder sharing or a shared link instead."
+    inside_public_folder
+        "A file inside a public folder can't be shared this way. Use a public link instead."
+    inside_osx_package
+        "A Mac OS X package can't be shared this way. Use a shared link instead."
+
+
+union FileErrorResult
+    file_not_found_error files.Id
+        "File specified by id was not found."
+    invalid_file_action_error files.Id
+        "User does not have permission to take the specified action on the file."
+    permission_denied_error files.Id
+        "User does not have permission to access file specified by file.Id."
+
+#############################################
+# File metadata and permissions definitions #
+#############################################
+
+union FileAction
+    "Sharing actions that may be taken on files."
+
+    disable_viewer_info
+        "Disable viewer information on the file."
+    edit_contents
+        "Change or edit contents of the file."
+    enable_viewer_info
+        "Enable viewer information on the file."
+    invite_viewer
+        "Add a member with view permissions."
+    invite_viewer_no_comment
+        "Add a member with view permissions but no comment permissions."
+    unshare
+        "Stop sharing this file."
+    relinquish_membership
+        "Relinquish one's own membership to the file."
+    share_link
+        "This action is deprecated. Use create_link instead."
+    create_link
+        "Create a shared link to the file."
+
+    example default
+        edit_contents = null
+
+struct FilePermission
+    "Whether the user is allowed to take the sharing action on the file."
+
+    action FileAction
+        "The action that the user may wish to take on the file."
+    allow Boolean
+        "True if the user is allowed to take the action."
+    reason PermissionDeniedReason?
+        "The reason why the user is denied the permission. Not present if the action
+        is allowed."
+
+    example default
+        action = edit_contents
+        allow = false
+        reason = user_not_same_team_as_owner
+
+struct SharedFileMetadata
+    "Properties of the shared file."
+
+    access_type AccessLevel?
+        "The current user's access level for this shared file."
+    id FileId
+        "The ID of the file."
+    expected_link_metadata ExpectedSharedContentLinkMetadata?
+        "The expected metadata of the link associated for the file when it is first shared.
+        Absent if the link already exists. This is for an unreleased feature so it may not be
+        returned yet."
+    link_metadata SharedContentLinkMetadata?
+        "The metadata of the link associated for the file. This is for an unreleased feature so
+        it may not be returned yet."
+    name String
+        "The name of this file."
+    owner_team users.Team?
+        "The team that owns the file. This field is not present if the file
+        is not owned by a team."
+    parent_shared_folder_id common.SharedFolderId?
+        "The ID of the parent shared folder. This field is present only if the
+        file is contained within a shared folder."
+    path_display String?
+        "The cased path to be used for display purposes only. In rare instances
+        the casing will not correctly match the user's filesystem, but this
+        behavior will match the path provided in the Core API v1.
+        Absent for unmounted files."
+    path_lower String?
+        "The lower-case full path of this file. Absent for unmounted files."
+    permissions List(FilePermission)?
+        "The sharing permissions that requesting user has on this file. This
+        corresponds to the entries given in :field:`GetFileMetadataBatchArg.actions`
+        or :field:`GetFileMetadataArg.actions`."
+    policy FolderPolicy
+        "Policies governing this shared file."
+    preview_url String
+        "URL for displaying a web preview of the shared file."
+    time_invited common.DropboxTimestamp?
+        "Timestamp indicating when the current user was invited to this shared file. If the user was
+        not invited to the shared file, the timestamp will indicate when the user was invited to the
+        parent shared folder. This value may be absent."
+
+
+    example default
+        policy = default
+        permissions = []
+        owner_team = default
+        preview_url = "https://www.dropbox.com/scl/fi/fir9vjelf"
+        path_lower = "/dir/file.txt"
+        path_display = "/dir/file.txt"
+        name = "file.txt"
+        id = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        time_invited = "2016-01-20T00:00:00Z"
+        access_type = viewer
+
+union ViewerInfoPolicy
+    enabled
+        "Viewer information is available on this file."
+    disabled
+        "Viewer information is disabled on this file."
+
+##################################
+# File membership mutation types #
+##################################
+
+struct FileMemberActionResult
+    "Per-member result for :route:`add_file_member` or :route:`change_file_member_access`."
+
+    member MemberSelector
+        "One of specified input members."
+    result FileMemberActionIndividualResult
+        "The outcome of the action on this member."
+
+union_closed FileMemberActionIndividualResult
+    success AccessLevel?
+        "Member was successfully removed from this file. If AccessLevel is given,
+        the member still has access via a parent shared folder."
+    member_error FileMemberActionError
+        "User was not able to perform this action."
+
+    example default
+        success = null
+
+union FileMemberRemoveActionResult
+    success MemberAccessLevelResult
+        "Member was successfully removed from this file."
+    member_error FileMemberActionError
+        "User was not able to remove this member."
+
+union AddFileMemberError
+    "Errors for :route:`add_file_member`."
+    user_error SharingUserError
+    access_error SharingFileAccessError
+    rate_limit
+        "The user has reached the rate limit for invitations."
+    invalid_comment
+        "The custom message did not pass comment permissions checks."
+
+union RemoveFileMemberError
+    "Errors for :route:`remove_file_member_2`."
+    user_error SharingUserError
+    access_error SharingFileAccessError
+    no_explicit_access MemberAccessLevelResult
+        "This member does not have explicit access to the file and therefore cannot be removed.
+        The return value is the access that a user might have to the file from a parent folder."
+
+union FileMemberActionError
+    invalid_member
+        "Specified member was not found."
+    no_permission
+        "User does not have permission to perform this action on this member."
+    access_error SharingFileAccessError
+        "Specified file was invalid or user does not have access."
+    no_explicit_access MemberAccessLevelResult
+        "The action cannot be completed because the target member does not have explicit access
+        to the file. The return value is the access that the member has to the file from a parent folder."
+
+#####################
+# Route Definitions #
+#####################
+
+route add_file_member(AddFileMemberArgs, List(FileMemberActionResult), AddFileMemberError)
+    "Adds specified members to a file."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct AddFileMemberArgs
+    "Arguments for :route:`add_file_member`."
+
+    file PathOrId
+        "File to which to add members."
+    members List(MemberSelector)
+        "Members to add. Note that even an email address is given, this
+        may result in a user being directy added to the membership if that
+        email is the user's main account email."
+    custom_message String?
+        "Message to send to added members in their invitation."
+    quiet Boolean = false
+        "Whether added members should be notified via device notifications of
+        their invitation."
+    access_level AccessLevel = viewer
+        "AccessLevel union object, describing what access level we want to give new members."
+    add_message_as_comment Boolean = false
+        "If the custom message should be added as a comment on the file."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        members = [default]
+        custom_message = "This is a custom message about ACME.doc"
+        quiet = false
+        access_level = viewer
+
+# --
+
+route change_file_member_access(ChangeFileMemberAccessArgs, FileMemberActionResult, FileMemberActionError) deprecated by update_file_member
+    "Identical to update_file_member but with less information returned."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct ChangeFileMemberAccessArgs
+    "Arguments for :route:`change_file_member_access`."
+
+    file PathOrId
+        "File for which we are changing a member's access."
+    member MemberSelector
+        "The member whose access we are changing."
+    access_level AccessLevel
+        "The new access level for the member."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        member = default
+        access_level = viewer
+
+struct UpdateFileMemberArgs extends ChangeFileMemberAccessArgs
+    "Arguments for :route:`update_file_member`."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        member = default
+        access_level = viewer
+
+route update_file_member(UpdateFileMemberArgs, MemberAccessLevelResult, FileMemberActionError)
+    "Changes a member's access on a shared file."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+# --
+
+route get_file_metadata(GetFileMetadataArg, SharedFileMetadata, GetFileMetadataError)
+    "Returns shared file metadata."
+
+    attrs
+        owner = "sharing"
+        api_group = "truelink-alpha"
+        allow_app_folder_app = true
+
+struct GetFileMetadataArg
+    "Arguments of :route:`get_file_metadata`."
+
+    file PathOrId
+        "The file to query."
+    actions List(FileAction)?
+        "A list of `FileAction`s corresponding to `FilePermission`s that should appear in the
+         response's :field:`SharedFileMetadata.permissions` field describing the actions the
+         authenticated user can perform on the file."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        actions = []
+
+union GetFileMetadataError
+    "Error result for :route:`get_file_metadata`."
+
+    user_error SharingUserError
+    access_error SharingFileAccessError
+
+# --
+
+route get_file_metadata/batch(GetFileMetadataBatchArg, List(GetFileMetadataBatchResult), SharingUserError)
+    "Returns shared file metadata."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct GetFileMetadataBatchArg
+    "Arguments of :route:`get_file_metadata/batch`."
+
+    files List(PathOrId, max_items=100)
+        "The files to query."
+    actions List(FileAction)?
+        "A list of `FileAction`s corresponding to `FilePermission`s that should appear in the
+         response's :field:`SharedFileMetadata.permissions` field describing the actions the
+         authenticated user can perform on the file."
+
+    example default
+        files = ["id:3kmLmQFnf1AAAAAAAAAAAw","id:VvTaJu2VZzAAAAAAAAAADQ"]
+        actions = []
+
+struct GetFileMetadataBatchResult
+    "Per file results of :route:`get_file_metadata/batch`."
+
+    file PathOrId
+        "This is the input file identifier corresponding to one of
+        :field:`GetFileMetadataBatchArg.files`."
+    result GetFileMetadataIndividualResult
+        "The result for this particular file."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        result = default
+
+    example file_error
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        result = file_error
+
+union GetFileMetadataIndividualResult
+    metadata SharedFileMetadata
+        "The result for this file if it was successful."
+    access_error SharingFileAccessError
+        "The result for this file if it was an error."
+
+    example default
+        metadata = default
+
+    example file_error
+        access_error = invalid_file
+
+# --
+
+route list_file_members(ListFileMembersArg, SharedFileMembers, ListFileMembersError)
+    "Use to obtain the members who have been invited to a file, both inherited
+    and uninherited members."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct ListFileMembersArg
+    "Arguments for :route:`list_file_members`."
+
+    file PathOrId
+        "The file for which you want to see members."
+    actions List(MemberAction)?
+        "The actions for which to return permissions on a member."
+    include_inherited Boolean = true
+        "Whether to include members who only have access from a parent shared folder."
+    limit UInt32(min_value=1, max_value=300) = 100
+        "Number of members to return max per query. Defaults to 100 if no limit is specified."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+
+struct SharedFileMembers
+    "Shared file user, group, and invitee membership.
+
+    Used for the results of :route:`list_file_members` and
+    :route:`list_file_members/continue`, and used as part of the results
+    for :route:`list_file_members/batch`."
+
+    users List(UserMembershipInfo)
+        "The list of user members of the shared file."
+    groups List(GroupMembershipInfo)
+        "The list of group members of the shared file."
+    invitees List(InviteeMembershipInfo)
+        "The list of invited members of a file, but have not logged in and
+        claimed this."
+    cursor String?
+        "Present if there are additional shared file members that have not been returned yet. Pass
+        the cursor into :route:`list_file_members/continue` to list additional members."
+
+    example default
+        users = [default]
+        groups = [default]
+        invitees = [default]
+
+union ListFileMembersError
+    "Error for :route:`list_file_members`."
+
+    user_error SharingUserError
+    access_error SharingFileAccessError
+
+# --
+
+route list_file_members/batch(ListFileMembersBatchArg, List(ListFileMembersBatchResult), SharingUserError)
+    "Get members of multiple files at once. The arguments
+    to this route are more limited, and the limit on query result size per file
+    is more strict. To customize the results more, use the individual file
+    endpoint.
+
+    Inherited users and groups are not included in the result, and permissions are not
+    returned for this endpoint."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct ListFileMembersBatchArg
+    "Arguments for :route:`list_file_members/batch`."
+
+    files List(PathOrId, max_items=100)
+        "Files for which to return members."
+    limit UInt32(max_value=20) = 10
+        "Number of members to return max per query. Defaults to 10 if no limit is specified."
+
+    example default
+        files = ["id:3kmLmQFnf1AAAAAAAAAAAw","id:VvTaJu2VZzAAAAAAAAAADQ"]
+        limit = 10
+
+struct ListFileMembersBatchResult
+    "Per-file result for :route:`list_file_members/batch`."
+
+    file PathOrId
+        "This is the input file identifier, whether an ID or a path."
+    result ListFileMembersIndividualResult
+        "The result for this particular file."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        result = default
+
+    example member_error
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        result = file_error
+
+
+struct ListFileMembersCountResult
+    members SharedFileMembers
+        "A list of members on this file."
+    member_count UInt32
+        "The number of members on this file. This does not include inherited members."
+
+    example default
+        members = default
+        member_count = 3
+
+union ListFileMembersIndividualResult
+    result ListFileMembersCountResult
+        "The results of the query for this file if it was successful."
+    access_error SharingFileAccessError
+        "The result of the query for this file if it was an error."
+
+    example default
+        result = default
+
+    example file_error
+        access_error = invalid_file
+
+# --
+
+route list_file_members/continue(ListFileMembersContinueArg, SharedFileMembers, ListFileMembersContinueError)
+    "Once a cursor has been retrieved from :route:`list_file_members` or
+    :route:`list_file_members/batch`, use this to paginate through all shared
+    file members."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct ListFileMembersContinueArg
+    "Arguments for :route:`list_file_members/continue`."
+
+    cursor String
+        "The cursor returned by your last call to :route:`list_file_members`,
+        :route:`list_file_members/continue`, or :route:`list_file_members/batch`."
+
+    example default
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+
+union ListFileMembersContinueError
+    "Error for :route:`list_file_members/continue`."
+
+    user_error SharingUserError
+    access_error SharingFileAccessError
+    invalid_cursor
+        ":field:`ListFileMembersContinueArg.cursor` is invalid."
+
+# --
+
+route list_received_files(ListFilesArg, ListFilesResult, SharingUserError)
+    "Returns a list of all files shared with current user.
+
+     Does not include files the user has received via shared folders, and does
+     not include unclaimed invitations."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct ListFilesArg
+    "Arguments for :route:`list_received_files`."
+
+    limit UInt32(min_value=1, max_value=300) = 100
+        "Number of files to return max per query. Defaults to 100 if no limit
+        is specified."
+    actions List(FileAction)?
+        "A list of `FileAction`s corresponding to `FilePermission`s that should appear in the
+         response's :field:`SharedFileMetadata.permissions` field describing the actions the
+         authenticated user can perform on the file."
+
+    example default
+        limit = 100
+        actions = []
+
+struct ListFilesResult
+    "Success results for :route:`list_received_files`."
+
+    entries List(SharedFileMetadata)
+        "Information about the files shared with current user."
+    cursor String?
+        "Cursor used to obtain additional shared files."
+
+    example default
+        entries = [default]
+        cursor = "AzJJbGlzdF90eXBdofe9c3RPbGlzdGFyZ3NfYnlfZ2lkMRhcbric7Rdog9cmV2aXNpb24H3Qf6o1fkHxQ"
+
+# --
+
+route list_received_files/continue (ListFilesContinueArg, ListFilesResult, ListFilesContinueError)
+    "Get more results with a cursor from :route:`list_received_files`."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct ListFilesContinueArg
+    "Arguments for :route:`list_received_files/continue`."
+
+    cursor String
+        "Cursor in :field:`ListFilesResult.cursor`."
+
+    example default
+        cursor = "AzJJbGlzdF90eXBdofe9c3RPbGlzdGFyZ3NfYnlfZ2lkMRhcbric7Rdog9emfGRlc2MCRWxpbWl0BGRId"
+
+union ListFilesContinueError
+    "Error results for :route:`list_received_files/continue`."
+
+    user_error SharingUserError
+        "User account had a problem."
+    invalid_cursor
+        ":field:`ListFilesContinueArg.cursor` is invalid."
+
+# --
+
+route remove_file_member(RemoveFileMemberArg, FileMemberActionIndividualResult, RemoveFileMemberError) deprecated by remove_file_member_2
+    "Identical to remove_file_member_2 but with less information returned."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+route remove_file_member_2(RemoveFileMemberArg, FileMemberRemoveActionResult, RemoveFileMemberError)
+    "Removes a specified member from the file."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct RemoveFileMemberArg
+    "Arguments for :route:`remove_file_member_2`."
+
+    file PathOrId
+        "File from which to remove members."
+    member MemberSelector
+        "Member to remove from this file. Note that even if an email is
+        specified, it may result in the removal of a user (not an invitee) if
+        the user's main account corresponds to that email address."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+        member = default
+
+# --
+
+route relinquish_file_membership(RelinquishFileMembershipArg, Void, RelinquishFileMembershipError)
+    "The current user relinquishes their membership in the designated file.
+    Note that the current user may still have inherited access to this file
+    through the parent folder.
+
+    Apps must have full Dropbox access to use this endpoint."
+
+    attrs
+        owner = "sharing"
+        allow_app_folder_app = true
+
+struct RelinquishFileMembershipArg
+    file PathOrId
+        "The path or id for the file."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+
+union RelinquishFileMembershipError
+    access_error SharingFileAccessError
+    group_access
+        "The current user has access to the shared file via a group.  You can't relinquish
+        membership to a file shared via groups."
+    no_permission
+        "The current user does not have permission to perform this action."
+
+# --
+
+route unshare_file(UnshareFileArg, Void, UnshareFileError)
+    "Remove all members from this file. Does not remove inherited members."
+
+    attrs
+        owner = "sharing"
+        api_group = "truelink-alpha"
+        allow_app_folder_app = true
+
+struct UnshareFileArg
+    "Arguments for :route:`unshare_file`."
+
+    file PathOrId
+        "The file to unshare."
+
+    example default
+        file = "id:3kmLmQFnf1AAAAAAAAAAAw"
+
+union UnshareFileError
+    "Error result for :route:`unshare_file`."
+    user_error SharingUserError
+    access_error SharingFileAccessError
+
+    example default
+        user_error = email_unverified

Файловите разлики са ограничени, защото са твърде много
+ 1186 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/sharing_folders.stone


+ 32 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/stone_cfg.stone

@@ -0,0 +1,32 @@
+namespace stone_cfg
+
+struct Route
+
+    auth String = "user"
+        "The auth type for the route. Valid values: user, team, app, noauth."
+    host String = "api"
+        "The server to make the request to. Valid values: api, content,
+        and notify."
+    style String = "rpc"
+        "The RPC format to use for the request. Valid values: rpc, download,
+        and upload."
+    api_group String?
+        "The API group to which this route belongs (useful for filtering
+        via Stone's -f command line argument)."
+    is_preview Boolean = false
+        "A flag indicating whether the route is subject to breaking
+        changes without notice."
+    # We can switch the type from String to a Union once all generators support
+    # unions in attributes.
+    owner String(pattern="adminx|dev-plat|sfi|home|sharing|company-dropbox-team|paper-eng|notifications-team|sub-growth|productivity-infra-team|prodsec|cash-team|premium-labs")?
+        "The team that currently owns the route."
+    cluster String(min_length=1) = "meta-api"
+        "The cluster that handles this route."
+    feature String?
+        "Set if the route belongs to an orion feature. Accounts must have access to the feature to
+        successfully make a request to the route. Only features with boolean value are supported"
+    allow_app_folder_app Boolean = false
+        "If app folder app is allowed to use this endpoint."
+    takes_path_root Boolean = false
+        "A flag indicating whether the route's behavior is affected by use of
+        the Dropbox-API-Path-Root header."

+ 243 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team.stone

@@ -0,0 +1,243 @@
+namespace team
+
+import common
+import team_common
+import team_policies
+import users_common
+
+# Note that in the database, we also have members that are in state "deleted"
+# meaning that the User has been permanently removed from the team.
+# But the API is not going to expose such users externally.  We will omit such users
+# in API responses.
+#
+union_closed TeamMemberStatus
+    "The user's status as a member of a specific team."
+
+    active
+        "User has successfully joined the team."
+    invited
+        "User has been invited to a team, but has not joined the team yet."
+    suspended
+        "User is no longer a member of the team, but the account can be un-suspended,
+        re-establishing the user as a team member."
+    removed RemovedStatus
+        "User is no longer a member of the team.
+        Removed users are only listed when include_removed is true in members/list."
+
+struct RemovedStatus
+    is_recoverable Boolean
+        "True if the removed team member is recoverable."
+
+    example default
+        is_recoverable = false
+
+union_closed TeamMembershipType
+    full
+        "User uses a license and has full access to team resources like the shared quota."
+    limited
+        "User does not have access to the shared quota and team admins have restricted administrative control."
+
+struct MemberProfile
+    "Basic member profile."
+
+    team_member_id team_common.TeamMemberId
+        "ID of user as a member of a team."
+    external_id String?
+        "External ID that a team can attach to the user.
+        An application using the API may find it easier to use their
+        own IDs instead of Dropbox IDs like account_id or team_member_id."
+    account_id users_common.AccountId?
+        "A user's account identifier."
+    email String
+        "Email address of user."
+    email_verified Boolean
+        "Is true if the user's email is verified to be owned by the user."
+    status TeamMemberStatus
+        "The user's status as a member of a specific team."
+    name users.Name
+        "Representations for a person's name."
+    membership_type TeamMembershipType
+        "The user's membership type: full (normal team member) vs limited (does not use a license; no access to the team's shared quota)."
+    joined_on common.DropboxTimestamp?
+        "The date and time the user joined as a member of a specific team."
+    persistent_id String?
+        "Persistent ID that a team can attach to the user.
+        The persistent ID is unique ID to be used for SAML authentication."
+
+    example default
+        team_member_id = "dbmid:1234567"
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+        email = "mary@lamb.com"
+        email_verified = true
+        status = active
+        name = default
+        membership_type = full
+        joined_on = "2015-05-12T15:50:38Z"
+
+union_closed UserSelectorArg
+    "Argument for selecting a single user, either by team_member_id, external_id or email."
+
+    team_member_id team_common.TeamMemberId
+    external_id team_common.MemberExternalId
+    email common.EmailAddress
+
+    example default
+        team_member_id = "dbmid:efgh5678"
+
+    example email
+        email = "dan@hotmail.com"
+
+
+union_closed UserSelectorError
+    "Error that can be returned whenever a struct derived from :type:`UserSelectorArg` is used."
+
+    user_not_found
+        "No matching user found. The provided team_member_id, email, or external_id does not exist on this team."
+
+
+union_closed UsersSelectorArg
+    "Argument for selecting a list of users, either by team_member_ids, external_ids or emails."
+
+    team_member_ids List(team_common.TeamMemberId)
+        "List of member IDs."
+    external_ids List(team_common.MemberExternalId)
+        "List of external user IDs."
+    emails List(common.EmailAddress)
+        "List of email addresses."
+
+
+
+#
+# Handle DfB routes that do not have a better place to be.
+#
+
+#
+# Route get_info
+#
+
+struct TeamGetInfoResult
+
+    name String
+        "The name of the team."
+    team_id String
+        "The ID of the team."
+    num_licensed_users UInt32
+        "The number of licenses available to the team."
+    num_provisioned_users UInt32
+        "The number of accounts that have been invited or are already active members of the team."
+    policies team_policies.TeamMemberPolicies
+
+    example default
+        name="Dropbox Inc."
+        team_id="dbtid:1234abcd"
+        num_licensed_users=5
+        num_provisioned_users=2
+        policies=default
+
+route get_info(Void, TeamGetInfoResult, Void)
+    "Retrieves information about a team."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+#
+# Structs for token/get_authenticated_admin
+#
+
+struct TokenGetAuthenticatedAdminResult
+    "Results for :route:`token/get_authenticated_admin`."
+
+    admin_profile TeamMemberProfile
+        "The admin who authorized the token."
+
+    example default
+        admin_profile = default
+
+union TokenGetAuthenticatedAdminError
+    "Error returned by :route:`token/get_authenticated_admin`."
+
+    mapping_not_found
+        "The current token is not associated with a team admin, because mappings were not
+        recorded when the token was created. Consider re-authorizing a new access token
+        to record its authenticating admin."
+    admin_not_active
+        "Either the team admin that authorized this token is no longer an active member of the
+        team or no longer a team admin."
+
+#
+# Route: token/get_authenticated_admin
+#
+
+route token/get_authenticated_admin(Void, TokenGetAuthenticatedAdminResult, TokenGetAuthenticatedAdminError)
+    "Returns the member profile of the admin who generated the team access token used to make the call."
+
+    attrs
+        auth = "team"
+        owner = "dev-plat"
+
+#
+# Common types
+#
+
+union Feature
+    "A set of features that Dropbox for Business account support."
+
+    upload_api_rate_limit
+        "The number of upload API calls allowed per month."
+
+union FeatureValue
+    "The values correspond to entries in :type:`Feature`. You may get different value according
+    to your Dropbox for Business plan."
+
+    upload_api_rate_limit UploadApiRateLimitValue
+
+    example uploadRateLimited
+        upload_api_rate_limit = limited
+
+union UploadApiRateLimitValue
+    "The value for :field:`Feature.upload_api_rate_limit`."
+
+    unlimited
+        "This team has unlimited upload API quota. So far both server version account and legacy
+         account type have unlimited monthly upload api quota."
+    limit UInt32
+        "The number of upload API calls allowed per month."
+
+    example limited
+        limit = 25000
+
+#
+# Route: feature/get_value_batch
+#
+
+struct FeaturesGetValuesBatchArg
+    features List(Feature)
+        "A list of features in :type:`Feature`. If the list is empty,
+        this route will return :type:`FeaturesGetValuesBatchError`."
+
+    example uploadRateLimit
+        features = [upload_api_rate_limit]
+
+
+struct FeaturesGetValuesBatchResult
+    values List(FeatureValue)
+
+    example uploadRateLimit
+        values = [uploadRateLimited]
+
+union FeaturesGetValuesBatchError
+    empty_features_list
+        "At least one :type:`Feature` must be included in the
+        :type:`FeaturesGetValuesBatchArg`.features list."
+
+route features/get_values(FeaturesGetValuesBatchArg, FeaturesGetValuesBatchResult, FeaturesGetValuesBatchError)
+    "Get the values for one or more featues. This route allows you to check your account's
+    capability for what feature you can access or what value you have for certain features.
+
+    Permission : Team information."
+
+    attrs
+        auth = "team"
+        owner = "dev-plat"

+ 56 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_common.stone

@@ -0,0 +1,56 @@
+namespace team_common
+
+import common
+
+alias TeamMemberId = String
+alias MemberExternalId = String(max_length=64)
+alias GroupExternalId = String
+
+alias GroupId = String
+alias ResellerId = String
+
+struct GroupSummary
+    "Information about a group."
+
+    group_name String
+    group_id GroupId
+    group_external_id GroupExternalId?
+        "External ID of group. This is an arbitrary ID that an admin can attach to a group."
+    member_count UInt32?
+        "The number of members in the group."
+    group_management_type GroupManagementType
+        "Who is allowed to manage the group."
+
+    example default
+        group_name = "Test group"
+        group_id = "g:e2db7665347abcd600000000001a2b3c"
+        member_count = 10
+        group_management_type = user_managed
+
+
+union GroupManagementType
+    "The group type determines how a group is managed."
+
+    user_managed
+        "A group which is managed by selected users."
+    company_managed
+        "A group which is managed by team admins only."
+    system_managed
+        "A group which is managed automatically by Dropbox."
+
+
+union GroupType
+    "The group type determines how a group is created and managed."
+
+    team
+        "A group to which team members are automatically added. Applicable to
+        :link:`team folders https://www.dropbox.com/help/986` only."
+    user_managed
+        "A group is created and managed by a user."
+
+struct TimeRange
+    "Time range."
+    start_time common.DropboxTimestamp?
+        "Optional starting time (inclusive)."
+    end_time common.DropboxTimestamp?
+        "Optional ending time (exclusive)."

+ 316 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_devices.stone

@@ -0,0 +1,316 @@
+namespace team
+
+import common
+
+#
+# Structs for devices/list_member_devices
+#
+
+struct ListMemberDevicesArg
+    team_member_id String
+        "The team's member id"
+    include_web_sessions Boolean = true
+        "Whether to list web sessions of the team's member"
+    include_desktop_clients Boolean = true
+        "Whether to list linked desktop devices of the team's member"
+    include_mobile_clients Boolean = true
+        "Whether to list linked mobile devices of the team's member"
+
+    example default
+        team_member_id="dbmid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+
+struct DeviceSession
+    session_id String
+        "The session id"
+    ip_address String?
+        "The IP address of the last activity from this session"
+    country String?
+        "The country from which the last activity from this session was made"
+    created common.DropboxTimestamp?
+        "The time this session was created"
+    updated common.DropboxTimestamp?
+        "The time of the last activity from this session"
+
+# TODO(gal) - for mobile, camera uploads don't count for updated activity (See T25556
+# for more context). This needs to be documented somewhere.
+
+struct ActiveWebSession extends DeviceSession
+    "Information on active web sessions"
+
+    user_agent String
+        "Information on the hosting device"
+    os String
+        "Information on the hosting operating system"
+    browser String
+        "Information on the browser used for this web session"
+    expires common.DropboxTimestamp?
+        "The time this session expires"
+
+    example default
+        session_id = "dbwsid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+        ip_address = "3.3.3.3"
+        country = "United States"
+        created = "2015-05-12T15:50:38Z"
+        updated = "2015-05-12T15:51:22Z"
+        user_agent = "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.0 Safari/535.1"
+        os = "Windows"
+        browser = "Chrome"
+        expires = "2015-05-13T15:51:22Z"
+
+union DesktopPlatform
+    windows
+        "Official Windows Dropbox desktop client"
+    mac
+        "Official Mac Dropbox desktop client"
+    linux
+        "Official Linux Dropbox desktop client"
+
+struct DesktopClientSession extends DeviceSession
+    "Information about linked Dropbox desktop client sessions"
+
+    host_name String
+        "Name of the hosting desktop"
+    client_type DesktopPlatform
+        "The Dropbox desktop client type"
+    client_version String
+        "The Dropbox client version"
+    platform String
+        "Information on the hosting platform"
+    is_delete_on_unlink_supported Boolean
+        "Whether it's possible to delete all of the account files upon unlinking"
+
+    example default
+        session_id= "dbdsid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+        ip_address = "3.3.3.3"
+        country = "United States"
+        created = "2015-05-12T15:50:38Z"
+        updated = "2015-05-12T15:51:22Z"
+        host_name = "Home_desktop"
+        client_type = mac
+        client_version = "3.9.19"
+        platform = "Mac OS X 10.10.3"
+        is_delete_on_unlink_supported = true
+
+union MobileClientPlatform
+    iphone
+        "Official Dropbox iPhone client"
+    ipad
+        "Official Dropbox iPad client"
+    android
+        "Official Dropbox Android client"
+    windows_phone
+        "Official Dropbox Windows phone client"
+    blackberry
+        "Official Dropbox Blackberry client"
+
+struct MobileClientSession extends DeviceSession
+    "Information about linked Dropbox mobile client sessions"
+
+    device_name String
+        "The device name"
+    client_type MobileClientPlatform
+        "The mobile application type"
+    client_version String?
+        "The dropbox client version"
+    os_version String?
+        "The hosting OS version"
+    last_carrier String?
+        "last carrier used by the device"
+
+    example default
+        session_id = "dbmsid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+        ip_address = "3.3.3.3"
+        country = "United States"
+        created = "2015-05-12T15:50:38Z"
+        updated = "2015-05-12T15:51:22Z"
+        device_name = "Iphone 6"
+        client_type = iphone
+        client_version = "3.9.5"
+        os_version = "8.4"
+        last_carrier = "Verizon"
+
+struct ListMemberDevicesResult
+    active_web_sessions List(ActiveWebSession)?
+        "List of web sessions made by this team member"
+    desktop_client_sessions List(DesktopClientSession)?
+        "List of desktop clients used by this team member"
+    mobile_client_sessions List(MobileClientSession)?
+        "List of mobile client used by this team member"
+
+union ListMemberDevicesError
+    member_not_found
+        "Member not found."
+
+#
+# Route: devices/list_member_devices
+#
+
+route devices/list_member_devices(ListMemberDevicesArg, ListMemberDevicesResult, ListMemberDevicesError)
+    "List all device sessions of a team's member."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Structs for devices/list_members_devices
+#
+
+struct ListMembersDevicesArg
+    cursor String?
+        "At the first call to the :route:`devices/list_members_devices` the cursor shouldn't be passed.
+        Then, if the result of the call includes a cursor, the following requests should include the
+        received cursors in order to receive the next sub list of team devices"
+    include_web_sessions Boolean = true
+        "Whether to list web sessions of the team members"
+    include_desktop_clients Boolean = true
+        "Whether to list desktop clients of the team members"
+    include_mobile_clients Boolean = true
+        "Whether to list mobile clients of the team members"
+
+struct MemberDevices
+    "Information on devices of a team's member."
+
+    team_member_id String
+        "The member unique Id"
+    web_sessions List(ActiveWebSession)?
+        "List of web sessions made by this team member"
+    desktop_clients List(DesktopClientSession)?
+        "List of desktop clients by this team member"
+    mobile_clients List(MobileClientSession)?
+        "List of mobile clients by this team member"
+
+struct ListMembersDevicesResult
+    devices List(MemberDevices)
+        "The devices of each member of the team"
+    has_more Boolean
+        "If true, then there are more devices available. Pass the
+        cursor to :route:`devices/list_members_devices` to retrieve the rest."
+    cursor String?
+        "Pass the cursor into :route:`devices/list_members_devices` to receive the next
+        sub list of team's devices."
+
+union ListMembersDevicesError
+    reset
+        "Indicates that the cursor has been invalidated. Call
+        :route:`devices/list_members_devices` again with an empty cursor to obtain a new cursor."
+
+#
+# Route: devices/list_members_devices
+#
+route devices/list_members_devices(ListMembersDevicesArg, ListMembersDevicesResult, ListMembersDevicesError)
+    "List all device sessions of a team."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Structs for devices/revoke_device_session
+#
+
+struct DeviceSessionArg
+    session_id String
+        "The session id"
+    team_member_id String
+        "The unique id of the member owning the device"
+
+struct RevokeDesktopClientArg extends DeviceSessionArg
+    delete_on_unlink Boolean = false
+        "Whether to delete all files of the account (this is possible only if supported by
+        the desktop client and  will be made the next time the client access the account)"
+
+union_closed RevokeDeviceSessionArg
+    web_session DeviceSessionArg
+        "End an active session"
+    desktop_client RevokeDesktopClientArg
+        "Unlink a linked desktop device"
+    mobile_client DeviceSessionArg
+        "Unlink a linked mobile device"
+
+union RevokeDeviceSessionError
+    device_session_not_found
+        "Device session not found."
+    member_not_found
+        "Member not found."
+
+#
+# Route: devices/revoke_device_session
+#
+
+route devices/revoke_device_session(RevokeDeviceSessionArg, Void, RevokeDeviceSessionError)
+    "Revoke a device session of a team's member"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# structs for devices/revoke_device_session_batch
+#
+
+struct RevokeDeviceSessionBatchArg
+    revoke_devices List(RevokeDeviceSessionArg)
+
+struct RevokeDeviceSessionStatus
+
+    success Boolean
+        "Result of the revoking request"
+    error_type RevokeDeviceSessionError?
+        "The error cause in case of a failure"
+
+struct RevokeDeviceSessionBatchResult
+    revoke_devices_status List(RevokeDeviceSessionStatus)
+
+union RevokeDeviceSessionBatchError
+    ""
+
+#
+# Route: devices/revoke_device_session_batch
+#
+
+route devices/revoke_device_session_batch(RevokeDeviceSessionBatchArg, RevokeDeviceSessionBatchResult, RevokeDeviceSessionBatchError)
+    "Revoke a list of device sessions of team members"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+#
+# Deprecated endpoints
+#
+
+struct ListTeamDevicesArg
+    cursor String?
+        "At the first call to the :route:`devices/list_team_devices` the cursor shouldn't be passed.
+        Then, if the result of the call includes a cursor, the following requests should include the
+        received cursors in order to receive the next sub list of team devices"
+    include_web_sessions Boolean = true
+        "Whether to list web sessions of the team members"
+    include_desktop_clients Boolean = true
+        "Whether to list desktop clients of the team members"
+    include_mobile_clients Boolean = true
+        "Whether to list mobile clients of the team members"
+
+struct ListTeamDevicesResult
+    devices List(MemberDevices)
+        "The devices of each member of the team"
+    has_more Boolean
+        "If true, then there are more devices available. Pass the
+        cursor to :route:`devices/list_team_devices` to retrieve the rest."
+    cursor String?
+        "Pass the cursor into :route:`devices/list_team_devices` to receive the next
+        sub list of team's devices."
+
+union ListTeamDevicesError
+    reset
+        "Indicates that the cursor has been invalidated. Call
+        :route:`devices/list_team_devices` again with an empty cursor to obtain a new cursor."
+
+route devices/list_team_devices(ListTeamDevicesArg, ListTeamDevicesResult, ListTeamDevicesError) deprecated by devices/list_members_devices
+    "List all device sessions of a team."
+
+    attrs
+        auth = "team"
+        owner = "adminx"

+ 292 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_folders.stone

@@ -0,0 +1,292 @@
+namespace team
+
+import async
+import common
+
+# Common structs
+
+union TeamFolderStatus
+    active
+        "The team folder and sub-folders are available to all members."
+    archived
+        "The team folder is not accessible outside of the team folder manager."
+    archive_in_progress
+        "The team folder is not accessible outside of the team folder manager."
+
+struct TeamFolderIdArg
+    team_folder_id common.SharedFolderId
+        "The ID of the team folder."
+
+    example default
+        team_folder_id = "123456789"
+
+struct TeamFolderIdListArg
+    team_folder_ids List(common.SharedFolderId, min_items=1)
+        "The list of team folder IDs."
+
+    example default
+        team_folder_ids = ["947182", "5819424", "852307532"]
+
+struct TeamFolderMetadata
+    "Properties of a team folder."
+
+    team_folder_id common.SharedFolderId
+        "The ID of the team folder."
+
+    name String
+        "The name of the team folder."
+
+    status TeamFolderStatus
+        "The status of the team folder."
+
+    example default
+        name = "Marketing"
+        team_folder_id = "123456789"
+        status = active
+
+union TeamFolderAccessError
+    invalid_team_folder_id
+        "The team folder ID is invalid."
+    no_access
+        "The authenticated app does not have permission to manage that team folder."
+
+union TeamFolderInvalidStatusError
+    active
+        "The folder is active and the operation did not succeed."
+    archived
+        "The folder is archived and the operation did not succeed."
+    archive_in_progress
+        "The folder is being archived and the operation did not succeed."
+
+union BaseTeamFolderError
+    "Base error that all errors for existing team folders should extend."
+    access_error TeamFolderAccessError
+    status_error TeamFolderInvalidStatusError
+
+#
+# Team folder create
+#
+
+route team_folder/create(TeamFolderCreateArg, TeamFolderMetadata, TeamFolderCreateError)
+    "Creates a new, active, team folder.
+
+    Permission : Team member file access.
+    "
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+struct TeamFolderCreateArg
+    name String
+        "Name for the new team folder."
+
+    example default
+        name = "Marketing"
+
+union TeamFolderCreateError
+    invalid_folder_name
+        "The provided name cannot be used."
+    folder_name_already_used
+        "There is already a team folder with the provided name."
+    folder_name_reserved
+        "The provided name cannot be used because it is reserved."
+
+#
+# Team folder rename
+#
+
+route team_folder/rename(TeamFolderRenameArg, TeamFolderMetadata, TeamFolderRenameError)
+    "Changes an active team folder's name.
+
+    Permission : Team member file access.
+    "
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+struct TeamFolderRenameArg extends TeamFolderIdArg
+    name String
+        "New team folder name."
+
+    example default
+        team_folder_id = "123456789"
+        name = "Sales"
+
+union TeamFolderRenameError extends BaseTeamFolderError
+    invalid_folder_name
+        "The provided folder name cannot be used."
+    folder_name_already_used
+        "There is already a team folder with the same name."
+    folder_name_reserved
+        "The provided name cannot be used because it is reserved."
+
+#
+# Team folder list
+#
+
+route team_folder/list(TeamFolderListArg, TeamFolderListResult, TeamFolderListError)
+    "Lists all team folders.
+
+    Permission : Team member file access.
+    "
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+struct TeamFolderListArg
+    limit UInt32(min_value=1, max_value=1000) = 1000
+        "The maximum number of results to return per request."
+
+    example default
+        limit = 100
+
+struct TeamFolderListResult
+    "Result for :route:`team_folder/list` and :route:`team_folder/list/continue`."
+
+    team_folders List(TeamFolderMetadata)
+        "List of all team folders in the authenticated team."
+    cursor String
+        "Pass the cursor into :route:`team_folder/list/continue` to obtain additional team folders."
+    has_more Boolean
+        "Is true if there are additional team folders that have not been returned
+        yet. An additional call to :route:`team_folder/list/continue` can retrieve them."
+
+    example default
+        team_folders = [default]
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+        has_more = false
+
+struct TeamFolderListError
+    access_error TeamFolderAccessError
+
+#
+# Team folder list/continue
+#
+
+route team_folder/list/continue(TeamFolderListContinueArg, TeamFolderListResult, TeamFolderListContinueError)
+    "Once a cursor has been retrieved from :route:`team_folder/list`, use this to paginate
+    through all team folders.
+
+    Permission : Team member file access."
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+struct TeamFolderListContinueArg
+    cursor String
+        "Indicates from what point to get the next set of team folders."
+
+    example default
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+
+union TeamFolderListContinueError
+    invalid_cursor
+        "The cursor is invalid."
+
+#
+# Team folder get info
+#
+
+route team_folder/get_info(TeamFolderIdListArg, List(TeamFolderGetInfoItem), Void)
+    "Retrieves metadata for team folders.
+
+    Permission : Team member file access.
+    "
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+union_closed TeamFolderGetInfoItem
+    id_not_found String
+        "An ID that was provided as a parameter to :route:`team_folder/get_info` did not
+        match any of the team's team folders."
+    team_folder_metadata TeamFolderMetadata
+        "Properties of a team folder."
+
+#
+# Team folder activate
+#
+
+route team_folder/activate(TeamFolderIdArg, TeamFolderMetadata, TeamFolderActivateError)
+    "Sets an archived team folder's status to active.
+
+    Permission : Team member file access.
+    "
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+union TeamFolderActivateError extends BaseTeamFolderError
+    ""
+
+#
+# Team folder archive
+#
+
+route team_folder/archive(TeamFolderArchiveArg, TeamFolderArchiveLaunch, TeamFolderArchiveError)
+    "Sets an active team folder's status to archived and removes all folder and file members.
+
+    Permission : Team member file access.
+    "
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+struct TeamFolderArchiveArg extends TeamFolderIdArg
+    force_async_off Boolean = false
+        "Whether to force the archive to happen synchronously."
+
+    example default
+        team_folder_id = "123456789"
+        force_async_off = false
+
+union_closed TeamFolderArchiveLaunch extends async.LaunchResultBase
+    complete TeamFolderMetadata
+
+    example default
+        complete = default
+
+union TeamFolderArchiveError extends BaseTeamFolderError
+    ""
+
+route team_folder/archive/check(async.PollArg, TeamFolderArchiveJobStatus, async.PollError)
+    "Returns the status of an asynchronous job for archiving a team folder.
+
+    Permission : Team member file access.
+    "
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+union_closed TeamFolderArchiveJobStatus extends async.PollResultBase
+    complete TeamFolderMetadata
+        "The archive job has finished. The value is the metadata for the resulting team folder."
+    failed TeamFolderArchiveError
+        "Error occurred while performing an asynchronous job from :route:`team_folder/archive`."
+
+    example default
+        complete = default
+
+#
+# Team folder permanently delete
+#
+
+route team_folder/permanently_delete(TeamFolderIdArg, Void, TeamFolderPermanentlyDeleteError)
+    "Permanently deletes an archived team folder.
+
+    Permission : Team member file access.
+    "
+
+    attrs
+        owner = "company-dropbox-team"
+        auth = "team"
+
+union TeamFolderPermanentlyDeleteError extends BaseTeamFolderError
+    ""

+ 558 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_groups.stone

@@ -0,0 +1,558 @@
+namespace team
+
+import async
+import team_common
+
+#
+# Common types.
+#
+
+union_closed GroupAccessType
+    "Role of a user in group."
+
+    member
+        "User is a member of the group, but has no special permissions."
+    owner
+        "User can rename the group, and add/remove members."
+
+    example default
+        member = null
+
+union_closed GroupSelector
+    "Argument for selecting a single group, either by group_id or by external group ID."
+
+    group_id team_common.GroupId
+        "Group ID."
+    group_external_id team_common.GroupExternalId
+        "External ID of the group."
+
+    example default
+        group_id = "g:e2db7665347abcd600000000001a2b3c"
+
+union GroupSelectorError
+    "Error that can be raised when :type:`GroupSelector` is used."
+
+    group_not_found
+        "No matching group found. No groups match the specified group ID."
+
+union GroupSelectorWithTeamGroupError extends GroupSelectorError
+    "Error that can be raised when :type:`GroupSelector` is used and team groups are disallowed from
+    being used."
+
+    system_managed_group_disallowed
+        "This operation is not supported on system-managed groups."
+
+
+union_closed GroupsSelector
+    "Argument for selecting a list of groups, either by group_ids, or external group IDs."
+
+    group_ids List(team_common.GroupId)
+        "List of group IDs."
+    group_external_ids List(String)
+        "List of external IDs of groups."
+
+    example default
+        group_ids = ["g:e2db7665347abcd600000000001a2b3c", "g:111111147abcd6000000000222222c"]
+
+
+struct GroupMemberSelector
+    "Argument for selecting a group and a single user."
+
+    group GroupSelector
+        "Specify a group."
+    user UserSelectorArg
+        "Identity of a user that is a member of :field:`group`."
+
+    example default
+        group = default
+        user = default
+
+
+union GroupMemberSelectorError extends GroupSelectorWithTeamGroupError
+    "Error that can be raised when :type:`GroupMemberSelector` is used, and the user
+    is required to be a member of the specified group."
+
+    member_not_in_group
+        "The specified user is not a member of this group."
+
+
+struct GroupMembersSelector
+    "Argument for selecting a group and a list of users."
+
+    group GroupSelector
+        "Specify a group."
+    users UsersSelectorArg
+        "A list of users that are members of :field:`group`."
+
+
+union GroupMembersSelectorError extends GroupSelectorWithTeamGroupError
+    "Error that can be raised when :type:`GroupMembersSelector` is used, and the users
+    are required to be members of the specified group."
+
+    member_not_in_group
+        "At least one of the specified users is not a member of the group."
+
+
+struct IncludeMembersArg
+
+    return_members Boolean = true
+        "Whether to return the list of members in the group.
+         Note that the default value will cause all the group members
+         to be returned in the response. This may take a long time for large groups."
+
+####################
+# Group Info methods
+####################
+
+
+struct GroupMemberInfo
+    "Profile of group member, and role in group."
+
+    profile MemberProfile
+        "Profile of group member."
+    access_type GroupAccessType
+        "The role that the user has in the group."
+
+    example default
+        profile = default
+        access_type = default
+
+
+struct GroupFullInfo extends team_common.GroupSummary
+    "Full description of a group."
+
+    members List(GroupMemberInfo)?
+        "List of group members."
+    created UInt64
+        "The group creation time as a UTC timestamp in milliseconds since the Unix epoch."
+
+    example default
+        group_name = "project launch"
+        group_id = "g:e2db7665347abcd600000000001a2b3c"
+        member_count = 5
+        group_management_type = user_managed
+        members = [default]
+        created = 1447255518000
+
+#
+# route groups/list
+#
+
+struct GroupsListArg
+    limit UInt32(min_value=1, max_value=1000) = 1000
+        "Number of results to return per call."
+
+    example default
+        limit = 100
+
+
+struct GroupsListResult
+    groups List(team_common.GroupSummary)
+    cursor String
+        "Pass the cursor into :route:`groups/list/continue` to obtain the additional groups."
+    has_more Boolean
+        "Is true if there are additional groups that have not been returned
+        yet. An additional call to :route:`groups/list/continue` can retrieve them."
+
+    example default
+        groups = [default]
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+        has_more = false
+
+route groups/list(GroupsListArg, GroupsListResult, Void)
+    "Lists groups on a team.
+
+    Permission : Team Information."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/list/continue
+#
+
+struct GroupsListContinueArg
+    cursor String
+        "Indicates from what point to get the next set of groups."
+
+    example default
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+
+union GroupsListContinueError
+    invalid_cursor
+        "The cursor is invalid."
+
+route groups/list/continue(GroupsListContinueArg, GroupsListResult, GroupsListContinueError)
+    "Once a cursor has been retrieved from :route:`groups/list`, use this to paginate
+    through all groups.
+
+    Permission : Team Information."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/get_info
+#
+
+union_closed GroupsGetInfoItem
+    id_not_found String
+        "An ID that was provided as a parameter to :route:`groups/get_info`, and
+        did not match a corresponding group. The ID can be a group ID, or an external ID,
+        depending on how the method was called."
+    group_info GroupFullInfo
+        "Info about a group."
+
+    example default
+        group_info = default
+
+
+alias GroupsGetInfoResult = List(GroupsGetInfoItem)
+
+
+union GroupsGetInfoError
+    group_not_on_team
+        "The group is not on your team."
+
+route groups/get_info(GroupsSelector, GroupsGetInfoResult, GroupsGetInfoError)
+    "Retrieves information about one or more groups. Note that the optional field
+     :field:`GroupFullInfo.members` is not returned for system-managed groups.
+
+    Permission : Team Information."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+##########################
+# Group management methods
+##########################
+
+
+#
+# route groups/create
+#
+
+struct GroupCreateArg
+    group_name String
+        "Group name."
+    group_external_id team_common.GroupExternalId?
+        "The creator of a team can associate an arbitrary external ID to the group."
+    group_management_type team_common.GroupManagementType?
+        "Whether the team can be managed by selected users, or only by team admins."
+
+    example default
+        group_name = "Europe sales"
+        group_external_id = "group-134"
+
+union GroupCreateError
+    group_name_already_used
+        "The requested group name is already being used by another group."
+    group_name_invalid
+        "Group name is empty or has invalid characters."
+    external_id_already_in_use
+        "The requested external ID is already being used by another group."
+    system_managed_group_disallowed
+        "System-managed group cannot be manually created."
+
+
+route groups/create(GroupCreateArg, GroupFullInfo, GroupCreateError)
+    "Creates a new, empty group, with a requested name.
+
+    Permission : Team member management."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/delete  (async method)
+#
+
+union GroupDeleteError extends GroupSelectorWithTeamGroupError
+    group_already_deleted
+        "This group has already been deleted."
+
+
+route groups/delete(GroupSelector, async.LaunchEmptyResult, GroupDeleteError)
+    "Deletes a group.
+
+    The group is deleted immediately. However the revoking of group-owned resources
+    may take additional time.
+    Use the :route:`groups/job_status/get` to determine whether this process has completed.
+
+    Permission : Team member management."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/update
+#
+
+struct GroupUpdateArgs extends IncludeMembersArg
+    group GroupSelector
+        "Specify a group."
+    new_group_name String?
+        "Optional argument. Set group name to this if provided."
+    new_group_external_id team_common.GroupExternalId?
+        "Optional argument. New group external ID.
+        If the argument is None, the group's external_id won't be updated.
+        If the argument is empty string, the group's external id will be cleared."
+    new_group_management_type team_common.GroupManagementType?
+        "Set new group management type, if provided."
+
+    example default
+        group = default
+        new_group_name = "Europe west sales"
+        new_group_external_id = "sales-234"
+        new_group_management_type = company_managed
+
+union GroupUpdateError extends GroupSelectorWithTeamGroupError
+    group_name_already_used
+        "The requested group name is already being used by another group."
+    group_name_invalid
+        "Group name is empty or has invalid characters."
+    external_id_already_in_use
+        "The requested external ID is already being used by another group."
+
+route groups/update(GroupUpdateArgs, GroupFullInfo, GroupUpdateError)
+    "Updates a group's name and/or external ID.
+
+    Permission : Team member management."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Structures common to groups/members/add and  groups/members/remove
+#
+
+struct GroupMembersChangeResult
+    "Result returned by :route:`groups/members/add` and :route:`groups/members/remove`."
+
+    group_info GroupFullInfo
+        "The group info after member change operation has been performed."
+    async_job_id async.AsyncJobId
+        "An ID that can be used to obtain the status of granting/revoking group-owned resources."
+
+    example default
+        group_info = default
+        async_job_id = "99988877733388"
+
+#
+# route groups/members/add  (async method)
+#
+
+struct MemberAccess
+    "Specify access type a member should have when joined to a group."
+
+    user UserSelectorArg
+        "Identity of a user."
+    access_type GroupAccessType
+        "Access type."
+
+    example default
+        user = default
+        access_type = default
+
+struct GroupMembersAddArg extends IncludeMembersArg
+    group GroupSelector
+        "Group to which users will be added."
+    members List(MemberAccess)
+        "List of users to be added to the group."
+
+    example default
+        group = default
+        members = [default]
+
+
+union GroupMembersAddError extends GroupSelectorWithTeamGroupError
+    duplicate_user
+        "You cannot add duplicate users. One or more of the members
+        you are trying to add is already a member of the group."
+    group_not_in_team
+        "Group is not in this team. You cannot add members to a
+        group that is outside of your team."
+    members_not_in_team List(String)
+        "These members are not part of your team. Currently, you cannot add members
+        to a group if they are not part of your team, though this
+        may change in a subsequent version. To add new members to your Dropbox
+        Business team, use the :route:`members/add` endpoint."
+    users_not_found List(String)
+        "These users were not found in Dropbox."
+    user_must_be_active_to_be_owner
+        "A suspended user cannot be added to a group as :field:`GroupAccessType.owner`."
+    user_cannot_be_manager_of_company_managed_group List(String)
+        "A company-managed group cannot be managed by a user."
+
+
+route groups/members/add(GroupMembersAddArg, GroupMembersChangeResult, GroupMembersAddError)
+    "Adds members to a group.
+
+    The members are added immediately. However the granting of group-owned resources
+    may take additional time.
+    Use the :route:`groups/job_status/get` to determine whether this process has completed.
+
+    Permission : Team member management."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/members/remove  (async method)
+#
+
+struct GroupMembersRemoveArg extends IncludeMembersArg
+    group GroupSelector
+        "Group from which users will be removed."
+    users List(UserSelectorArg)
+        "List of users to be removed from the group."
+
+    example default
+        group = default
+        users = [default]
+
+union GroupMembersRemoveError extends GroupMembersSelectorError
+    group_not_in_team
+        "Group is not in this team. You cannot remove members from a group
+        that is outside of your team."
+    members_not_in_team List(String)
+        "These members are not part of your team."
+    users_not_found List(String)
+        "These users were not found in Dropbox."
+
+route groups/members/remove(GroupMembersRemoveArg, GroupMembersChangeResult, GroupMembersRemoveError)
+    "Removes members from a group.
+
+    The members are removed immediately. However the revoking of group-owned resources
+    may take additional time.
+    Use the :route:`groups/job_status/get` to determine whether this process has completed.
+
+    This method permits removing the only owner of a group, even in cases where this is not
+    possible via the web client.
+
+    Permission : Team member management."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/members/set_access_type
+#
+
+
+struct GroupMembersSetAccessTypeArg extends GroupMemberSelector
+    access_type GroupAccessType
+        "New group access type the user will have."
+    return_members Boolean = true
+        "Whether to return the list of members in the group.
+         Note that the default value will cause all the group members
+         to be returned in the response. This may take a long time for large groups."
+
+    example default
+        group = default
+        user = default
+        access_type = default
+
+union GroupMemberSetAccessTypeError extends GroupMemberSelectorError
+    user_cannot_be_manager_of_company_managed_group
+        "A company managed group cannot be managed by a user."
+
+route groups/members/set_access_type(GroupMembersSetAccessTypeArg, GroupsGetInfoResult, GroupMemberSetAccessTypeError)
+    "Sets a member's access type in a group.
+
+    Permission : Team member management."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+#
+# route groups/members/list
+#
+
+struct GroupsMembersListArg
+    group GroupSelector
+        "The group whose members are to be listed."
+    limit UInt32(min_value=1, max_value=1000) = 1000
+        "Number of results to return per call."
+
+    example default
+        group = default
+        limit = 100
+
+struct GroupsMembersListResult
+    members List(GroupMemberInfo)
+    cursor String
+        "Pass the cursor into :route:`groups/members/list/continue` to obtain additional group members."
+    has_more Boolean
+        "Is true if there are additional group members that have not been returned
+        yet. An additional call to :route:`groups/members/list/continue` can retrieve them."
+
+    example default
+        members = []
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+        has_more = false
+
+
+route groups/members/list(GroupsMembersListArg, GroupsMembersListResult, GroupSelectorError)
+    "Lists members of a group.
+
+    Permission : Team Information."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/members/list/continue
+#
+
+struct GroupsMembersListContinueArg
+    cursor String
+        "Indicates from what point to get the next set of groups."
+
+    example default
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+
+union GroupsMembersListContinueError
+    invalid_cursor
+        "The cursor is invalid."
+
+route groups/members/list/continue(GroupsMembersListContinueArg, GroupsMembersListResult, GroupsMembersListContinueError)
+    "Once a cursor has been retrieved from :route:`groups/members/list`, use this to paginate
+    through all members of the group.
+
+    Permission : Team information."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# route groups/job_status/get
+#
+
+union GroupsPollError extends async.PollError
+    access_denied
+        "You are not allowed to poll this job."
+
+route groups/job_status/get(async.PollArg, async.PollEmptyResult, GroupsPollError)
+    "Once an async_job_id is returned from :route:`groups/delete`,
+    :route:`groups/members/add` , or :route:`groups/members/remove`
+    use this method to poll the status of granting/revoking
+    group members' access to group-owned resources.
+
+    Permission : Team member management."
+
+    attrs
+        auth = "team"
+        owner = "adminx"

+ 215 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_linked_apps.stone

@@ -0,0 +1,215 @@
+namespace team
+
+import common
+
+#
+# Structures for linked_apps/list_member_linked_apps
+#
+
+struct ListMemberAppsArg
+    team_member_id String
+        "The team member id"
+
+    example default
+        team_member_id = "dbmid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+
+struct ApiApp
+    "Information on linked third party applications"
+
+    app_id String
+        "The application unique id"
+    app_name String
+        "The application name"
+    publisher String?
+        "The application publisher name"
+    publisher_url String?
+        "The publisher's URL"
+    linked common.DropboxTimestamp?
+        "The time this application was linked"
+    is_app_folder Boolean
+        "Whether the linked application uses a dedicated folder"
+
+    example default
+        app_id = "dbaid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+        app_name = "Notes"
+        publisher = "Notes company"
+        publisher_url = "http://company.com"
+        linked = "2015-05-12T15:50:38Z"
+        is_app_folder = true
+
+struct ListMemberAppsResult
+    linked_api_apps List(ApiApp)
+        "List of third party applications linked by this team member"
+
+union ListMemberAppsError
+    "Error returned by :route:`linked_apps/list_member_linked_apps`."
+
+    member_not_found
+        "Member not found."
+
+#
+# Route: linked_apps/list_member_linked_apps
+#
+
+route linked_apps/list_member_linked_apps(ListMemberAppsArg, ListMemberAppsResult, ListMemberAppsError)
+    "List all linked applications of the team member.
+
+    Note, this endpoint does not list any team-linked applications. "
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Structs for linked_apps/list_members_linked_apps
+#
+
+struct ListMembersAppsArg
+    "Arguments for :route:`linked_apps/list_members_linked_apps`."
+
+    cursor String?
+        "At the first call to the :route:`linked_apps/list_members_linked_apps` the cursor shouldn't be
+        passed. Then, if the result of the call includes a cursor, the following requests should
+        include the received cursors in order to receive the next sub list of the team applications"
+
+struct MemberLinkedApps
+    "Information on linked applications of a team member."
+
+    team_member_id String
+        "The member unique Id"
+    linked_api_apps List(ApiApp)
+        "List of third party applications linked by this team member"
+
+struct ListMembersAppsResult
+    "Information returned by :route:`linked_apps/list_members_linked_apps`."
+
+    apps List(MemberLinkedApps)
+        "The linked applications of each member of the team"
+    has_more Boolean
+        "If true, then there are more apps available. Pass the
+        cursor to :route:`linked_apps/list_members_linked_apps` to retrieve the rest."
+    cursor String?
+        "Pass the cursor into :route:`linked_apps/list_members_linked_apps` to receive the next
+        sub list of team's applications."
+
+union ListMembersAppsError
+    "Error returned by :route:`linked_apps/list_members_linked_apps`"
+
+    reset
+        "Indicates that the cursor has been invalidated. Call
+        :route:`linked_apps/list_members_linked_apps` again with an empty cursor to obtain a new cursor."
+
+#
+# Route: linked_apps/list_members_linked_apps
+#
+route linked_apps/list_members_linked_apps(ListMembersAppsArg, ListMembersAppsResult, ListMembersAppsError)
+    "List all applications linked to the team members' accounts.
+
+    Note, this endpoint does not list any team-linked applications. "
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Structs for linked_apps/revoke_linked_app
+#
+
+struct RevokeLinkedApiAppArg
+    app_id String
+        "The application's unique id"
+    team_member_id String
+        "The unique id of the member owning the device"
+    keep_app_folder Boolean = true
+        "Whether to keep the application dedicated folder (in case the application uses
+         one)"
+
+union RevokeLinkedAppError
+    "Error returned by :route:`linked_apps/revoke_linked_app`."
+
+    app_not_found
+        "Application not found."
+    member_not_found
+        "Member not found."
+
+#
+# Route: linked_apps/revoke_linked_app
+#
+
+route linked_apps/revoke_linked_app(RevokeLinkedApiAppArg, Void, RevokeLinkedAppError)
+    "Revoke a linked application of the team member"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# structs for linked_apps/revoke_linked_app_batch
+#
+
+struct RevokeLinkedApiAppBatchArg
+    revoke_linked_app List(RevokeLinkedApiAppArg)
+
+struct RevokeLinkedAppStatus
+
+    success Boolean
+        "Result of the revoking request"
+    error_type RevokeLinkedAppError?
+        "The error cause in case of a failure"
+
+struct RevokeLinkedAppBatchResult
+    revoke_linked_app_status List(RevokeLinkedAppStatus)
+
+union RevokeLinkedAppBatchError
+    "Error returned by :route:`linked_apps/revoke_linked_app_batch`."
+
+#
+# Route: linked_apps/revoke_linked_app_batch
+#
+
+route linked_apps/revoke_linked_app_batch(RevokeLinkedApiAppBatchArg, RevokeLinkedAppBatchResult, RevokeLinkedAppBatchError)
+    "Revoke a list of linked applications of the team members"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Deprecated endpoints
+#
+
+struct ListTeamAppsArg
+    "Arguments for :route:`linked_apps/list_team_linked_apps`."
+
+    cursor String?
+        "At the first call to the :route:`linked_apps/list_team_linked_apps` the cursor shouldn't be
+        passed. Then, if the result of the call includes a cursor, the following requests should
+        include the received cursors in order to receive the next sub list of the team applications"
+
+struct ListTeamAppsResult
+    "Information returned by :route:`linked_apps/list_team_linked_apps`."
+
+    apps List(MemberLinkedApps)
+        "The linked applications of each member of the team"
+    has_more Boolean
+        "If true, then there are more apps available. Pass the
+        cursor to :route:`linked_apps/list_team_linked_apps` to retrieve the rest."
+    cursor String?
+        "Pass the cursor into :route:`linked_apps/list_team_linked_apps` to receive the next
+        sub list of team's applications."
+
+union ListTeamAppsError
+    "Error returned by :route:`linked_apps/list_team_linked_apps`"
+
+    reset
+        "Indicates that the cursor has been invalidated. Call
+        :route:`linked_apps/list_team_linked_apps` again with an empty cursor to obtain a new cursor."
+
+route linked_apps/list_team_linked_apps(ListTeamAppsArg, ListTeamAppsResult, ListTeamAppsError) deprecated by linked_apps/list_members_linked_apps
+    "List all applications linked to the team members' accounts.
+
+    Note, this endpoint doesn't list any team-linked applications. "
+
+    attrs
+        auth = "team"
+        owner = "adminx"

+ 90 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_log.stone

@@ -0,0 +1,90 @@
+namespace team_log
+
+import async
+import team
+import team_common
+import users_common
+
+###############################
+# Routes declarations
+###############################
+
+struct GetTeamEventsArg
+    limit UInt32(min_value=1, max_value=1000) = 1000
+        "Number of results to return per call."
+    account_id users_common.AccountId?
+        "Filter the events by account ID. Return ony events with this account_id as either
+        Actor, Context, or Participants."
+    time team_common.TimeRange?
+        "Filter by time range."
+    # category filtering is disablled for now. to be enabled when
+    # TeamEventGetEvents.SUPPORT_CATEGORY_BASED_FILTERING_FLAG is changed
+    # category EventCategory?
+    #    "Filter the returned events to a single category."
+
+    example default
+        limit=50
+        #category=groups
+
+
+struct GetTeamEventsResult
+    events List(TeamEvent)
+        "List of events."
+    cursor String
+        "Pass the cursor into :route:`get_events/continue` to obtain additional events."
+    has_more Boolean
+        "Is true if there are additional events that have not been returned yet.
+        An additional call to :route:`get_events/continue` can retrieve them."
+
+    example default
+        events = [default]
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+        has_more = false
+
+union GetTeamEventsError
+    "Errors that can be raised when calling :route:`get_events`."
+
+    account_id_not_found
+        "No user found matching the provided account_id."
+    invalid_time_range
+        "Invalid time range."
+
+    example default
+        account_id_not_found = null
+
+route get_events(GetTeamEventsArg, GetTeamEventsResult, GetTeamEventsError)
+    "Retrieves team events.
+
+    Permission : Team Auditing."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+        is_preview = true
+
+struct GetTeamEventsContinueArg
+    cursor String
+        "Indicates from what point to get the next set of events."
+
+    example default
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+
+
+union GetTeamEventsContinueError
+    "Errors that can be raised when calling :route:`get_events/continue`."
+
+    bad_cursor
+        "Bad cursor."
+
+    example default
+        bad_cursor = null
+
+route get_events/continue(GetTeamEventsContinueArg, GetTeamEventsResult, GetTeamEventsContinueError)
+    "Once a cursor has been retrieved from :route:`get_events`, use this to paginate through all events.
+
+    Permission : Team Auditing."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+        is_preview = true

Файловите разлики са ограничени, защото са твърде много
+ 4341 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_log_generated.stone


+ 625 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_members.stone

@@ -0,0 +1,625 @@
+namespace team
+
+import async
+import users
+import common
+import team_common
+
+union_closed AdminTier
+    "Describes which team-related admin permissions a user has."
+
+    team_admin
+        "User is an administrator of the team - has all permissions."
+    user_management_admin
+        "User can do most user provisioning, de-provisioning and management."
+    support_admin
+        "User can do a limited set of common support tasks for existing users."
+    member_only
+        "User is not an admin of the team."
+
+    example default
+        member_only = null
+
+#
+# Common structs
+#
+
+struct TeamMemberProfile extends MemberProfile
+    "Profile of a user as a member of a team."
+
+    groups List(team_common.GroupId)
+        "List of group IDs of groups that the user belongs to."
+
+    example default
+        team_member_id = "dbmid:FDFSVF-DFSDF"
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+        external_id = "244423"
+        email = "tami@seagull.com"
+        email_verified = false
+        status = active
+        name = default
+        groups = ["g:e2db7665347abcd600000000001a2b3c"]
+        membership_type = full
+        joined_on = "2015-05-12T15:50:38Z"
+
+union_closed MemberSelectorError extends UserSelectorError
+    user_not_in_team
+        "The user is not a member of the team."
+
+
+########################
+# Member info routes
+########################
+
+#
+# Route: members/list
+#
+
+struct MembersListArg
+    limit UInt32(min_value=1, max_value=1000) = 1000
+        "Number of results to return per call."
+    include_removed Boolean = false
+        "Whether to return removed members."
+
+    example default
+        limit = 100
+        include_removed = false
+
+
+struct TeamMemberInfo
+    "Information about a team member."
+
+    profile TeamMemberProfile
+        "Profile of a user as a member of a team."
+    role AdminTier
+        "The user's role in the team."
+
+    example default
+        profile = default
+        role = member_only
+
+
+struct MembersListResult
+    members List(TeamMemberInfo)
+        "List of team members."
+    cursor String
+        "Pass the cursor into :route:`members/list/continue` to obtain the additional members."
+    has_more Boolean
+        "Is true if there are additional team members that have not been returned
+        yet. An additional call to :route:`members/list/continue` can retrieve them."
+
+    example default
+        members = [default]
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+        has_more = true
+
+
+union MembersListError
+    ""
+
+route members/list(MembersListArg, MembersListResult, MembersListError)
+    "Lists members of a team.
+
+    Permission : Team information"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+#
+# Route: members/list/continue
+#
+
+struct MembersListContinueArg
+    cursor String
+        "Indicates from what point to get the next set of members."
+
+    example default
+        cursor = "ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu"
+
+union MembersListContinueError
+    invalid_cursor
+        "The cursor is invalid."
+
+route members/list/continue(MembersListContinueArg, MembersListResult, MembersListContinueError)
+    "Once a cursor has been retrieved from :route:`members/list`, use this to paginate
+    through all team members.
+
+    Permission : Team information"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+#
+# Route: members/get_info
+#
+
+struct MembersGetInfoArgs
+    members List(UserSelectorArg)
+        "List of team members."
+
+    example default
+        members = [default]
+
+union_closed MembersGetInfoItem
+    "Describes a result obtained for a single user whose id was specified in the
+    parameter of :route:`members/get_info`."
+
+    id_not_found String
+        "An ID that was provided as a parameter to :route:`members/get_info`,
+        and did not match a corresponding user. This might be a team_member_id, an
+        email, or an external ID, depending on how the method was called."
+    member_info TeamMemberInfo
+        "Info about a team member."
+
+    example default
+        member_info = default
+
+
+# Information returned by :route:`members/get_info`.
+# describing multiple team members."
+alias MembersGetInfoResult = List(MembersGetInfoItem)
+
+
+union MembersGetInfoError
+    ""
+
+
+route members/get_info(MembersGetInfoArgs, MembersGetInfoResult, MembersGetInfoError)
+    "Returns information about multiple team members.
+
+    Permission : Team information
+
+    This endpoint will return :field:`MembersGetInfoItem.id_not_found`,
+    for IDs (or emails) that cannot be matched to a valid team member."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+##########################
+# Member management routes
+##########################
+
+
+#
+# Route members/add
+#
+
+struct MemberAddArg
+    member_email common.EmailAddress
+    member_given_name common.NamePart?
+        "Member's first name."
+    member_surname common.NamePart?
+        "Member's last name."
+    member_external_id team_common.MemberExternalId?
+        "External ID for member."
+    member_persistent_id String?
+        "Persistent ID for member. This field is only available to teams using persistent ID SAML configuration."
+    send_welcome_email Boolean = true
+        "Whether to send a welcome email to the member.
+        If send_welcome_email is false, no email invitation will be sent to the user.
+        This may be useful for apps using single sign-on (SSO) flows for onboarding that
+        want to handle announcements themselves."
+    role AdminTier = member_only
+
+    example default
+        member_email = "tom.s@company.com"
+        member_given_name = "Tom"
+        member_surname = "Silverstone"
+        member_external_id = "company_id:342432"
+        role = default
+
+struct MembersAddArg
+    new_members List(MemberAddArg)
+        "Details of new members to be added to the team."
+    force_async Boolean = false
+        "Whether to force the add to happen asynchronously."
+
+    example default
+        new_members = [default]
+
+
+union_closed MemberAddResult
+    "Describes the result of attempting to add a single user to the team.
+    'success' is the only value indicating that a user was indeed added to the team -
+    the other values explain the type of failure that occurred, and include the email
+    of the user for which the operation has failed."
+
+    success TeamMemberInfo
+        "Describes a user that was successfully added to the team."
+    team_license_limit common.EmailAddress
+        "Team is already full. The organization has no available licenses."
+    free_team_member_limit_reached common.EmailAddress
+        "Team is already full. The free team member limit has been reached."
+    user_already_on_team common.EmailAddress
+        "User is already on this team. The provided email address is associated
+        with a user who is already a member of (including in recoverable state) or invited to the team."
+    user_on_another_team common.EmailAddress
+        "User is already on another team. The provided email address is associated
+        with a user that is already a member or invited to another team."
+    user_already_paired common.EmailAddress
+        "User is already paired."
+    user_migration_failed common.EmailAddress
+        "User migration has failed."
+    duplicate_external_member_id common.EmailAddress
+        "A user with the given external member ID already exists on the team (including in recoverable state)."
+    duplicate_member_persistent_id common.EmailAddress
+        "A user with the given persistent ID already exists on the team (including in recoverable state)."
+    persistent_id_disabled common.EmailAddress
+        "Persistent ID is only available to teams with persistent ID SAML configuration. Please contact Dropbox for more information."
+    user_creation_failed common.EmailAddress
+        "User creation has failed."
+
+    example default
+        success = default
+
+union_closed MembersAddLaunch extends async.LaunchResultBase
+    complete List(MemberAddResult)
+
+    example default
+        complete = [default]
+
+route members/add(MembersAddArg, MembersAddLaunch, Void)
+    "Adds members to a team.
+
+    Permission : Team member management
+
+    A maximum of 20 members can be specified in a single call.
+
+    If no Dropbox account exists with the email address specified, a new Dropbox account will
+    be created with the given email address, and that account will be invited to the team.
+
+    If a personal Dropbox account exists with the email address specified in the call,
+    this call will create a placeholder Dropbox account for the user on the team and send an
+    email inviting the user to migrate their existing personal account onto the team.
+
+    Team member management apps are required to set an initial given_name and surname for a
+    user to use in the team invitation and for 'Perform as team member' actions taken on
+    the user before they become 'active'."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Route members/add/job_status/get
+#
+
+union_closed MembersAddJobStatus extends async.PollResultBase
+    complete List(MemberAddResult)
+        "The asynchronous job has finished. For each member that was specified in the
+        parameter :type:`MembersAddArg` that was provided to :route:`members/add`, a
+        corresponding item is returned in this list. "
+    failed String
+        "The asynchronous job returned an error. The string contains an error message."
+
+    example default
+        complete = [default]
+
+route members/add/job_status/get(async.PollArg, MembersAddJobStatus, async.PollError)
+    "Once an async_job_id is returned from :route:`members/add` ,
+    use this to poll the status of the asynchronous request.
+
+    Permission : Team member management"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Route members/set_profile
+#
+
+# Note that we do not allow changing 'familiar_name' and 'display_name from users.Name, since they
+# are derived from the given_name, surname and locale.
+struct MembersSetProfileArg
+    "Exactly one of team_member_id, email, or external_id must be provided to identify the user account.
+
+    At least one of new_email, new_external_id, new_given_name, and/or new_surname must be provided."
+
+    user UserSelectorArg
+        "Identity of user whose profile will be set."
+    new_email common.EmailAddress?
+        "New email for member."
+    new_external_id team_common.MemberExternalId?
+        "New external ID for member."
+    new_given_name common.NamePart?
+        "New given name for member."
+    new_surname common.NamePart?
+        "New surname for member."
+    new_persistent_id String?
+        "New persistent ID. This field only available to teams using persistent ID SAML configuration."
+
+    example default
+        user = default
+        new_email = "t.smith@domain.com"
+        new_surname = "Smith"
+
+union MembersSetProfileError extends MemberSelectorError
+    external_id_and_new_external_id_unsafe
+        "It is unsafe to use both external_id and new_external_id"
+    no_new_data_specified
+        "None of new_email, new_given_name, new_surname, or new_external_id are specified"
+    email_reserved_for_other_user
+        "Email is already reserved for another user."
+    external_id_used_by_other_user
+        "The external ID is already in use by another team member."
+    set_profile_disallowed
+        "Pending team member's email cannot be modified."
+    param_cannot_be_empty
+        "Parameter new_email cannot be empty."
+    persistent_id_disabled
+        "Persistent ID is only available to teams with persistent ID SAML configuration. Please contact Dropbox for more information."
+    persistent_id_used_by_other_user
+        "The persistent ID is already in use by another team member."
+
+
+route members/set_profile(MembersSetProfileArg, TeamMemberInfo, MembersSetProfileError)
+    "Updates a team member's profile.
+
+    Permission : Team member management"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Route members/set_admin_permissions
+#
+
+struct MembersSetPermissionsArg
+    "Exactly one of team_member_id, email, or external_id must be provided to identify the user account."
+
+    user UserSelectorArg
+        "Identity of user whose role will be set."
+    new_role AdminTier
+        "The new role of the member."
+
+    example default
+        user = default
+        new_role = default
+
+
+struct MembersSetPermissionsResult
+    team_member_id team_common.TeamMemberId
+        "The member ID of the user to which the change was applied."
+    role AdminTier
+        "The role after the change."
+
+    example default
+        team_member_id = "dbmid:9978889"
+        role = default
+
+
+union MembersSetPermissionsError extends UserSelectorError
+    last_admin
+        "Cannot remove the admin setting of the last admin."
+    user_not_in_team
+        "The user is not a member of the team."
+    cannot_set_permissions
+        "Cannot remove/grant permissions."
+    team_license_limit
+        "Team is full. The organization has no available licenses."
+
+route members/set_admin_permissions(MembersSetPermissionsArg, MembersSetPermissionsResult, MembersSetPermissionsError)
+    "Updates a team member's permissions.
+
+    Permission : Team member management"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+#
+# Route members/send_welcome_email
+#
+
+union MembersSendWelcomeError extends MemberSelectorError
+    ""
+
+route members/send_welcome_email(UserSelectorArg, Void, MembersSendWelcomeError)
+    "Sends welcome email to pending team member.
+
+    Permission : Team member management
+
+    Exactly one of team_member_id, email, or external_id must be provided to identify the user account.
+
+    No-op if team member is not pending."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+
+#
+# Route members/remove
+#
+
+struct MembersDeactivateArg
+    "Exactly one of team_member_id, email, or external_id must be provided to identify the user account."
+
+    user UserSelectorArg
+        "Identity of user to remove/suspend."
+    wipe_data Boolean = true
+        "If provided, controls if the user's data will be deleted on their linked devices."
+
+    example default
+        user = default
+        wipe_data = false
+
+struct MembersRemoveArg extends MembersDeactivateArg
+
+    transfer_dest_id UserSelectorArg?
+        "If provided, files from the deleted member account will be
+        transferred to this user."
+    transfer_admin_id UserSelectorArg?
+        "If provided, errors during the transfer process will be sent via
+        email to this user. If the transfer_dest_id argument was provided,
+        then this argument must be provided as well."
+    keep_account Boolean = false
+        "Downgrade the member to a Basic account. The user will retain the email address associated with their Dropbox
+         account and data in their account that is not restricted to team members. In order to keep the account the argument wipe_data should be set to False."
+
+    example default
+        user = default
+        wipe_data = true
+        transfer_dest_id = default
+        transfer_admin_id = default
+        keep_account = false
+
+union MembersDeactivateError extends UserSelectorError
+    user_not_in_team
+        "The user is not a member of the team."
+
+union MembersRemoveError extends MembersDeactivateError
+    remove_last_admin
+        "The user is the last admin of the team, so it cannot be removed from it."
+    removed_and_transfer_dest_should_differ
+        "Expected removed user and transfer_dest user to be different"
+    removed_and_transfer_admin_should_differ
+        "Expected removed user and transfer_admin user to be different."
+    transfer_dest_user_not_found
+        "No matching user found for the argument transfer_dest_id."
+    transfer_dest_user_not_in_team
+        "The provided transfer_dest_id does not exist on this team."
+    transfer_admin_user_not_found
+        "No matching user found for the argument transfer_admin_id."
+    transfer_admin_user_not_in_team
+        "The provided transfer_admin_id does not exist on this team."
+    unspecified_transfer_admin_id
+        "The transfer_admin_id argument must be provided when file transfer is requested."
+    transfer_admin_is_not_admin
+        "Specified transfer_admin user is not a team admin."
+    cannot_keep_account_and_transfer
+        "Cannot keep account and transfer the data to another user at the same time."
+    cannot_keep_account_and_delete_data
+        "Cannot keep account and delete the data at the same time. To keep the account the argument wipe_data should be set to False."
+    email_address_too_long_to_be_disabled
+        # Added value in order to handle task T82902.
+        "The email address of the user is too long to be disabled."
+
+route members/remove(MembersRemoveArg, async.LaunchEmptyResult, MembersRemoveError)
+    "Removes a member from a team.
+
+    Permission : Team member management
+
+    Exactly one of team_member_id, email, or external_id must be provided to identify the user account.
+
+    Accounts can be recovered via :route:`members/recover` for a 7 day period
+    or until the account has been permanently deleted or transferred to another account
+    (whichever comes first). Calling :route:`members/add` while a user is still recoverable
+    on your team will return with :field:`MemberAddResult.user_already_on_team`.
+
+    This endpoint may initiate an asynchronous job. To obtain the final result
+    of the job, the client should periodically poll :route:`members/remove/job_status/get`."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Route members/remove/job_status/get
+#
+
+route members/remove/job_status/get(async.PollArg, async.PollEmptyResult, async.PollError)
+    "Once an async_job_id is returned from :route:`members/remove` ,
+    use this to poll the status of the asynchronous request.
+
+    Permission : Team member management"
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Route members/suspend
+#
+
+union MembersSuspendError extends MembersDeactivateError
+    suspend_inactive_user
+        "The user is not active, so it cannot be suspended."
+    suspend_last_admin
+        "The user is the last admin of the team, so it cannot be suspended."
+    team_license_limit
+        "Team is full. The organization has no available licenses."
+
+route members/suspend(MembersDeactivateArg, Void, MembersSuspendError)
+    "Suspend a member from a team.
+
+    Permission : Team member management
+
+    Exactly one of team_member_id, email, or external_id must be provided to identify the user account."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Route members/unsuspend
+#
+
+struct MembersUnsuspendArg
+    "Exactly one of team_member_id, email, or external_id must be provided to identify the user account."
+
+    user UserSelectorArg
+        "Identity of user to unsuspend."
+
+    example default
+        user = default
+
+
+union MembersUnsuspendError extends MembersDeactivateError
+    unsuspend_non_suspended_member
+        "The user is unsuspended, so it cannot be unsuspended again."
+    team_license_limit
+        "Team is full. The organization has no available licenses."
+
+route members/unsuspend(MembersUnsuspendArg, Void, MembersUnsuspendError)
+    "Unsuspend a member from a team.
+
+    Permission : Team member management
+
+    Exactly one of team_member_id, email, or external_id must be provided to identify the user account."
+
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# Route members/recover
+#
+
+struct MembersRecoverArg
+    "Exactly one of team_member_id, email, or external_id must be provided to identify the user account."
+
+    user UserSelectorArg
+        "Identity of user to recover."
+
+    example default
+        user = default
+
+
+union MembersRecoverError extends UserSelectorError
+    user_unrecoverable
+        "The user is not recoverable."
+    user_not_in_team
+        "The user is not a member of the team."
+    team_license_limit
+        "Team is full. The organization has no available licenses."
+
+route members/recover(MembersRecoverArg, Void, MembersRecoverError)
+    "Recover a deleted member.
+
+    Permission : Team member management
+
+    Exactly one of team_member_id, email, or external_id must be provided to identify the user account."
+
+    attrs
+        auth = "team"
+        owner = "adminx"

+ 73 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_policies.stone

@@ -0,0 +1,73 @@
+namespace team_policies
+
+struct TeamMemberPolicies
+    "Policies governing team members."
+
+    sharing TeamSharingPolicies
+        "Policies governing sharing."
+    emm_state EmmState
+        "This describes the Enterprise Mobility Management (EMM) state for this team.
+        This information can be used to understand if an organization is integrating with
+        a third-party EMM vendor to further manage and apply restrictions upon the team's
+        Dropbox usage on mobile devices.
+        This is a new feature and in the future we'll be adding more new fields and additional
+        documentation."
+
+    example default
+        sharing = default
+        emm_state = disabled
+
+struct TeamSharingPolicies
+    "Policies governing sharing within and outside of the team."
+
+    shared_folder_member_policy SharedFolderMemberPolicy
+        "Who can join folders shared by team members."
+    shared_folder_join_policy SharedFolderJoinPolicy
+        "Which shared folders team members can join."
+    shared_link_create_policy SharedLinkCreatePolicy
+        "Who can view shared links owned by team members."
+
+    example default
+        shared_folder_member_policy = team
+        shared_folder_join_policy = from_anyone
+        shared_link_create_policy = team_only
+
+# NOTE: we do not reuse sharing.MemberPolicy here since we may want to enable folder-specific member
+# policies that work on top of the broader team policies.
+union SharedFolderMemberPolicy
+    "Policy governing who can be a member of a folder shared by a team member."
+
+    team
+        "Only a teammate can be a member of a folder shared by a team member."
+    anyone
+        "Anyone can be a member of a folder shared by a team member."
+
+union SharedFolderJoinPolicy
+    "Policy governing which shared folders a team member can join."
+
+    from_team_only
+        "Team members can only join folders shared by teammates."
+    from_anyone
+        "Team members can join any shared folder, including those shared by users outside the team."
+
+union SharedLinkCreatePolicy
+    "Policy governing the visibility of shared links. This policy can apply to newly created shared
+    links, or all shared links."
+
+    default_public
+        "By default, anyone can access newly created shared links.
+        No login will be required to access the shared links unless overridden."
+    default_team_only
+        "By default, only members of the same team can access newly created shared links.
+        Login will be required to access the shared links unless overridden."
+    team_only
+        "Only members of the same team can access all shared links.
+        Login will be required to access all shared links."
+
+union EmmState
+    disabled
+        "Emm token is disabled"
+    optional
+        "Emm token is optional"
+    required
+        "Emm token is required"

+ 86 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_property_templates.stone

@@ -0,0 +1,86 @@
+namespace team
+
+import properties
+
+#
+# Property Group Template Routes
+#
+
+route properties/template/add(AddPropertyTemplateArg, AddPropertyTemplateResult, properties.ModifyPropertyTemplateError)
+    "Add a property template. See route files/properties/add to add properties to a file."
+
+    attrs
+        auth="team"
+        api_group="properties"
+        is_preview=true
+        owner="dev-plat"
+
+route properties/template/update(UpdatePropertyTemplateArg, UpdatePropertyTemplateResult, properties.ModifyPropertyTemplateError)
+    "Update a property template. This route can update the template name,
+    the template description and add optional properties to templates."
+
+    attrs
+        auth="team"
+        api_group="properties"
+        is_preview=true
+        owner="dev-plat"
+
+route properties/template/get(properties.GetPropertyTemplateArg, properties.GetPropertyTemplateResult, properties.PropertyTemplateError)
+    "Get the schema for a specified template."
+
+    attrs
+        auth="team"
+        api_group="properties"
+        is_preview=true
+        owner="dev-plat"
+
+route properties/template/list(Void, properties.ListPropertyTemplateIds, properties.PropertyTemplateError)
+    "Get the property template identifiers for a team. To get the schema of
+    each template use :route:`properties/template/get`."
+
+    attrs
+        auth="team"
+        api_group="properties"
+        is_preview=true
+        owner="dev-plat"
+
+struct AddPropertyTemplateArg extends properties.PropertyGroupTemplate
+    "Arguments for adding property templates."
+
+    example default
+        name = "Security"
+        description = "These properties describe how confidential this file is."
+        fields = [default]
+
+struct AddPropertyTemplateResult
+    template_id properties.TemplateId
+        "An identifier for property template added by :route:`properties/template/add`."
+
+    example default
+        template_id = "ptid:1a5n2i6d3OYEAAAAAAAAAYa"
+
+struct UpdatePropertyTemplateArg
+    template_id properties.TemplateId
+        "An identifier for property template added by :route:`properties/template/add`."
+    name String?
+        "A display name for the property template. Property template names can
+        be up to 256 bytes."
+    description String?
+        "Description for new property template. Property template descriptions
+        can be up to 1024 bytes."
+    add_fields List(properties.PropertyFieldTemplate)?
+        "This is a list of custom properties to add to the property template.
+        There can be up to 64 properties in a single property template."
+
+    example default
+        template_id = "ptid:1a5n2i6d3OYEAAAAAAAAAYa"
+        name = "New Security Template Name"
+        description = "These properties will describe how confidential this file is."
+        add_fields = [default]
+
+struct UpdatePropertyTemplateResult
+    template_id properties.TemplateId
+        "An identifier for property template added by :route:`properties/template/add`."
+
+    example default
+        template_id = "ptid:1a5n2i6d3OYEAAAAAAAAAYa"

+ 184 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/team_reports.stone

@@ -0,0 +1,184 @@
+namespace team
+
+import common
+
+alias NumberPerDay = List(UInt64?)
+
+struct DateRange
+    "Input arguments that can be provided for most reports."
+    start_date common.Date?
+        "Optional starting date (inclusive)"
+    end_date common.Date?
+        "Optional ending date (exclusive)"
+
+union DateRangeError
+    "Errors that can originate from problems in input arguments to reports."
+
+struct StorageBucket
+    "Describes the number of users in a specific storage bucket."
+    bucket String
+        "The name of the storage bucket.
+        For example, '1G' is a bucket of users with storage size up to 1 Giga."
+    users UInt64
+        "The number of people whose storage is in the range of this storage bucket."
+
+    example default
+        bucket = "1G"
+        users = 21
+
+
+struct BaseDfbReport
+    "Base report structure."
+    start_date String
+        "First date present in the results as 'YYYY-MM-DD' or None."
+
+#
+# get_storage
+#
+
+struct GetStorageReport extends BaseDfbReport
+    "Storage Report Result.
+    Each of the items in the storage report is an array of values, one value per day.
+    If there is no data for a day, then the value will be None."
+
+    total_usage NumberPerDay
+        "Sum of the shared, unshared, and datastore usages, for each day."
+    shared_usage NumberPerDay
+        "Array of the combined size (bytes) of team members' shared folders, for each day."
+    unshared_usage NumberPerDay
+        "Array of the combined size (bytes) of team members' root namespaces, for each day."
+    shared_folders NumberPerDay
+        "Array of the number of shared folders owned by team members, for each day."
+    member_storage_map List(List(StorageBucket))
+        "Array of storage summaries of team members' account sizes.
+        Each storage summary is an array of key, value pairs, where each pair describes
+        a storage bucket.
+        The key indicates the upper bound of the bucket and the value is the
+        number of users in that bucket. There is one such summary per day.
+        If there is no data for a day, the storage summary will be empty."
+
+route reports/get_storage(DateRange, GetStorageReport, DateRangeError)
+    "Retrieves reporting data about a team's storage usage."
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# get_activity
+#
+
+struct GetActivityReport extends BaseDfbReport
+    "Activity Report Result.
+    Each of the items in the storage report is an array of values, one value per day.
+    If there is no data for a day, then the value will be None."
+
+    adds NumberPerDay
+        "Array of total number of adds by team members."
+    edits NumberPerDay
+        "Array of number of edits by team members.
+        If the same user edits the same file multiple times this is counted as a single edit."
+    deletes NumberPerDay
+        "Array of total number of deletes by team members."
+    active_users_28_day NumberPerDay
+        "Array of the number of users who have been active in the last 28 days."
+    active_users_7_day NumberPerDay
+        "Array of the number of users who have been active in the last week."
+    active_users_1_day NumberPerDay
+        "Array of the number of users who have been active in the last day."
+    active_shared_folders_28_day NumberPerDay
+        "Array of the number of shared folders with some activity in the last 28 days."
+    active_shared_folders_7_day NumberPerDay
+        "Array of the number of shared folders with some activity in the last week."
+    active_shared_folders_1_day NumberPerDay
+        "Array of the number of shared folders with some activity in the last day."
+    shared_links_created NumberPerDay
+        "Array of the number of shared links created."
+    shared_links_viewed_by_team NumberPerDay
+        "Array of the number of views by team users to shared links created by the team."
+    shared_links_viewed_by_outside_user NumberPerDay
+        "Array of the number of views by users outside of the team to shared links created by the team."
+    shared_links_viewed_by_not_logged_in NumberPerDay
+        "Array of the number of views by non-logged-in users to shared links created by the team."
+    shared_links_viewed_total NumberPerDay
+        "Array of the total number of views to shared links created by the team."
+
+route reports/get_activity(DateRange, GetActivityReport, DateRangeError)
+    "Retrieves reporting data about a team's user activity."
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# get_membership
+#
+
+struct GetMembershipReport extends BaseDfbReport
+    "Membership Report Result.
+    Each of the items in the storage report is an array of values, one value per day.
+    If there is no data for a day, then the value will be None."
+
+    team_size NumberPerDay
+        "Team size, for each day."
+    pending_invites NumberPerDay
+        "The number of pending invites to the team, for each day."
+    members_joined NumberPerDay
+        "The number of members that joined the team, for each day."
+    suspended_members NumberPerDay
+        "The number of suspended team members, for each day."
+    licenses NumberPerDay
+        "The total number of licenses the team has, for each day."
+
+
+
+route reports/get_membership(DateRange, GetMembershipReport, DateRangeError)
+    "Retrieves reporting data about a team's membership."
+    attrs
+        auth = "team"
+        owner = "adminx"
+
+#
+# get_devices
+#
+
+
+struct DevicesActive
+    "Each of the items is an array of values, one value per day.
+    The value is the number of devices active within a time window, ending with that day.
+
+    If there is no data for a day, then the value will be None."
+
+    windows NumberPerDay
+        "Array of number of linked windows (desktop) clients with activity."
+    macos NumberPerDay
+        "Array of number of linked mac (desktop) clients with activity."
+    linux NumberPerDay
+        "Array of number of linked linus (desktop) clients with activity."
+    ios NumberPerDay
+        "Array of number of linked ios devices with activity."
+    android NumberPerDay
+        "Array of number of linked android devices with activity."
+    other NumberPerDay
+        "Array of number of other linked devices (blackberry, windows phone, etc)
+         with activity."
+    total NumberPerDay
+        "Array of total number of linked clients with activity."
+
+struct GetDevicesReport extends BaseDfbReport
+    "Devices Report Result. Contains subsections for different time ranges of activity.
+    Each of the items in each subsection of the storage report is an array of values,
+    one value per day.
+    If there is no data for a day, then the value will be None."
+
+    active_1_day DevicesActive
+        "Report of the number of devices active in the last day."
+    active_7_day DevicesActive
+        "Report of the number of devices active in the last 7 days."
+    active_28_day DevicesActive
+        "Report of the number of devices active in the last 28 days."
+
+
+route reports/get_devices(DateRange, GetDevicesReport, DateRangeError)
+    "Retrieves reporting data about a team's linked devices."
+    attrs
+        auth = "team"
+        owner = "adminx"

+ 277 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/users.stone

@@ -0,0 +1,277 @@
+namespace users
+    "This namespace contains endpoints and data types for user management."
+
+import common
+
+import team_policies
+
+import users_common
+
+#
+# Route: get_account
+#
+
+route get_account (GetAccountArg, BasicAccount, GetAccountError)
+    "Get information about a user's account."
+
+    attrs
+        owner = "dev-plat"
+        allow_app_folder_app = true
+
+struct GetAccountArg
+    account_id users_common.AccountId
+        "A user's account identifier."
+
+    example default
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+
+union GetAccountError
+    no_account
+        "The specified :field:`GetAccountArg.account_id` does not exist."
+
+struct Account
+    "The amount of detail revealed about an account depends on the user
+    being queried and the user making the query."
+
+    account_id users_common.AccountId
+        "The user's unique Dropbox ID."
+    name Name
+        "Details of a user's name."
+    email String
+        "The user's e-mail address. Do not rely on this without checking the
+        :field:`email_verified` field. Even then, it's possible that the user
+        has since lost access to their e-mail."
+    email_verified Boolean
+        "Whether the user has verified their e-mail address."
+    profile_photo_url String?
+        "URL for the photo representing the user, if one is set."
+    disabled Boolean
+        "Whether the user has been disabled."
+
+    example default
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+        name = default
+        email = "franz@dropbox.com"
+        email_verified = true
+        profile_photo_url = "https://dl-web.dropbox.com/account_photo/get/dbid%3AAAH4f99T0taONIb-OurWxbNQ6ywGRopQngc?vers=1453416582218&size=128x128"
+        disabled = false
+
+struct BasicAccount extends Account
+    "Basic information about any account."
+
+    is_teammate Boolean
+        "Whether this user is a teammate of the current user. If this account
+        is the current user's account, then this will be :val:`true`."
+
+    team_member_id String?
+        "The user's unique team member id. This field will only be present if
+        the user is part of a team and :field:`is_teammate` is :val:`true`."
+
+    example default
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+        name = default
+        email = "franz@dropbox.com"
+        email_verified = true
+        profile_photo_url = "https://dl-web.dropbox.com/account_photo/get/dbid%3AAAH4f99T0taONIb-OurWxbNQ6ywGRopQngc?vers=1453416696524&size=128x128"
+        is_teammate = false
+        disabled = false
+
+    example team
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+        name = default
+        email = "franz@dropbox.com"
+        email_verified = true
+        profile_photo_url = "https://dl-web.dropbox.com/account_photo/get/dbid%3AAAH4f99T0taONIb-OurWxbNQ6ywGRopQngc?vers=1453416696524&size=128x128"
+        is_teammate = true
+        team_member_id = "dbmid:AAHhy7WsR0x-u4ZCqiDl5Fz5zvuL3kmspwU"
+        disabled = false
+#
+# Route: get_current_account
+#
+
+route get_current_account (Void, FullAccount, Void)
+    "Get information about the current user's account."
+
+    attrs
+        owner = "dev-plat"
+        allow_app_folder_app = true
+
+struct FullAccount extends Account
+    "Detailed information about the current user's account."
+
+    country String(min_length=2, max_length=2)?
+        "The user's two-letter country code, if available. Country codes are
+        based on :link:`ISO 3166-1 http://en.wikipedia.org/wiki/ISO_3166-1`."
+    locale String(min_length=2)
+        "The language that the user specified. Locale tags will be
+        :link:`IETF language tags http://en.wikipedia.org/wiki/IETF_language_tag`."
+    referral_link String
+        "The user's :link:`referral link https://www.dropbox.com/referrals`."
+    team FullTeam?
+        "If this account is a member of a team, information about that team."
+    team_member_id String?
+        "This account's unique team member id. This field will only be present if
+        :field:`team` is present."
+    is_paired Boolean
+        "Whether the user has a personal and work account. If the current
+        account is personal, then :field:`team` will always be :val:`null`,
+        but :field:`is_paired` will indicate if a work account is linked."
+    account_type users_common.AccountType
+        "What type of account this user has."
+
+    example default
+        "Paired account"
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+        name = default
+        email = "franz@dropbox.com"
+        email_verified = true
+        country = "US"
+        locale = "en"
+        referral_link = "https://db.tt/ZITNuhtI"
+        team = default
+        team_member_id = "dbmid:AAHhy7WsR0x-u4ZCqiDl5Fz5zvuL3kmspwU"
+        is_paired = true
+        account_type = business
+        disabled = false
+
+    example unpaired
+        "A personal account that is not paired with a team."
+        account_id = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
+        name = default
+        email = "franz@gmail.com"
+        email_verified = false
+        country = "US"
+        locale = "en"
+        referral_link = "https://db.tt/ZITNuhtI"
+        team = null
+        is_paired = false
+        account_type = default
+        profile_photo_url = "https://dl-web.dropbox.com/account_photo/get/dbid%3AAAH4f99T0taONIb-OurWxbNQ6ywGRopQngc?vers=1453416673259&size=128x128"
+        disabled = false
+
+struct Team
+    "Information about a team."
+
+    id String
+        "The team's unique ID."
+    name String
+        "The name of the team."
+
+    example default
+        id = "dbtid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+        name = "Acme, Inc."
+
+struct FullTeam extends Team
+    "Detailed information about a team."
+
+    sharing_policies team_policies.TeamSharingPolicies
+        "Team policies governing sharing."
+
+    example default
+        id = "dbtid:AAFdgehTzw7WlXhZJsbGCLePe8RvQGYDr-I"
+        name = "Acme, Inc."
+        sharing_policies = default
+
+struct Name
+    "Representations for a person's name to assist with internationalization."
+
+    given_name String
+        "Also known as a first name."
+    surname String
+        "Also known as a last name or family name."
+    familiar_name String
+        "Locale-dependent name. In the US, a person's familiar name is their
+        :field:`given_name`, but elsewhere, it could be any combination of a
+        person's :field:`given_name` and :field:`surname`."
+    display_name String
+        "A name that can be used directly to represent the name of a user's
+        Dropbox account."
+    abbreviated_name String
+        "An abbreviated form of the person's name. Their initials in most locales."
+
+    example default
+        given_name = "Franz"
+        surname = "Ferdinand"
+        familiar_name = "Franz"
+        display_name = "Franz Ferdinand (Personal)"
+        abbreviated_name = "FF"
+
+#
+# Route: get_space_usage
+#
+
+route get_space_usage(Void, SpaceUsage, Void)
+    "Get the space usage information for the current user's account."
+
+    attrs
+        owner = "dev-plat"
+        allow_app_folder_app = true
+
+struct SpaceUsage
+    "Information about a user's space usage and quota."
+
+    used UInt64
+        "The user's total space usage (bytes)."
+    allocation SpaceAllocation
+        "The user's space allocation."
+
+    example default
+        used = 314159265
+        allocation = default
+
+union SpaceAllocation
+    "Space is allocated differently based on the type of account."
+
+    individual IndividualSpaceAllocation
+        "The user's space allocation applies only to their individual account."
+    team TeamSpaceAllocation
+        "The user shares space with other members of their team."
+
+    example default
+        individual = default
+
+struct IndividualSpaceAllocation
+    allocated UInt64
+        "The total space allocated to the user's account (bytes)."
+
+    example default
+        allocated = 10000000000
+
+struct TeamSpaceAllocation
+    used UInt64
+        "The total space currently used by the user's team (bytes)."
+    allocated UInt64
+        "The total space allocated to the user's team (bytes)."
+
+    example default
+        used = 27182818284
+        allocated = 100000000000
+
+#
+# Route: get_account_batch
+#
+
+route get_account_batch (GetAccountBatchArg, GetAccountBatchResult, GetAccountBatchError)
+    "Get information about multiple user accounts.  At most 300 accounts may be queried
+    per request."
+
+    attrs
+        owner = "dev-plat"
+        allow_app_folder_app = true
+
+struct GetAccountBatchArg
+    account_ids List(users_common.AccountId, min_items=1)
+        "List of user account identifiers.  Should not contain any duplicate account IDs."
+
+    example default
+        account_ids = ["dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc", "dbid:AAH1Vcz-DVoRDeixtr_OA8oUGgiqhs4XPOQ"]
+
+alias GetAccountBatchResult = List(BasicAccount)
+
+union GetAccountBatchError
+    no_account users_common.AccountId
+        "The value is an account ID specified in :field:`GetAccountBatchArg.account_ids`
+        that does not exist."
+
+    example default
+        no_account = "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"

+ 20 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/dropbox-api-spec/users_common.stone

@@ -0,0 +1,20 @@
+namespace users_common
+    "This namespace contains common data types used within the users namespace"
+
+alias AccountId = String(min_length=40, max_length=40)
+
+union_closed AccountType
+    "What type of account this user has."
+
+    basic
+        "The basic account type."
+    pro
+        "The Dropbox Pro account type."
+    business
+        "The Dropbox Business account type."
+
+    example default
+        basic = null
+
+    example business
+        business = null

+ 25 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/generate-sdk.sh

@@ -0,0 +1,25 @@
+#! /usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -ne 0 ]]; then
+    echo "$0: Not expecting any command-line arguments, got $#." 1>&2
+    exit 1
+fi
+
+loc=$(realpath -e $0)
+base_dir=$(dirname "$loc")
+spec_dir="$base_dir/dropbox-api-spec"
+gen_dir=$(dirname ${base_dir})/dropbox
+
+stone -v -a :all go_types.stoneg.py "$gen_dir" "$spec_dir"/*.stone
+stone -v -a :all go_client.stoneg.py "$gen_dir" "$spec_dir"/*.stone
+
+# Update SDK and API spec versions
+sdk_version="1.0.0-beta"
+pushd ${spec_dir}
+spec_version=$(git rev-parse --short HEAD)
+popd
+
+sed -i '' -e "s/UNKNOWN SDK VERSION/${sdk_version}/" \
+	-e "s/UNKNOWN SPEC VERSION/${spec_version}/" ${gen_dir}/sdk.go
+goimports -l -w ${gen_dir}

+ 202 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_client.stoneg.py

@@ -0,0 +1,202 @@
+import os
+
+from stone.generator import CodeGenerator
+from stone.data_type import (
+    is_void_type,
+    is_struct_type
+)
+
+from go_helpers import (
+    HEADER,
+    fmt_type,
+    fmt_var,
+    generate_doc,
+)
+
+
+class GoClientGenerator(CodeGenerator):
+    def generate(self, api):
+        for namespace in api.namespaces.values():
+            if len(namespace.routes) > 0:
+                self._generate_client(namespace)
+
+    def _generate_client(self, namespace):
+        file_name = os.path.join(self.target_folder_path, namespace.name,
+                                 'client.go')
+        with self.output_to_relative_path(file_name):
+            self.emit_raw(HEADER)
+            self.emit()
+            self.emit('package %s' % namespace.name)
+            self.emit()
+
+            self.emit('// Client interface describes all routes in this namespace')
+            with self.block('type Client interface'):
+                for route in namespace.routes:
+                    generate_doc(self, route)
+                    self.emit(self._generate_route_signature(namespace, route))
+            self.emit()
+
+            self.emit('type apiImpl dropbox.Context')
+            for route in namespace.routes:
+                self._generate_route(namespace, route)
+            self.emit('// New returns a Client implementation for this namespace')
+            with self.block('func New(c dropbox.Config) *apiImpl'):
+                self.emit('ctx := apiImpl(dropbox.NewContext(c))')
+                self.emit('return &ctx')
+
+    def _generate_route_signature(self, namespace, route):
+        req = fmt_type(route.arg_data_type, namespace)
+        res = fmt_type(route.result_data_type, namespace, use_interface=True)
+        fn = fmt_var(route.name)
+        style = route.attrs.get('style', 'rpc')
+
+        arg = '' if is_void_type(route.arg_data_type) else 'arg {req}'
+        ret = '(err error)' if is_void_type(route.result_data_type) else \
+            '(res {res}, err error)'
+        signature = '{fn}(' + arg + ') ' + ret
+        if style == 'download':
+            signature = '{fn}(' + arg + \
+                ') (res {res}, content io.ReadCloser, err error)'
+        elif style == 'upload':
+            signature = '{fn}(' + arg + ', content io.Reader) ' + ret
+            if is_void_type(route.arg_data_type):
+                signature = '{fn}(content io.Reader) ' + ret
+        return signature.format(fn=fn, req=req, res=res)
+
+
+    def _generate_route(self, namespace, route):
+        out = self.emit
+        fn = fmt_var(route.name)
+        err = fmt_type(route.error_data_type, namespace)
+        out('//%sAPIError is an error-wrapper for the %s route' %
+            (fn, route.name))
+        with self.block('type {fn}APIError struct'.format(fn=fn)):
+            out('dropbox.APIError')
+            out('EndpointError {err} `json:"error"`'.format(err=err))
+        out()
+
+        signature = 'func (dbx *apiImpl) ' + self._generate_route_signature(
+            namespace, route)
+        with self.block(signature):
+            out('cli := dbx.Client')
+            out()
+
+            self._generate_request(namespace, route)
+            self._generate_post()
+            self._generate_response(route)
+            with self.block('if resp.StatusCode == http.StatusOK'):
+                self._generate_result(route)
+            self._generate_error_handling(route)
+
+        out()
+
+    def _generate_request(self, namespace, route):
+        out = self.emit
+        auth = route.attrs.get('auth', '')
+        host = route.attrs.get('host', 'api')
+        style = route.attrs.get('style', 'rpc')
+
+        body = 'nil'
+        if not is_void_type(route.arg_data_type):
+            with self.block('if dbx.Config.Verbose'):
+                out('log.Printf("arg: %v", arg)')
+            out('b, err := json.Marshal(arg)')
+            with self.block('if err != nil'):
+                out('return')
+            out()
+            if host != 'content':
+                body = 'bytes.NewReader(b)'
+        if style == 'upload':
+            body = 'content'
+
+        headers = {}
+        if not is_void_type(route.arg_data_type):
+            if host == 'content':
+                headers["Dropbox-API-Arg"] = "string(b)"
+            else:
+                headers["Content-Type"] = '"application/json"'
+        if style == 'upload':
+            headers["Content-Type"] = '"application/octet-stream"'
+
+        out('headers := map[string]string{')
+        for k, v in headers.items():
+            out('\t"{}": {},'.format(k, v))
+        out('}')
+        if auth != 'noauth' and auth != 'team':
+            with self.block('if dbx.Config.AsMemberID != ""'):
+                out('headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID')
+        out()
+
+        authed = 'false' if auth == 'noauth' else 'true'
+        out('req, err := (*dropbox.Context)(dbx).NewRequest("{}", "{}", {}, "{}", "{}", headers, {})'.format(
+            host, style, authed, namespace.name, route.name, body))
+        with self.block('if err != nil'):
+            out('return')
+        with self.block('if dbx.Config.Verbose'):
+            out('log.Printf("req: %v", req)')
+        out()
+
+    def _generate_post(self):
+        out = self.emit
+
+        out('resp, err := cli.Do(req)')
+        with self.block('if dbx.Config.Verbose'):
+            out('log.Printf("resp: %v", resp)')
+
+        with self.block('if err != nil'):
+            out('return')
+        out()
+
+    def _generate_response(self, route):
+        out = self.emit
+        style = route.attrs.get('style', 'rpc')
+        if style == 'download':
+            out('body := []byte(resp.Header.Get("Dropbox-API-Result"))')
+            out('content = resp.Body')
+        else:
+            out('defer resp.Body.Close()')
+            with self.block('body, err := ioutil.ReadAll(resp.Body);'
+                            'if err != nil'):
+                out('return')
+            out()
+        with self.block('if dbx.Config.Verbose'):
+            out('log.Printf("body: %s", body)')
+
+    def _generate_error_handling(self, route):
+        out = self.emit
+        with self.block('if resp.StatusCode == http.StatusConflict'):
+            out('var apiError %sAPIError' % fmt_var(route.name))
+            with self.block('err = json.Unmarshal(body, &apiError);'
+                            'if err != nil'):
+                out('return')
+            out('err = apiError')
+            out('return')
+        out('var apiError dropbox.APIError')
+        with self.block('if resp.StatusCode == http.StatusBadRequest'):
+            out('apiError.ErrorSummary = string(body)')
+            out('err = apiError')
+            out('return')
+        with self.block('err = json.Unmarshal(body, &apiError);'
+                        'if err != nil'):
+            out('return')
+        out('err = apiError')
+        out('return')
+
+    def _generate_result(self, route):
+        out = self.emit
+        if is_struct_type(route.result_data_type) and \
+                route.result_data_type.has_enumerated_subtypes():
+            out('var tmp %sUnion' % fmt_var(route.result_data_type.name, export=False))
+            with self.block('err = json.Unmarshal(body, &tmp);'
+                            'if err != nil'):
+                out('return')
+            with self.block('switch tmp.Tag'):
+                for t in route.result_data_type.get_enumerated_subtypes():
+                    with self.block('case "%s":' % t.name, delim=(None, None)):
+                        self.emit('res = tmp.%s' % fmt_var(t.name))
+        elif not is_void_type(route.result_data_type):
+            with self.block('err = json.Unmarshal(body, &res);'
+                            'if err != nil'):
+                out('return')
+            out()
+        out('return')

+ 134 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_helpers.py

@@ -0,0 +1,134 @@
+from stone.api import ApiNamespace
+from stone.data_type import (
+    Boolean,
+    Float32,
+    Float64,
+    Int32,
+    Int64,
+    String,
+    Timestamp,
+    UInt32,
+    UInt64,
+    unwrap_nullable,
+    is_composite_type,
+    is_list_type,
+    is_struct_type,
+    Void,
+)
+from stone.target import helpers
+
+HEADER = """\
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+"""
+
+_reserved_keywords = {
+    'break', 'default', 'func', 'interface', 'select',
+    'case', 'defer', 'go',   'map',  'struct',
+    'chan', 'else', 'goto', 'package', 'switch',
+    'const', 'fallthrough', 'if',   'range', 'type',
+    'continue', 'for',  'import',  'return',  'var',
+}
+
+_type_table = {
+    UInt64: 'uint64',
+    Int64: 'int64',
+    UInt32: 'uint32',
+    Int32: 'int32',
+    Float64: 'float64',
+    Float32: 'float32',
+    Boolean: 'bool',
+    String: 'string',
+    Timestamp: 'time.Time',
+    Void: 'struct{}',
+}
+
+
+def _rename_if_reserved(s):
+    if s in _reserved_keywords:
+        return s + '_'
+    else:
+        return s
+
+
+def fmt_type(data_type, namespace=None, use_interface=False):
+    data_type, nullable = unwrap_nullable(data_type)
+    if is_list_type(data_type):
+        return '[]%s' % fmt_type(data_type.data_type, namespace, use_interface)
+    type_name = data_type.name
+    if use_interface and _needs_base_type(data_type):
+        type_name = 'Is' + type_name
+    if is_composite_type(data_type) and namespace is not None and \
+            namespace.name != data_type.namespace.name:
+        type_name = data_type.namespace.name + '.' + type_name
+    if use_interface and _needs_base_type(data_type):
+        return _type_table.get(data_type.__class__, type_name)
+    else:
+        return _type_table.get(data_type.__class__, '*' + type_name)
+
+
+def fmt_var(name, export=True, check_reserved=False):
+    s = helpers.fmt_pascal(name) if export else helpers.fmt_camel(name)
+    return _rename_if_reserved(s) if check_reserved else s
+
+
+def _doc_handler(tag, val):
+    if tag == 'type':
+        return '`{}`'.format(val)
+    elif tag == 'route':
+        return '`{}`'.format(helpers.fmt_camel(val))
+    elif tag == 'link':
+        anchor, link = val.rsplit(' ', 1)
+        return '`{}` <{}>'.format(anchor, link)
+    elif tag == 'val':
+        if val == 'null':
+            return 'nil'
+        else:
+            return val
+    elif tag == 'field':
+        return '`{}`'.format(val)
+    else:
+        raise RuntimeError('Unknown doc ref tag %r' % tag)
+
+
+def generate_doc(code_generator, t):
+    doc = t.doc
+    if doc is None:
+        doc = 'has no documentation (yet)'
+    doc = code_generator.process_doc(doc, _doc_handler)
+    d = '%s : %s' % (fmt_var(t.name), doc)
+    if isinstance(t, ApiNamespace):
+        d = 'Package %s : %s' % (t.name, doc)
+    code_generator.emit_wrapped_text(d, prefix='// ')
+
+
+def _needs_base_type(data_type):
+    if is_struct_type(data_type) and data_type.has_enumerated_subtypes():
+        return True
+    if is_list_type(data_type):
+        return _needs_base_type(data_type.data_type)
+    return False
+
+
+def needs_base_type(struct):
+    for field in struct.fields:
+        if _needs_base_type(field.data_type):
+            return True
+    return False

+ 75 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_rsrc/files/metadata.go

@@ -0,0 +1,75 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package files
+
+import "encoding/json"
+
+type listFolderResult struct {
+	Entries []metadataUnion `json:"entries"`
+	Cursor  string          `json:"cursor"`
+	HasMore bool            `json:"has_more"`
+}
+
+// UnmarshalJSON deserializes into a ListFolderResult instance
+func (r *ListFolderResult) UnmarshalJSON(b []byte) error {
+	var l listFolderResult
+	if err := json.Unmarshal(b, &l); err != nil {
+		return err
+	}
+	r.Cursor = l.Cursor
+	r.HasMore = l.HasMore
+	r.Entries = make([]IsMetadata, len(l.Entries))
+	for i, e := range l.Entries {
+		switch e.Tag {
+		case "file":
+			r.Entries[i] = e.File
+		case "folder":
+			r.Entries[i] = e.Folder
+		case "deleted":
+			r.Entries[i] = e.Deleted
+		}
+	}
+	return nil
+}
+
+type searchMatch struct {
+	MatchType *SearchMatchType `json:"match_type"`
+	Metadata  metadataUnion    `json:"metadata"`
+}
+
+// UnmarshalJSON deserializes into a SearchMatch instance
+func (s *SearchMatch) UnmarshalJSON(b []byte) error {
+	var m searchMatch
+	if err := json.Unmarshal(b, &m); err != nil {
+		return err
+	}
+	s.MatchType = m.MatchType
+	e := m.Metadata
+	switch e.Tag {
+	case "file":
+		s.Metadata = e.File
+	case "folder":
+		s.Metadata = e.Folder
+	case "deleted":
+		s.Metadata = e.Deleted
+	}
+	return nil
+}

+ 170 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_rsrc/sdk.go

@@ -0,0 +1,170 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package dropbox
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+
+	"golang.org/x/oauth2"
+)
+
+const (
+	apiVersion    = 2
+	defaultDomain = ".dropboxapi.com"
+	hostAPI       = "api"
+	hostContent   = "content"
+	hostNotify    = "notify"
+	sdkVersion    = "UNKNOWN SDK VERSION"
+	specVersion   = "UNKNOWN SPEC VERSION"
+)
+
+// Version returns the current SDK version and API Spec version
+func Version() (string, string) {
+	return sdkVersion, specVersion
+}
+
+// Config contains parameters for configuring the SDK.
+type Config struct {
+	// OAuth2 access token
+	Token string
+	// Enable verbose logging in SDK
+	Verbose bool
+	// Used with APIs that support operations as another user
+	AsMemberID string
+	// No need to set -- for testing only
+	Domain string
+	// No need to set -- for testing only
+	Client *http.Client
+	// No need to set -- for testing only
+	HeaderGenerator func(hostType string, style string, namespace string, route string) map[string]string
+	// No need to set -- for testing only
+	URLGenerator func(hostType string, style string, namespace string, route string) string
+}
+
+// Context is the base client context used to implement per-namespace clients.
+type Context struct {
+	Config          Config
+	Client          *http.Client
+	HeaderGenerator func(hostType string, style string, namespace string, route string) map[string]string
+	URLGenerator    func(hostType string, style string, namespace string, route string) string
+}
+
+// NewRequest returns an appropriate Request object for the given namespace/route.
+func (c *Context) NewRequest(
+	hostType string,
+	style string,
+	authed bool,
+	namespace string,
+	route string,
+	headers map[string]string,
+	body io.Reader,
+) (*http.Request, error) {
+	url := c.URLGenerator(hostType, style, namespace, route)
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range headers {
+		req.Header.Add(k, v)
+	}
+	for k, v := range c.HeaderGenerator(hostType, style, namespace, route) {
+		req.Header.Add(k, v)
+	}
+	if req.Header.Get("Host") != "" {
+		req.Host = req.Header.Get("Host")
+	}
+	if !authed {
+		req.Header.Del("Authorization")
+	}
+	return req, nil
+}
+
+// NewContext returns a new Context with the given Config.
+func NewContext(c Config) Context {
+	domain := c.Domain
+	if domain == "" {
+		domain = defaultDomain
+	}
+
+	client := c.Client
+	if client == nil {
+		var conf = &oauth2.Config{Endpoint: OAuthEndpoint(domain)}
+		tok := &oauth2.Token{AccessToken: c.Token}
+		client = conf.Client(oauth2.NoContext, tok)
+	}
+
+	headerGenerator := c.HeaderGenerator
+	if headerGenerator == nil {
+		headerGenerator = func(hostType string, style string, namespace string, route string) map[string]string {
+			return map[string]string{}
+		}
+	}
+
+	urlGenerator := c.URLGenerator
+	if urlGenerator == nil {
+		hostMap := map[string]string{
+			hostAPI:     hostAPI + domain,
+			hostContent: hostContent + domain,
+			hostNotify:  hostNotify + domain,
+		}
+		urlGenerator = func(hostType string, style string, namespace string, route string) string {
+			fqHost := hostMap[hostType]
+			return fmt.Sprintf("https://%s/%d/%s/%s", fqHost, apiVersion, namespace, route)
+		}
+	}
+
+	return Context{c, client, headerGenerator, urlGenerator}
+}
+
+// OAuthEndpoint constructs an `oauth2.Endpoint` for the given domain
+func OAuthEndpoint(domain string) oauth2.Endpoint {
+	if domain == "" {
+		domain = defaultDomain
+	}
+	authURL := fmt.Sprintf("https://meta%s/1/oauth2/authorize", domain)
+	tokenURL := fmt.Sprintf("https://api%s/1/oauth2/token", domain)
+	if domain == defaultDomain {
+		authURL = "https://www.dropbox.com/1/oauth2/authorize"
+	}
+	return oauth2.Endpoint{AuthURL: authURL, TokenURL: tokenURL}
+}
+
+// Tagged is used for tagged unions.
+type Tagged struct {
+	Tag string `json:".tag"`
+}
+
+// APIError is the base type for endpoint-specific errors.
+type APIError struct {
+	ErrorSummary string `json:"error_summary"`
+}
+
+func (e APIError) Error() string {
+	return e.ErrorSummary
+}
+
+func init() {
+	// These are not registered in the oauth library by default
+	oauth2.RegisterBrokenAuthHeaderProvider("https://api.dropboxapi.com")
+	oauth2.RegisterBrokenAuthHeaderProvider("https://api-dbdev.dev.corp.dropbox.com")
+}

+ 49 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_rsrc/sharing/metadata.go

@@ -0,0 +1,49 @@
+// Copyright (c) Dropbox, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package sharing
+
+import "encoding/json"
+
+type listSharedLinksResult struct {
+	Links   []sharedLinkMetadataUnion `json:"links"`
+	HasMore bool                      `json:"has_more"`
+	Cursor  string                    `json:"cursor,omitempty"`
+}
+
+// UnmarshalJSON deserializes into a ListSharedLinksResult instance
+func (r *ListSharedLinksResult) UnmarshalJSON(b []byte) error {
+	var l listSharedLinksResult
+	if err := json.Unmarshal(b, &l); err != nil {
+		return err
+	}
+	r.Cursor = l.Cursor
+	r.HasMore = l.HasMore
+	r.Links = make([]IsSharedLinkMetadata, len(l.Links))
+	for i, e := range l.Links {
+		switch e.Tag {
+		case "file":
+			r.Links[i] = e.File
+		case "folder":
+			r.Links[i] = e.Folder
+		}
+	}
+	return nil
+}

+ 196 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/generator/go_types.stoneg.py

@@ -0,0 +1,196 @@
+import os
+import shutil
+
+from stone.generator import CodeGenerator
+from stone.data_type import (
+    is_boolean_type,
+    is_nullable_type,
+    is_primitive_type,
+    is_struct_type,
+    is_union_type,
+    is_void_type,
+)
+
+from go_helpers import (
+    HEADER,
+    fmt_type,
+    fmt_var,
+    generate_doc,
+)
+
+
+class GoTypesGenerator(CodeGenerator):
+    def generate(self, api):
+        rsrc_folder = os.path.join(os.path.dirname(__file__), 'go_rsrc')
+        shutil.copy(os.path.join(rsrc_folder, 'sdk.go'),
+                    self.target_folder_path)
+        for namespace in api.namespaces.values():
+            self._generate_namespace(namespace)
+            if namespace.name == 'files' or namespace.name == 'sharing':
+                self.logger.info('Copying metadata.go to files')
+                shutil.copy(os.path.join(rsrc_folder, namespace.name, 'metadata.go'),
+                            os.path.join(self.target_folder_path, namespace.name))
+
+    def _generate_namespace(self, namespace):
+        file_name = os.path.join(self.target_folder_path, namespace.name,
+                                 'types.go')
+        with self.output_to_relative_path(file_name):
+            self.emit_raw(HEADER)
+            self.emit()
+            generate_doc(self, namespace)
+            self.emit('package %s' % namespace.name)
+            self.emit()
+
+            for data_type in namespace.linearize_data_types():
+                self._generate_data_type(data_type)
+
+    def _generate_data_type(self, data_type):
+        generate_doc(self, data_type)
+        if is_struct_type(data_type):
+            self._generate_struct(data_type)
+            if data_type.has_enumerated_subtypes():
+                self._generate_base_type(data_type)
+        elif is_union_type(data_type):
+            self._generate_union(data_type)
+        else:
+            self.logger.info("Unhandled data type", data_type)
+
+    def _generate_base_type(self, base):
+        t = fmt_type(base).lstrip('*')
+        self.emit('// Is{0} is the interface type for {0} and its subtypes'.format(t))
+        with self.block('type Is%s interface' % t):
+            self.emit('Is%s()' % t)
+        self.emit()
+        self.emit('// Is{0} implements the Is{0} interface'.format(t))
+        self.emit("func (u *{0}) Is{0}() {{}}".format(t))
+        self.emit()
+        self._generate_union_helper(base)
+
+        self.emit("// Is{0}FromJSON converts JSON to a concrete Is{0} instance".format(t))
+        with self.block("func Is{0}FromJSON(data []byte) (Is{0}, error)".format(t)):
+            name = fmt_var(t, export=False) + 'Union'
+            self.emit("var t {0}".format(name))
+            with self.block("if err := json.Unmarshal(data, &t); err != nil"):
+                self.emit("return nil, err")
+            with self.block("switch t.Tag"):
+                fields = base.get_enumerated_subtypes()
+                for field in fields:
+                    with self.block('case "%s":' % field.name, delim=(None, None)):
+                        self.emit("return t.{0}, nil".format(fmt_var(field.name)))
+            # FIX THIS
+            self.emit("return nil, nil")
+
+    def _generate_struct(self, struct):
+        with self.block('type %s struct' % struct.name):
+            if struct.parent_type:
+                self.emit(fmt_type(struct.parent_type, struct.namespace).lstrip('*'))
+            for field in struct.fields:
+                self._generate_field(field, namespace=struct.namespace)
+            self.emit()
+        self._generate_struct_builder(struct)
+
+    def _generate_struct_builder(self, struct):
+        fields = ["%s %s" % (fmt_var(field.name),
+                             fmt_type(field.data_type, struct.namespace,
+                                      use_interface=True))
+                  for field in struct.all_required_fields]
+        self.emit('// New{0} returns a new {0} instance'.format(struct.name))
+        signature = "func New{0}({1}) *{0}".format(struct.name, ', '.join(fields))
+        with self.block(signature):
+            self.emit('s := new({0})'.format(struct.name))
+            for field in struct.all_required_fields:
+                field_name = fmt_var(field.name)
+                self.emit("s.{0} = {0}".format(field_name))
+
+            for field in struct.all_optional_fields:
+                if field.has_default:
+                    if is_primitive_type(field.data_type):
+                        default = field.default
+                        if is_boolean_type(field.data_type):
+                            default = str(default).lower()
+                        self.emit('s.{0} = {1}'.format(fmt_var(field.name), default))
+                    elif is_union_type(field.data_type):
+                        self.emit('s.%s = &%s{Tagged:dropbox.Tagged{"%s"}}' %
+                                  (fmt_var(field.name),
+                                   fmt_type(field.data_type, struct.namespace).lstrip('*'),
+                                   field.default.tag_name))
+            self.emit('return s')
+        self.emit()
+
+    def _generate_field(self, field, union_field=False, namespace=None, raw=False):
+        generate_doc(self, field)
+        field_name = fmt_var(field.name)
+        type_name = fmt_type(field.data_type, namespace, use_interface=True)
+        json_tag = '`json:"%s"`' % field.name
+        if is_nullable_type(field.data_type) or union_field:
+            json_tag = '`json:"%s,omitempty"`' % field.name
+        if raw:
+            self.emit('%s json.RawMessage %s' % (field_name, json_tag))
+        else:
+            self.emit('%s %s %s' % (field_name, type_name, json_tag))
+
+    def _generate_union(self, union):
+        self._generate_union_helper(union)
+
+    def _generate_union_helper(self, u):
+        name = u.name
+        namespace = u.namespace
+        fields = u.fields
+        if is_struct_type(u) and u.has_enumerated_subtypes():
+            name = fmt_var(name, export=False) + 'Union'
+            fields = u.get_enumerated_subtypes()
+
+        with self.block('type %s struct' % name):
+            self.emit('dropbox.Tagged')
+            for field in fields:
+                if is_void_type(field.data_type):
+                    continue
+                self._generate_field(field, union_field=True,
+                                     namespace=namespace)
+        self.emit()
+        self.emit('// Valid tag values for %s' % fmt_var(u.name))
+        with self.block('const', delim=('(', ')')):
+            for field in fields:
+                self.emit('%s%s = "%s"' % (fmt_var(u.name), fmt_var(field.name), field.name))
+        self.emit()
+
+        num_void_fields = sum([is_void_type(f.data_type) for f in fields])
+        # Simple structure, no need in UnmarshalJSON
+        if len(fields) == num_void_fields:
+            return
+
+        self.emit('// UnmarshalJSON deserializes into a %s instance' % name)
+        with self.block('func (u *%s) UnmarshalJSON(body []byte) error' % name):
+            with self.block('type wrap struct'):
+                self.emit('dropbox.Tagged')
+                for field in fields:
+                    if is_void_type(field.data_type) or \
+                            is_primitive_type(field.data_type):
+                        continue
+                    self._generate_field(field, union_field=True,
+                                         namespace=namespace, raw=True)
+            self.emit('var w wrap')
+            self.emit('var err error')
+            with self.block('if err = json.Unmarshal(body, &w); err != nil'):
+                self.emit('return err')
+            self.emit('u.Tag = w.Tag')
+            with self.block('switch u.Tag'):
+                for field in fields:
+                    if is_void_type(field.data_type):
+                        continue
+                    field_name = fmt_var(field.name)
+                    with self.block('case "%s":' % field.name, delim=(None, None)):
+                        if is_union_type(field.data_type):
+                            self.emit('err = json.Unmarshal(w.{0}, &u.{0})'
+                                            .format(field_name))
+                        elif is_struct_type(field.data_type) and \
+                            field.data_type.has_enumerated_subtypes():
+                            self.emit("u.{0}, err = Is{1}FromJSON(body)"
+                                      .format(field_name, field.data_type.name))
+                        else:
+                            self.emit('err = json.Unmarshal(body, &u.{0})'
+                                            .format(field_name))
+                    with self.block("if err != nil"):
+                        self.emit("return err")
+            self.emit('return nil')
+        self.emit()