records/musicindex/index.go

203 lines
4.1 KiB
Go

// Copyright (c) 2026 Jeremy Baxter.
// Music library indexing code
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("\033[2K\r \033[1;36mcaching\033[0m %s - %s",
artist.Name, albumName)
albumDir := artistDir + "/" + albumName
album, err := indexAlbum(artist, albumName, albumDir)
if err != nil {
util.Iprint("\r")
log.Printf("warn: skipping inaccessible album %s: %s", albumName, err.Error())
} else {
albums[albumName] = album
}
}
util.Iprint("\r")
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
}