diff --git a/main.go b/main.go index c60295a..1a7d925 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,3 @@ -// Copyright (c) 2026 Jeremy Baxter. -// entry point for records - package main import ( @@ -19,33 +16,18 @@ const version = "0-pre" var addr *string var port *uint16 -var tempDir *string -var deleteAndExit *bool var regenerate *bool var showVersion *bool func main() { addr = getopt.String('a', "0.0.0.0") port = getopt.Uint16('p', 8000) - tempDir = getopt.String('t', "") - deleteAndExit = getopt.Bool('d') regenerate = getopt.Bool('r') showVersion = getopt.Bool('V') if err := getopt.Getopt(nil); err != nil { util.Die(err.Error()) } - if len(*tempDir) != 0 { - musicindex.TempDir = *tempDir - } - - if *deleteAndExit { - err := os.RemoveAll(musicindex.TempDir) - if err != nil { - util.Die("cannot remove %s: %s", musicindex.TempDir, err.Error()) - } - os.Exit(0) - } if *showVersion { fmt.Printf("records %s\n", version) os.Exit(0) @@ -53,20 +35,13 @@ func main() { args := getopt.Args() if len(args) != 1 { - fmt.Fprintf(os.Stderr, "usage: %s [-drV] [-a address] [-p port] directory\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "usage: %s [-rV] [-a address] [-p port] directory\n", os.Args[0]) os.Exit(1) } mediaDir := args[0] - { - interactive := "" - if util.Interactive() { - interactive = ", running with interactive features" - } - log.Printf("This is records %s%s", version, interactive) - } - + log.Printf("This is records %s\n", version) log.Println("https://git.baxters.nz/jeremy/records") musicindex.Init(mediaDir, *regenerate) diff --git a/musicindex/index.go b/musicindex/index.go index 8a41d95..3c034c1 100644 --- a/musicindex/index.go +++ b/musicindex/index.go @@ -1,6 +1,3 @@ -// Copyright (c) 2026 Jeremy Baxter. -// Music library indexing code - package musicindex import ( @@ -116,13 +113,11 @@ func indexAlbums(artist *Artist) (albums map[string]Album) { albums = make(map[string]Album) for _, albumName := range entries { - util.Iprint("\033[2K\r \033[1;36mcaching\033[0m %s - %s", - artist.Name, albumName) + util.Iprint("* index %s - %s\r", 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 diff --git a/musicindex/musicindex.go b/musicindex/musicindex.go index b78d0ff..b70b0d3 100644 --- a/musicindex/musicindex.go +++ b/musicindex/musicindex.go @@ -1,5 +1,7 @@ -// Copyright (c) 2026 Jeremy Baxter. -// In-memory album and song database +// musicindex.go +// Copyright (c) 2025 Jeremy Baxter. + +// Ephemeral music (artist, album, cover & track) index package musicindex diff --git a/server/pages.go b/server/pages.go index 5bc4e72..3d21cee 100644 --- a/server/pages.go +++ b/server/pages.go @@ -1,6 +1,3 @@ -// Copyright (c) 2026 Jeremy Baxter. -// HTML builders - package server import ( @@ -31,8 +28,7 @@ type Page interface { func writePage(p Page, w http.ResponseWriter) { t, err := template.ParseFS(staticFS, "static/templates/" + p.SourceFile()) if err != nil { - http.Error(w, "Internal server error: " + err.Error(), - http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -42,26 +38,13 @@ func writePage(p Page, w http.ResponseWriter) { } } -type ErrorPage struct { - Status string - Reason string -} - -func MakeErrorPage(status, reason string) (p ErrorPage) { - p.Status = status - p.Reason = reason - return -} - -func (p ErrorPage) SourceFile() string { return "error.html" } -func (p ErrorPage) Title() string { return "" } -func (p ErrorPage) Body() template.HTML { return template.HTML("") } - type IndexPage struct { Showcase string } -func MakeIndexPage() (p IndexPage) { +func MakeIndexPage() IndexPage { + var p IndexPage + albums := musicindex.Albums() rand.Shuffle(len(albums), func(i, j int) { albums[i], albums[j] = albums[j], albums[i] @@ -85,11 +68,9 @@ func MakeIndexPage() (p IndexPage) { } b.WriteString(``) } - b.WriteString(``) p.Showcase = b.String() - - return + return p } func (p IndexPage) SourceFile() string { return "base.html" } @@ -118,7 +99,8 @@ func ArtistSortOptions() []string { return []string{"name", "albums", "songs"} } -func MakeArtistsPage(sortBy string) (p ArtistsPage) { +func MakeArtistsPage(sortBy string) ArtistsPage { + var p ArtistsPage var b strings.Builder artistNames := musicindex.Artists() @@ -153,11 +135,9 @@ func MakeArtistsPage(sortBy string) (p ArtistsPage) { b.WriteString(fmt.Sprintf("
%d album%s, %d songs
", albums, util.OptionalString(albums != 1, "s"), artist.Songs)) } - b.WriteString(``) p.bodyHTML = b.String() - - return + return p } func (p ArtistsPage) SourceFile() string { return "base.html" } @@ -213,13 +193,14 @@ func writeAlbums(b *strings.Builder, sortBy string, albums []musicindex.Album, a b.WriteString(``) } -func MakeAlbumsPage(sortBy string) (p AlbumsPage) { +func MakeAlbumsPage(sortBy string) AlbumsPage { + var p AlbumsPage var b strings.Builder writeAlbums(&b, sortBy, musicindex.Albums(), false) p.bodyHTML = b.String() - return + return p } func (p AlbumsPage) SourceFile() string { return "base.html" } @@ -231,7 +212,8 @@ type ArtistPage struct { bodyHTML string } -func MakeArtistPage(sortBy string, name string) (p ArtistPage) { +func MakeArtistPage(sortBy string, name string) ArtistPage { + var p ArtistPage var b strings.Builder artist := musicindex.FindArtist(name) @@ -242,7 +224,7 @@ func MakeArtistPage(sortBy string, name string) (p ArtistPage) { p.Artist = artist p.bodyHTML = b.String() - return + return p } func (p ArtistPage) SourceFile() string { return "base.html" } @@ -254,7 +236,8 @@ type AlbumPage struct { bodyHTML string } -func MakeAlbumPage(album musicindex.Album) (p AlbumPage) { +func MakeAlbumPage(album musicindex.Album) AlbumPage { + var p AlbumPage var b strings.Builder b.WriteString(`
`) @@ -283,7 +266,7 @@ func MakeAlbumPage(album musicindex.Album) (p AlbumPage) { p.Album = album p.bodyHTML = b.String() - return + return p } func (p AlbumPage) SourceFile() string { return "base.html" } diff --git a/server/server.go b/server/server.go index a0a8ca9..ea8040d 100644 --- a/server/server.go +++ b/server/server.go @@ -1,6 +1,3 @@ -// Copyright (c) 2026 Jeremy Baxter. -// HTTP music server - package server import ( @@ -28,16 +25,6 @@ var artistPages map[string]map[string]ArtistPage // mapping of artist names to album names to pages var albumPages map[string]map[string]AlbumPage -func httpErrorReason(w http.ResponseWriter, error int, reason string) { - errorName := fmt.Sprintf("%d %s", error, http.StatusText(error)) - w.WriteHeader(error) - writePage(MakeErrorPage(errorName, reason), w) -} - -func httpError(w http.ResponseWriter, error int) { - httpErrorReason(w, error, "") -} - func doIndexPage(w http.ResponseWriter, req *http.Request) { writePage(indexPage, w) } @@ -74,13 +61,13 @@ func detectTextType(fileName string) string { func serveStaticFiles(w http.ResponseWriter, req *http.Request) { if strings.HasPrefix(req.RequestURI, "/static/templates/") { - httpError(w, http.StatusForbidden) + http.Error(w, "forbidden", http.StatusForbidden) return } contents, err := staticFS.ReadFile(req.RequestURI[1:]) if err != nil { - httpError(w, http.StatusNotFound) + http.Error(w, err.Error(), http.StatusNotFound) return } @@ -96,31 +83,30 @@ func serveStaticFiles(w http.ResponseWriter, req *http.Request) { func serveMediaDirectory(w http.ResponseWriter, req *http.Request) { path, err := url.QueryUnescape("/" + strings.SplitN(req.RequestURI[1:], "/", 2)[1]) if err != nil { - httpError(w, http.StatusBadRequest) + http.Error(w, "bad request", http.StatusBadRequest) return } filePath := musicindex.MediaDirectory() + path - if strings.Contains(path, "/../") || strings.Contains(path, "/./") { - httpError(w, http.StatusForbidden) + if strings.Contains(path, "/..") || strings.Contains(path, "/.") { + http.Error(w, "forbidden", http.StatusForbidden) return } stat, err := os.Stat(filePath) if err != nil { - httpError(w, http.StatusNotFound) + http.Error(w, "not found", http.StatusNotFound) return } if stat.IsDir() { - httpErrorReason(w, http.StatusForbidden, - "is a directory; maybe you are looking for " + path) + http.Error(w, "is a directory; maybe you are looking for " + path, http.StatusForbidden) return } f, err := os.Open(filePath) if err != nil { - httpError(w, http.StatusNotFound) + http.Error(w, "not found", http.StatusNotFound) return } defer f.Close() @@ -131,14 +117,14 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) { // test artist rx, err := regexp.Compile("^/([^/?]+)/?(\\?.*)?$") if err != nil { - httpError(w, http.StatusInternalServerError) + http.Error(w, "internal server error", http.StatusInternalServerError) return } if (rx.MatchString(req.RequestURI)) { artist, err := url.QueryUnescape(rx.FindStringSubmatch(req.RequestURI)[1]) if err != nil { - httpError(w, http.StatusBadRequest) + http.Error(w, "bad request", http.StatusBadRequest) return } if musicindex.ArtistExists(artist) { @@ -153,14 +139,14 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) { } // artist not found - httpError(w, http.StatusNotFound) + http.Error(w, "not found", http.StatusNotFound) return } // test album tarball rx, err = regexp.Compile("^/([^/?]+)/([^/?]+)\\.tar\\.gz$") if err != nil { - httpError(w, http.StatusInternalServerError) + http.Error(w, "internal server error", http.StatusInternalServerError) return } @@ -168,12 +154,12 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) { captures := rx.FindStringSubmatch(req.RequestURI) artist, err := url.QueryUnescape(captures[1]) if err != nil { - httpError(w, http.StatusBadRequest) + http.Error(w, "bad request", http.StatusBadRequest) return } albumName, err := url.QueryUnescape(captures[2]) if err != nil { - httpError(w, http.StatusBadRequest) + http.Error(w, "bad request", http.StatusBadRequest) return } @@ -182,7 +168,8 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) { if ok { f, err := os.Open(album.Tarball) if err != nil { - httpErrorReason(w, http.StatusInternalServerError, err.Error()) + http.Error(w, "internal server error: " + err.Error(), + http.StatusInternalServerError) return } defer f.Close() @@ -197,14 +184,14 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) { } // artist or album not found - httpError(w, http.StatusNotFound) + http.Error(w, "not found", http.StatusNotFound) return } // test album rx, err = regexp.Compile("^/([^/?]+)/([^/?]+)/?(\\?.*)?$") if err != nil { - httpErrorReason(w, http.StatusInternalServerError, err.Error()) + http.Error(w, "internal server error", http.StatusInternalServerError) return } @@ -212,12 +199,12 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) { captures := rx.FindStringSubmatch(req.RequestURI) artist, err := url.QueryUnescape(captures[1]) if err != nil { - httpError(w, http.StatusBadRequest) + http.Error(w, "bad request", http.StatusBadRequest) return } albumName, err := url.QueryUnescape(captures[2]) if err != nil { - httpError(w, http.StatusBadRequest) + http.Error(w, "bad request", http.StatusBadRequest) return } @@ -230,12 +217,12 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) { } // artist or album not found - httpError(w, http.StatusNotFound) + http.Error(w, "not found", http.StatusNotFound) return } // illegal URI format - httpError(w, http.StatusNotFound) + http.Error(w, "not found", http.StatusNotFound) } func RunOn(address string) { diff --git a/util/util.go b/util/util.go index abe7e6c..77149eb 100644 --- a/util/util.go +++ b/util/util.go @@ -1,6 +1,3 @@ -// Copyright (c) 2026 Jeremy Baxter. -// Reusable utility functions - package util import ( @@ -86,13 +83,9 @@ func HashOf(s string) string { return fmt.Sprintf("%x", h.Sum(nil)) } -func Interactive() bool { - return term.IsTerminal(int(os.Stderr.Fd())) -} - // print only if interactive func Iprint(format string, args ...any) { - if Interactive() { + if term.IsTerminal(int(os.Stderr.Fd())) { fmt.Fprintf(os.Stderr, format, args...) } }