github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/fichier/api.go (about)

     1  package fichier
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/http"
     7  	"regexp"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/rclone/rclone/fs"
    13  	"github.com/rclone/rclone/fs/fserrors"
    14  	"github.com/rclone/rclone/lib/rest"
    15  )
    16  
    17  // retryErrorCodes is a slice of error codes that we will retry
    18  var retryErrorCodes = []int{
    19  	429, // Too Many Requests.
    20  	403, // Forbidden (may happen when request limit is exceeded)
    21  	500, // Internal Server Error
    22  	502, // Bad Gateway
    23  	503, // Service Unavailable
    24  	504, // Gateway Timeout
    25  	509, // Bandwidth Limit Exceeded
    26  }
    27  
    28  // shouldRetry returns a boolean as to whether this resp and err
    29  // deserve to be retried.  It returns the err as a convenience
    30  func shouldRetry(resp *http.Response, err error) (bool, error) {
    31  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
    32  }
    33  
    34  var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
    35  
    36  func (f *Fs) getDownloadToken(ctx context.Context, url string) (*GetTokenResponse, error) {
    37  	request := DownloadRequest{
    38  		URL:    url,
    39  		Single: 1,
    40  	}
    41  	opts := rest.Opts{
    42  		Method: "POST",
    43  		Path:   "/download/get_token.cgi",
    44  	}
    45  
    46  	var token GetTokenResponse
    47  	err := f.pacer.Call(func() (bool, error) {
    48  		resp, err := f.rest.CallJSON(ctx, &opts, &request, &token)
    49  		return shouldRetry(resp, err)
    50  	})
    51  	if err != nil {
    52  		return nil, errors.Wrap(err, "couldn't list files")
    53  	}
    54  
    55  	return &token, nil
    56  }
    57  
    58  func fileFromSharedFile(file *SharedFile) File {
    59  	return File{
    60  		URL:      file.Link,
    61  		Filename: file.Filename,
    62  		Size:     file.Size,
    63  	}
    64  }
    65  
    66  func (f *Fs) listSharedFiles(ctx context.Context, id string) (entries fs.DirEntries, err error) {
    67  	opts := rest.Opts{
    68  		Method:     "GET",
    69  		RootURL:    "https://1fichier.com/dir/",
    70  		Path:       id,
    71  		Parameters: map[string][]string{"json": {"1"}},
    72  	}
    73  
    74  	var sharedFiles SharedFolderResponse
    75  	err = f.pacer.Call(func() (bool, error) {
    76  		resp, err := f.rest.CallJSON(ctx, &opts, nil, &sharedFiles)
    77  		return shouldRetry(resp, err)
    78  	})
    79  	if err != nil {
    80  		return nil, errors.Wrap(err, "couldn't list files")
    81  	}
    82  
    83  	entries = make([]fs.DirEntry, len(sharedFiles))
    84  
    85  	for i, sharedFile := range sharedFiles {
    86  		entries[i] = f.newObjectFromFile(ctx, "", fileFromSharedFile(&sharedFile))
    87  	}
    88  
    89  	return entries, nil
    90  }
    91  
    92  func (f *Fs) listFiles(ctx context.Context, directoryID int) (filesList *FilesList, err error) {
    93  	// fs.Debugf(f, "Requesting files for dir `%s`", directoryID)
    94  	request := ListFilesRequest{
    95  		FolderID: directoryID,
    96  	}
    97  
    98  	opts := rest.Opts{
    99  		Method: "POST",
   100  		Path:   "/file/ls.cgi",
   101  	}
   102  
   103  	filesList = &FilesList{}
   104  	err = f.pacer.Call(func() (bool, error) {
   105  		resp, err := f.rest.CallJSON(ctx, &opts, &request, filesList)
   106  		return shouldRetry(resp, err)
   107  	})
   108  	if err != nil {
   109  		return nil, errors.Wrap(err, "couldn't list files")
   110  	}
   111  	for i := range filesList.Items {
   112  		item := &filesList.Items[i]
   113  		item.Filename = f.opt.Enc.ToStandardName(item.Filename)
   114  	}
   115  
   116  	return filesList, nil
   117  }
   118  
   119  func (f *Fs) listFolders(ctx context.Context, directoryID int) (foldersList *FoldersList, err error) {
   120  	// fs.Debugf(f, "Requesting folders for id `%s`", directoryID)
   121  
   122  	request := ListFolderRequest{
   123  		FolderID: directoryID,
   124  	}
   125  
   126  	opts := rest.Opts{
   127  		Method: "POST",
   128  		Path:   "/folder/ls.cgi",
   129  	}
   130  
   131  	foldersList = &FoldersList{}
   132  	err = f.pacer.Call(func() (bool, error) {
   133  		resp, err := f.rest.CallJSON(ctx, &opts, &request, foldersList)
   134  		return shouldRetry(resp, err)
   135  	})
   136  	if err != nil {
   137  		return nil, errors.Wrap(err, "couldn't list folders")
   138  	}
   139  	foldersList.Name = f.opt.Enc.ToStandardName(foldersList.Name)
   140  	for i := range foldersList.SubFolders {
   141  		folder := &foldersList.SubFolders[i]
   142  		folder.Name = f.opt.Enc.ToStandardName(folder.Name)
   143  	}
   144  
   145  	// fs.Debugf(f, "Got FoldersList for id `%s`", directoryID)
   146  
   147  	return foldersList, err
   148  }
   149  
   150  func (f *Fs) listDir(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   151  	err = f.dirCache.FindRoot(ctx, false)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	directoryID, err := f.dirCache.FindDir(ctx, dir, false)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	folderID, err := strconv.Atoi(directoryID)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	files, err := f.listFiles(ctx, folderID)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	folders, err := f.listFolders(ctx, folderID)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	entries = make([]fs.DirEntry, len(files.Items)+len(folders.SubFolders))
   177  
   178  	for i, item := range files.Items {
   179  		entries[i] = f.newObjectFromFile(ctx, dir, item)
   180  	}
   181  
   182  	for i, folder := range folders.SubFolders {
   183  		createDate, err := time.Parse("2006-01-02 15:04:05", folder.CreateDate)
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  
   188  		fullPath := getRemote(dir, folder.Name)
   189  		folderID := strconv.Itoa(folder.ID)
   190  
   191  		entries[len(files.Items)+i] = fs.NewDir(fullPath, createDate).SetID(folderID)
   192  
   193  		// fs.Debugf(f, "Put Path `%s` for id `%d` into dircache", fullPath, folder.ID)
   194  		f.dirCache.Put(fullPath, folderID)
   195  	}
   196  
   197  	return entries, nil
   198  }
   199  
   200  func (f *Fs) newObjectFromFile(ctx context.Context, dir string, item File) *Object {
   201  	return &Object{
   202  		fs:     f,
   203  		remote: getRemote(dir, item.Filename),
   204  		file:   item,
   205  	}
   206  }
   207  
   208  func getRemote(dir, fileName string) string {
   209  	if dir == "" {
   210  		return fileName
   211  	}
   212  
   213  	return dir + "/" + fileName
   214  }
   215  
   216  func (f *Fs) makeFolder(ctx context.Context, leaf string, folderID int) (response *MakeFolderResponse, err error) {
   217  	name := f.opt.Enc.FromStandardName(leaf)
   218  	// fs.Debugf(f, "Creating folder `%s` in id `%s`", name, directoryID)
   219  
   220  	request := MakeFolderRequest{
   221  		FolderID: folderID,
   222  		Name:     name,
   223  	}
   224  
   225  	opts := rest.Opts{
   226  		Method: "POST",
   227  		Path:   "/folder/mkdir.cgi",
   228  	}
   229  
   230  	response = &MakeFolderResponse{}
   231  	err = f.pacer.Call(func() (bool, error) {
   232  		resp, err := f.rest.CallJSON(ctx, &opts, &request, response)
   233  		return shouldRetry(resp, err)
   234  	})
   235  	if err != nil {
   236  		return nil, errors.Wrap(err, "couldn't create folder")
   237  	}
   238  
   239  	// fs.Debugf(f, "Created Folder `%s` in id `%s`", name, directoryID)
   240  
   241  	return response, err
   242  }
   243  
   244  func (f *Fs) removeFolder(ctx context.Context, name string, folderID int) (response *GenericOKResponse, err error) {
   245  	// fs.Debugf(f, "Removing folder with id `%s`", directoryID)
   246  
   247  	request := &RemoveFolderRequest{
   248  		FolderID: folderID,
   249  	}
   250  
   251  	opts := rest.Opts{
   252  		Method: "POST",
   253  		Path:   "/folder/rm.cgi",
   254  	}
   255  
   256  	response = &GenericOKResponse{}
   257  	var resp *http.Response
   258  	err = f.pacer.Call(func() (bool, error) {
   259  		resp, err = f.rest.CallJSON(ctx, &opts, request, response)
   260  		return shouldRetry(resp, err)
   261  	})
   262  	if err != nil {
   263  		return nil, errors.Wrap(err, "couldn't remove folder")
   264  	}
   265  	if response.Status != "OK" {
   266  		return nil, errors.New("Can't remove non-empty dir")
   267  	}
   268  
   269  	// fs.Debugf(f, "Removed Folder with id `%s`", directoryID)
   270  
   271  	return response, nil
   272  }
   273  
   274  func (f *Fs) deleteFile(ctx context.Context, url string) (response *GenericOKResponse, err error) {
   275  	request := &RemoveFileRequest{
   276  		Files: []RmFile{
   277  			{url},
   278  		},
   279  	}
   280  
   281  	opts := rest.Opts{
   282  		Method: "POST",
   283  		Path:   "/file/rm.cgi",
   284  	}
   285  
   286  	response = &GenericOKResponse{}
   287  	err = f.pacer.Call(func() (bool, error) {
   288  		resp, err := f.rest.CallJSON(ctx, &opts, request, response)
   289  		return shouldRetry(resp, err)
   290  	})
   291  
   292  	if err != nil {
   293  		return nil, errors.Wrap(err, "couldn't remove file")
   294  	}
   295  
   296  	// fs.Debugf(f, "Removed file with url `%s`", url)
   297  
   298  	return response, nil
   299  }
   300  
   301  func (f *Fs) getUploadNode(ctx context.Context) (response *GetUploadNodeResponse, err error) {
   302  	// fs.Debugf(f, "Requesting Upload node")
   303  
   304  	opts := rest.Opts{
   305  		Method:      "GET",
   306  		ContentType: "application/json", // 1Fichier API is bad
   307  		Path:        "/upload/get_upload_server.cgi",
   308  	}
   309  
   310  	response = &GetUploadNodeResponse{}
   311  	err = f.pacer.Call(func() (bool, error) {
   312  		resp, err := f.rest.CallJSON(ctx, &opts, nil, response)
   313  		return shouldRetry(resp, err)
   314  	})
   315  	if err != nil {
   316  		return nil, errors.Wrap(err, "didnt got an upload node")
   317  	}
   318  
   319  	// fs.Debugf(f, "Got Upload node")
   320  
   321  	return response, err
   322  }
   323  
   324  func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, fileName, folderID, uploadID, node string, options ...fs.OpenOption) (response *http.Response, err error) {
   325  	// fs.Debugf(f, "Uploading File `%s`", fileName)
   326  
   327  	fileName = f.opt.Enc.FromStandardName(fileName)
   328  
   329  	if len(uploadID) > 10 || !isAlphaNumeric(uploadID) {
   330  		return nil, errors.New("Invalid UploadID")
   331  	}
   332  
   333  	opts := rest.Opts{
   334  		Method: "POST",
   335  		Path:   "/upload.cgi",
   336  		Parameters: map[string][]string{
   337  			"id": {uploadID},
   338  		},
   339  		NoResponse:           true,
   340  		Body:                 in,
   341  		ContentLength:        &size,
   342  		Options:              options,
   343  		MultipartContentName: "file[]",
   344  		MultipartFileName:    fileName,
   345  		MultipartParams: map[string][]string{
   346  			"did": {folderID},
   347  		},
   348  	}
   349  
   350  	if node != "" {
   351  		opts.RootURL = "https://" + node
   352  	}
   353  
   354  	err = f.pacer.CallNoRetry(func() (bool, error) {
   355  		resp, err := f.rest.CallJSON(ctx, &opts, nil, nil)
   356  		return shouldRetry(resp, err)
   357  	})
   358  
   359  	if err != nil {
   360  		return nil, errors.Wrap(err, "couldn't upload file")
   361  	}
   362  
   363  	// fs.Debugf(f, "Uploaded File `%s`", fileName)
   364  
   365  	return response, err
   366  }
   367  
   368  func (f *Fs) endUpload(ctx context.Context, uploadID string, nodeurl string) (response *EndFileUploadResponse, err error) {
   369  	// fs.Debugf(f, "Ending File Upload `%s`", uploadID)
   370  
   371  	if len(uploadID) > 10 || !isAlphaNumeric(uploadID) {
   372  		return nil, errors.New("Invalid UploadID")
   373  	}
   374  
   375  	opts := rest.Opts{
   376  		Method:  "GET",
   377  		Path:    "/end.pl",
   378  		RootURL: "https://" + nodeurl,
   379  		Parameters: map[string][]string{
   380  			"xid": {uploadID},
   381  		},
   382  		ExtraHeaders: map[string]string{
   383  			"JSON": "1",
   384  		},
   385  	}
   386  
   387  	response = &EndFileUploadResponse{}
   388  	err = f.pacer.Call(func() (bool, error) {
   389  		resp, err := f.rest.CallJSON(ctx, &opts, nil, response)
   390  		return shouldRetry(resp, err)
   391  	})
   392  
   393  	if err != nil {
   394  		return nil, errors.Wrap(err, "couldn't finish file upload")
   395  	}
   396  
   397  	return response, err
   398  }