hoarder/hoarder.go

258 lines
6.4 KiB
Go
Raw Normal View History

2014-12-23 17:38:01 -05:00
// Watch a torrent directory, poll rtorrent, and download completed torrents over SFTP.
package main
import (
"errors"
"github.com/adampresley/sigint"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
)
// Load information from a given config file config_path
func loadConfig(config_path string) (map[string]string, error) {
file, err := os.Open(config_path)
if err != nil {
log.Println("Failed to open configuration file " + config_path)
return nil, err
}
data, err := ioutil.ReadAll(file)
if err != nil {
log.Println("Failed to read configuration file " + config_path)
return nil, err
}
config := make(map[string]string)
lines := strings.Split(string(data), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
// Ignore comments
if len(line) <= 2 || line[:2] == "//" {
continue
}
// Ignore malformed lines
sep_position := strings.Index(line, ": \"")
if sep_position == -1 {
continue
}
config[line[:sep_position]] = line[sep_position + 3:len(line) - 1]
}
return config, nil
}
// Checker routine to see if torrents are completed
func checker(config map[string]string, checker_chan <- chan map[string]string, com chan <- error) error {
for {
torrent_info := <-checker_chan
log.Println("Started checking " + torrent_info["torrent_path"])
torrent, err := NewTorrent(config["xml_user"], config["xml_pass"], config["xml_address"], torrent_info["torrent_path"])
if err != nil {
log.Println("Failed to initialize torrent for " + torrent_info["torrent_path"] + ": " + err.Error())
continue
}
syncer, err := NewSync(config["threads"], config["ssh_user"], config["ssh_pass"], config["ssh_server"], config["ssh_port"])
if err != nil {
log.Println("Failed to create a new sync: " + err.Error())
com <- err
return err
}
destination_torrent := filepath.Join(config["remote_torrent_dir"], filepath.Base(torrent.path))
exists, err := syncer.Exists(destination_torrent)
if err != nil {
log.Println("Failed to see if " + torrent_info["torrent_path"] + " already exists on the server: " + err.Error())
com <- err
return err
}
if !exists {
err = syncer.SendFiles(map[string]string{torrent.path: destination_torrent})
if err != nil {
log.Println("Failed to send " + torrent.path + " to the server: " + err.Error())
com <- err
return err
}
// continue because rtorrent more than likely will not finish the torrent by the next call
continue
}
completed, err := torrent.GetTorrentComplete()
if err != nil {
log.Println("Failed to see if " + torrent.path + " is completed: " + err.Error())
com <- err
return err
}
name, err := torrent.GetTorrentName()
if err != nil {
com <- err
return err
}
if completed {
log.Println(name + " is completed, starting download now")
remote_download_path := filepath.Join(config["remote_download_dir"], name)
exists, err := syncer.Exists(remote_download_path)
if err != nil {
log.Println("Failed to see if " + remote_download_path + " exists: " + err.Error())
com <- err
return err
}
// file/dir to downlaod does not exist!
if !exists {
err = errors.New(remote_download_path + " does not exist on remote server")
com <- err
return err
}
completed_destination := filepath.Join(config["local_download_dir"], name)
_, err = os.Stat(completed_destination)
if err == nil {
err = errors.New(completed_destination + " already exists, not downloading")
continue
} else if !os.IsNotExist(err) {
log.Println("Failed to stat: " + completed_destination + ": " + err.Error())
com <- err
return err
}
err = syncer.GetPath(remote_download_path, config["temp_download_dir"])
if err != nil {
log.Println("Failed to download " + remote_download_path + ": " + err.Error())
com <- err
return err
}
log.Println("Successfully downloaded " + name)
temp_destination := filepath.Join(config["temp_download_dir"], name)
err = os.Rename(temp_destination, completed_destination)
if err != nil {
log.Println("Failed to move " + temp_destination + " to " + completed_destination + ": " + err.Error())
com <- err
return err
}
err = os.Remove(torrent.path)
if err != nil && !os.IsNotExist(err) {
log.Println("Failed to remove " + torrent.path + ": " + err.Error())
com <- err
return err
}
} else {
log.Println(name + " is not completed, waiting for it to finish")
}
}
com <- nil
return nil
}
// Scanner routine to see if there are new torrent_files
func scanner(config map[string]string, checker_chan chan <- map[string]string, com chan <- error) error {
watch_dirs := map[string]string{config["local_torrent_dir"]: config["local_download_dir"]}
dir_contents, err := ioutil.ReadDir(config["local_torrent_dir"])
if err != nil {
com <- err
return err
}
for _, file := range dir_contents {
if file.IsDir() {
watch_dirs[filepath.Join(config["local_torrent_dir"], file.Name())] = filepath.Join(config["local_download_dir"], file.Name())
}
}
for {
for watch_dir, download_dir := range watch_dirs {
torrent_files, err := ioutil.ReadDir(watch_dir)
if err != nil {
com <- err
return err
}
for _, torrent_file := range torrent_files {
if torrent_file.IsDir() {
// skip because we don't do more than one level of watching
continue
}
checker_chan <- map[string]string{
"torrent_path": filepath.Join(watch_dir, torrent_file.Name()),
"local_download_dir": download_dir,
}
}
}
time.Sleep(time.Second * 30)
}
com <- nil
return nil
}
func main() {
sigint.ListenForSIGINT(func() {
log.Println("Quiting")
os.Exit(1)
})
log.Println("Reading configuration file")
config, err := loadConfig("hoarder.conf")
if err != nil {
log.Println(err)
os.Exit(1)
}
log.Println("Successfully read configuration file")
checker_chan := make(chan map[string]string, 50)
if err != nil {
log.Println(err)
os.Exit(1)
}
log.Println("Starting the scanner routine")
scanner_com := make(chan error)
go scanner(config, checker_chan, scanner_com)
log.Println("Starting the checker routine")
checker_com := make(chan error)
go checker(config, checker_chan, checker_com)
for {
select {
case err := <-scanner_com:
if err != nil {
log.Println("Scanner failed: " + err.Error())
os.Exit(1)
}
case err := <-checker_com:
if err != nil {
log.Println("Checker failed: " + err.Error())
os.Exit(1)
}
}
time.Sleep(time.Second * 5)
}
}