Skip to content

Commit bb4b0cb

Browse files
Support exporting Paper files in get
1 parent 3caf6bd commit bb4b0cb

5 files changed

Lines changed: 413 additions & 21 deletions

File tree

cmd/files_client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type filesClient interface {
1313
CreateFolderV2Context(context.Context, *files.CreateFolderArg) (*files.CreateFolderResult, error)
1414
DeleteV2Context(context.Context, *files.DeleteArg) (*files.DeleteResult, error)
1515
DownloadContext(context.Context, *files.DownloadArg) (*files.FileMetadata, io.ReadCloser, error)
16+
ExportContext(context.Context, *files.ExportArg) (*files.ExportResult, io.ReadCloser, error)
1617
GetMetadataContext(context.Context, *files.GetMetadataArg) (files.IsMetadata, error)
1718
ListFolderContext(context.Context, *files.ListFolderArg) (*files.ListFolderResult, error)
1819
ListFolderContinueContext(context.Context, *files.ListFolderContinueArg) (*files.ListFolderResult, error)

cmd/get.go

Lines changed: 117 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ func get(cmd *cobra.Command, args []string) (err error) {
7575
if srcRef.isPath() {
7676
dst = path.Base(src)
7777
}
78-
if len(args) == 2 {
78+
79+
dstExplicit := len(args) == 2
80+
if dstExplicit {
7981
dst = args[1]
8082
}
8183

@@ -104,7 +106,7 @@ func get(cmd *cobra.Command, args []string) (err error) {
104106
}
105107
dst = filepath.Join(dst, path.Base(src))
106108
}
107-
result, err := downloadFileWithResult(dbx, src, dst, opts)
109+
result, err := downloadFileWithResult(dbx, src, dst, nil, true, opts)
108110
if err != nil {
109111
return withJSONErrorDetails(err, operationErrorDetails("download"), pathErrorDetails(src), relocationErrorDetails(src, dst))
110112
}
@@ -149,17 +151,23 @@ func get(cmd *cobra.Command, args []string) (err error) {
149151
}, results)
150152
}
151153

154+
dstFilenameExplicit := dstExplicit
152155
if f, statErr := os.Stat(dst); statErr == nil && f.IsDir() {
153156
dst = filepath.Join(dst, sourceName)
157+
dstFilenameExplicit = false
154158
}
155159

156-
result, err := downloadFileWithResult(dbx, src, dst, opts)
160+
fileMeta, ok := meta.(*files.FileMetadata)
161+
if !ok {
162+
return fmt.Errorf("unexpected metadata type for %s", src)
163+
}
164+
result, err := downloadFileWithResult(dbx, src, dst, fileMeta, dstFilenameExplicit, opts)
157165
if err != nil {
158166
return withJSONErrorDetails(err, operationErrorDetails("download"), pathErrorDetails(src), relocationErrorDetails(src, dst))
159167
}
160168
return renderGetResults(cmd, getCommandInput{
161169
Source: src,
162-
Target: dst,
170+
Target: result.Input.Target,
163171
Recursive: false,
164172
Stdout: false,
165173
}, []getResult{result})
@@ -227,7 +235,8 @@ func getStdout(cmd *cobra.Command, src string, recursive bool) error {
227235
}
228236
}
229237

230-
return withJSONErrorDetails(downloadToStdout(dbx, src, cmd.OutOrStdout()), operationErrorDetails("download"), pathErrorDetails(src))
238+
fileMeta, _ := meta.(*files.FileMetadata)
239+
return withJSONErrorDetails(downloadToStdoutWithMetadata(dbx, src, fileMeta, cmd.OutOrStdout()), operationErrorDetails("download"), pathErrorDetails(src))
231240
}
232241

233242
func getRecursive(dbx filesClient, src, dst string) error {
@@ -321,16 +330,22 @@ func getRecursiveInternal(dbx filesClient, src, dst string, rootMeta files.IsMet
321330
}
322331
fmt.Fprintf(getErrorOutput(opts), "Downloading %s -> %s\n", f.PathDisplay, localPath)
323332
if collectResults {
324-
result, err := downloadFileWithResult(dbx, f.PathDisplay, localPath, opts)
333+
result, err := downloadFileWithResult(dbx, f.PathDisplay, localPath, f, false, opts)
325334
if err != nil {
326-
downloadErrors = append(downloadErrors, fmt.Errorf("%s: %w", f.PathDisplay, err))
335+
downloadErrors = append(
336+
downloadErrors,
337+
fmt.Errorf("%s: %w", f.PathDisplay, err),
338+
)
327339
continue
328340
}
329341
results = append(results, result)
330342
continue
331343
}
332-
if err := downloadFile(dbx, f.PathDisplay, localPath); err != nil {
333-
downloadErrors = append(downloadErrors, fmt.Errorf("%s: %w", f.PathDisplay, err))
344+
if _, _, err := downloadFileWithMetadata(dbx, f.PathDisplay, localPath, f, false, getErrorOutput(opts)); err != nil {
345+
downloadErrors = append(
346+
downloadErrors,
347+
fmt.Errorf("%s: %w", f.PathDisplay, err),
348+
)
334349
}
335350
}
336351
}
@@ -374,34 +389,54 @@ func relativeTo(base, full string) (string, error) {
374389
}
375390

376391
func downloadFile(dbx filesClient, src string, dst string) error {
377-
_, err := downloadFileWithMetadata(dbx, src, dst, os.Stderr)
392+
_, _, err := downloadFileWithMetadata(dbx, src, dst, nil, false, os.Stderr)
378393
return err
379394
}
380395

381-
func downloadFileWithResult(dbx filesClient, src string, dst string, opts getOptions) (getResult, error) {
382-
metadata, err := downloadFileWithMetadata(dbx, src, dst, getErrorOutput(opts))
396+
func downloadFileWithResult(
397+
dbx filesClient,
398+
src string,
399+
dst string,
400+
metadata *files.FileMetadata,
401+
dstExplicit bool,
402+
opts getOptions,
403+
) (getResult, error) {
404+
metadata, actualDst, err := downloadFileWithMetadata(dbx, src, dst, metadata, dstExplicit, getErrorOutput(opts))
383405
if err != nil {
384406
return getResult{}, err
385407
}
386-
return newGetResult(getStatusDownloaded, getKindFile, src, dst, metadata)
408+
return newGetResult(getStatusDownloaded, getKindFile, src, actualDst, metadata)
387409
}
388410

389-
func downloadFileWithMetadata(dbx filesClient, src string, dst string, errOut io.Writer) (*files.FileMetadata, error) {
390-
arg := files.NewDownloadArg(src)
391-
var metadata *files.FileMetadata
411+
func downloadFileWithMetadata(
412+
dbx filesClient,
413+
src string,
414+
dst string,
415+
metadata *files.FileMetadata,
416+
dstExplicit bool,
417+
errOut io.Writer,
418+
) (*files.FileMetadata, string, error) {
419+
var result *files.FileMetadata
420+
actualDst := dst
392421

393422
err := retryWithBackoff(func() error {
394423
var err error
395-
metadata, err = downloadFileOnce(dbx, arg, dst, errOut)
424+
if isExportOnlyFile(metadata) {
425+
result, actualDst, err = exportFileToPath(dbx, src, dst, dstExplicit)
426+
} else {
427+
arg := files.NewDownloadArg(src)
428+
result, err = downloadFileOnce(dbx, arg, dst, errOut)
429+
}
396430
return err
397431
})
398-
return metadata, err
432+
433+
return result, actualDst, err
399434
}
400435

401436
func createDownloadTemp(dst string) (*os.File, string, error) {
402437
dir := filepath.Dir(dst)
403438
base := filepath.Base(dst)
404-
for i := 0; i < 100; i++ {
439+
for i := range 100 {
405440
tmp := filepath.Join(dir, fmt.Sprintf(".%s.tmp-%d-%d", base, os.Getpid(), time.Now().UnixNano()+int64(i)))
406441
f, err := os.OpenFile(tmp, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
407442
if errors.Is(err, os.ErrExist) {
@@ -413,7 +448,7 @@ func createDownloadTemp(dst string) (*os.File, string, error) {
413448
}
414449

415450
func downloadDestinationPath(dst string) (string, error) {
416-
for i := 0; i < 255; i++ {
451+
for range 255 {
417452
info, err := os.Lstat(dst)
418453
if err != nil {
419454
if os.IsNotExist(err) {
@@ -485,13 +520,75 @@ func downloadFileOnce(dbx filesClient, arg *files.DownloadArg, dst string, errOu
485520
return res, nil
486521
}
487522

523+
func exportFile(
524+
dbx filesClient,
525+
src string,
526+
) (*files.ExportResult, io.ReadCloser, error) {
527+
return dbx.ExportContext(
528+
currentContext(),
529+
files.NewExportArg(src),
530+
)
531+
}
532+
533+
func exportFileToPath(dbx filesClient, src string, dst string, dstExplicit bool) (*files.FileMetadata, string, error) {
534+
res, contents, err := exportFile(dbx, src)
535+
if err != nil {
536+
return nil, "", err
537+
}
538+
defer func() { _ = contents.Close() }()
539+
540+
if !dstExplicit {
541+
dst = filepath.Join(filepath.Dir(dst), res.ExportMetadata.Name)
542+
}
543+
544+
finalDst, err := downloadDestinationPath(dst)
545+
if err != nil {
546+
return nil, "", err
547+
}
548+
549+
f, tmp, err := createDownloadTemp(finalDst)
550+
if err != nil {
551+
return nil, "", err
552+
}
553+
554+
removeTemp := true
555+
defer func() {
556+
if removeTemp {
557+
_ = os.Remove(tmp)
558+
}
559+
}()
560+
561+
_, copyErr := io.Copy(f, contents)
562+
closeErr := f.Close()
563+
564+
if copyErr != nil {
565+
return nil, "", copyErr
566+
}
567+
if closeErr != nil {
568+
return nil, "", closeErr
569+
}
570+
571+
if err := os.Rename(tmp, finalDst); err != nil {
572+
return nil, "", err
573+
}
574+
575+
removeTemp = false
576+
return res.FileMetadata, dst, nil
577+
}
578+
488579
func downloadMetadataSize(metadata *files.FileMetadata) int64 {
489580
if metadata == nil {
490581
return 0
491582
}
492583
return int64(metadata.Size)
493584
}
494585

586+
func isExportOnlyFile(metadata *files.FileMetadata) bool {
587+
return metadata != nil &&
588+
metadata.ExportInfo != nil &&
589+
metadata.ExportInfo.ExportAs != ""
590+
}
591+
495592
// getCmd represents the get command
496593
var getCmd = &cobra.Command{
497594
Use: "get [flags] <source> [<target>]",

0 commit comments

Comments
 (0)