github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/fichier/fichier.go (about)

     1  package fichier
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/ncw/rclone/fs"
    13  	"github.com/ncw/rclone/fs/config/configmap"
    14  	"github.com/ncw/rclone/fs/config/configstruct"
    15  	"github.com/ncw/rclone/fs/fshttp"
    16  	"github.com/ncw/rclone/fs/hash"
    17  	"github.com/ncw/rclone/lib/dircache"
    18  	"github.com/ncw/rclone/lib/pacer"
    19  	"github.com/ncw/rclone/lib/rest"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  const (
    24  	rootID        = "0"
    25  	apiBaseURL    = "https://api.1fichier.com/v1"
    26  	minSleep      = 334 * time.Millisecond // 3 API calls per second is recommended
    27  	maxSleep      = 5 * time.Second
    28  	decayConstant = 2 // bigger for slower decay, exponential
    29  )
    30  
    31  func init() {
    32  	fs.Register(&fs.RegInfo{
    33  		Name:        "fichier",
    34  		Description: "1Fichier",
    35  		Config: func(name string, config configmap.Mapper) {
    36  		},
    37  		NewFs: NewFs,
    38  		Options: []fs.Option{
    39  			{
    40  				Help: "Your API Key, get it from https://1fichier.com/console/params.pl",
    41  				Name: "api_key",
    42  			},
    43  			{
    44  				Help:     "If you want to download a shared folder, add this parameter",
    45  				Name:     "shared_folder",
    46  				Required: false,
    47  				Advanced: true,
    48  			},
    49  		},
    50  	})
    51  }
    52  
    53  // Options defines the configuration for this backend
    54  type Options struct {
    55  	APIKey       string `config:"api_key"`
    56  	SharedFolder string `config:"shared_folder"`
    57  }
    58  
    59  // Fs is the interface a cloud storage system must provide
    60  type Fs struct {
    61  	root       string
    62  	name       string
    63  	features   *fs.Features
    64  	dirCache   *dircache.DirCache
    65  	baseClient *http.Client
    66  	options    *Options
    67  	pacer      *fs.Pacer
    68  	rest       *rest.Client
    69  }
    70  
    71  // FindLeaf finds a directory of name leaf in the folder with ID pathID
    72  func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) {
    73  	folderID, err := strconv.Atoi(pathID)
    74  	if err != nil {
    75  		return "", false, err
    76  	}
    77  	folders, err := f.listFolders(folderID)
    78  	if err != nil {
    79  		return "", false, err
    80  	}
    81  
    82  	for _, folder := range folders.SubFolders {
    83  		if folder.Name == leaf {
    84  			pathIDOut := strconv.Itoa(folder.ID)
    85  			return pathIDOut, true, nil
    86  		}
    87  	}
    88  
    89  	return "", false, nil
    90  }
    91  
    92  // CreateDir makes a directory with pathID as parent and name leaf
    93  func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) {
    94  	folderID, err := strconv.Atoi(pathID)
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  	resp, err := f.makeFolder(leaf, folderID)
    99  	if err != nil {
   100  		return "", err
   101  	}
   102  	return strconv.Itoa(resp.FolderID), err
   103  }
   104  
   105  // Name of the remote (as passed into NewFs)
   106  func (f *Fs) Name() string {
   107  	return f.name
   108  }
   109  
   110  // Root of the remote (as passed into NewFs)
   111  func (f *Fs) Root() string {
   112  	return f.root
   113  }
   114  
   115  // String returns a description of the FS
   116  func (f *Fs) String() string {
   117  	return fmt.Sprintf("1Fichier root '%s'", f.root)
   118  }
   119  
   120  // Precision of the ModTimes in this Fs
   121  func (f *Fs) Precision() time.Duration {
   122  	return fs.ModTimeNotSupported
   123  }
   124  
   125  // Hashes returns the supported hash types of the filesystem
   126  func (f *Fs) Hashes() hash.Set {
   127  	return hash.Set(hash.Whirlpool)
   128  }
   129  
   130  // Features returns the optional features of this Fs
   131  func (f *Fs) Features() *fs.Features {
   132  	return f.features
   133  }
   134  
   135  // NewFs makes a new Fs object from the path
   136  //
   137  // The path is of the form remote:path
   138  //
   139  // Remotes are looked up in the config file.  If the remote isn't
   140  // found then NotFoundInConfigFile will be returned.
   141  //
   142  // On Windows avoid single character remote names as they can be mixed
   143  // up with drive letters.
   144  func NewFs(name string, rootleaf string, config configmap.Mapper) (fs.Fs, error) {
   145  	root := replaceReservedChars(rootleaf)
   146  	opt := new(Options)
   147  	err := configstruct.Set(config, opt)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	// If using a Shared Folder override root
   153  	if opt.SharedFolder != "" {
   154  		root = ""
   155  	}
   156  
   157  	//workaround for wonky parser
   158  	root = strings.Trim(root, "/")
   159  
   160  	f := &Fs{
   161  		name:       name,
   162  		root:       root,
   163  		options:    opt,
   164  		pacer:      fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
   165  		baseClient: &http.Client{},
   166  	}
   167  
   168  	f.features = (&fs.Features{
   169  		DuplicateFiles:          true,
   170  		CanHaveEmptyDirectories: true,
   171  	}).Fill(f)
   172  
   173  	client := fshttp.NewClient(fs.Config)
   174  
   175  	f.rest = rest.NewClient(client).SetRoot(apiBaseURL)
   176  
   177  	f.rest.SetHeader("Authorization", "Bearer "+f.options.APIKey)
   178  
   179  	f.dirCache = dircache.New(root, rootID, f)
   180  
   181  	ctx := context.Background()
   182  
   183  	// Find the current root
   184  	err = f.dirCache.FindRoot(ctx, false)
   185  	if err != nil {
   186  		// Assume it is a file
   187  		newRoot, remote := dircache.SplitPath(root)
   188  		tempF := *f
   189  		tempF.dirCache = dircache.New(newRoot, rootID, &tempF)
   190  		tempF.root = newRoot
   191  		// Make new Fs which is the parent
   192  		err = tempF.dirCache.FindRoot(ctx, false)
   193  		if err != nil {
   194  			// No root so return old f
   195  			return f, nil
   196  		}
   197  		_, err := tempF.NewObject(ctx, remote)
   198  		if err != nil {
   199  			if err == fs.ErrorObjectNotFound {
   200  				// File doesn't exist so return old f
   201  				return f, nil
   202  			}
   203  			return nil, err
   204  		}
   205  		f.features.Fill(&tempF)
   206  		// XXX: update the old f here instead of returning tempF, since
   207  		// `features` were already filled with functions having *f as a receiver.
   208  		// See https://github.com/ncw/rclone/issues/2182
   209  		f.dirCache = tempF.dirCache
   210  		f.root = tempF.root
   211  		// return an error with an fs which points to the parent
   212  		return f, fs.ErrorIsFile
   213  	}
   214  	return f, nil
   215  }
   216  
   217  // List the objects and directories in dir into entries.  The
   218  // entries can be returned in any order but should be for a
   219  // complete directory.
   220  //
   221  // dir should be "" to list the root, and should not have
   222  // trailing slashes.
   223  //
   224  // This should return ErrDirNotFound if the directory isn't
   225  // found.
   226  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   227  	if f.options.SharedFolder != "" {
   228  		return f.listSharedFiles(ctx, f.options.SharedFolder)
   229  	}
   230  
   231  	dirContent, err := f.listDir(ctx, dir)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	return dirContent, nil
   237  }
   238  
   239  // NewObject finds the Object at remote.  If it can't be found
   240  // it returns the error ErrorObjectNotFound.
   241  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   242  	leaf, directoryID, err := f.dirCache.FindRootAndPath(ctx, remote, false)
   243  	if err != nil {
   244  		if err == fs.ErrorDirNotFound {
   245  			return nil, fs.ErrorObjectNotFound
   246  		}
   247  		return nil, err
   248  	}
   249  
   250  	folderID, err := strconv.Atoi(directoryID)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	files, err := f.listFiles(folderID)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	for _, file := range files.Items {
   260  		if file.Filename == leaf {
   261  			path, ok := f.dirCache.GetInv(directoryID)
   262  
   263  			if !ok {
   264  				return nil, errors.New("Cannot find dir in dircache")
   265  			}
   266  
   267  			return f.newObjectFromFile(ctx, path, file), nil
   268  		}
   269  	}
   270  
   271  	return nil, fs.ErrorObjectNotFound
   272  }
   273  
   274  // Put in to the remote path with the modTime given of the given size
   275  //
   276  // When called from outside a Fs by rclone, src.Size() will always be >= 0.
   277  // But for unknown-sized objects (indicated by src.Size() == -1), Put should either
   278  // return an error or upload it properly (rather than e.g. calling panic).
   279  //
   280  // May create the object even if it returns an error - if so
   281  // will return the object and the error, otherwise will return
   282  // nil and the error
   283  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   284  	exisitingObj, err := f.NewObject(ctx, src.Remote())
   285  	switch err {
   286  	case nil:
   287  		return exisitingObj, exisitingObj.Update(ctx, in, src, options...)
   288  	case fs.ErrorObjectNotFound:
   289  		// Not found so create it
   290  		return f.PutUnchecked(ctx, in, src, options...)
   291  	default:
   292  		return nil, err
   293  	}
   294  }
   295  
   296  // putUnchecked uploads the object with the given name and size
   297  //
   298  // This will create a duplicate if we upload a new file without
   299  // checking to see if there is one already - use Put() for that.
   300  func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size int64, options ...fs.OpenOption) (fs.Object, error) {
   301  	if size > int64(100E9) {
   302  		return nil, errors.New("File too big, cant upload")
   303  	} else if size == 0 {
   304  		return nil, fs.ErrorCantUploadEmptyFiles
   305  	}
   306  
   307  	nodeResponse, err := f.getUploadNode()
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	leaf, directoryID, err := f.dirCache.FindRootAndPath(ctx, remote, true)
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	_, err = f.uploadFile(in, size, leaf, directoryID, nodeResponse.ID, nodeResponse.URL)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	fileUploadResponse, err := f.endUpload(nodeResponse.ID, nodeResponse.URL)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	if len(fileUploadResponse.Links) != 1 {
   328  		return nil, errors.New("unexpected amount of files")
   329  	}
   330  
   331  	link := fileUploadResponse.Links[0]
   332  	fileSize, err := strconv.ParseInt(link.Size, 10, 64)
   333  
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  
   338  	return &Object{
   339  		fs:     f,
   340  		remote: remote,
   341  		file: File{
   342  			ACL:         0,
   343  			CDN:         0,
   344  			Checksum:    link.Whirlpool,
   345  			ContentType: "",
   346  			Date:        time.Now().Format("2006-01-02 15:04:05"),
   347  			Filename:    link.Filename,
   348  			Pass:        0,
   349  			Size:        int(fileSize),
   350  			URL:         link.Download,
   351  		},
   352  	}, nil
   353  }
   354  
   355  // PutUnchecked uploads the object
   356  //
   357  // This will create a duplicate if we upload a new file without
   358  // checking to see if there is one already - use Put() for that.
   359  func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   360  	return f.putUnchecked(ctx, in, src.Remote(), src.Size(), options...)
   361  }
   362  
   363  // Mkdir makes the directory (container, bucket)
   364  //
   365  // Shouldn't return an error if it already exists
   366  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   367  	err := f.dirCache.FindRoot(ctx, true)
   368  	if err != nil {
   369  		return err
   370  	}
   371  	if dir != "" {
   372  		_, err = f.dirCache.FindDir(ctx, dir, true)
   373  	}
   374  	return err
   375  }
   376  
   377  // Rmdir removes the directory (container, bucket) if empty
   378  //
   379  // Return an error if it doesn't exist or isn't empty
   380  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   381  	err := f.dirCache.FindRoot(ctx, false)
   382  	if err != nil {
   383  		return err
   384  	}
   385  
   386  	directoryID, err := f.dirCache.FindDir(ctx, dir, false)
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	folderID, err := strconv.Atoi(directoryID)
   392  	if err != nil {
   393  		return err
   394  	}
   395  
   396  	_, err = f.removeFolder(dir, folderID)
   397  	if err != nil {
   398  		return err
   399  	}
   400  
   401  	f.dirCache.FlushDir(dir)
   402  
   403  	return nil
   404  }
   405  
   406  // Check the interfaces are satisfied
   407  var (
   408  	_ fs.Fs              = (*Fs)(nil)
   409  	_ fs.PutUncheckeder  = (*Fs)(nil)
   410  	_ dircache.DirCacher = (*Fs)(nil)
   411  )