// // rhimportd // // The Radio Helsinki Rivendell Import Daemon // // // Copyright (C) 2015 Christian Pointner // // This file is part of rhimportd. // // rhimportd is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // rhimportd is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with rhimportd. If not, see . // package rhimport import ( "fmt" "github.com/golang-basic/go-curl" "io/ioutil" "mime" "net/url" "os" "path" "strings" "time" ) type CurlCBData struct { basepath string filename string remotename string *os.File } func (self *CurlCBData) Cleanup() { if self.File != nil { self.File.Close() } } func curlHeaderCallback(ptr []byte, userdata interface{}) bool { hdr := fmt.Sprintf("%s", ptr) data := userdata.(*CurlCBData) if strings.HasPrefix(hdr, "Content-Disposition:") { if mediatype, params, err := mime.ParseMediaType(strings.TrimPrefix(hdr, "Content-Disposition:")); err == nil { if mediatype == "attachment" { data.filename = data.basepath + "/" + params["filename"] } } } return true } func curlWriteCallback(ptr []byte, userdata interface{}) bool { data := userdata.(*CurlCBData) if data.File == nil { if data.filename == "" { data.filename = data.basepath + "/" + data.remotename } fp, err := os.OpenFile(data.filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) if err != nil { rhl.Printf("Unable to create file %s: %s", data.filename, err) return false } data.File = fp } if _, err := data.File.Write(ptr); err != nil { rhl.Printf("Unable to write file %s: %s", data.filename, err) return false } return true } func FetchFileCurl(ctx *ImportContext, uri *url.URL) (err error) { rhl.Printf("curl-based fetcher called for '%s'", ctx.SourceUri) easy := curl.EasyInit() defer easy.Cleanup() if easy != nil { easy.Setopt(curl.OPT_FOLLOWLOCATION, true) easy.Setopt(curl.OPT_URL, ctx.SourceUri) cbdata := &CurlCBData{remotename: path.Base(uri.Path)} defer cbdata.Cleanup() if cbdata.basepath, err = ioutil.TempDir(ctx.Config.TempDir, "rhimportd-"); err != nil { return } easy.Setopt(curl.OPT_HEADERFUNCTION, curlHeaderCallback) easy.Setopt(curl.OPT_HEADERDATA, cbdata) easy.Setopt(curl.OPT_WRITEFUNCTION, curlWriteCallback) easy.Setopt(curl.OPT_WRITEDATA, cbdata) easy.Setopt(curl.OPT_NOPROGRESS, false) started := int64(0) easy.Setopt(curl.OPT_PROGRESSFUNCTION, func(dltotal, dlnow, ultotal, ulnow float64, userdata interface{}) bool { if started == 0 { started = time.Now().Unix() } fmt.Printf("Downloaded: %3.2f%%, Speed: %.1fKiB/s \r", dlnow/dltotal*100, dlnow/1000/float64((time.Now().Unix()-started))) return true }) easy.Setopt(curl.OPT_PROGRESSDATA, ctx) if err = easy.Perform(); err != nil { return } fmt.Printf("\n") ctx.SourceFile = cbdata.filename ctx.DeleteSourceFile = true ctx.DeleteSourceDir = true } return } func FetchFileLocal(ctx *ImportContext, uri *url.URL) (err error) { rhl.Printf("Local fetcher called for '%s'", ctx.SourceUri) ctx.SourceFile = uri.Path ctx.DeleteSourceFile = false ctx.DeleteSourceDir = false return } type FetchFunc func(*ImportContext, *url.URL) (err error) var ( fetchers = map[string]FetchFunc{ "local": FetchFileLocal, } curl_protos = map[string]bool{ "http": false, "https": false, "ftp": false, "ftps": false, } ) func init() { curl.GlobalInit(curl.GLOBAL_ALL) info := curl.VersionInfo(curl.VERSION_FIRST) protos := info.Protocols for _, proto := range protos { if _, ok := curl_protos[proto]; ok { rhdl.Printf("curl: enabling protocol %s", proto) fetchers[proto] = FetchFileCurl curl_protos[proto] = true } else { rhdl.Printf("curl: ignoring protocol %s", proto) } } for proto, enabled := range curl_protos { if !enabled { rhl.Printf("curl: protocol %s is disabled because the installed library version doesn't support it!", proto) } } } func FetchFile(ctx *ImportContext) (err error) { var uri *url.URL if uri, err = url.Parse(ctx.SourceUri); err != nil { return } if fetcher, ok := fetchers[uri.Scheme]; ok { err = fetcher(ctx, uri) } else { err = fmt.Errorf("No fetcher for uri scheme '%s' found.", uri.Scheme) } return }