classifier/main.go
2021-08-03 00:37:51 +02:00

212 lines
4.2 KiB
Go

package main
import (
"fmt"
"io/fs"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
const (
absBasePath = "/opt/synapse/download"
seriesPath = absBasePath + "/series"
filmPath = absBasePath + "/film"
musicPath = absBasePath + "/music"
programsPath = absBasePath + "/programs"
)
type kindOf int
func (k kindOf) String() string {
switch k {
case series:
return "series"
case film:
return "film"
case music:
return "music"
case program:
return "program"
default:
panic("Unkwnown kind")
}
}
func (k kindOf) replace(other kindOf) bool {
switch {
case k == program && other == film:
return true
case k == program && other == series:
return true
default:
return false
}
}
const (
series kindOf = iota
film
music
program
)
var maybeSeries, maybeFilm, maybeMusic, maybeProgram *regexp.Regexp
type classification map[string]kindOf
func (c classification) add(element string, kind kindOf) {
if pre, ok := c[element]; ok {
if kind == pre {
return
}
l.Printf("%s yet present with different type (%s != %s)\n", element, pre, kind)
if !pre.replace(kind) {
l.Printf("(%s > %s), replacing kind for %s\n", kind, pre, element)
return
}
}
c[element] = kind
}
func (c classification) get(element string) (string, bool) {
kind, ok := c[element]
if !ok {
return "", false
}
return fmt.Sprint(kind), true
}
var cls classification
var l *log.Logger
func init() {
maybeSeries = regexp.MustCompile(`(?i)^.*(((s|S)(\d+)((e|E)(\d+))?)|((season)(\ )?(\d+)?)).*\.(mkv|mp4|avi)$`)
maybeFilm = regexp.MustCompile(`(?i)^.*\.(mkv|mp4|avi)$`)
maybeMusic = regexp.MustCompile(`(?i)^.*\.(mp3|ogg|flac)$`)
maybeProgram = regexp.MustCompile(`(?i)^.*\.(zip|tar|tar\.gz|exe|msi|rar)$`)
cls = make(classification)
l = log.Default()
}
func isInSpecialDir(dir string) bool {
if strings.Contains(dir, seriesPath) {
return true
}
if strings.Contains(dir, filmPath) {
return true
}
if strings.Contains(dir, musicPath) {
return true
}
if strings.Contains(dir, programsPath) {
return true
}
return false
}
func baseDirOrFile(name string) string {
dir := filepath.Dir(name)
if dir == absBasePath {
return name
}
rel := strings.Trim(strings.Replace(dir, absBasePath, "", 1), "/")
comps := strings.Split(rel, "/")
return filepath.Join(absBasePath, comps[0])
}
func findSeriesByDir(path string, d fs.DirEntry, err error) error {
if !d.IsDir() {
return nil
}
files, err := ioutil.ReadDir(path)
if err != nil {
return err
}
videoFiles := []string{}
for _, f := range files {
if maybeFilm.Match([]byte(f.Name())) {
videoFiles = append(videoFiles, f.Name())
}
}
if len(videoFiles) > 2 {
cls.add(baseDirOrFile(path), series)
}
return nil
}
func findByFile(name string, info fs.FileInfo, err error) error {
// skip directories
if info.IsDir() {
return nil
}
// skip special directories
if isInSpecialDir(name) {
return nil
}
baseFile := baseDirOrFile(name)
base := filepath.Base(name)
v := []byte(base)
switch {
case maybeSeries.Match(v):
cls.add(baseFile, series)
case maybeFilm.Match(v):
cls.add(baseFile, film)
case maybeMusic.Match(v):
cls.add(baseFile, music)
case maybeProgram.Match(v):
cls.add(baseFile, program)
default:
l.Printf("No match: %s\n", base)
}
return nil
}
func link(basePath, target string, kind kindOf) error {
targetName := filepath.Base(target)
switch kind {
case series:
src := filepath.Join(basePath, "series", targetName)
return doLink(target, src)
case film:
src := filepath.Join(basePath, "film", targetName)
return doLink(target, src)
case music:
src := filepath.Join(basePath, "music", targetName)
return doLink(target, src)
case program:
src := filepath.Join(basePath, "programs", targetName)
return doLink(target, src)
}
return nil
}
func doLink(target, src string) error {
err := os.Symlink(target, src)
if err == os.ErrExist {
return nil
}
return err
}
func main() {
if err := filepath.WalkDir(absBasePath, findSeriesByDir); err != nil {
l.Fatal(err)
}
if err := filepath.Walk(absBasePath, findByFile); err != nil {
l.Fatal(err)
}
l.Printf("Result: %s\n", cls)
for file, kind := range cls {
if err := link(absBasePath, file, kind); err != nil {
l.Fatal(err)
}
}
}