initial commit
This commit is contained in:
commit
811a8bf5fa
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
music-library
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module git.milar.in/milarin/music-library
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.milar.in/milarin/envvars/v2 v2.0.0
|
||||||
|
git.milar.in/milarin/slices v0.0.6
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE=
|
||||||
|
git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY=
|
||||||
|
git.milar.in/milarin/slices v0.0.6 h1:AQoSarZ58WHYol9c6woWJSe8wFpPC2RC4cvIlZpfg9s=
|
||||||
|
git.milar.in/milarin/slices v0.0.6/go.mod h1:NOr53AOeur/qscu/FBj3lsFR262PNYBccLYSTCAXRk4=
|
||||||
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
55
library.go
Normal file
55
library.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.milar.in/milarin/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Playlist struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Songs []string `json:"songs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAll() ([]Playlist, error) {
|
||||||
|
playlistNames, err := GetPlaylists()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
playlists := make([]Playlist, 0, len(playlistNames))
|
||||||
|
for _, playlistName := range playlistNames {
|
||||||
|
songs, err := GetSongs(playlistName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
playlists = append(playlists, Playlist{
|
||||||
|
Name: playlistName,
|
||||||
|
Songs: songs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlaylists() ([]string, error) {
|
||||||
|
entries, err := os.ReadDir(LibraryPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
directories := slices.Filter(entries, And(IsDir, Not(IsHidden)))
|
||||||
|
return slices.Map(directories, FsEntry2Name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSongs(playlist string) ([]string, error) {
|
||||||
|
entries, err := os.ReadDir(filepath.Join(LibraryPath, playlist))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
directories := slices.Filter(entries, Not(IsDir))
|
||||||
|
return slices.Map(directories, FsEntry2Name), nil
|
||||||
|
}
|
107
main.go
Normal file
107
main.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.milar.in/milarin/envvars/v2"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
HttpIntf = envvars.String("HTTP_INTERFACE", "")
|
||||||
|
HttpPort = envvars.Uint16("HTTP_PORT", 80)
|
||||||
|
LibraryPath = envvars.String("LIBRARY_PATH", "/music")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
r.HandleFunc("/all/", GetAllHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/all/hash/", GetAllHashHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/playlist/", GetPlaylistsHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/playlist/{playlist}/", GetPlaylistHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/playlist/{playlist}/song/{song}/", GetSongHandler).Methods("GET")
|
||||||
|
|
||||||
|
fmt.Printf("Starting music server on port %d\n", HttpPort)
|
||||||
|
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", HttpIntf, HttpPort), r); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllHashHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
playlists, err := GetAll()
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
if err := json.NewEncoder(b).Encode(playlists); err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha512.Sum512(b.Bytes())
|
||||||
|
if _, err := fmt.Fprint(w, hex.EncodeToString(hash[:])); err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
playlists, err := GetAll()
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(playlists); err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSongHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
playlist := vars["playlist"]
|
||||||
|
song := vars["song"]
|
||||||
|
|
||||||
|
http.ServeFile(w, r, filepath.Join(LibraryPath, playlist, song))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlaylistHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
playlist := mux.Vars(r)["playlist"]
|
||||||
|
|
||||||
|
songs, err := GetSongs(playlist)
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(songs); err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlaylistsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
playlists, err := GetPlaylists()
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(playlists); err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
50
utils.go
Normal file
50
utils.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MethodNotAllowed(w http.ResponseWriter, allowedMethods ...string) {
|
||||||
|
for _, method := range allowedMethods {
|
||||||
|
w.Header().Add("Allow", strings.ToUpper(method))
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InternalServerError(w http.ResponseWriter, err error) {
|
||||||
|
log.Println(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func And[T any](f1, f2 func(T) bool) func(T) bool {
|
||||||
|
return func(v T) bool {
|
||||||
|
return f1(v) && f2(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Or[T any](f1, f2 func(T) bool) func(T) bool {
|
||||||
|
return func(v T) bool {
|
||||||
|
return f1(v) || f2(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Not[T any](f func(T) bool) func(T) bool {
|
||||||
|
return func(v T) bool {
|
||||||
|
return !f(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsDir(entry os.DirEntry) bool {
|
||||||
|
return entry.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsHidden(entry os.DirEntry) bool {
|
||||||
|
return strings.HasPrefix(entry.Name(), ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func FsEntry2Name(entry os.DirEntry) string {
|
||||||
|
return entry.Name()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user