Passed
Push — master ( a2eed9...a44a1f )
by Abouzar
06:50
created

main.main   B

Complexity

Conditions 8

Size

Total Lines 60
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 40
nop 0
dl 0
loc 60
rs 7.0533
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
package main
2
3
import (
4
	"bufio"
5
	"flag"
6
	"io"
7
	"os"
8
	"os/signal"
9
	"runtime"
10
	"strings"
11
	"syscall"
12
13
	"github.com/imkira/go-task"
14
)
15
16
var displayProgress = true
17
18
func main() {
19
	// var err error
20
	var proxy, filePath, bwLimit, resumeTask string
21
22
	conn := flag.Int("n", runtime.NumCPU(), "number of connections")
23
	skiptls := flag.Bool("skip-tls", false, "skip certificate verification for https")
24
	flag.StringVar(&proxy, "proxy", "", "proxy for downloading, e.g. -proxy '127.0.0.1:12345' for socks5 or -proxy 'http://proxy.com:8080' for http proxy")
25
	flag.StringVar(&filePath, "file", "", "path to a file that contains one URL per line")
26
	flag.StringVar(&bwLimit, "rate", "", "bandwidth limit during download, e.g. -rate 10kB or -rate 10MiB")
27
	flag.StringVar(&resumeTask, "resume", "", "resume download task with given task name (or URL)")
28
	probe := flag.String("probe", "", "probe URL for range and content-length without downloading")
29
30
	flag.Parse()
31
	args := flag.Args()
32
33
	// Probe diagnostics mode
34
	if *probe != "" {
35
		DebugProbe(*probe, *skiptls, proxy)
36
		return
37
	}
38
39
	// If the resume flag is provided, use that path (ignoring other arguments)
40
	if resumeTask != "" {
41
		state, err := Resume(resumeTask)
42
		FatalCheck(err)
43
		Execute(state.URL, state, *conn, *skiptls, proxy, bwLimit)
44
		return
45
	}
46
47
	// If no resume flag, then check for positional URL or file input
48
	if len(args) < 1 {
49
		if len(filePath) < 1 {
50
			Errorln("A URL or input file with URLs is required")
51
			usage()
52
			os.Exit(1)
53
		}
54
		// Create a serial group for processing multiple URLs in a file.
55
		g1 := task.NewSerialGroup()
56
		file, err := os.Open(filePath)
57
		if err != nil {
58
			FatalCheck(err)
59
		}
60
		defer file.Close()
61
62
		reader := bufio.NewReader(file)
63
		for {
64
			line, _, err := reader.ReadLine()
65
			if err == io.EOF {
66
				break
67
			}
68
			url := strings.TrimSpace(string(line))
69
			if url == "" || strings.HasPrefix(url, "#") {
70
				continue
71
			}
72
			// Add the download task for each URL
73
			g1.AddChild(downloadTask(url, nil, *conn, *skiptls, proxy, bwLimit))
74
		}
75
		g1.Run(nil)
76
		return
77
	}
78
79
	// Otherwise, if a URL is provided as positional argument, treat it as a new download.
80
	downloadURL := args[0]
81
	// Check if a folder already exists for the task and remove if necessary.
82
	if ExistDir(FolderOf(downloadURL)) {
83
		Warnf("Downloading task already exists, remove it first \n")
84
		err := os.RemoveAll(FolderOf(downloadURL))
85
		FatalCheck(err)
86
	}
87
	Execute(downloadURL, nil, *conn, *skiptls, proxy, bwLimit)
88
}
89
90
func downloadTask(url string, state *State, conn int, skiptls bool, proxy string, bwLimit string) task.Task {
91
	run := func(t task.Task, ctx task.Context) {
92
		Execute(url, state, conn, skiptls, proxy, bwLimit)
93
	}
94
	return task.NewTaskWithFunc(run)
95
}
96
97
// Execute configures the HTTPDownloader and uses it to download the target.
98
func Execute(url string, state *State, conn int, skiptls bool, proxy string, bwLimit string) {
99
	// Capture OS interrupt signals
100
	signalChan := make(chan os.Signal, 1)
101
	signal.Notify(signalChan,
102
		syscall.SIGHUP,
103
		syscall.SIGINT,
104
		syscall.SIGTERM,
105
		syscall.SIGQUIT)
106
107
	var files = make([]string, 0)
108
	var parts = make([]Part, 0)
109
	var isInterrupted = false
110
111
	doneChan := make(chan bool, conn)
112
	fileChan := make(chan string, conn)
113
	errorChan := make(chan error, 1)
114
	stateChan := make(chan Part, 1)
115
	interruptChan := make(chan bool, conn)
116
117
	var downloader *HTTPDownloader
118
	if state == nil {
119
		downloader = NewHTTPDownloader(url, conn, skiptls, proxy, bwLimit)
120
	} else {
121
		downloader = &HTTPDownloader{
122
			url:       state.URL,
123
			file:      TaskFromURL(state.URL),
124
			par:       int64(len(state.Parts)),
125
			parts:     state.Parts,
126
			resumable: true,
127
		}
128
	}
129
	go downloader.Do(doneChan, fileChan, errorChan, interruptChan, stateChan)
130
131
	for {
132
		select {
133
		case <-signalChan:
134
			// Signal all active download routines to interrupt.
135
			isInterrupted = true
136
			for i := 0; i < conn; i++ {
137
				interruptChan <- true
138
			}
139
		case file := <-fileChan:
140
			files = append(files, file)
141
		case err := <-errorChan:
142
			Errorf("%v", err)
143
			panic(err)
144
		case part := <-stateChan:
145
			parts = append(parts, part)
146
		case <-doneChan:
147
			// Ensure we drain remaining part notifications before finalizing.
148
			numParts := len(downloader.parts)
149
			for len(files) < numParts {
150
				file := <-fileChan
151
				files = append(files, file)
152
			}
153
			for len(parts) < numParts {
154
				part := <-stateChan
155
				parts = append(parts, part)
156
			}
157
			if isInterrupted {
158
				if downloader.resumable {
159
					Printf("Interrupted, saving state...\n")
160
					s := &State{URL: url, Parts: parts}
161
					if err := s.Save(); err != nil {
162
						Errorf("%v\n", err)
163
					}
164
				} else {
165
					Warnf("Interrupted, but the download is not resumable. Exiting silently.\n")
166
				}
167
			} else {
168
				err := JoinFile(files, TaskFromURL(url))
169
				FatalCheck(err)
170
				err = os.RemoveAll(FolderOf(url))
171
				FatalCheck(err)
172
			}
173
			return
174
		}
175
	}
176
}
177
178
func usage() {
179
	Printf(`Usage:
180
 hget [options] URL
181
 hget [options] --resume=TaskName
182
183
 Options:
184
   -n int          number of connections (default number of CPUs)
185
   -skip-tls bool  skip certificate verification for https (default false)
186
   -proxy string   proxy address (e.g., '127.0.0.1:12345' for socks5 or 'http://proxy.com:8080')
187
   -file string    file path containing URLs (one per line)
188
   -rate string    bandwidth limit during download (e.g., 10kB, 10MiB)
189
   -resume string  resume a stopped download by providing its task name or URL
190
   -probe string   probe URL for range and content-length without downloading
191
 `)
192
}
193