podcatch/podCatch/podcatch.go

294 lines
6.9 KiB
Go

package podcatch
import (
"fmt"
. "podcatch/structs"
"github.com/pelletier/go-toml"
"encoding/xml"
"io"
"io/ioutil"
"net/http"
"regexp"
"os"
"log"
"strings"
id3 "github.com/mikkyang/id3-go"
v2 "github.com/mikkyang/id3-go/v2"
)
var Version string = "0.1"
var Settings Settings
var Podcasts map[string]Podcast = make(map[string]Podcast)
var donefile string
var podcatchdir string
var homedir string
var dbdir string
func Start(){
fmt.Printf("Starting PodCatch Version : %s...\r\n", Version )
getHomeDirs()
getSettings()
getPodcasts()
}
func getHomeDirs(){
h, err := os.UserHomeDir()
if err != nil {
log.Fatal( err )
}
homedir = h
podcatchdir = h + "/.podcatch/"
}
func getSettings(){
settings := podcatchdir + "settings.toml"
if !checkFileExists(settings){
pwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
fmt.Println("Copying default settings.toml to user dir.")
ok, err := copyFile(pwd + "/defaults/settings.toml",settings)
if err != nil {
log.Fatal(err)
}
if ok > 0 {
fmt.Println("Copied.")
}
}
content, err := ioutil.ReadFile(settings)
if err != nil {
log.Fatal(err)
}
e := toml.Unmarshal(content,&Settings)
if e != nil {
log.Fatal(err)
}
Settings.Directory = strings.Replace(Settings.Directory,"~",homedir,1)
dbdir = Settings.Directory + ".db/"
os.Mkdir(dbdir,0755)
}
func getPodcasts(){
if len(Podcasts) == 0 {
getPodcastFiles()
}
for shortname,podcast := range Podcasts {
podcast.Directory = shortname
fmt.Printf("Checking RSS for %s...\r\n", podcast.Name)
podcast.RSS = getRSS(podcast)
downloadCasts(podcast)
}
}
func getPodcastFiles() {
pcs := podcatchdir + "podcasts.toml"
if !checkFileExists(pcs){
pwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
fmt.Println("Copying default podcasts.toml to user dir.")
ok, err := copyFile(pwd + "/defaults/podcasts.toml",pcs)
if err != nil {
log.Fatal(err)
}
if ok > 0 {
fmt.Println("Copied.")
}
}
content, err := ioutil.ReadFile(podcatchdir + "podcasts.toml")
if err != nil {
log.Fatal(err)
}
e := toml.Unmarshal(content,&Podcasts)
if e != nil {
log.Fatal(err)
}
fmt.Printf("Found %d podcasts.\r\n",len(Podcasts))
}
func getRSS(podcast Podcast) Rss {
resp, err := http.Get(podcast.URL)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
html, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
return parseRSS(podcast,html)
}
func parseRSS(podcast Podcast, rssxml []byte) Rss {
var rss Rss
e := xml.Unmarshal(rssxml,&rss)
if e != nil {
log.Fatal(e)
}
return rss
}
func downloadCasts(podcast Podcast) {
count := 0
for _,item := range podcast.RSS.Channel.Items {
if count >= Settings.Limit {
break
}
if !podcastDownloaded(item){
fmt.Printf("Downloading '%s %s' from : %s.\r\n", item.Episode, item.Title, item.Media.URL)
re := regexp.MustCompile(`[^0-9a-zA-Z-_]+`)
filename := item.Episode + re.ReplaceAllString(item.Title,"_") + ".mp3"
dir := Settings.Directory + podcast.Directory
err := os.Mkdir(dir, 0777)
if err != nil && err.Error() != fmt.Sprintf("mkdir %s: file exists",dir){
log.Fatal(err)
}
ok := downloadMp3(item.Media.URL, dir + "/" + filename)
if ok {
// createNFO(item, strings.Replace(dir + "/" + filename,".mp3",".nfo",1))
addId3(podcast.Name, item,dir + "/" + filename)
markAsReceived(item)
} else {
markAsErrored(item)
}
} else {
fmt.Printf("Skipping '%s' - already downloaded\r\n", item.Title)
}
count = count + 1
}
}
func podcastDownloaded(item Item) bool {
db := dbdir + "complete"
if checkCreate(db) {
if len(donefile) < 1 {
content, err := ioutil.ReadFile(db)
if err != nil {
log.Fatal(err)
}
donefile = string(content)
}
if strings.Contains(donefile,item.Title){
return true
}
if strings.Contains(donefile,item.Media.URL){
return true
}
}
return false
}
func downloadMp3(url string, file string) bool {
ok := false
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
err = ioutil.WriteFile(file, data, 0775)
if err != nil {
log.Fatal(err)
}
ok = true
return ok
}
func addId3(artist string, item Item, file string) {
fmt.Printf("Saving ID3 to %s\r\n",file)
mp3File, err := id3.Open(file)
if err != nil {
log.Fatal(err)
}
defer mp3File.Close()
if mp3File.Artist() == "" {
mp3File.SetArtist(artist)
}
if mp3File.Album() == "" {
mp3File.SetAlbum(artist)
}
if mp3File.Title() == "" {
mp3File.SetTitle(item.Title)
}
if len(mp3File.Comments()) == 0 {
ft := v2.V23FrameTypeMap["COMM"]
textFrame := v2.NewTextFrame(ft, item.Description)
mp3File.AddFrames(textFrame)
}
}
func markAsReceived(item Item) {
db := dbdir + "complete"
checkCreate(db)
file, err := os.OpenFile(db, os.O_APPEND|os.O_WRONLY, 0755)
if err != nil {
log.Println(err)
}
defer file.Close()
content := fmt.Sprintf("%s - %s\r\n",item.Title, item.Media.URL)
if _, err := file.WriteString(content); err != nil {
log.Fatal(err)
}
}
func markAsErrored(item Item) {
db := dbdir + "error"
checkCreate(db)
file, err := os.OpenFile(db, os.O_APPEND|os.O_WRONLY, 0755)
if err != nil {
log.Println(err)
}
defer file.Close()
content := fmt.Sprintf("%s\r\n%s\r\n",item.Title, item.Media.URL)
if _, err := file.WriteString(content); err != nil {
log.Fatal(err)
}
}
func checkFileExists(file string) bool {
if _, err := os.Stat(file); err == nil {
// fmt.Println("Exists")
// exists
return true
} else if os.IsNotExist(err) {
// fmt.Println("Not Exists")
// not exists
return false
} else {
// fmt.Println("Maybe Exists, Maybe Not")
return false
// Schrodinger: file may or may not exist. See err for details.
// Therefore, do *NOT* use !os.IsNotExist(err) to test for file existence
}
return false
}
func checkCreate(file string) bool {
if checkFileExists(file) {
return true
} else {
if createFile(file) {
return true
}
}
return false
}
func createFile(file string) bool {
f, err := os.Create(file)
if err != nil {
log.Fatal(err)
return false
}
defer f.Close()
return true
}
func copyFile(src, dst string) (int64, error) {
sourceFileStat, err := os.Stat(src)
if err != nil {
return 0, err
}
if !sourceFileStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
}
source, err := os.Open(src)
if err != nil {
return 0, err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destination.Close()
nBytes, err := io.Copy(destination, source)
return nBytes, err
}