Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go 1.21 upgrade, logger replacement #256

Merged
merged 5 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.21'
cache: false
- uses: actions/checkout@v3
- name: golangci-lint
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ $ ./install.sh

You will be prompted to provide the directory path to your music, a stream hostname, a rate limit timeout, a service password, and optional DNS. If you need help figuring out what values to use, refer the [Installation Guide](https://github.com/kenellorando/cadence/wiki/Installation#interactive-prompt-guide). Your radio stack will automatically launch and Cadence's web UI will become accessible at `localhost:8080`.

After initial installation, simply run `docker compose up` to start your station. Run `./install.sh` again at any time to reconfigure.
After initial installation, simply run `docker compose pull` to check for container updates, then `docker compose up` to start your station again. Run `./install.sh` again at any time to reconfigure.

## 🔬 Technical Details

Expand Down
4 changes: 2 additions & 2 deletions cadence/cadence.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# syntax=docker/dockerfile:1
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.20-bullseye as builder
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.21-bullseye as builder
ARG TARGETPLATFORM BUILDPLATFORM TARGETOS TARGETARCH
WORKDIR /cadence
COPY ./* ./
RUN go mod download
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-w -s" -o /cadence-server

ARG ARCH=
FROM ${ARCH}golang:1.20-alpine
FROM ${ARCH}golang:1.21-alpine
LABEL maintainer="Ken Ellorando (kenellorando.com)"
LABEL source="github.com/kenellorando/cadence"
COPY --from=builder /cadence/public /cadence/server/public
Expand Down
2 changes: 1 addition & 1 deletion cadence/icecast2.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ARG ARCH=
FROM ${ARCH}alpine:3
LABEL maintainer="Ken Ellorando (kenellorando.com)"
LABEL source="github.com/kenellorando/cadence"
RUN apk update && apk add icecast=2.4.4-r8
RUN apk update && apk add icecast=2.4.4-r10
EXPOSE 8000
USER icecast
CMD [ "icecast", "-c", "/etc/icecast/cadence.xml" ]
82 changes: 41 additions & 41 deletions cadence/server/api.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
// handlers.go
// API functions and fileservers.
// The functions are named exactly as their API paths are.
// API functions.
// The functions are named exactly the same as their API paths.
// See complete API documentation: https://github.com/kenellorando/cadence/wiki/API-Reference

package main

import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"os"
"strconv"

"github.com/dhowden/tag"
"github.com/kenellorando/clog"
)

// POST /api/search
// Receives a search query, which it looks in the database for.
// Returns a JSON list of text metadata (excluding art and path) of any matching songs.
func Search() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
clog.Debug("Search", fmt.Sprintf("Search request from client %s.", r.RemoteAddr))
slog.Debug(fmt.Sprintf("Search request from client %s.", r.RemoteAddr), "func", "Search")
type Search struct {
Query string `json:"search"`
}
var search Search
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&search)
if err != nil {
clog.Error("Search", "Unable to decode search body.", err)
slog.Error("Unable to decode search body.", "func", "Search", "error", err)
w.WriteHeader(http.StatusBadRequest) // 400 Bad Request
return
}
queryResults, err := searchByQuery(search.Query)
if err != nil {
clog.Error("Search", "Unable to execute search by query.", err)
slog.Error("Unable to execute search by query.", "func", "Search", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
jsonMarshal, err := json.Marshal(queryResults)
if err != nil {
clog.Error("Search", "Failed to marshal results from the search.", err)
slog.Error("Failed to marshal results from the search.", "func", "Search", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("Search", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "Search", "error", err)
return
}
}
Expand All @@ -59,33 +59,33 @@ func Search() http.HandlerFunc {
// This ID is translated to a filesystem path, which is passed to Liquidsoap for processing.
func RequestID() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
clog.Info("Request", fmt.Sprintf("Request-by-ID by client %s.", r.RemoteAddr))
slog.Info(fmt.Sprintf("Request-by-ID by client %s.", r.RemoteAddr), "func", "Request")
type Request struct {
ID string `json:"ID"`
}
var request Request
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&request)
if err != nil {
clog.Error("RequestID", "Unable to decode request.", err)
slog.Error("Unable to decode request.", "func", "RequestID", "error", err)
w.WriteHeader(http.StatusBadRequest) // 400 Bad Request
return
}
reqID, err := strconv.Atoi(request.ID)
if err != nil {
clog.Error("RequestID", "Unable to convert request ID to an integer.", err)
slog.Error("Unable to convert request ID to an integer.", "func", "RequestID", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
path, err := getPathById(reqID)
if err != nil {
clog.Error("RequestID", "Unable to find file path by song ID.", err)
slog.Error("Unable to find file path by song ID.", "func", "RequestID", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
_, err = liquidsoapRequest(path)
if err != nil {
clog.Error("RequestID", "Unable to submit song request.", err)
slog.Error("Unable to submit song request.", "func", "RequestID", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
Expand All @@ -98,33 +98,33 @@ func RequestID() http.HandlerFunc {
// The number one result of the search has its path taken and submitted to Liquidsoap for processing.
func RequestBestMatch() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
clog.Debug("Search", fmt.Sprintf("Decoding http-request data from client %s.", r.RemoteAddr))
slog.Debug(fmt.Sprintf("Decoding http-request data from client %s.", r.RemoteAddr), "func", "Search")
type RequestBestMatch struct {
Query string `json:"Search"`
}
var rbm RequestBestMatch
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&rbm)
if err != nil {
clog.Error("RequestBestMatch", "Unable to decode request body.", err)
slog.Error("Unable to decode request body.", "func", "RequestBestMatch", "error", err)
w.WriteHeader(http.StatusBadRequest) // 400 Bad Request
return
}
queryResults, err := searchByQuery(rbm.Query)
if err != nil {
clog.Error("RequestBestMatch", "Unable to search by query.", err)
slog.Error("Unable to search by query.", "func", "RequestBestMatch", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
path, err := getPathById(queryResults[0].ID)
if err != nil {
clog.Error("RequestBestMatch", "Unable to find file path by song ID", err)
slog.Error("Unable to find file path by song ID", "func", "RequestBestMatch", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
_, err = liquidsoapRequest(path)
if err != nil {
clog.Error("RequestBestMatch", "Unable to submit song request.", err)
slog.Error("Unable to submit song request.", "func", "RequestBestMatch", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
Expand All @@ -138,25 +138,25 @@ func NowPlayingMetadata() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
queryResults, err := searchByTitleArtist(now.Song.Title, now.Song.Artist)
if err != nil {
clog.Error("NowPlayingMetadata", "Unable to search by title and artist.", err)
slog.Error("Unable to search by title and artist.", "func", "NowPlayingMetadata", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
if len(queryResults) < 1 {
clog.Warn("NowPlayingMetadata", "The currently playing song could not be found in the database. The database may not be populated.")
slog.Warn("The currently playing song could not be found in the database. The database may not be populated.", "func", "NowPlayingMetadata")
w.WriteHeader(http.StatusNotFound) // 404 Not Found
return
}
jsonMarshal, err := json.Marshal(queryResults[0])
if err != nil {
clog.Error("NowPlayingMetadata", "Failed to marshal results from the search.", err)
slog.Error("Failed to marshal results from the search.", "func", "NowPlayingMetadata", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("NowPlayingMetadata", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "NowPlayingMetadata", "error", err)
return
}
}
Expand All @@ -168,35 +168,35 @@ func NowPlayingAlbumArt() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
queryResults, err := searchByTitleArtist(now.Song.Title, now.Song.Artist)
if err != nil {
clog.Error("NowPlayingAlbumArt", "Unable to search by title and artist.", err)
slog.Error("Unable to search by title and artist.", "func", "NowPlayingAlbumArt", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
if len(queryResults) < 1 {
clog.Warn("NowPlayingAlbumArt", "The currently playing song could not be found in the database. The database may not be populated.")
slog.Warn("The currently playing song could not be found in the database. The database may not be populated.", "func", "NowPlayingAlbumArt")
w.WriteHeader(http.StatusNotFound) // 404 Not Found
return
}
path, err := getPathById(queryResults[0].ID)
if err != nil {
clog.Error("NowPlayingAlbumArt", "Unable to find file path by song ID.", err)
slog.Error("Unable to find file path by song ID.", "func", "NowPlayingAlbumArt", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
file, err := os.Open(path)
if err != nil {
clog.Error("NowPlayingAlbumArt", "Unable to open a file for album art extraction.", err)
slog.Error("Unable to open a file for album art extraction.", "func", "NowPlayingAlbumArt", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
tags, err := tag.ReadFrom(file)
if err != nil {
clog.Error("NowPlayingAlbumArt", "Unable to read tags on file for art extraction.", err)
slog.Error("Unable to read tags on file for art extraction.", "func", "NowPlayingAlbumArt", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
if tags.Picture() == nil {
clog.Debug("NowPlayingAlbumArt", "The currently playing song has no album art metadata.")
slog.Debug("The currently playing song has no album art metadata.", "func", "NowPlayingAlbumArt")
w.WriteHeader(http.StatusNoContent) // 204 No Content
return
}
Expand All @@ -206,14 +206,14 @@ func NowPlayingAlbumArt() http.HandlerFunc {
result := SongData{Picture: tags.Picture().Data}
jsonMarshal, err := json.Marshal(result)
if err != nil {
clog.Error("NowPlayingAlbumArt", "Failed to marshal art data.", err)
slog.Error("Failed to marshal art data.", "func", "NowPlayingAlbumArt", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("NowPlayingAlbumArt", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "NowPlayingAlbumArt", "error", err)
return
}
}
Expand All @@ -225,14 +225,14 @@ func History() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
jsonMarshal, err := json.Marshal(history)
if err != nil {
clog.Error("History", "Failed to marshal play history.", err)
slog.Error("Failed to marshal play history.", "func", "History", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("History", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "History", "error", err)
return
}
}
Expand All @@ -248,14 +248,14 @@ func ListenURL() http.HandlerFunc {
listenurl := ListenURL{ListenURL: string(now.Host + "/" + now.Mountpoint)}
jsonMarshal, err := json.Marshal(listenurl)
if err != nil {
clog.Error("ListenURL", "Failed to marshal listen URL.", err)
slog.Error("Failed to marshal listen URL.", "func", "ListenURL", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("ListenURL", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "ListenURL", "error", err)
return
}
}
Expand All @@ -271,14 +271,14 @@ func Listeners() http.HandlerFunc {
listeners := Listeners{Listeners: int(now.Listeners)}
jsonMarshal, err := json.Marshal(listeners)
if err != nil {
clog.Error("Listeners", "Failed to marshal listeners.", err)
slog.Error("Failed to marshal listeners.", "func", "Listeners", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("Listeners", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "Listeners", "error", err)
return
}
}
Expand All @@ -294,14 +294,14 @@ func Bitrate() http.HandlerFunc {
bitrate := Bitrate{Bitrate: int(now.Bitrate)}
jsonMarshal, err := json.Marshal(bitrate)
if err != nil {
clog.Error("Bitrate", "Failed to marshal bitrate.", err)
slog.Error("Failed to marshal bitrate.", "func", "Bitrate", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("Bitrate", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "Bitrate", "error", err)
return
}
}
Expand All @@ -317,14 +317,14 @@ func Version() http.HandlerFunc {
version := Version{Version: c.Version}
jsonMarshal, err := json.Marshal(version)
if err != nil {
clog.Error("Version", "Failed to marshal version.", err)
slog.Error("Failed to marshal version.", "func", "Version", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonMarshal)
if err != nil {
clog.Error("Version", "Failed to write response.", err)
slog.Error("Failed to write response.", "func", "Version", "error", err)
return
}
}
Expand All @@ -345,7 +345,7 @@ func DevSkip() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, err := liquidsoapSkip()
if err != nil {
clog.Error("DevSkip", "Unable to skip the playing song.", err)
slog.Error("Unable to skip the playing song.", "func", "DevSkip", "error", err)
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
return
}
Expand Down
Loading