Skip to content

Instantly share code, notes, and snippets.

@gregtaole
Last active August 3, 2018 22:32
Show Gist options
  • Save gregtaole/c50d51086ce098e721a17cb8fbc38fde to your computer and use it in GitHub Desktop.
Save gregtaole/c50d51086ce098e721a17cb8fbc38fde to your computer and use it in GitHub Desktop.
package main
import (
"encoding/json"
"encoding/xml"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path"
"strings"
)
/*
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>Arch Linux: Releases</title>
<link>https://www.archlinux.org/download/</link>
<description>Release ISOs</description>
<atom:link href="https://www.archlinux.org/feeds/releases/" rel="self"></atom:link>
<language>en-us</language>
<lastBuildDate>Sun, 01 Jul 2018 05:03:32 +0000</lastBuildDate>
<item>
<title>2018.07.01</title>
<link>https://www.archlinux.org/releng/releases/2018.07.01/</link>
<description></description>
<pubDate>Sun, 01 Jul 2018 00:00:00 +0000</pubDate>
<guid isPermaLink="false">tag:www.archlinux.org,2018-07-01:/releng/releases/2018.07.01/</guid>
<enclosure url="https://www.archlinux.org/iso/2018.07.01/archlinux-2018.07.01-x86_64.iso.torrent" length="601882624" type="application/x-bittorrent"></enclosure>
</item>
<item>
<title>2018.06.01</title>
<link>https://www.archlinux.org/releng/releases/2018.06.01/</link>
<description></description>
<pubDate>Fri, 01 Jun 2018 00:00:00 +0000</pubDate>
<guid isPermaLink="false">tag:www.archlinux.org,2018-06-01:/releng/releases/2018.06.01/</guid>
<enclosure url="https://www.archlinux.org/iso/2018.06.01/archlinux-2018.06.01-x86_64.iso.torrent" length="598736896" type="application/x-bittorrent"></enclosure>
</item>
<item>
<title>2018.05.01</title>
<link>https://www.archlinux.org/releng/releases/2018.05.01/</link>
<description></description>
<pubDate>Tue, 01 May 2018 00:00:00 +0000</pubDate>
<guid isPermaLink="false">tag:www.archlinux.org,2018-05-01:/releng/releases/2018.05.01/</guid>
<enclosure url="https://www.archlinux.org/iso/2018.05.01/archlinux-2018.05.01-x86_64.iso.torrent" length="587202560" type="application/x-bittorrent"></enclosure>
</item>
</channel>
</rss>
*/
type feed struct {
Channel channel `xml:"channel"`
}
type channel struct {
ItemList []item `xml:"item"`
}
type item struct {
Title date `xml:"title" json:"title"`
Enclosure enclosure `xml:"enclosure" json:"enclosure"`
}
type date string
func (t *date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v string
d.DecodeElement(&v, &start)
*t = date(v)
return nil
}
func (i item) String() string {
return fmt.Sprintf("%v, %v\n", i.Title, i.Enclosure.URL)
}
type enclosure struct {
URL url `xml:"url,attr" json:"url"`
}
type url string
func (u *url) UnmarshalXMLAttr(attr xml.Attr) error {
*u = url(attr.Value)
return nil
}
var (
logFileFlag *string
dbFileFlag *string
torrentDirFlag *string
downloadDirFlag *string
logger *log.Logger
)
func init() {
logFileFlag = flag.String("l", "arch_release.log", "path to log file")
dbFileFlag = flag.String("o", "release_list.json", "json file where previous releases are listed")
torrentDirFlag = flag.String("d", "torrent", "directory where torrents are stored")
downloadDirFlag = flag.String("w", "ISOs", "directory where downloaded ISOs are stored")
flag.Parse()
logFile, err := os.OpenFile(*logFileFlag, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("could not open log file %v for writing : %v", *logFileFlag, err)
}
logger = log.New(logFile, "arch_release : ", log.LstdFlags|log.Lshortfile)
}
func main() {
resp, err := http.Get("https://www.archlinux.org/feeds/releases/")
if err != nil {
writeLog("could not get url : %v", err)
}
defer resp.Body.Close()
feed := &feed{}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
writeLog("could not read response body : %v", err)
}
err = xml.Unmarshal(body, feed)
if err != nil {
writeLog("could not unmarshal xml : %v", err)
}
torrentURL := string(feed.Channel.ItemList[0].Enclosure.URL)
_, err = os.Stat(*dbFileFlag)
if os.IsNotExist(err) {
dbFile, err := os.OpenFile(*dbFileFlag, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
writeLog("could not create database file %v : %v", *dbFileFlag, err)
}
jsonEncoder := json.NewEncoder(dbFile)
for _, item := range feed.Channel.ItemList {
err = jsonEncoder.Encode(item)
if err != nil {
writeLog("could not encode item to json : %v", err)
}
}
err = createTorrent(torrentURL)
if err != nil {
writeLog("could not create torrent file : %v", err)
}
} else {
dbFile, err := os.Open(*dbFileFlag)
if err != nil {
writeLog("could not open %v for reading : %v", *dbFileFlag, err)
}
jsonDecoder := json.NewDecoder(dbFile)
existing := []item{}
for jsonDecoder.More() {
var i item
err = jsonDecoder.Decode(&i)
if err != nil {
writeLog("could not decode json from %v : %v", *dbFileFlag, err)
}
existing = append(existing, i)
}
lastItem := feed.Channel.ItemList[0]
exists := false
for k := range existing {
if lastItem == existing[k] {
exists = true
}
}
if !exists {
err = createTorrent(torrentURL)
if err != nil {
writeLog("could not create torrent file : %v", err)
}
}
}
urlComp := strings.Split(torrentURL, "/")
torrentName := path.Join(*torrentDirFlag, urlComp[len(urlComp)-1])
cmd := exec.Command("transmission-remote", "-w", *downloadDirFlag, "-a", torrentName)
err = cmd.Run()
if err != nil {
writeLog("error while running %v : %v", cmd.Args[0], err)
}
}
func writeLog(format string, v ...interface{}) {
fmt.Fprintf(os.Stderr, format, v)
logger.Fatalf(format, v)
}
func createTorrent(torrentURL string) error {
if _, err := os.Stat(*torrentDirFlag); os.IsNotExist(err) {
if err = os.MkdirAll(*torrentDirFlag, 0755); err != nil {
return fmt.Errorf("could not create destination folder for torrent files : %v", err)
}
}
urlComp := strings.Split(torrentURL, "/")
torrentName := path.Join(*torrentDirFlag, urlComp[len(urlComp)-1])
torrentFile, err := os.Create(torrentName)
if err != nil {
return fmt.Errorf("could not create torrent file %v : %v", torrentName, err)
}
defer torrentFile.Close()
resp, err := http.Get(torrentURL)
if err != nil {
return fmt.Errorf("could not get torrent from address %v : %v", torrentURL, err)
}
defer resp.Body.Close()
_, err = io.Copy(torrentFile, resp.Body)
if err != nil {
return fmt.Errorf("could not write torrent file %v to disk : %v", torrentName, err)
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment