github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/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  	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(ctx context.Context, 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(ctx, &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(ctx, &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(ctx context.Context, 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(ctx, &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  	for i := range filesList.Items {
   111  		item := &filesList.Items[i]
   112  		item.Filename = f.opt.Enc.ToStandardName(item.Filename)
   113  	}
   114  
   115  	return filesList, nil
   116  }
   117  
   118  func (f *Fs) listFolders(ctx context.Context, directoryID int) (foldersList *FoldersList, err error) {
   119  	// fs.Debugf(f, "Requesting folders for id `%s`", directoryID)
   120  
   121  	request := ListFolderRequest{
   122  		FolderID: directoryID,
   123  	}
   124  
   125  	opts := rest.Opts{
   126  		Method: "POST",
   127  		Path:   "/folder/ls.cgi",
   128  	}
   129  
   130  	foldersList = &FoldersList{}
   131  	err = f.pacer.Call(func() (bool, error) {
   132  		resp, err := f.rest.CallJSON(ctx, &opts, &request, foldersList)
   133  		return shouldRetry(resp, err)
   134  	})
   135  	if err != nil {
   136  		return nil, errors.Wrap(err, "couldn't list folders")
   137  	}
   138  	foldersList.Name = f.opt.Enc.ToStandardName(foldersList.Name)
   139  	for i := range foldersList.SubFolders {
   140  		folder := &foldersList.SubFolders[i]
   141  		folder.Name = f.opt.Enc.ToStandardName(folder.Name)
   142  	}
   143  
   144  	// fs.Debugf(f, "Got FoldersList for id `%s`", directoryID)
   145  
   146  	return foldersList, err
   147  }
   148  
   149  func (f *Fs) listDir(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   150  	err = f.dirCache.FindRoot(ctx, false)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	directoryID, err := f.dirCache.FindDir(ctx, dir, false)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	folderID, err := strconv.Atoi(directoryID)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	files, err := f.listFiles(ctx, folderID)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	folders, err := f.listFolders(ctx, folderID)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	entries = make([]fs.DirEntry, len(files.Items)+len(folders.SubFolders))
   176  
   177  	for i, item := range files.Items {
   178  		entries[i] = f.newObjectFromFile(ctx, dir, item)
   179  	}
   180  
   181  	for i, folder := range folders.SubFolders {
   182  		createDate, err := time.Parse("2006-01-02 15:04:05", folder.CreateDate)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  
   187  		fullPath := getRemote(dir, folder.Name)
   188  		folderID := strconv.Itoa(folder.ID)
   189  
   190  		entries[len(files.Items)+i] = fs.NewDir(fullPath, createDate).SetID(folderID)
   191  
   192  		// fs.Debugf(f, "Put Path `%s` for id `%d` into dircache", fullPath, folder.ID)
   193  		f.dirCache.Put(fullPath, folderID)
   194  	}
   195  
   196  	return entries, nil
   197  }
   198  
   199  func (f *Fs) newObjectFromFile(ctx context.Context, dir string, item File) *Object {
   200  	return &Object{
   201  		fs:     f,
   202  		remote: getRemote(dir, item.Filename),
   203  		file:   item,
   204  	}
   205  }
   206  
   207  func getRemote(dir, fileName string) string {
   208  	if dir == "" {
   209  		return fileName
   210  	}
   211  
   212  	return dir + "/" + fileName
   213  }
   214  
   215  func (f *Fs) makeFolder(ctx context.Context, leaf string, folderID int) (response *MakeFolderResponse, err error) {
   216  	name := f.opt.Enc.FromStandardName(leaf)
   217  	// fs.Debugf(f, "Creating folder `%s` in id `%s`", name, directoryID)
   218  
   219  	request := MakeFolderRequest{
   220  		FolderID: folderID,
   221  		Name:     name,
   222  	}
   223  
   224  	opts := rest.Opts{
   225  		Method: "POST",
   226  		Path:   "/folder/mkdir.cgi",
   227  	}
   228  
   229  	response = &MakeFolderResponse{}
   230  	err = f.pacer.Call(func() (bool, error) {
   231  		resp, err := f.rest.CallJSON(ctx, &opts, &request, response)
   232  		return shouldRetry(resp, err)
   233  	})
   234  	if err != nil {
   235  		return nil, errors.Wrap(err, "couldn't create folder")
   236  	}
   237  
   238  	// fs.Debugf(f, "Created Folder `%s` in id `%s`", name, directoryID)
   239  
   240  	return response, err
   241  }
   242  
   243  func (f *Fs) removeFolder(ctx context.Context, name string, folderID int) (response *GenericOKResponse, err error) {
   244  	// fs.Debugf(f, "Removing folder with id `%s`", directoryID)
   245  
   246  	request := &RemoveFolderRequest{
   247  		FolderID: folderID,
   248  	}
   249  
   250  	opts := rest.Opts{
   251  		Method: "POST",
   252  		Path:   "/folder/rm.cgi",
   253  	}
   254  
   255  	response = &GenericOKResponse{}
   256  	var resp *http.Response
   257  	err = f.pacer.Call(func() (bool, error) {
   258  		resp, err = f.rest.CallJSON(ctx, &opts, request, response)
   259  		return shouldRetry(resp, err)
   260  	})
   261  	if err != nil {
   262  		return nil, errors.Wrap(err, "couldn't remove folder")
   263  	}
   264  	if response.Status != "OK" {
   265  		return nil, errors.New("Can't remove non-empty dir")
   266  	}
   267  
   268  	// fs.Debugf(f, "Removed Folder with id `%s`", directoryID)
   269  
   270  	return response, nil
   271  }
   272  
   273  func (f *Fs) deleteFile(ctx context.Context, url string) (response *GenericOKResponse, err error) {
   274  	request := &RemoveFileRequest{
   275  		Files: []RmFile{
   276  			{url},
   277  		},
   278  	}
   279  
   280  	opts := rest.Opts{
   281  		Method: "POST",
   282  		Path:   "/file/rm.cgi",
   283  	}
   284  
   285  	response = &GenericOKResponse{}
   286  	err = f.pacer.Call(func() (bool, error) {
   287  		resp, err := f.rest.CallJSON(ctx, &opts, request, response)
   288  		return shouldRetry(resp, err)
   289  	})
   290  
   291  	if err != nil {
   292  		return nil, errors.Wrap(err, "couldn't remove file")
   293  	}
   294  
   295  	// fs.Debugf(f, "Removed file with url `%s`", url)
   296  
   297  	return response, nil
   298  }
   299  
   300  func (f *Fs) getUploadNode(ctx context.Context) (response *GetUploadNodeResponse, err error) {
   301  	// fs.Debugf(f, "Requesting Upload node")
   302  
   303  	opts := rest.Opts{
   304  		Method:      "GET",
   305  		ContentType: "application/json", // 1Fichier API is bad
   306  		Path:        "/upload/get_upload_server.cgi",
   307  	}
   308  
   309  	response = &GetUploadNodeResponse{}
   310  	err = f.pacer.Call(func() (bool, error) {
   311  		resp, err := f.rest.CallJSON(ctx, &opts, nil, response)
   312  		return shouldRetry(resp, err)
   313  	})
   314  	if err != nil {
   315  		return nil, errors.Wrap(err, "didnt got an upload node")
   316  	}
   317  
   318  	// fs.Debugf(f, "Got Upload node")
   319  
   320  	return response, err
   321  }
   322  
   323  func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, fileName, folderID, uploadID, node string) (response *http.Response, err error) {
   324  	// fs.Debugf(f, "Uploading File `%s`", fileName)
   325  
   326  	fileName = f.opt.Enc.FromStandardName(fileName)
   327  
   328  	if len(uploadID) > 10 || !isAlphaNumeric(uploadID) {
   329  		return nil, errors.New("Invalid UploadID")
   330  	}
   331  
   332  	opts := rest.Opts{
   333  		Method: "POST",
   334  		Path:   "/upload.cgi",
   335  		Parameters: map[string][]string{
   336  			"id": {uploadID},
   337  		},
   338  		NoResponse:           true,
   339  		Body:                 in,
   340  		ContentLength:        &size,
   341  		MultipartContentName: "file[]",
   342  		MultipartFileName:    fileName,
   343  		MultipartParams: map[string][]string{
   344  			"did": {folderID},
   345  		},
   346  	}
   347  
   348  	if node != "" {
   349  		opts.RootURL = "https://" + node
   350  	}
   351  
   352  	err = f.pacer.CallNoRetry(func() (bool, error) {
   353  		resp, err := f.rest.CallJSON(ctx, &opts, nil, nil)
   354  		return shouldRetry(resp, err)
   355  	})
   356  
   357  	if err != nil {
   358  		return nil, errors.Wrap(err, "couldn't upload file")
   359  	}
   360  
   361  	// fs.Debugf(f, "Uploaded File `%s`", fileName)
   362  
   363  	return response, err
   364  }
   365  
   366  func (f *Fs) endUpload(ctx context.Context, uploadID string, nodeurl string) (response *EndFileUploadResponse, err error) {
   367  	// fs.Debugf(f, "Ending File Upload `%s`", uploadID)
   368  
   369  	if len(uploadID) > 10 || !isAlphaNumeric(uploadID) {
   370  		return nil, errors.New("Invalid UploadID")
   371  	}
   372  
   373  	opts := rest.Opts{
   374  		Method:  "GET",
   375  		Path:    "/end.pl",
   376  		RootURL: "https://" + nodeurl,
   377  		Parameters: map[string][]string{
   378  			"xid": {uploadID},
   379  		},
   380  		ExtraHeaders: map[string]string{
   381  			"JSON": "1",
   382  		},
   383  	}
   384  
   385  	response = &EndFileUploadResponse{}
   386  	err = f.pacer.Call(func() (bool, error) {
   387  		resp, err := f.rest.CallJSON(ctx, &opts, nil, response)
   388  		return shouldRetry(resp, err)
   389  	})
   390  
   391  	if err != nil {
   392  		return nil, errors.Wrap(err, "couldn't finish file upload")
   393  	}
   394  
   395  	return response, err
   396  }