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

Misc. small changes #479

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
191 changes: 124 additions & 67 deletions cmd/gonic/gonic.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//nolint:lll,gocyclo,forbidigo,nilerr,errcheck
package main

import (
Expand Down Expand Up @@ -49,119 +48,159 @@ import (
"go.senan.xyz/gonic/transcode"
)

func main() {
confListenAddr := flag.String("listen-addr", "0.0.0.0:4747", "listen address (optional)")
var errNotConfigured = errors.New("not configured")

//nolint:gochecknoglobals
var (
confListenAddr = flag.String("listen-addr", "0.0.0.0:4747", "listen address (optional)")

confTLSCert = flag.String("tls-cert", "", "path to TLS certificate (optional)")
confTLSKey = flag.String("tls-key", "", "path to TLS private key (optional)")

confPodcastPurgeAgeDays = flag.Uint("podcast-purge-age", 0, "age (in days) to purge podcast episodes if not accessed (optional)")
confPodcastPath = flag.String("podcast-path", "", "path to podcasts")

confTLSCert := flag.String("tls-cert", "", "path to TLS certificate (optional)")
confTLSKey := flag.String("tls-key", "", "path to TLS private key (optional)")
confCachePath = flag.String("cache-path", "", "path to cache")

confPodcastPurgeAgeDays := flag.Uint("podcast-purge-age", 0, "age (in days) to purge podcast episodes if not accessed (optional)")
confPodcastPath := flag.String("podcast-path", "", "path to podcasts")
confMusicPaths = flagVar[pathAliases]("music-path", "path to music")

confCachePath := flag.String("cache-path", "", "path to cache")
confPlaylistsPath = flag.String("playlists-path", "", "path to your list of new or existing m3u playlists that gonic can manage")

var confMusicPaths pathAliases
flag.Var(&confMusicPaths, "music-path", "path to music")
confDBPath = flag.String("db-path", "gonic.db", "path to database (optional)")

confPlaylistsPath := flag.String("playlists-path", "", "path to your list of new or existing m3u playlists that gonic can manage")
confScanIntervalMins = flag.Uint("scan-interval", 0, "interval (in minutes) to automatically scan music (optional)")
confScanAtStart = flag.Bool("scan-at-start-enabled", false, "whether to perform an initial scan at startup (optional)")
confScanWatcher = flag.Bool("scan-watcher-enabled", false, "whether to watch file system for new music and rescan (optional)")

confDBPath := flag.String("db-path", "gonic.db", "path to database (optional)")
confJukeboxEnabled = flag.Bool("jukebox-enabled", false, "whether the subsonic jukebox api should be enabled (optional)")
confJukeboxMPVExtraArgs = flag.String("jukebox-mpv-extra-args", "", "extra command line arguments to pass to the jukebox mpv daemon (optional)")

confScanIntervalMins := flag.Uint("scan-interval", 0, "interval (in minutes) to automatically scan music (optional)")
confScanAtStart := flag.Bool("scan-at-start-enabled", false, "whether to perform an initial scan at startup (optional)")
confScanWatcher := flag.Bool("scan-watcher-enabled", false, "whether to watch file system for new music and rescan (optional)")
confProxyPrefix = flag.String("proxy-prefix", "", "url path prefix to use if behind proxy. eg '/gonic' (optional)")
confHTTPLog = flag.Bool("http-log", true, "http request logging (optional)")

confJukeboxEnabled := flag.Bool("jukebox-enabled", false, "whether the subsonic jukebox api should be enabled (optional)")
confJukeboxMPVExtraArgs := flag.String("jukebox-mpv-extra-args", "", "extra command line arguments to pass to the jukebox mpv daemon (optional)")
confShowVersion = flag.Bool("version", false, "show gonic version")
confConfigPath = flag.String("config-path", "", "path to config (optional)")

confProxyPrefix := flag.String("proxy-prefix", "", "url path prefix to use if behind proxy. eg '/gonic' (optional)")
confHTTPLog := flag.Bool("http-log", true, "http request logging (optional)")
confExcludePattern = flag.String("exclude-pattern", "", "regex pattern to exclude files from scan (optional)")

confShowVersion := flag.Bool("version", false, "show gonic version")
confConfigPath := flag.String("config-path", "", "path to config (optional)")
confMultiValueGenre = flagVar[multiValueSetting]("multi-value-genre", "setting for mutli-valued genre scanning (optional)")
confMultiValueArtist = flagVar[multiValueSetting]("multi-value-artist", "setting for mutli-valued track artist scanning (optional)")
confMultiValueAlbumArtist = flagVar[multiValueSetting]("multi-value-album-artist", "setting for mutli-valued album artist scanning (optional)")

confExcludePattern := flag.String("exclude-pattern", "", "regex pattern to exclude files from scan (optional)")
confPprof = flag.Bool("pprof", false, "enable the /debug/pprof endpoint (optional)")
confExpvar = flag.Bool("expvar", false, "enable the /debug/vars endpoint (optional)")

var confMultiValueGenre, confMultiValueArtist, confMultiValueAlbumArtist multiValueSetting
flag.Var(&confMultiValueGenre, "multi-value-genre", "setting for mutli-valued genre scanning (optional)")
flag.Var(&confMultiValueArtist, "multi-value-artist", "setting for mutli-valued track artist scanning (optional)")
flag.Var(&confMultiValueAlbumArtist, "multi-value-album-artist", "setting for mutli-valued album artist scanning (optional)")
deprecatedConfGenreSplit = flag.String("genre-split", "", "(deprecated, see multi-value settings)")
)

type flagValue[T any] interface {
flag.Value
*T
}

confPprof := flag.Bool("pprof", false, "enable the /debug/pprof endpoint (optional)")
confExpvar := flag.Bool("expvar", false, "enable the /debug/vars endpoint (optional)")
func flagVar[T any, TPtr flagValue[T]](name, usage string) *T {
storage := new(T)
flag.Var((TPtr)(storage), name, usage)

deprecatedConfGenreSplit := flag.String("genre-split", "", "(deprecated, see multi-value settings)")
return storage
}

func loadConfig() error {
flag.Parse()
flagconf.ParseEnv()
flagconf.ParseConfig(*confConfigPath)

err := flagconf.ParseEnv()
if err != nil {
return err
}

err = flagconf.ParseConfig(*confConfigPath)
if err != nil {
return err
}

if *confShowVersion {
fmt.Printf("v%s\n", gonic.Version)
fmt.Printf("v%s\n", gonic.Version) //nolint:forbidigo
os.Exit(0)
}

if _, err := regexp.Compile(*confExcludePattern); err != nil {
log.Fatalf("invalid exclude pattern: %v\n", err)
return fmt.Errorf("invalid exclude pattern: %w", err)
}

if len(confMusicPaths) == 0 {
log.Fatalf("please provide a music directory")
if len(*confMusicPaths) == 0 {
return errors.New("please provide a music directory")
}

var err error
for i, confMusicPath := range confMusicPaths {
if confMusicPaths[i].path, err = validatePath(confMusicPath.path); err != nil {
log.Fatalf("checking music dir %q: %v", confMusicPath.path, err)
for i, confMusicPath := range *confMusicPaths {
if (*confMusicPaths)[i].path, err = validatePath(confMusicPath.path); err != nil {
return fmt.Errorf("checking music dir %q: %w", confMusicPath.path, err)
}
}

if *confPodcastPath, err = validatePath(*confPodcastPath); err != nil {
log.Fatalf("checking podcast directory: %v", err)
if *confPodcastPath, err = validatePath(*confPodcastPath); err != nil && *confPodcastPath != "" {
return fmt.Errorf("checking podcast directory: %w", err)
}
if *confCachePath, err = validatePath(*confCachePath); err != nil {
log.Fatalf("checking cache directory: %v", err)
return fmt.Errorf("checking cache directory: %w", err)
}
if *confPlaylistsPath, err = validatePath(*confPlaylistsPath); err != nil {
log.Fatalf("checking playlist directory: %v", err)
return fmt.Errorf("checking playlist directory: %w", err)
}

cacheDirAudio := path.Join(*confCachePath, "audio")
cacheDirCovers := path.Join(*confCachePath, "covers")
if err := os.MkdirAll(cacheDirAudio, os.ModePerm); err != nil {
log.Fatalf("couldn't create audio cache path: %v\n", err)
}
if err := os.MkdirAll(cacheDirCovers, os.ModePerm); err != nil {
log.Fatalf("couldn't create covers cache path: %v\n", err)
}
return nil
}

func newDBConn() (*db.DB, error) {
dbc, err := db.New(*confDBPath, db.DefaultOptions())
if err != nil {
log.Fatalf("error opening database: %v\n", err)
return nil, fmt.Errorf("error opening database: %w", err)
}
defer dbc.Close()

err = dbc.Migrate(db.MigrationContext{
Production: true,
DBPath: *confDBPath,
OriginalMusicPath: confMusicPaths[0].path,
OriginalMusicPath: (*confMusicPaths)[0].path,
PlaylistsPath: *confPlaylistsPath,
PodcastsPath: *confPodcastPath,
})
if err != nil {
log.Panicf("error migrating database: %v\n", err)
return nil, fmt.Errorf("error migrating database (PLEASE REPORT A BUG): %w", err)
}

return dbc, nil
}

//nolint:gocyclo
func main() {
if err := loadConfig(); err != nil {
log.Fatal(err.Error())
}

cacheDirAudio := path.Join(*confCachePath, "audio")
cacheDirCovers := path.Join(*confCachePath, "covers")
if err := os.MkdirAll(cacheDirAudio, os.ModePerm); err != nil {
log.Fatalf("couldn't create audio cache path: %v\n", err)
}
if err := os.MkdirAll(cacheDirCovers, os.ModePerm); err != nil {
log.Fatalf("couldn't create covers cache path: %v\n", err)
}

dbc, err := newDBConn()
if err != nil {
log.Fatal(err.Error())
}
defer dbc.Close()

var musicPaths []ctrlsubsonic.MusicPath
for _, pa := range confMusicPaths {
for _, pa := range *confMusicPaths {
musicPaths = append(musicPaths, ctrlsubsonic.MusicPath{Alias: pa.alias, Path: pa.path})
}

proxyPrefixExpr := regexp.MustCompile(`^\/*(.*?)\/*$`)
*confProxyPrefix = proxyPrefixExpr.ReplaceAllString(*confProxyPrefix, `/$1`)

if *deprecatedConfGenreSplit != "" && *deprecatedConfGenreSplit != "\n" {
confMultiValueGenre = multiValueSetting{Mode: scanner.Delim, Delim: *deprecatedConfGenreSplit}
*confMultiValueGenre = multiValueSetting{Mode: scanner.Delim, Delim: *deprecatedConfGenreSplit}
*deprecatedConfGenreSplit = "<deprecated>"
}
if confMultiValueArtist.Mode == scanner.None && confMultiValueAlbumArtist.Mode > scanner.None {
Expand All @@ -175,6 +214,11 @@ func main() {
log.Printf("starting gonic v%s\n", gonic.Version)
log.Printf("provided config\n")
flag.VisitAll(func(f *flag.Flag) {
switch f.Name {
case "version": // always "false"
return
}

value := strings.ReplaceAll(f.Value.String(), "\n", "")
log.Printf(" %-25s %s\n", f.Name, value)
})
Expand All @@ -189,9 +233,9 @@ func main() {
ctrlsubsonic.MusicPaths(musicPaths),
dbc,
map[scanner.Tag]scanner.MultiValueSetting{
scanner.Genre: scanner.MultiValueSetting(confMultiValueGenre),
scanner.Artist: scanner.MultiValueSetting(confMultiValueArtist),
scanner.AlbumArtist: scanner.MultiValueSetting(confMultiValueAlbumArtist),
scanner.Genre: scanner.MultiValueSetting(*confMultiValueGenre),
scanner.Artist: scanner.MultiValueSetting(*confMultiValueArtist),
scanner.AlbumArtist: scanner.MultiValueSetting(*confMultiValueAlbumArtist),
},
tagReader,
*confExcludePattern,
Expand All @@ -203,11 +247,20 @@ func main() {
)

lastfmClientKeySecretFunc := func() (string, string, error) {
apiKey, _ := dbc.GetSetting(db.LastFMAPIKey)
secret, _ := dbc.GetSetting(db.LastFMSecret)
apiKey, err := dbc.GetSetting(db.LastFMAPIKey)
if err != nil {
return "", "", err
}

secret, err := dbc.GetSetting(db.LastFMSecret)
if err != nil {
return "", "", err
}

if apiKey == "" || secret == "" {
return "", "", fmt.Errorf("not configured")
return "", "", errNotConfigured
}

return apiKey, secret, nil
}

Expand Down Expand Up @@ -413,7 +466,11 @@ func main() {

errgrp.Go(func() error {
if _, _, err := lastfmClientKeySecretFunc(); err != nil {
return nil
if errors.Is(err, errNotConfigured) {
return nil //nolint:nilerr
}

return err
}

defer logJob("refresh artist info")()
Expand Down Expand Up @@ -456,7 +513,7 @@ func main() {
log.Panic(err)
}

fmt.Println("shutdown complete")
log.Println("shutdown complete")
}

const pathAliasSep = "->"
Expand Down Expand Up @@ -489,10 +546,10 @@ func (pa *pathAliases) Set(value string) error {

func validatePath(p string) (string, error) {
if p == "" {
return "", errors.New("path can't be empty")
return "", errors.New("path configuration can't be empty")
}
if _, err := os.Stat(p); os.IsNotExist(err) {
return "", errors.New("path does not exist, please provide one")
return "", errors.New("path does not exist, please create it")
}
p, err := filepath.Abs(p)
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion db/migrations.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//nolint:goerr113
package db

import (
Expand Down
1 change: 0 additions & 1 deletion infocache/albuminfocache/albuminfocache.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//nolint:revive
package albuminfocache

import (
Expand Down
1 change: 0 additions & 1 deletion infocache/artistinfocache/artistinfocache.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//nolint:revive
package artistinfocache

import (
Expand Down
3 changes: 1 addition & 2 deletions scanner/scanner.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//nolint:nestif
package scanner

import (
Expand Down Expand Up @@ -328,7 +327,7 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, st *State, i int, album *db
}

// metadata for the album table comes only from the first track's tags
if i == 0 {
if i == 0 { //nolint:nestif
if err := tx.Where("album_id=?", album.ID).Delete(db.ArtistAppearances{}).Error; err != nil {
return fmt.Errorf("delete artist appearances: %w", err)
}
Expand Down
1 change: 0 additions & 1 deletion server/ctrladmin/handlers.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//nolint:goerr113
package ctrladmin

import (
Expand Down
4 changes: 3 additions & 1 deletion transcode/transcode.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// author: spijet (https://github.com/spijet/)
// author: sentriz (https://github.com/sentriz/)

//nolint:gochecknoglobals
package transcode

import (
Expand All @@ -18,6 +17,7 @@ type Transcoder interface {
Transcode(ctx context.Context, profile Profile, in string, out io.Writer) error
}

//nolint:gochecknoglobals
var UserProfiles = map[string]Profile{
"mp3": MP3,
"mp3_320": MP3320,
Expand All @@ -32,6 +32,8 @@ var UserProfiles = map[string]Profile{
}

// Store as simple strings, since we may let the user provide their own profiles soon
//
//nolint:gochecknoglobals
var (
MP3 = NewProfile("audio/mpeg", "mp3", 128, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libmp3lame -f mp3 -`)
MP3320 = NewProfile("audio/mpeg", "mp3", 320, `ffmpeg -v 0 -i <file> -ss <seek> -map 0:a:0 -vn -b:a <bitrate> -c:a libmp3lame -f mp3 -`)
Expand Down
11 changes: 7 additions & 4 deletions version.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
//nolint:gochecknoglobals,golint,stylecheck
package gonic

import (
_ "embed"
"strings"
)

//go:embed version.txt
var version string
var Version = strings.TrimSpace(version)
//nolint:gochecknoglobals
var (
//go:embed version.txt
version string

Version = strings.TrimSpace(version)
)

const (
Name = "gonic"
Expand Down
Loading