331 lines
8.1 KiB
Go
331 lines
8.1 KiB
Go
// Watch a torrent directory, poll rtorrent, and download completed torrents over SFTP.
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"github.com/adampresley/sigint"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Load information from a given config file config_path
|
|
func loadConfig(configPath string) (map[string]string, error) {
|
|
file, err := os.Open(configPath)
|
|
if err != nil {
|
|
log.Println("Failed to open configuration file " + configPath)
|
|
return nil, err
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
log.Println("Failed to read configuration file " + configPath)
|
|
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
|
|
sepPosition := strings.Index(line, ": \"")
|
|
if sepPosition == -1 {
|
|
continue
|
|
}
|
|
|
|
config[line[:sepPosition]] = line[sepPosition+3 : len(line)-1]
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// Checker routine to see if torrents are completed
|
|
func checker(config map[string]string, checkerChan <-chan map[string]string, com chan<- error) error {
|
|
for {
|
|
torrentInfo := <-checkerChan
|
|
|
|
log.Println("Started checking " + torrentInfo["torrent_path"])
|
|
|
|
torrent, err := NewTorrent(config["xml_user"], config["xml_pass"], config["xml_address"], torrentInfo["torrent_path"])
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
log.Println("Failed to initialize torrent for " + torrentInfo["torrent_path"] + ": " + err.Error())
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
syncer, err := NewSync(config["threads"], config["ssh_user"], config["ssh_pass"], config["ssh_server"], config["ssh_port"])
|
|
defer syncer.Close()
|
|
if err != nil {
|
|
log.Println("Failed to create a new sync: " + err.Error())
|
|
com <- err
|
|
return err
|
|
}
|
|
|
|
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")
|
|
|
|
remoteDownloadPath := filepath.Join(config["remote_download_dir"], name)
|
|
exists, err := syncer.Exists(remoteDownloadPath)
|
|
if err != nil {
|
|
log.Println("Failed to see if " + remoteDownloadPath + " exists: " + err.Error())
|
|
com <- err
|
|
return err
|
|
}
|
|
|
|
// file/dir to downlaod does not exist!
|
|
if !exists {
|
|
err = errors.New(remoteDownloadPath + " does not exist on remote server")
|
|
com <- err
|
|
return err
|
|
}
|
|
|
|
completedDestination := filepath.Join(torrentInfo["local_download_dir"], name)
|
|
|
|
_, err = os.Stat(completedDestination)
|
|
if err == nil {
|
|
err = errors.New(completedDestination + " already exists, not downloading")
|
|
continue
|
|
} else if !os.IsNotExist(err) {
|
|
log.Println("Failed to stat: " + completedDestination + ": " + err.Error())
|
|
com <- err
|
|
return err
|
|
}
|
|
|
|
err = syncer.GetPath(remoteDownloadPath, config["temp_download_dir"])
|
|
if err != nil {
|
|
log.Println("Failed to download " + remoteDownloadPath + ": " + err.Error())
|
|
com <- err
|
|
return err
|
|
}
|
|
|
|
log.Println("Successfully downloaded " + name)
|
|
|
|
tempDestination := filepath.Join(config["temp_download_dir"], name)
|
|
|
|
err = os.Rename(tempDestination, completedDestination)
|
|
if err != nil {
|
|
log.Println("Failed to move " + tempDestination + " to " + completedDestination + ": " + 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")
|
|
}
|
|
|
|
syncer.Close()
|
|
}
|
|
|
|
com <- nil
|
|
return nil
|
|
}
|
|
|
|
// Scanner routine to see if there are new torrent_files
|
|
func scanner(config map[string]string, checkerChan chan<- map[string]string, com chan<- error) error {
|
|
watchDirs := map[string]string{config["local_torrent_dir"]: config["local_download_dir"]}
|
|
dirContents, err := ioutil.ReadDir(config["local_torrent_dir"])
|
|
|
|
if err != nil {
|
|
com <- err
|
|
return err
|
|
}
|
|
|
|
for _, file := range dirContents {
|
|
if file.IsDir() {
|
|
watchDirs[filepath.Join(config["local_torrent_dir"], file.Name())] = filepath.Join(config["local_download_dir"], file.Name())
|
|
}
|
|
}
|
|
|
|
uploaded := make(map[string]bool)
|
|
downloadingTorrentPath := ""
|
|
for {
|
|
for watchDir, downloadDir := range watchDirs {
|
|
torrentFiles, err := ioutil.ReadDir(watchDir)
|
|
if err != nil {
|
|
com <- err
|
|
return err
|
|
}
|
|
|
|
for _, torrentFile := range torrentFiles {
|
|
if torrentFile.IsDir() {
|
|
// skip because we don't do more than one level of watching
|
|
continue
|
|
}
|
|
|
|
torrentPath := filepath.Join(watchDir, torrentFile.Name())
|
|
|
|
if !uploaded[torrentPath] {
|
|
syncer, err := NewSync("1", 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())
|
|
syncer.Close()
|
|
continue
|
|
}
|
|
|
|
destinationTorrent := filepath.Join(config["remote_torrent_dir"], filepath.Base(torrentPath))
|
|
exists, err := syncer.Exists(destinationTorrent)
|
|
if err != nil {
|
|
log.Println("Failed to see if " + torrentPath + " already exists on the server: " + err.Error())
|
|
syncer.Close()
|
|
continue
|
|
}
|
|
|
|
if exists {
|
|
uploaded[torrentPath] = true
|
|
} else {
|
|
err = syncer.SendFiles(map[string]string{torrentPath: destinationTorrent})
|
|
if err == nil {
|
|
log.Println("Successfully uploaded " + torrentPath + " to " + destinationTorrent)
|
|
uploaded[torrentPath] = true
|
|
} else {
|
|
log.Println("Failed to upload " + torrentPath + " to " + destinationTorrent + ": " + err.Error())
|
|
}
|
|
|
|
syncer.Close()
|
|
continue
|
|
}
|
|
|
|
syncer.Close()
|
|
}
|
|
|
|
downloadInfo := map[string]string{
|
|
"torrent_path": torrentPath,
|
|
"local_download_dir": downloadDir,
|
|
}
|
|
|
|
// try to send the info to the checker goroutine (nonblocking)
|
|
select {
|
|
case checkerChan <- downloadInfo:
|
|
// don't keep track of completed downloads in the uploaded map
|
|
if downloadingTorrentPath != "" {
|
|
delete(uploaded, downloadingTorrentPath)
|
|
}
|
|
|
|
downloadingTorrentPath = torrentPath
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
time.Sleep(time.Second * 30)
|
|
}
|
|
|
|
com <- nil
|
|
return nil
|
|
}
|
|
|
|
func die(exitCode int) {
|
|
log.Println("Quiting")
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func main() {
|
|
sigint.ListenForSIGINT(func() {
|
|
die(1)
|
|
})
|
|
|
|
var configPath string
|
|
flag.StringVar(&configPath, "config", "", "Location of the config file")
|
|
flag.Parse()
|
|
|
|
if configPath == "" {
|
|
log.Println("Missing argument for configuration file path")
|
|
flag.PrintDefaults()
|
|
die(1)
|
|
}
|
|
|
|
log.Println("Reading configuration file")
|
|
config, err := loadConfig(configPath)
|
|
if err != nil {
|
|
log.Println(err)
|
|
die(1)
|
|
}
|
|
|
|
log.Println("Successfully read configuration file")
|
|
|
|
checkerChan := make(chan map[string]string, 50)
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
die(1)
|
|
}
|
|
|
|
log.Println("Starting the scanner routine")
|
|
scannerCom := make(chan error)
|
|
go scanner(config, checkerChan, scannerCom)
|
|
|
|
log.Println("Starting the checker routine")
|
|
checkerCom := make(chan error)
|
|
go checker(config, checkerChan, checkerCom)
|
|
|
|
restartOnError := true
|
|
if config["restart_on_error"] != "" {
|
|
restartOnError = config["restart_on_error"] == "true"
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case err := <-scannerCom:
|
|
if err != nil {
|
|
log.Println("Scanner failed: " + err.Error())
|
|
|
|
if restartOnError {
|
|
log.Println("Restarting scanner")
|
|
go scanner(config, checkerChan, scannerCom)
|
|
} else {
|
|
log.Println("Quiting due to scanner error")
|
|
die(1)
|
|
}
|
|
}
|
|
case err := <-checkerCom:
|
|
if err != nil {
|
|
log.Println("Checker failed: " + err.Error())
|
|
|
|
if restartOnError {
|
|
log.Println("Restarting checker")
|
|
go checker(config, checkerChan, checkerCom)
|
|
} else {
|
|
log.Println("Quiting due to checker error")
|
|
die(1)
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
|
|
time.Sleep(time.Second * 5)
|
|
}
|
|
}
|