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) } } }