github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/core/httpclient/getfile.go (about) 1 package httpclient 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/url" 8 "os" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/lmorg/murex/builtins/pipes/file" 14 "github.com/lmorg/murex/lang" 15 "github.com/lmorg/murex/utils/ansi/codes" 16 "github.com/lmorg/murex/utils/humannumbers" 17 "github.com/lmorg/murex/utils/readline" 18 ) 19 20 func cmdGetFile(p *lang.Process) (err error) { 21 if p.Parameters.Len() == 0 { 22 return errors.New("URL required") 23 } 24 25 url, err := p.Parameters.String(0) 26 if err != nil { 27 return err 28 } 29 validateURL(&url, p.Config) 30 31 var body io.Reader 32 if p.IsMethod { 33 body = p.Stdin 34 } else { 35 body = nil 36 } 37 38 resp, err := Request(p.Context, "GET", url, body, p.Config, disableTimeout) 39 if err != nil { 40 return err 41 } 42 43 p.Stdout.SetDataType(lang.MimeToMurex(resp.Header.Get("Content-Type"))) 44 45 quit := make(chan bool) 46 cl := resp.Header.Get("Content-Length") 47 filename := extractFileName(url) 48 if p.Stdout.IsTTY() { 49 p.Stdout, err = file.NewFile(filename) 50 if err != nil { 51 return err 52 } 53 p.Stdout.Open() 54 } 55 56 var i int 57 if cl == "" { 58 cl = "{unknown}" 59 } else { 60 i, _ = strconv.Atoi(cl) 61 cl = humannumbers.Bytes(uint64(i)) 62 } 63 64 defer func() { 65 quit <- true 66 resp.Body.Close() 67 written, _ := p.Stdout.Stats() 68 69 os.Stderr.WriteString(fmt.Sprintf( 70 "%sDownloaded %s to %s\n", 71 "\x1b["+strconv.Itoa(readline.GetTermWidth()+2)+"D"+codes.ClearLine+codes.Reset, 72 humannumbers.Bytes(written), 73 filename, 74 )) 75 }() 76 77 go func() { 78 var last, written, speed uint64 79 select { 80 case <-quit: 81 return 82 default: 83 } 84 85 for { 86 if p.Stderr.IsTTY() { 87 time.Sleep(10 * time.Millisecond) 88 written, _ = p.Stdout.Stats() 89 speed = (written - last) * 100 90 } else { 91 time.Sleep(2 * time.Second) 92 written, _ = p.Stdout.Stats() 93 speed = (written - last) * 2000 94 } 95 last = written 96 97 select { 98 case <-quit: 99 return 100 default: 101 } 102 103 msg := fmt.Sprintf( 104 "Downloading... %s of %s @ %s/s....", 105 humannumbers.Bytes(written), 106 cl, 107 humannumbers.Bytes(speed), 108 ) 109 printGaugeBar(float64(written), float64(i), msg) 110 } 111 }() 112 113 _, err = io.Copy(p.Stdout, resp.Body) 114 return err 115 } 116 117 func extractFileName(address string) string { 118 u, err := url.Parse(address) 119 if err != nil { 120 return address 121 } 122 123 if len(u.Path) == 0 || u.Path == "/" { 124 return u.Host 125 } 126 127 split := strings.Split(u.Path, "/") 128 for i := len(split) - 1; i > -1; i-- { 129 if len(split[i]) != 0 && split[i] != "/" { 130 return split[i] 131 } 132 } 133 134 return u.Path 135 } 136 137 func printGaugeBar(value, max float64, message string) { 138 width := readline.GetTermWidth() 139 cells := int((float64(width) / max) * value) 140 141 s := "\x1b[" + strconv.Itoa(width+2) + "D" + codes.ClearLine + codes.Reset 142 if cells > 0 { 143 s += codes.Invert 144 } 145 146 for i := 0; i < width; i++ { 147 if cells+1 == i { 148 s += codes.Reset 149 } 150 151 if i < len(message) { 152 s += string([]byte{message[i]}) 153 } else { 154 s += " " 155 } 156 } 157 158 os.Stderr.WriteString(s + codes.Reset) 159 }