Skip to content

Instantly share code, notes, and snippets.

@xor-gate
Forked from marconi/gofetch.go
Created February 12, 2016 11:07
Show Gist options
  • Save xor-gate/c957dd6aec33650968e6 to your computer and use it in GitHub Desktop.
Save xor-gate/c957dd6aec33650968e6 to your computer and use it in GitHub Desktop.
Downloads file by splitting it into multiple download workers making download faster.
package main
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"math"
"net/http"
"os"
"strconv"
)
var f_url string
var f_workers int
var f_name string
func init() {
flag.StringVar(&f_url, "url", "", "URL of the file to download")
flag.StringVar(&f_name, "filename", "", "Name of downloaded file")
flag.IntVar(&f_workers, "workers", 2, "Number of download workers")
}
type Downloader struct {
url string
headers map[string]string
workers int
filename string
}
func (d *Downloader) GetHeaders() (map[string]string, error) {
resp, err := http.Head(d.url)
if err != nil {
return d.headers, err
}
if resp.StatusCode != 200 {
return d.headers, errors.New(resp.Status)
}
for key, val := range resp.Header {
d.headers[key] = val[0]
}
return d.headers, err
}
func (d *Downloader) DownloadChunk(url string, out string, start int, stop int, c chan<- string) {
client := new(http.Client)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", start, stop))
resp, _ := client.Do(req)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
return
}
file, err := os.OpenFile(out, os.O_WRONLY, 0600)
if err != nil {
if file, err = os.Create(out); err != nil {
log.Fatalln(err)
return
}
}
defer file.Close()
if _, err := file.WriteAt(body, int64(start)); err != nil {
log.Fatalln(err)
return
}
c <- fmt.Sprintf("Range %d-%d: %d", start, stop, resp.ContentLength)
}
func (d *Downloader) Download() {
length, _ := strconv.Atoi(d.headers["Content-Length"])
bytes_chunk := int(math.Ceil(float64(length) / float64(d.workers)))
fmt.Println("bytes chunk: ", bytes_chunk)
fmt.Println("file length: ", length)
if length == 0 {
return
}
c := make(chan string)
for i := 0; i < d.workers; i++ {
start := i * bytes_chunk
stop := start + (bytes_chunk - 1)
go d.DownloadChunk(d.url, d.filename, start, stop, c)
}
for i := 0; i < d.workers; i++ {
fmt.Println(<-c)
}
fmt.Println("\nDownload complete! press <enter> to quit.")
}
func NewDownloader() *Downloader {
d := Downloader{
url: f_url,
workers: f_workers,
filename: f_name,
headers: make(map[string]string),
}
return &d
}
func main() {
flag.Parse()
d := NewDownloader()
_, err := d.GetHeaders()
if err != nil {
fmt.Println(err)
} else {
d.Download()
var input string
fmt.Scanln(&input)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment