// Communicates with rtorrent's XMLRPC interface, and can gather info regarding a .torrent file. package main import ( "bytes" "crypto/sha1" "encoding/hex" "errors" "io" "io/ioutil" "net/http" "os" "regexp" "strings" bencode "github.com/jackpal/bencode-go" ) // Torrent Keeps track of a torrent file's and rtorrent's XMLRPC information. type Torrent struct { path string hash string xmlUser string xmlPass string xmlAddress string } // NewTorrent Create a new Torrent instance while computing its hash. func NewTorrent(xmlUser string, xmlPass string, xmlAddress string, filePath string) (*Torrent, error) { hash, err := getTorrentHash(filePath) if err != nil { return nil, err } return &Torrent{filePath, hash, xmlUser, xmlPass, xmlAddress}, nil } // Compute the torrent hash for a given torrent file path returning an all caps sha1 hash as a string. func getTorrentHash(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { return "", err } defer file.Close() data, err := bencode.Decode(file) if err != nil { return "", err } decoded, ok := data.(map[string]interface{}) if !ok { return "", errors.New("unable to convert data to map") } var encoded bytes.Buffer bencode.Marshal(&encoded, decoded["info"]) encodedString := encoded.String() hash := sha1.New() io.WriteString(hash, encodedString) hashString := strings.ToUpper(hex.EncodeToString(hash.Sum(nil))) return hashString, nil } // Send a command and its argument to the rtorrent XMLRPC and get the response. func (t Torrent) xmlRPCSend (command string, arg string) (string, error) { // This is hacky XML to send to the server buf := []byte("\n" + "\n" + "" + command + "\n" + "\n" + "\n" + "" + arg + "\n" + "\n" + "\n" + "\n") buffer := bytes.NewBuffer(buf) request, err := http.NewRequest("POST", t.xmlAddress, buffer) if err != nil { return "", err } // Set the basic HTTP auth if we have a user or password if t.xmlUser != "" || t.xmlPass != "" { request.SetBasicAuth(t.xmlUser, t.xmlPass) } client := &http.Client{} resp, err := client.Do(request) if err != nil { return "", err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } re, err := regexp.Compile("<.*>(.*)") if err != nil { return "", err } values := re.FindAllStringSubmatch(string(body), -1) if len(values) != 1 { return "", nil } return values[0][1], nil } // GetTorrentName Get the torrent's name from rtorrent. func (t Torrent) GetTorrentName() (string, error) { return t.xmlRPCSend("d.get_name", t.hash) } // GetTorrentComplete Get the completion status of the torrent from rtorrent. func (t Torrent) GetTorrentComplete() (bool, error) { complete, err := t.xmlRPCSend("d.get_complete", t.hash) if err != nil { return false, err } return complete == "1", nil }