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