Blame


1 f10bc8eb 2024-02-07 aa // Copyright (c) 2024 Alexander Arkhipov <aa@manpager.org>
2 f10bc8eb 2024-02-07 aa //
3 f10bc8eb 2024-02-07 aa // Permission to use, copy, modify, and distribute this software for any
4 f10bc8eb 2024-02-07 aa // purpose with or without fee is hereby granted, provided that the above
5 f10bc8eb 2024-02-07 aa // copyright notice and this permission notice appear in all copies.
6 f10bc8eb 2024-02-07 aa //
7 f10bc8eb 2024-02-07 aa // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 f10bc8eb 2024-02-07 aa // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 f10bc8eb 2024-02-07 aa // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 f10bc8eb 2024-02-07 aa // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 f10bc8eb 2024-02-07 aa // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 f10bc8eb 2024-02-07 aa // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 f10bc8eb 2024-02-07 aa // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 f10bc8eb 2024-02-07 aa
15 f10bc8eb 2024-02-07 aa package main
16 f10bc8eb 2024-02-07 aa
17 f10bc8eb 2024-02-07 aa import (
18 f10bc8eb 2024-02-07 aa "bufio"
19 f10bc8eb 2024-02-07 aa "errors"
20 f10bc8eb 2024-02-07 aa "flag"
21 f10bc8eb 2024-02-07 aa "fmt"
22 f10bc8eb 2024-02-07 aa "io"
23 f10bc8eb 2024-02-07 aa "io/fs"
24 f10bc8eb 2024-02-07 aa "net/http"
25 f10bc8eb 2024-02-07 aa "os"
26 f10bc8eb 2024-02-07 aa "strings"
27 f10bc8eb 2024-02-07 aa )
28 f10bc8eb 2024-02-07 aa
29 f10bc8eb 2024-02-07 aa var qflag = flag.Bool("q", false, "be quiet")
30 f10bc8eb 2024-02-07 aa var pflag = flag.Int("p", 1, "number of parallel downloads")
31 f10bc8eb 2024-02-07 aa
32 f10bc8eb 2024-02-07 aa type filename struct {
33 f10bc8eb 2024-02-07 aa n int // how many times the url has been accessed
34 f10bc8eb 2024-02-07 aa name string // end-file name
35 f10bc8eb 2024-02-07 aa tmpfiles []string // temporary file names
36 f10bc8eb 2024-02-07 aa }
37 f10bc8eb 2024-02-07 aa
38 f10bc8eb 2024-02-07 aa // filemap maps URLs to corresponding filenames
39 f10bc8eb 2024-02-07 aa var filemap = make(map[string]filename)
40 f10bc8eb 2024-02-07 aa
41 f10bc8eb 2024-02-07 aa func getUrl(url, f string, ch chan int) {
42 f10bc8eb 2024-02-07 aa defer func() { ch <- 0 }()
43 f10bc8eb 2024-02-07 aa
44 f10bc8eb 2024-02-07 aa rm := func() {
45 f10bc8eb 2024-02-07 aa os.Remove(f)
46 f10bc8eb 2024-02-07 aa }
47 f10bc8eb 2024-02-07 aa
48 f10bc8eb 2024-02-07 aa if !*qflag {
49 f10bc8eb 2024-02-07 aa fmt.Println("GET", url)
50 f10bc8eb 2024-02-07 aa }
51 f10bc8eb 2024-02-07 aa
52 f10bc8eb 2024-02-07 aa fp, err := os.Create(f)
53 f10bc8eb 2024-02-07 aa if err != nil {
54 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, err)
55 f10bc8eb 2024-02-07 aa rm()
56 f10bc8eb 2024-02-07 aa return
57 f10bc8eb 2024-02-07 aa }
58 f10bc8eb 2024-02-07 aa defer fp.Close()
59 f10bc8eb 2024-02-07 aa fmt.Println("created", fp.Name())
60 f10bc8eb 2024-02-07 aa
61 f10bc8eb 2024-02-07 aa resp, err := http.Get(url)
62 f10bc8eb 2024-02-07 aa if err != nil {
63 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, err)
64 f10bc8eb 2024-02-07 aa rm()
65 f10bc8eb 2024-02-07 aa return
66 f10bc8eb 2024-02-07 aa }
67 f10bc8eb 2024-02-07 aa
68 f10bc8eb 2024-02-07 aa buf := make([]byte, 4096)
69 f10bc8eb 2024-02-07 aa reader := bufio.NewReader(resp.Body)
70 f10bc8eb 2024-02-07 aa writer := bufio.NewWriter(fp)
71 f10bc8eb 2024-02-07 aa
72 f10bc8eb 2024-02-07 aa for readErr := error(nil); readErr == nil; {
73 f10bc8eb 2024-02-07 aa n, readErr := io.ReadFull(reader, buf)
74 f10bc8eb 2024-02-07 aa if readErr == io.EOF {
75 f10bc8eb 2024-02-07 aa break
76 f10bc8eb 2024-02-07 aa }
77 f10bc8eb 2024-02-07 aa if readErr != nil && readErr != io.ErrUnexpectedEOF {
78 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, readErr)
79 f10bc8eb 2024-02-07 aa rm()
80 f10bc8eb 2024-02-07 aa break
81 f10bc8eb 2024-02-07 aa }
82 f10bc8eb 2024-02-07 aa
83 f10bc8eb 2024-02-07 aa _, writeErr := writer.Write(buf[:n])
84 f10bc8eb 2024-02-07 aa if writeErr != nil {
85 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, writeErr)
86 f10bc8eb 2024-02-07 aa rm()
87 f10bc8eb 2024-02-07 aa break
88 f10bc8eb 2024-02-07 aa }
89 f10bc8eb 2024-02-07 aa }
90 f10bc8eb 2024-02-07 aa writer.Flush()
91 f10bc8eb 2024-02-07 aa }
92 f10bc8eb 2024-02-07 aa
93 f10bc8eb 2024-02-07 aa func prepUrl(url, d string) (string, error) {
94 f10bc8eb 2024-02-07 aa if !(strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) {
95 f10bc8eb 2024-02-07 aa url = "http://" + url
96 f10bc8eb 2024-02-07 aa }
97 f10bc8eb 2024-02-07 aa
98 f10bc8eb 2024-02-07 aa fmentry := filemap[url]
99 f10bc8eb 2024-02-07 aa defer func() { filemap[url] = fmentry }()
100 f10bc8eb 2024-02-07 aa
101 f10bc8eb 2024-02-07 aa var fname string
102 f10bc8eb 2024-02-07 aa
103 f10bc8eb 2024-02-07 aa _, fname, _ = strings.Cut(url, "://")
104 f10bc8eb 2024-02-07 aa _, fname, _ = strings.Cut(fname, "/")
105 f10bc8eb 2024-02-07 aa parts := strings.Split(fname, "/")
106 f10bc8eb 2024-02-07 aa fname = parts[len(parts)-1]
107 f10bc8eb 2024-02-07 aa if fname == "" {
108 f10bc8eb 2024-02-07 aa fname = "index.html"
109 f10bc8eb 2024-02-07 aa }
110 f10bc8eb 2024-02-07 aa
111 f10bc8eb 2024-02-07 aa tmpfp, err := os.CreateTemp(d, fname+"*")
112 f10bc8eb 2024-02-07 aa if err != nil {
113 f10bc8eb 2024-02-07 aa return "", err
114 f10bc8eb 2024-02-07 aa }
115 f10bc8eb 2024-02-07 aa defer tmpfp.Close()
116 f10bc8eb 2024-02-07 aa
117 f10bc8eb 2024-02-07 aa fmentry.name = fname
118 f10bc8eb 2024-02-07 aa fmentry.tmpfiles = append(fmentry.tmpfiles, tmpfp.Name())
119 f10bc8eb 2024-02-07 aa
120 f10bc8eb 2024-02-07 aa return url, nil
121 f10bc8eb 2024-02-07 aa }
122 f10bc8eb 2024-02-07 aa
123 f10bc8eb 2024-02-07 aa func main() {
124 f10bc8eb 2024-02-07 aa flag.Parse()
125 f10bc8eb 2024-02-07 aa
126 f10bc8eb 2024-02-07 aa if *pflag < 1 {
127 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, "can't do less than 1 parallel downloads")
128 f10bc8eb 2024-02-07 aa os.Exit(1)
129 f10bc8eb 2024-02-07 aa }
130 f10bc8eb 2024-02-07 aa
131 f10bc8eb 2024-02-07 aa var urls []string
132 f10bc8eb 2024-02-07 aa
133 f10bc8eb 2024-02-07 aa tmpdir, err := os.MkdirTemp(".", ".goget*")
134 f10bc8eb 2024-02-07 aa if err != nil {
135 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, err)
136 f10bc8eb 2024-02-07 aa os.Exit(1)
137 f10bc8eb 2024-02-07 aa }
138 f10bc8eb 2024-02-07 aa defer func() {
139 f10bc8eb 2024-02-07 aa rename := func(url string) {
140 f10bc8eb 2024-02-07 aa fentry := filemap[url]
141 f10bc8eb 2024-02-07 aa defer func() {
142 f10bc8eb 2024-02-07 aa fentry.tmpfiles = fentry.tmpfiles[1:]
143 f10bc8eb 2024-02-07 aa filemap[url] = fentry
144 f10bc8eb 2024-02-07 aa }()
145 f10bc8eb 2024-02-07 aa os.Rename(fentry.tmpfiles[0], fentry.name)
146 f10bc8eb 2024-02-07 aa // Ignoring ErrNotExist since the temporary file might
147 f10bc8eb 2024-02-07 aa // have been removed on purpose.
148 f10bc8eb 2024-02-07 aa //
149 f10bc8eb 2024-02-07 aa // TODO It would be better to have such purposeful
150 f10bc8eb 2024-02-07 aa // removals marked explicitly.
151 f10bc8eb 2024-02-07 aa if err != nil && !errors.Is(err, fs.ErrNotExist) {
152 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, err)
153 f10bc8eb 2024-02-07 aa }
154 f10bc8eb 2024-02-07 aa }
155 f10bc8eb 2024-02-07 aa
156 f10bc8eb 2024-02-07 aa for _, url := range urls {
157 f10bc8eb 2024-02-07 aa rename(url)
158 f10bc8eb 2024-02-07 aa }
159 f10bc8eb 2024-02-07 aa
160 f10bc8eb 2024-02-07 aa err := os.Remove(tmpdir)
161 f10bc8eb 2024-02-07 aa if err != nil {
162 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, err)
163 f10bc8eb 2024-02-07 aa os.Exit(1)
164 f10bc8eb 2024-02-07 aa }
165 f10bc8eb 2024-02-07 aa }()
166 f10bc8eb 2024-02-07 aa
167 f10bc8eb 2024-02-07 aa for _, arg := range flag.Args() {
168 f10bc8eb 2024-02-07 aa url, err := prepUrl(arg, tmpdir)
169 f10bc8eb 2024-02-07 aa if err != nil {
170 f10bc8eb 2024-02-07 aa fmt.Fprintln(os.Stderr, err)
171 f10bc8eb 2024-02-07 aa continue
172 f10bc8eb 2024-02-07 aa }
173 f10bc8eb 2024-02-07 aa urls = append(urls, url)
174 f10bc8eb 2024-02-07 aa }
175 f10bc8eb 2024-02-07 aa
176 f10bc8eb 2024-02-07 aa ch := make(chan int, *pflag)
177 f10bc8eb 2024-02-07 aa routines := 0
178 f10bc8eb 2024-02-07 aa for _, url := range urls {
179 f10bc8eb 2024-02-07 aa if routines >= *pflag {
180 f10bc8eb 2024-02-07 aa <-ch
181 f10bc8eb 2024-02-07 aa routines--
182 f10bc8eb 2024-02-07 aa }
183 f10bc8eb 2024-02-07 aa if fmentry, ok := filemap[url]; ok {
184 f10bc8eb 2024-02-07 aa go getUrl(url, fmentry.tmpfiles[fmentry.n], ch)
185 f10bc8eb 2024-02-07 aa fmentry.n++
186 f10bc8eb 2024-02-07 aa filemap[url] = fmentry
187 f10bc8eb 2024-02-07 aa routines++
188 f10bc8eb 2024-02-07 aa }
189 f10bc8eb 2024-02-07 aa }
190 f10bc8eb 2024-02-07 aa
191 f10bc8eb 2024-02-07 aa for routines > 0 {
192 f10bc8eb 2024-02-07 aa <-ch
193 f10bc8eb 2024-02-07 aa routines--
194 f10bc8eb 2024-02-07 aa }
195 f10bc8eb 2024-02-07 aa }