@@ -10,9 +10,12 @@ import (
1010 "regexp"
1111 "strconv"
1212 "strings"
13+ "time"
1314
15+ drycc "github.com/drycc/controller-sdk-go"
1416 "github.com/drycc/controller-sdk-go/api"
1517 "github.com/drycc/controller-sdk-go/volumes"
18+ "github.com/schollz/progressbar/v3"
1619 "sigs.k8s.io/yaml"
1720)
1821
@@ -251,7 +254,7 @@ func (d *DryccCmd) volumesClientLs(appID, vol string) error {
251254 }
252255 dirs , _ , err := volumes .ListDir (s .Client , appID , name , path , 3000 )
253256 if err != nil {
254- return err
257+ return fmt . Errorf ( "ls: cannot access '%s': No such file or directory" , path )
255258 }
256259
257260 table := d .getDefaultFormatTable ([]string {})
@@ -280,48 +283,102 @@ func (d *DryccCmd) volumesClientLs(appID, vol string) error {
280283 return nil
281284}
282285
283- // volumesClientCp copy files between volume and local file
284- func (d * DryccCmd ) volumesClientCp (appID , src , dst string ) error {
285- s , appID , err := load (d .ConfigFile , appID )
286+ func (d * DryccCmd ) volumesClientGetAll (client * drycc.Client , appID , volumeID , volumePath , localPath string ) error {
287+ dirs , _ , err := volumes .ListDir (client , appID , volumeID , volumePath , 3000 )
286288 if err != nil {
287289 return err
288290 }
289- if strings .HasPrefix (src , "vol://" ) {
290- name , urlpath , err := parseVol (src )
291+ for _ , dir := range dirs {
292+ _ , subpath := path .Split (dir .Path )
293+ filepath := path .Join (localPath , subpath )
294+ if dir .Type == "file" {
295+ res , err := volumes .GetFile (client , appID , volumeID , dir .Path )
296+ if err != nil {
297+ return err
298+ }
299+ w , err := os .OpenFile (filepath , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , 0644 )
300+ if err != nil {
301+ return err
302+ }
303+ bar := d .newProgressbar (res .ContentLength , "↓" , filepath )
304+ defer w .Close ()
305+ if _ , err = io .Copy (io .MultiWriter (w , bar ), res .Body ); err != nil {
306+ return err
307+ }
308+ } else {
309+ os .MkdirAll (filepath , os .ModePerm )
310+ if err := d .volumesClientGetAll (client , appID , volumeID , dir .Path , filepath ); err != nil {
311+ return err
312+ }
313+ }
314+ }
315+ return nil
316+ }
317+
318+ func (d * DryccCmd ) volumesClientPostAll (client * drycc.Client , appID , volumeID , volumePath string , localPath string ) error {
319+ if file , err := os .Stat (localPath ); err != nil {
320+ return err
321+ } else if ! file .IsDir () {
322+ file , err := os .Open (localPath )
291323 if err != nil {
292324 return err
293325 }
294- if urlpath == "" || urlpath == "/" {
295- return fmt .Errorf ("path is a directory, not a file" )
296- }
297- res , err := volumes .GetFile (s .Client , appID , name , urlpath )
326+ defer file .Close ()
327+
328+ stat , err := file .Stat ()
298329 if err != nil {
299330 return err
300331 }
301-
302- if f , err := os .Stat (dst ); err == nil {
303- if f .IsDir () {
304- arrays := strings .Split (urlpath , "/" )
305- dst = path .Join (dst , arrays [len (arrays )- 1 ])
332+ reader := progressbar .NewReader (file , d .newProgressbar (stat .Size (), "↑" , localPath ))
333+ if _ , err := volumes .PostFile (client , appID , volumeID , volumePath , file .Name (), & reader ); err != nil {
334+ return err
335+ }
336+ return nil
337+ }
338+ if entries , err := os .ReadDir (localPath ); err == nil {
339+ for _ , entry := range entries {
340+ var dstFilepath string
341+ if entry .IsDir () {
342+ dstFilepath = path .Join (volumePath , entry .Name ())
343+ } else {
344+ dstFilepath = volumePath
306345 }
346+ d .volumesClientPostAll (client , appID , volumeID , dstFilepath , path .Join (localPath , entry .Name ()))
307347 }
308- w , err := os .OpenFile (dst , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , 0644 )
348+ } else {
349+ return err
350+ }
351+ return nil
352+ }
353+
354+ // volumesClientCp copy files between volume and local file
355+ func (d * DryccCmd ) volumesClientCp (appID , src , dst string ) error {
356+ s , appID , err := load (d .ConfigFile , appID )
357+ if err != nil {
358+ return err
359+ }
360+ if strings .HasPrefix (src , "vol://" ) {
361+ f , err := os .Stat (dst )
309362 if err != nil {
310363 return err
311364 }
312-
313- defer w .Close ()
314- if _ , err = io .Copy (w , res .Body ); err != nil {
365+ if ! f .IsDir () {
366+ return fmt .Errorf ("the local path must be an existing dir" )
367+ }
368+ volumeID , volumePath , err := parseVol (src )
369+ if err != nil {
315370 return err
316371 }
372+ return d .volumesClientGetAll (s .Client , appID , volumeID , volumePath , dst )
317373 } else if strings .HasPrefix (dst , "vol://" ) {
318- name , path , err := parseVol (dst )
374+ volumeID , volumePath , err := parseVol (dst )
319375 if err != nil {
320376 return err
321377 }
322- if _ , err := volumes .PostFile (s .Client , appID , name , path , src ); err != nil {
323- return err
378+ if dirs , _ , err := volumes .ListDir (s .Client , appID , volumeID , volumePath , 3000 ); err != nil && len ( dirs ) == 1 && dirs [ 0 ]. Type == "file" {
379+ return fmt . Errorf ( "the volume path cannot be an existing file" )
324380 }
381+ return d .volumesClientPostAll (s .Client , appID , volumeID , volumePath , src )
325382 }
326383 return nil
327384}
@@ -388,3 +445,32 @@ func printVolumes(d *DryccCmd, volumes api.Volumes) {
388445 }
389446 table .Render ()
390447}
448+
449+ func (d * DryccCmd ) newProgressbar (maxBytes int64 , icon , description string ) * progressbar.ProgressBar {
450+ description = fmt .Sprintf ("%-32s" , description )
451+ if len (description ) > 32 {
452+ description = fmt .Sprintf ("...%s" , description [len (description )- 29 :])
453+ }
454+ return progressbar .NewOptions64 (
455+ maxBytes ,
456+ progressbar .OptionSetDescription (description ),
457+ progressbar .OptionSetWriter (os .Stderr ),
458+ progressbar .OptionShowBytes (true ),
459+ progressbar .OptionEnableColorCodes (true ),
460+ progressbar .OptionSetWidth (10 ),
461+ progressbar .OptionThrottle (65 * time .Millisecond ),
462+ progressbar .OptionShowCount (),
463+ progressbar .OptionOnCompletion (func () { fmt .Fprint (os .Stderr , "\n " ) }),
464+ progressbar .OptionSpinnerType (14 ),
465+ progressbar .OptionFullWidth (),
466+ progressbar .OptionSetRenderBlankState (true ),
467+ progressbar .OptionSetDescription (fmt .Sprintf ("[cyan][%s][reset] %s" , icon , description )),
468+ progressbar .OptionSetTheme (progressbar.Theme {
469+ Saucer : "[green]=[reset]" ,
470+ SaucerHead : "[green]>[reset]" ,
471+ SaucerPadding : " " ,
472+ BarStart : "[" ,
473+ BarEnd : "]" ,
474+ }),
475+ )
476+ }
0 commit comments