195 lines
4 KiB
Go
195 lines
4 KiB
Go
package musicindex
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"log"
|
|
"os"
|
|
"os/user"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"github.com/walle/targz"
|
|
|
|
"git.baxters.nz/jeremy/records/util"
|
|
)
|
|
|
|
var TempDir = "/tmp/records"
|
|
|
|
func assertAccessTo(path string) {
|
|
f, err := os.Stat(path)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
util.Die("no such file or directory: %s", path)
|
|
}
|
|
util.Die("cannot stat %s: %s", path, err.Error())
|
|
}
|
|
if !f.IsDir() {
|
|
util.Die("not a directory: %s", path)
|
|
}
|
|
|
|
if unix.Access(path, unix.R_OK) == nil {
|
|
return
|
|
}
|
|
|
|
procUser, err := user.Current()
|
|
if err != nil {
|
|
util.Die("cannot access directory: %s", path)
|
|
return
|
|
}
|
|
|
|
groupObject, err := user.LookupGroupId(strconv.Itoa(syscall.Getgid()))
|
|
if err != nil {
|
|
util.Die("cannot access directory: %s", path)
|
|
return
|
|
}
|
|
|
|
util.Die("cannot access directory %s\nrepair all directories with: chown -R %s:%s %s",
|
|
path, procUser.Username, groupObject.Name, mediaDir)
|
|
}
|
|
|
|
func indexArtists() {
|
|
entries, err := util.Dirents(mediaDir)
|
|
if err != nil {
|
|
util.Die("%s", err.Error())
|
|
}
|
|
|
|
if (regenerateTarballs) {
|
|
err = os.RemoveAll(TempDir)
|
|
if err != nil {
|
|
util.Die("cannot remove temporary directory %s: %s", TempDir, err.Error())
|
|
}
|
|
}
|
|
|
|
f, err := os.Stat(TempDir)
|
|
if err != nil {
|
|
err = os.Mkdir(TempDir, 0711)
|
|
if err != nil {
|
|
util.Die("cannot make temporary directory %s: %s", TempDir,
|
|
err.Error())
|
|
}
|
|
log.Println("generating new tarball index")
|
|
} else {
|
|
if !f.IsDir() {
|
|
util.Die("cannot make temporary directory %s: %s", TempDir,
|
|
"not a directory")
|
|
}
|
|
}
|
|
|
|
artists = make(map[string]Artist)
|
|
for _, artist := range entries {
|
|
stat, err := os.Stat(mediaDir + "/" + artist)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if !stat.IsDir() {
|
|
continue
|
|
}
|
|
|
|
var a Artist
|
|
a.Name = artist
|
|
a.Albums = indexAlbums(&a)
|
|
a.Songs = 0
|
|
a.Valid = true
|
|
|
|
for _, album := range a.Albums {
|
|
a.Songs += len(album.TrackFiles)
|
|
}
|
|
|
|
artists[a.Name] = a
|
|
}
|
|
}
|
|
|
|
func indexAlbums(artist *Artist) (albums map[string]Album) {
|
|
artistDir := mediaDir + "/" + artist.Name
|
|
assertAccessTo(artistDir)
|
|
entries, err := util.Dirents(artistDir)
|
|
if err != nil {
|
|
util.Die("%s", err.Error())
|
|
}
|
|
|
|
albums = make(map[string]Album)
|
|
for _, albumName := range entries {
|
|
util.Iprint("* index %s - %s\r", artist.Name, albumName)
|
|
|
|
albumDir := artistDir + "/" + albumName
|
|
album, err := indexAlbum(artist, albumName, albumDir)
|
|
if err != nil {
|
|
log.Printf("warn: skipping inaccessible album %s: %s", albumName, err.Error())
|
|
} else {
|
|
albums[albumName] = album
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func indexAlbum(artist *Artist, albumName, albumDir string) (a Album, err error) {
|
|
stat, err := os.Stat(albumDir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !stat.IsDir() {
|
|
err = errors.New("not a directory")
|
|
}
|
|
|
|
a.Artist = artist
|
|
a.Name = albumName
|
|
a.HasCover = false
|
|
a.CoverFile = ""
|
|
|
|
err = unix.Access(albumDir + "/" + defaultCoverFile, unix.R_OK)
|
|
if err == nil {
|
|
a.HasCover = true
|
|
a.CoverFile = defaultCoverFile
|
|
}
|
|
|
|
var tracks []string
|
|
albumDirEntries, err := util.Dirents(albumDir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, track := range albumDirEntries {
|
|
if strings.HasSuffix(track, trackFileExtension) {
|
|
tracks = append(tracks, track)
|
|
}
|
|
}
|
|
|
|
slices.Sort(tracks)
|
|
a.TrackFiles = tracks
|
|
|
|
generate := false
|
|
hash := util.HashOf(a.Artist.Name + "::" + a.Name)
|
|
a.Tarball = TempDir + "/" + hash
|
|
|
|
if _, err = os.Stat(a.Tarball); errors.Is(err, os.ErrNotExist) {
|
|
// if the file isn't there we definitely want to generate one
|
|
generate = true
|
|
} else { // if the file exists or if it's some other error
|
|
if regenerateTarballs {
|
|
// not sure why this is here since we deleted the whole
|
|
// directory beforehand but let's try overwrite it anyway
|
|
generate = true
|
|
}
|
|
}
|
|
|
|
if generate {
|
|
err = targz.Compress(a.Directory(), a.Tarball)
|
|
if err != nil {
|
|
a.Tarball = ""
|
|
return
|
|
}
|
|
}
|
|
|
|
tarball, err := os.Stat(a.Tarball)
|
|
if err != nil {
|
|
a.Tarball = ""
|
|
return
|
|
}
|
|
a.TarballSize = tarball.Size()
|
|
|
|
err = nil
|
|
return
|
|
}
|