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 }