uptobox.go 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  1. // Package uptobox provides an interface to the Uptobox storage system.
  2. package uptobox
  3. import (
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "path"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/rclone/rclone/backend/uptobox/api"
  17. "github.com/rclone/rclone/fs"
  18. "github.com/rclone/rclone/fs/config"
  19. "github.com/rclone/rclone/fs/config/configmap"
  20. "github.com/rclone/rclone/fs/config/configstruct"
  21. "github.com/rclone/rclone/fs/fserrors"
  22. "github.com/rclone/rclone/fs/fshttp"
  23. "github.com/rclone/rclone/fs/hash"
  24. "github.com/rclone/rclone/lib/encoder"
  25. "github.com/rclone/rclone/lib/pacer"
  26. "github.com/rclone/rclone/lib/random"
  27. "github.com/rclone/rclone/lib/rest"
  28. )
  29. const (
  30. apiBaseURL = "https://uptobox.com/api"
  31. minSleep = 400 * time.Millisecond // api is extremely rate limited now
  32. maxSleep = 5 * time.Second
  33. decayConstant = 2 // bigger for slower decay, exponential
  34. attackConstant = 0 // start with max sleep
  35. )
  36. func init() {
  37. fs.Register(&fs.RegInfo{
  38. Name: "uptobox",
  39. Description: "Uptobox",
  40. NewFs: NewFs,
  41. Options: []fs.Option{{
  42. Help: "Your access token.\n\nGet it from https://uptobox.com/my_account.",
  43. Name: "access_token",
  44. Sensitive: true,
  45. }, {
  46. Help: "Set to make uploaded files private",
  47. Name: "private",
  48. Advanced: true,
  49. Default: false,
  50. }, {
  51. Name: config.ConfigEncoding,
  52. Help: config.ConfigEncodingHelp,
  53. Advanced: true,
  54. // maxFileLength = 255
  55. Default: (encoder.Display |
  56. encoder.EncodeBackQuote |
  57. encoder.EncodeDoubleQuote |
  58. encoder.EncodeLtGt |
  59. encoder.EncodeLeftSpace |
  60. encoder.EncodeInvalidUtf8),
  61. }},
  62. })
  63. }
  64. // Options defines the configuration for this backend
  65. type Options struct {
  66. AccessToken string `config:"access_token"`
  67. Private bool `config:"private"`
  68. Enc encoder.MultiEncoder `config:"encoding"`
  69. }
  70. // Fs is the interface a cloud storage system must provide
  71. type Fs struct {
  72. root string
  73. name string
  74. opt Options
  75. features *fs.Features
  76. srv *rest.Client
  77. pacer *fs.Pacer
  78. IDRegexp *regexp.Regexp
  79. public string // "0" to make objects private
  80. }
  81. // Object represents an Uptobox object
  82. type Object struct {
  83. fs *Fs // what this object is part of
  84. remote string // The remote path
  85. hasMetaData bool // whether info below has been set
  86. size int64 // Bytes in the object
  87. // modTime time.Time // Modified time of the object
  88. code string
  89. }
  90. // Name of the remote (as passed into NewFs)
  91. func (f *Fs) Name() string {
  92. return f.name
  93. }
  94. // Root of the remote (as passed into NewFs)
  95. func (f *Fs) Root() string {
  96. return f.root
  97. }
  98. // String returns a description of the FS
  99. func (f *Fs) String() string {
  100. return fmt.Sprintf("Uptobox root '%s'", f.root)
  101. }
  102. // Precision of the ModTimes in this Fs
  103. func (f *Fs) Precision() time.Duration {
  104. return fs.ModTimeNotSupported
  105. }
  106. // Hashes returns the supported hash types of the filesystem
  107. func (f *Fs) Hashes() hash.Set {
  108. return hash.Set(hash.None)
  109. }
  110. // Features returns the optional features of this Fs
  111. func (f *Fs) Features() *fs.Features {
  112. return f.features
  113. }
  114. // retryErrorCodes is a slice of error codes that we will retry
  115. var retryErrorCodes = []int{
  116. 429, // Too Many Requests.
  117. 500, // Internal Server Error
  118. 502, // Bad Gateway
  119. 503, // Service Unavailable
  120. 504, // Gateway Timeout
  121. 509, // Bandwidth Limit Exceeded
  122. }
  123. // shouldRetry returns a boolean as to whether this resp and err
  124. // deserve to be retried. It returns the err as a convenience
  125. func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, error) {
  126. if fserrors.ContextError(ctx, &err) {
  127. return false, err
  128. }
  129. return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
  130. }
  131. // dirPath returns an escaped file path (f.root, file)
  132. func (f *Fs) dirPath(file string) string {
  133. //return path.Join(f.diskRoot, file)
  134. if file == "" || file == "." {
  135. return "//" + f.root
  136. }
  137. return "//" + path.Join(f.root, file)
  138. }
  139. // returns the full path based on root and the last element
  140. func (f *Fs) splitPathFull(pth string) (string, string) {
  141. fullPath := strings.Trim(path.Join(f.root, pth), "/")
  142. i := len(fullPath) - 1
  143. for i >= 0 && fullPath[i] != '/' {
  144. i--
  145. }
  146. if i < 0 {
  147. return "//" + fullPath[:i+1], fullPath[i+1:]
  148. }
  149. // do not include the / at the split
  150. return "//" + fullPath[:i], fullPath[i+1:]
  151. }
  152. // splitPath is modified splitPath version that doesn't include the separator
  153. // in the base path
  154. func (f *Fs) splitPath(pth string) (string, string) {
  155. // chop of any leading or trailing '/'
  156. pth = strings.Trim(pth, "/")
  157. i := len(pth) - 1
  158. for i >= 0 && pth[i] != '/' {
  159. i--
  160. }
  161. if i < 0 {
  162. return pth[:i+1], pth[i+1:]
  163. }
  164. return pth[:i], pth[i+1:]
  165. }
  166. // NewFs makes a new Fs object from the path
  167. //
  168. // The path is of the form remote:path
  169. //
  170. // Remotes are looked up in the config file. If the remote isn't
  171. // found then NotFoundInConfigFile will be returned.
  172. //
  173. // On Windows avoid single character remote names as they can be mixed
  174. // up with drive letters.
  175. func NewFs(ctx context.Context, name string, root string, config configmap.Mapper) (fs.Fs, error) {
  176. opt := new(Options)
  177. err := configstruct.Set(config, opt)
  178. if err != nil {
  179. return nil, err
  180. }
  181. f := &Fs{
  182. name: name,
  183. root: root,
  184. opt: *opt,
  185. pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant), pacer.AttackConstant(attackConstant))),
  186. }
  187. if root == "/" || root == "." {
  188. f.root = ""
  189. } else {
  190. f.root = root
  191. }
  192. f.features = (&fs.Features{
  193. DuplicateFiles: true,
  194. CanHaveEmptyDirectories: true,
  195. ReadMimeType: false,
  196. }).Fill(ctx, f)
  197. if f.opt.Private {
  198. f.public = "0"
  199. }
  200. client := fshttp.NewClient(ctx)
  201. f.srv = rest.NewClient(client).SetRoot(apiBaseURL)
  202. f.IDRegexp = regexp.MustCompile(`^https://uptobox\.com/([a-zA-Z0-9]+)`)
  203. _, err = f.readMetaDataForPath(ctx, f.dirPath(""), &api.MetadataRequestOptions{Limit: 10})
  204. if err != nil {
  205. if _, ok := err.(api.Error); !ok {
  206. return nil, err
  207. }
  208. // assume it's a file than
  209. oldRoot := f.root
  210. rootDir, file := f.splitPath(root)
  211. f.root = rootDir
  212. _, err = f.NewObject(ctx, file)
  213. if err == nil {
  214. return f, fs.ErrorIsFile
  215. }
  216. f.root = oldRoot
  217. }
  218. return f, nil
  219. }
  220. func (f *Fs) decodeError(resp *http.Response, response interface{}) (err error) {
  221. defer fs.CheckClose(resp.Body, &err)
  222. body, err := io.ReadAll(resp.Body)
  223. if err != nil {
  224. return err
  225. }
  226. // try to unmarshal into correct structure
  227. err = json.Unmarshal(body, response)
  228. if err == nil {
  229. return nil
  230. }
  231. // try to unmarshal into Error
  232. var apiErr api.Error
  233. err = json.Unmarshal(body, &apiErr)
  234. if err != nil {
  235. return err
  236. }
  237. return apiErr
  238. }
  239. func (f *Fs) readMetaDataForPath(ctx context.Context, path string, options *api.MetadataRequestOptions) (*api.ReadMetadataResponse, error) {
  240. opts := rest.Opts{
  241. Method: "GET",
  242. Path: "/user/files",
  243. Parameters: url.Values{
  244. "token": []string{f.opt.AccessToken},
  245. "path": []string{f.opt.Enc.FromStandardPath(path)},
  246. "limit": []string{strconv.FormatUint(options.Limit, 10)},
  247. },
  248. }
  249. if options.Offset != 0 {
  250. opts.Parameters.Set("offset", strconv.FormatUint(options.Offset, 10))
  251. }
  252. var err error
  253. var info api.ReadMetadataResponse
  254. var resp *http.Response
  255. err = f.pacer.Call(func() (bool, error) {
  256. resp, err = f.srv.Call(ctx, &opts)
  257. return shouldRetry(ctx, resp, err)
  258. })
  259. if err != nil {
  260. return nil, err
  261. }
  262. err = f.decodeError(resp, &info)
  263. if err != nil {
  264. return nil, err
  265. }
  266. if info.StatusCode != 0 {
  267. return nil, errors.New(info.Message)
  268. }
  269. return &info, nil
  270. }
  271. // List the objects and directories in dir into entries. The
  272. // entries can be returned in any order but should be for a
  273. // complete directory.
  274. //
  275. // dir should be "" to list the root, and should not have
  276. // trailing slashes.
  277. //
  278. // This should return ErrDirNotFound if the directory isn't
  279. // found.
  280. func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
  281. root := f.dirPath(dir)
  282. var limit uint64 = 100 // max number of objects per request - 100 seems to be the maximum the api accepts
  283. var page uint64 = 1
  284. var offset uint64 // for the next page of requests
  285. for {
  286. opts := &api.MetadataRequestOptions{
  287. Limit: limit,
  288. Offset: offset,
  289. }
  290. info, err := f.readMetaDataForPath(ctx, root, opts)
  291. if err != nil {
  292. if apiErr, ok := err.(api.Error); ok {
  293. // might indicate other errors but we can probably assume not found here
  294. if apiErr.StatusCode == 1 {
  295. return nil, fs.ErrorDirNotFound
  296. }
  297. }
  298. return nil, err
  299. }
  300. for _, item := range info.Data.Files {
  301. remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
  302. o, err := f.newObjectWithInfo(ctx, remote, &item)
  303. if err != nil {
  304. continue
  305. }
  306. entries = append(entries, o)
  307. }
  308. // folders are always listed entirely on every page grr.
  309. if page == 1 {
  310. for _, item := range info.Data.Folders {
  311. remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
  312. d := fs.NewDir(remote, time.Time{}).SetID(strconv.FormatUint(item.FolderID, 10))
  313. entries = append(entries, d)
  314. }
  315. }
  316. //offset for the next page of items
  317. page++
  318. offset += limit
  319. //check if we reached end of list
  320. if page > uint64(info.Data.PageCount) {
  321. break
  322. }
  323. }
  324. return entries, nil
  325. }
  326. // Return an Object from a path
  327. //
  328. // If it can't be found it returns the error fs.ErrorObjectNotFound.
  329. func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.FileInfo) (fs.Object, error) {
  330. o := &Object{
  331. fs: f,
  332. remote: remote,
  333. size: info.Size,
  334. code: info.Code,
  335. hasMetaData: true,
  336. }
  337. return o, nil
  338. }
  339. // NewObject finds the Object at remote. If it can't be found it
  340. // returns the error fs.ErrorObjectNotFound.
  341. func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
  342. // no way to directly access an object by path so we have to list the parent dir
  343. entries, err := f.List(ctx, path.Dir(remote))
  344. if err != nil {
  345. // need to change error type
  346. // if the parent dir doesn't exist the object doesn't exist either
  347. if err == fs.ErrorDirNotFound {
  348. return nil, fs.ErrorObjectNotFound
  349. }
  350. return nil, err
  351. }
  352. for _, entry := range entries {
  353. if o, ok := entry.(fs.Object); ok {
  354. if o.Remote() == remote {
  355. return o, nil
  356. }
  357. }
  358. }
  359. return nil, fs.ErrorObjectNotFound
  360. }
  361. func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, filename string, uploadURL string, options ...fs.OpenOption) (*api.UploadResponse, error) {
  362. opts := rest.Opts{
  363. Method: "POST",
  364. RootURL: "https:" + uploadURL,
  365. Body: in,
  366. ContentLength: &size,
  367. Options: options,
  368. MultipartContentName: "files",
  369. MultipartFileName: filename,
  370. }
  371. var err error
  372. var resp *http.Response
  373. var ul api.UploadResponse
  374. err = f.pacer.CallNoRetry(func() (bool, error) {
  375. resp, err = f.srv.CallJSON(ctx, &opts, nil, &ul)
  376. return shouldRetry(ctx, resp, err)
  377. })
  378. if err != nil {
  379. return nil, fmt.Errorf("couldn't upload file: %w", err)
  380. }
  381. return &ul, nil
  382. }
  383. // dstPath starts from root and includes //
  384. func (f *Fs) move(ctx context.Context, dstPath string, fileID string) (err error) {
  385. meta, err := f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 10})
  386. if err != nil {
  387. return err
  388. }
  389. opts := rest.Opts{
  390. Method: "PATCH",
  391. Path: "/user/files",
  392. }
  393. mv := api.CopyMoveFileRequest{
  394. Token: f.opt.AccessToken,
  395. FileCodes: fileID,
  396. DestinationFolderID: meta.Data.CurrentFolder.FolderID,
  397. Action: "move",
  398. }
  399. var resp *http.Response
  400. var info api.UpdateResponse
  401. err = f.pacer.Call(func() (bool, error) {
  402. resp, err = f.srv.CallJSON(ctx, &opts, &mv, &info)
  403. return shouldRetry(ctx, resp, err)
  404. })
  405. if err != nil {
  406. return fmt.Errorf("couldn't move file: %w", err)
  407. }
  408. if info.StatusCode != 0 {
  409. return fmt.Errorf("move: api error: %d - %s", info.StatusCode, info.Message)
  410. }
  411. return err
  412. }
  413. // updateFileInformation set's various file attributes most importantly it's name
  414. func (f *Fs) updateFileInformation(ctx context.Context, update *api.UpdateFileInformation) (err error) {
  415. opts := rest.Opts{
  416. Method: "PATCH",
  417. Path: "/user/files",
  418. }
  419. var resp *http.Response
  420. var info api.UpdateResponse
  421. err = f.pacer.Call(func() (bool, error) {
  422. resp, err = f.srv.CallJSON(ctx, &opts, update, &info)
  423. return shouldRetry(ctx, resp, err)
  424. })
  425. if err != nil {
  426. return fmt.Errorf("couldn't update file info: %w", err)
  427. }
  428. if info.StatusCode != 0 {
  429. return fmt.Errorf("updateFileInfo: api error: %d - %s", info.StatusCode, info.Message)
  430. }
  431. return err
  432. }
  433. func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size int64, options ...fs.OpenOption) error {
  434. if size > int64(200e9) { // max size 200GB
  435. return errors.New("file too big, can't upload")
  436. } else if size == 0 {
  437. return fs.ErrorCantUploadEmptyFiles
  438. }
  439. // yes it does take 4 requests if we're uploading to root and 6+ if we're uploading to any subdir :(
  440. // create upload request
  441. opts := rest.Opts{
  442. Method: "GET",
  443. Path: "/upload",
  444. }
  445. token := api.Token{
  446. Token: f.opt.AccessToken,
  447. }
  448. var info api.UploadInfo
  449. err := f.pacer.Call(func() (bool, error) {
  450. resp, err := f.srv.CallJSON(ctx, &opts, &token, &info)
  451. return shouldRetry(ctx, resp, err)
  452. })
  453. if err != nil {
  454. return err
  455. }
  456. if info.StatusCode != 0 {
  457. return fmt.Errorf("putUnchecked api error: %d - %s", info.StatusCode, info.Message)
  458. }
  459. // we need to have a safe name for the upload to work
  460. tmpName := "rcloneTemp" + random.String(8)
  461. upload, err := f.uploadFile(ctx, in, size, tmpName, info.Data.UploadLink, options...)
  462. if err != nil {
  463. return err
  464. }
  465. if len(upload.Files) != 1 {
  466. return errors.New("upload unexpected response")
  467. }
  468. match := f.IDRegexp.FindStringSubmatch(upload.Files[0].URL)
  469. // move file to destination folder
  470. base, leaf := f.splitPath(remote)
  471. fullBase := f.dirPath(base)
  472. if fullBase != "//" {
  473. // make all the parent folders
  474. err = f.Mkdir(ctx, base)
  475. if err != nil {
  476. // this might need some more error handling. if any of the following requests fail
  477. // we'll leave an orphaned temporary file floating around somewhere
  478. // they rarely fail though
  479. return err
  480. }
  481. err = f.move(ctx, fullBase, match[1])
  482. if err != nil {
  483. return err
  484. }
  485. }
  486. // rename file to final name
  487. err = f.updateFileInformation(ctx, &api.UpdateFileInformation{
  488. Token: f.opt.AccessToken,
  489. FileCode: match[1],
  490. NewName: f.opt.Enc.FromStandardName(leaf),
  491. Public: f.public,
  492. })
  493. if err != nil {
  494. return err
  495. }
  496. return nil
  497. }
  498. // Put in to the remote path with the modTime given of the given size
  499. //
  500. // When called from outside an Fs by rclone, src.Size() will always be >= 0.
  501. // But for unknown-sized objects (indicated by src.Size() == -1), Put should either
  502. // return an error or upload it properly (rather than e.g. calling panic).
  503. //
  504. // May create the object even if it returns an error - if so
  505. // will return the object and the error, otherwise will return
  506. // nil and the error
  507. func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
  508. existingObj, err := f.NewObject(ctx, src.Remote())
  509. switch err {
  510. case nil:
  511. return existingObj, existingObj.Update(ctx, in, src, options...)
  512. case fs.ErrorObjectNotFound:
  513. // Not found so create it
  514. return f.PutUnchecked(ctx, in, src, options...)
  515. default:
  516. return nil, err
  517. }
  518. }
  519. // PutUnchecked uploads the object
  520. //
  521. // This will create a duplicate if we upload a new file without
  522. // checking to see if there is one already - use Put() for that.
  523. func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
  524. err := f.putUnchecked(ctx, in, src.Remote(), src.Size(), options...)
  525. if err != nil {
  526. return nil, err
  527. }
  528. return f.NewObject(ctx, src.Remote())
  529. }
  530. // CreateDir dir creates a directory with the given parent path
  531. // base starts from root and may or may not include //
  532. func (f *Fs) CreateDir(ctx context.Context, base string, leaf string) (err error) {
  533. base = "//" + strings.Trim(base, "/")
  534. var resp *http.Response
  535. var apiErr api.Error
  536. opts := rest.Opts{
  537. Method: "PUT",
  538. Path: "/user/files",
  539. }
  540. mkdir := api.CreateFolderRequest{
  541. Name: f.opt.Enc.FromStandardName(leaf),
  542. Path: f.opt.Enc.FromStandardPath(base),
  543. Token: f.opt.AccessToken,
  544. }
  545. err = f.pacer.Call(func() (bool, error) {
  546. resp, err = f.srv.CallJSON(ctx, &opts, &mkdir, &apiErr)
  547. return shouldRetry(ctx, resp, err)
  548. })
  549. if err != nil {
  550. return err
  551. }
  552. // checking if the dir exists beforehand would be slower so we'll just ignore the error here
  553. if apiErr.StatusCode != 0 && !strings.Contains(apiErr.Data, "already exists") {
  554. return apiErr
  555. }
  556. return nil
  557. }
  558. func (f *Fs) mkDirs(ctx context.Context, path string) (err error) {
  559. // chop of any leading or trailing slashes
  560. dirs := strings.Split(path, "/")
  561. var base = ""
  562. for _, element := range dirs {
  563. // create every dir one by one
  564. if element != "" {
  565. err = f.CreateDir(ctx, base, element)
  566. if err != nil {
  567. return err
  568. }
  569. base += "/" + element
  570. }
  571. }
  572. return nil
  573. }
  574. // Mkdir makes the directory (container, bucket)
  575. //
  576. // Shouldn't return an error if it already exists
  577. func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) {
  578. if dir == "" || dir == "." {
  579. return f.mkDirs(ctx, f.root)
  580. }
  581. return f.mkDirs(ctx, path.Join(f.root, dir))
  582. }
  583. // may or may not delete folders with contents?
  584. func (f *Fs) purge(ctx context.Context, folderID uint64) (err error) {
  585. var resp *http.Response
  586. var apiErr api.Error
  587. opts := rest.Opts{
  588. Method: "DELETE",
  589. Path: "/user/files",
  590. }
  591. rm := api.DeleteFolderRequest{
  592. FolderID: folderID,
  593. Token: f.opt.AccessToken,
  594. }
  595. err = f.pacer.Call(func() (bool, error) {
  596. resp, err = f.srv.CallJSON(ctx, &opts, &rm, &apiErr)
  597. return shouldRetry(ctx, resp, err)
  598. })
  599. if err != nil {
  600. return err
  601. }
  602. if apiErr.StatusCode != 0 {
  603. return apiErr
  604. }
  605. return nil
  606. }
  607. // Rmdir removes the directory (container, bucket) if empty
  608. //
  609. // Return an error if it doesn't exist or isn't empty
  610. func (f *Fs) Rmdir(ctx context.Context, dir string) error {
  611. info, err := f.readMetaDataForPath(ctx, f.dirPath(dir), &api.MetadataRequestOptions{Limit: 10})
  612. if err != nil {
  613. return err
  614. }
  615. if len(info.Data.Folders) > 0 || len(info.Data.Files) > 0 {
  616. return fs.ErrorDirectoryNotEmpty
  617. }
  618. return f.purge(ctx, info.Data.CurrentFolder.FolderID)
  619. }
  620. // Move src to this remote using server side move operations.
  621. func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
  622. srcObj, ok := src.(*Object)
  623. if !ok {
  624. fs.Debugf(src, "Can't move - not same remote type")
  625. return nil, fs.ErrorCantMove
  626. }
  627. srcBase, srcLeaf := srcObj.fs.splitPathFull(src.Remote())
  628. dstBase, dstLeaf := f.splitPathFull(remote)
  629. needRename := srcLeaf != dstLeaf
  630. needMove := srcBase != dstBase
  631. // do the move if required
  632. if needMove {
  633. err := f.mkDirs(ctx, strings.Trim(dstBase, "/"))
  634. if err != nil {
  635. return nil, fmt.Errorf("move: failed to make destination dirs: %w", err)
  636. }
  637. err = f.move(ctx, dstBase, srcObj.code)
  638. if err != nil {
  639. return nil, err
  640. }
  641. }
  642. // rename to final name if we need to
  643. if needRename {
  644. err := f.updateFileInformation(ctx, &api.UpdateFileInformation{
  645. Token: f.opt.AccessToken,
  646. FileCode: srcObj.code,
  647. NewName: f.opt.Enc.FromStandardName(dstLeaf),
  648. Public: f.public,
  649. })
  650. if err != nil {
  651. return nil, fmt.Errorf("move: failed final rename: %w", err)
  652. }
  653. }
  654. // copy the old object and apply the changes
  655. newObj := *srcObj
  656. newObj.remote = remote
  657. newObj.fs = f
  658. return &newObj, nil
  659. }
  660. // renameDir renames a directory
  661. func (f *Fs) renameDir(ctx context.Context, folderID uint64, newName string) (err error) {
  662. var resp *http.Response
  663. var apiErr api.Error
  664. opts := rest.Opts{
  665. Method: "PATCH",
  666. Path: "/user/files",
  667. }
  668. rename := api.RenameFolderRequest{
  669. Token: f.opt.AccessToken,
  670. FolderID: folderID,
  671. NewName: newName,
  672. }
  673. err = f.pacer.Call(func() (bool, error) {
  674. resp, err = f.srv.CallJSON(ctx, &opts, &rename, &apiErr)
  675. return shouldRetry(ctx, resp, err)
  676. })
  677. if err != nil {
  678. return err
  679. }
  680. if apiErr.StatusCode != 0 {
  681. return apiErr
  682. }
  683. return nil
  684. }
  685. // DirMove moves src, srcRemote to this remote at dstRemote
  686. // using server-side move operations.
  687. //
  688. // Will only be called if src.Fs().Name() == f.Name()
  689. //
  690. // If it isn't possible then return fs.ErrorCantDirMove
  691. //
  692. // If destination exists then return fs.ErrorDirExists
  693. func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
  694. srcFs, ok := src.(*Fs)
  695. if !ok {
  696. fs.Debugf(srcFs, "Can't move directory - not same remote type")
  697. return fs.ErrorCantDirMove
  698. }
  699. // find out source
  700. srcPath := srcFs.dirPath(srcRemote)
  701. srcInfo, err := f.readMetaDataForPath(ctx, srcPath, &api.MetadataRequestOptions{Limit: 1})
  702. if err != nil {
  703. return fmt.Errorf("dirmove: source not found: %w", err)
  704. }
  705. // check if the destination already exists
  706. dstPath := f.dirPath(dstRemote)
  707. _, err = f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 1})
  708. if err == nil {
  709. return fs.ErrorDirExists
  710. }
  711. // make the destination parent path
  712. dstBase, dstName := f.splitPathFull(dstRemote)
  713. err = f.mkDirs(ctx, strings.Trim(dstBase, "/"))
  714. if err != nil {
  715. return fmt.Errorf("dirmove: failed to create dirs: %w", err)
  716. }
  717. // find the destination parent dir
  718. dstInfo, err := f.readMetaDataForPath(ctx, dstBase, &api.MetadataRequestOptions{Limit: 1})
  719. if err != nil {
  720. return fmt.Errorf("dirmove: failed to read destination: %w", err)
  721. }
  722. srcBase, srcName := srcFs.splitPathFull(srcRemote)
  723. needRename := srcName != dstName
  724. needMove := srcBase != dstBase
  725. // if we have to rename we'll have to use a temporary name since
  726. // there could already be a directory with the same name as the src directory
  727. if needRename {
  728. // rename to a temporary name
  729. tmpName := "rcloneTemp" + random.String(8)
  730. err = f.renameDir(ctx, srcInfo.Data.CurrentFolder.FolderID, tmpName)
  731. if err != nil {
  732. return fmt.Errorf("dirmove: failed initial rename: %w", err)
  733. }
  734. }
  735. // do the move
  736. if needMove {
  737. opts := rest.Opts{
  738. Method: "PATCH",
  739. Path: "/user/files",
  740. }
  741. move := api.MoveFolderRequest{
  742. Token: f.opt.AccessToken,
  743. FolderID: srcInfo.Data.CurrentFolder.FolderID,
  744. DestinationFolderID: dstInfo.Data.CurrentFolder.FolderID,
  745. Action: "move",
  746. }
  747. var resp *http.Response
  748. var apiErr api.Error
  749. err = f.pacer.Call(func() (bool, error) {
  750. resp, err = f.srv.CallJSON(ctx, &opts, &move, &apiErr)
  751. return shouldRetry(ctx, resp, err)
  752. })
  753. if err != nil {
  754. return fmt.Errorf("dirmove: failed to move: %w", err)
  755. }
  756. if apiErr.StatusCode != 0 {
  757. return apiErr
  758. }
  759. }
  760. // rename to final name
  761. if needRename {
  762. err = f.renameDir(ctx, srcInfo.Data.CurrentFolder.FolderID, dstName)
  763. if err != nil {
  764. return fmt.Errorf("dirmove: failed final rename: %w", err)
  765. }
  766. }
  767. return nil
  768. }
  769. func (f *Fs) copy(ctx context.Context, dstPath string, fileID string) (err error) {
  770. meta, err := f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 10})
  771. if err != nil {
  772. return err
  773. }
  774. opts := rest.Opts{
  775. Method: "PATCH",
  776. Path: "/user/files",
  777. }
  778. cp := api.CopyMoveFileRequest{
  779. Token: f.opt.AccessToken,
  780. FileCodes: fileID,
  781. DestinationFolderID: meta.Data.CurrentFolder.FolderID,
  782. Action: "copy",
  783. }
  784. var resp *http.Response
  785. var info api.UpdateResponse
  786. err = f.pacer.Call(func() (bool, error) {
  787. resp, err = f.srv.CallJSON(ctx, &opts, &cp, &info)
  788. return shouldRetry(ctx, resp, err)
  789. })
  790. if err != nil {
  791. return fmt.Errorf("couldn't copy file: %w", err)
  792. }
  793. if info.StatusCode != 0 {
  794. return fmt.Errorf("copy: api error: %d - %s", info.StatusCode, info.Message)
  795. }
  796. return err
  797. }
  798. // Copy src to this remote using server side move operations.
  799. func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
  800. srcObj, ok := src.(*Object)
  801. if !ok {
  802. fs.Debugf(src, "Can't copy - not same remote type")
  803. return nil, fs.ErrorCantMove
  804. }
  805. _, srcLeaf := f.splitPath(src.Remote())
  806. dstBase, dstLeaf := f.splitPath(remote)
  807. needRename := srcLeaf != dstLeaf
  808. err := f.mkDirs(ctx, path.Join(f.root, dstBase))
  809. if err != nil {
  810. return nil, fmt.Errorf("copy: failed to make destination dirs: %w", err)
  811. }
  812. err = f.copy(ctx, f.dirPath(dstBase), srcObj.code)
  813. if err != nil {
  814. return nil, err
  815. }
  816. newObj, err := f.NewObject(ctx, path.Join(dstBase, srcLeaf))
  817. if err != nil {
  818. return nil, fmt.Errorf("copy: couldn't find copied object: %w", err)
  819. }
  820. if needRename {
  821. err := f.updateFileInformation(ctx, &api.UpdateFileInformation{
  822. Token: f.opt.AccessToken,
  823. FileCode: newObj.(*Object).code,
  824. NewName: f.opt.Enc.FromStandardName(dstLeaf),
  825. Public: f.public,
  826. })
  827. if err != nil {
  828. return nil, fmt.Errorf("copy: failed final rename: %w", err)
  829. }
  830. newObj.(*Object).remote = remote
  831. }
  832. return newObj, nil
  833. }
  834. // ------------------------------------------------------------
  835. // Fs returns the parent Fs
  836. func (o *Object) Fs() fs.Info {
  837. return o.fs
  838. }
  839. // Return a string version
  840. func (o *Object) String() string {
  841. if o == nil {
  842. return "<nil>"
  843. }
  844. return o.remote
  845. }
  846. // Remote returns the remote path
  847. func (o *Object) Remote() string {
  848. return o.remote
  849. }
  850. // ModTime returns the modification time of the object
  851. //
  852. // It attempts to read the objects mtime and if that isn't present the
  853. // LastModified returned in the http headers
  854. func (o *Object) ModTime(ctx context.Context) time.Time {
  855. ci := fs.GetConfig(ctx)
  856. return time.Time(ci.DefaultTime)
  857. }
  858. // Size returns the size of an object in bytes
  859. func (o *Object) Size() int64 {
  860. return o.size
  861. }
  862. // Hash returns the Md5sum of an object returning a lowercase hex string
  863. func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
  864. return "", hash.ErrUnsupported
  865. }
  866. // ID returns the ID of the Object if known, or "" if not
  867. func (o *Object) ID() string {
  868. return o.code
  869. }
  870. // Storable returns whether this object is storable
  871. func (o *Object) Storable() bool {
  872. return true
  873. }
  874. // SetModTime sets the modification time of the local fs object
  875. func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
  876. return fs.ErrorCantSetModTime
  877. }
  878. // Open an object for read
  879. func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
  880. opts := rest.Opts{
  881. Method: "GET",
  882. Path: "/link",
  883. Parameters: url.Values{
  884. "token": []string{o.fs.opt.AccessToken},
  885. "file_code": []string{o.code},
  886. },
  887. }
  888. var dl api.Download
  889. var resp *http.Response
  890. err = o.fs.pacer.Call(func() (bool, error) {
  891. resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl)
  892. return shouldRetry(ctx, resp, err)
  893. })
  894. if err != nil {
  895. return nil, fmt.Errorf("open: failed to get download link: %w", err)
  896. }
  897. fs.FixRangeOption(options, o.size)
  898. opts = rest.Opts{
  899. Method: "GET",
  900. RootURL: dl.Data.DownloadLink,
  901. Options: options,
  902. }
  903. err = o.fs.pacer.Call(func() (bool, error) {
  904. resp, err = o.fs.srv.Call(ctx, &opts)
  905. return shouldRetry(ctx, resp, err)
  906. })
  907. if err != nil {
  908. return nil, err
  909. }
  910. return resp.Body, err
  911. }
  912. // Update the already existing object
  913. //
  914. // Copy the reader into the object updating modTime and size.
  915. //
  916. // The new object may have been created if an error is returned
  917. func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
  918. if src.Size() < 0 {
  919. return errors.New("refusing to update with unknown size")
  920. }
  921. // upload with new size but old name
  922. err := o.fs.putUnchecked(ctx, in, o.Remote(), src.Size(), options...)
  923. if err != nil {
  924. return err
  925. }
  926. // delete duplicate object after successful upload
  927. err = o.Remove(ctx)
  928. if err != nil {
  929. return fmt.Errorf("failed to remove old version: %w", err)
  930. }
  931. // Fetch new object after deleting the duplicate
  932. info, err := o.fs.NewObject(ctx, o.Remote())
  933. if err != nil {
  934. return err
  935. }
  936. // Replace guts of old object with new one
  937. *o = *info.(*Object)
  938. return nil
  939. }
  940. // Remove an object
  941. func (o *Object) Remove(ctx context.Context) error {
  942. opts := rest.Opts{
  943. Method: "DELETE",
  944. Path: "/user/files",
  945. }
  946. delete := api.RemoveFileRequest{
  947. Token: o.fs.opt.AccessToken,
  948. FileCodes: o.code,
  949. }
  950. var info api.UpdateResponse
  951. err := o.fs.pacer.Call(func() (bool, error) {
  952. resp, err := o.fs.srv.CallJSON(ctx, &opts, &delete, &info)
  953. return shouldRetry(ctx, resp, err)
  954. })
  955. if err != nil {
  956. return err
  957. }
  958. if info.StatusCode != 0 {
  959. return fmt.Errorf("remove: api error: %d - %s", info.StatusCode, info.Message)
  960. }
  961. return nil
  962. }
  963. // Check the interfaces are satisfied
  964. var (
  965. _ fs.Fs = (*Fs)(nil)
  966. _ fs.Copier = (*Fs)(nil)
  967. _ fs.Mover = (*Fs)(nil)
  968. _ fs.DirMover = (*Fs)(nil)
  969. _ fs.Object = (*Object)(nil)
  970. )