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 }