@@ -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
233242func 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
376391func 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
401436func 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
415450func 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+
488579func 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
496593var getCmd = & cobra.Command {
497594 Use : "get [flags] <source> [<target>]" ,
0 commit comments