Sin Descripción

archive.go 5.0KB

    package main import ( "bytes" "crypto/rand" "encoding/json" "flag" "fmt" "io" "net/url" "os" "os/exec" "path" "path/filepath" ) var flags struct { open bool archiveDir string } type Archive struct { Mappings map[string]string `json:"mappings"` } func init() { flag.BoolVar(&flags.open, "open", false, "Open the archived page") wd, err := os.Getwd() if err != nil { exit("os.Getwd", err) } flags.archiveDir = path.Join(wd, ".archive") } func main() { flag.Parse() u, err := url.Parse(flag.Arg(0)) if err != nil { exit("url.Parse", err) } f, err := os.Open("archive.json") if err != nil { exit("os.Open", err) } var archive Archive dec := json.NewDecoder(f) err = dec.Decode(&archive) if err != nil { exit("dec.Decode", err) } f.Close() p, ok := archive.Mappings[u.String()] if ok { fmt.Println("==> Archived at", p) if flags.open { fmt.Println("==> Opening archive of", u) open(u, p) } return } if u.Scheme != "http" && u.Scheme != "https" { fmt.Fprintf(os.Stderr, "Unknown url scheme %q\n", u.Scheme) os.Exit(1) } fmt.Println("==> Archiving", u) var archiver string var archiveFunc func(string, *url.URL) (string, error) switch { case u.Host == "youtube.com" || u.Host == "www.youtube.com" || u.Host == "youtu.be" || u.Host == "vimeo.com": archiver, archiveFunc = "youtube-dl", archiveWithYoutubeDL default: archiver, archiveFunc = "prince", archiveWithPrince } resultPath, err := archiveFunc(flags.archiveDir, u) if err != nil { exit(archiver, err) } if archive.Mappings == nil { archive.Mappings = make(map[string]string, 1) } p = fmt.Sprintf("file://%s", resultPath) archive.Mappings[u.String()] = p f, err = os.OpenFile("archive.json", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660) if err != nil { exit("os.OpenFile", err) } enc := json.NewEncoder(f) err = enc.Encode(&archive) if err != nil { exit("enc.Encode", err) } f.Close() fmt.Println("==> Archived at", p) if flags.open { fmt.Println("==> Opening archive of", u) open(u, p) } } func archiveWithYoutubeDL(dir string, u *url.URL) (resultPath string, err error) { cmd := exec.Command("youtube-dl", "--get-filename", u.String(), "--output", "%(url)s.%(ext)s") cmd.Dir = dir cmd.Stderr = prefixWriter(" | ", os.Stderr) buf := new(bytes.Buffer) cmd.Stdout = buf fmt.Print(" ", cmd.Args[0]) for _, arg := range cmd.Args[1:] { fmt.Print(" ", arg) } fmt.Println() err = cmd.Run() if err != nil { return "", err } p := path.Join(dir, buf.String()) cmd = exec.Command("youtube-dl", u.String(), "--output", "%(url)s.%(ext)s") cmd.Dir = dir cmd.Stderr = prefixWriter(" | ", os.Stderr) cmd.Stdout = prefixWriter(" | ", os.Stdout) fmt.Print(" ", cmd.Args[0]) for _, arg := range cmd.Args[1:] { fmt.Print(" ", arg) } fmt.Println() err = cmd.Run() if err != nil { return "", err } return p, nil } func archiveWithPrince(dir string, u *url.URL) (resultPath string, err error) { buf := make([]byte, 16) _, err = rand.Read(buf) if err != nil { exit("rand.Read", err) } cmd := exec.Command("prince", "--javascript", "--raster-output", fmt.Sprintf("%x-%%02d.png", buf), u.String()) cmd.Dir = dir cmd.Stderr = prefixWriter(" | ", os.Stderr) cmd.Stdout = prefixWriter(" | ", os.Stdout) fmt.Print(" ", cmd.Args[0]) for _, arg := range cmd.Args[1:] { fmt.Print(" ", arg) } fmt.Println() err = cmd.Run() if err != nil { return "", err } parts, err := filepath.Glob(path.Join(dir, fmt.Sprintf("%x-*.png", buf))) if err != nil { return "", fmt.Errorf("filepath.Glob: %s", err) } if len(parts) == 0 { return "", fmt.Errorf("filepath.Glob: no matches") } h := path.Join(dir, fmt.Sprintf("%x.html", buf)) f, err := os.OpenFile(h, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0660) if err != nil { return "", fmt.Errorf("os.OpenFile: %s", err) } fmt.Fprintf(f, `<doctype html> <html> <head> <title>%s</title> </head> <body> `, u) for _, p := range parts { fmt.Fprintf(f, "<img src=%q />\n", p) } fmt.Fprintf(f, "\n\t</body>\n</html>") f.Close() return h, nil } func open(u *url.URL, path string) { cmd := exec.Command("xdg-open", path) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout err := cmd.Start() if err != nil { exit("cmd.Start", err) } } func exit(msg string, err error) { fmt.Fprintf(os.Stderr, "Error: %s: %s\n", msg, err) os.Exit(1) } type prefixLineWriter struct { prefix string needsPrefix bool w io.Writer } func prefixWriter(prefix string, w io.Writer) io.Writer { return &prefixLineWriter{ prefix: prefix, needsPrefix: true, w: w, } } func (p *prefixLineWriter) Write(b []byte) (n int, err error) { if p.needsPrefix { p.w.Write([]byte(p.prefix)) p.needsPrefix = false } n = 0 for { i := bytes.IndexByte(b, '\n') if i == -1 { nn, err := p.w.Write(b) return n + nn, err } if i+1 == len(b) { p.needsPrefix = true nn, err := p.w.Write(b) return n + nn, err } nn, err := p.w.Write(b[:i+1]) if err != nil { return n + nn, err } n += nn p.w.Write([]byte(p.prefix)) b = b[i+1:] } }