mirror of
https://github.com/navidrome/navidrome.git
synced 2026-07-09 17:18:45 +00:00
* fix(scanner): import playlists skipped when no admin existed yet (#5499) On a fresh install the first scan runs before any admin user exists, so the scanner's playlist phase skips all playlists (playlists are owned by the first admin). Nothing re-imported them afterwards because folder selection is gated on updated_at > last_scan_at, which nothing bumps. The playlist phase now: - resolves the admin at phase time (FindFirstAdmin) instead of trusting the context snapshot taken at scan start, so a long admin-less scan still imports playlists in its own phase if an admin was created meanwhile; - records a persisted PlaylistsImportPending flag when no admin exists yet; - when that flag is set, imports ALL playlist folders via a new GetAllWithPlaylists (bypassing the timestamp gate) and clears the flag. Playlists are recovered by the next scan that runs with an admin, with no dependency on scan duration and no changes to the auth/server layers. * fix(scanner): surface datastore errors in playlist import deferral (#5499) Address review feedback: - distinguish model.ErrNotFound (no admin yet -> defer) from real datastore errors when resolving the admin, so DB failures are propagated, not swallowed; - propagate the error if the pending-import flag can't be persisted, so a scan doesn't complete as successful without recording the recovery; - surface read errors when checking the pending flag. Also name the no-admin condition for readability. * fix(scanner): simplify admin existence check in playlist import Signed-off-by: Deluan <deluan@navidrome.org> * fix(scanner): streamline folder access in playlist import logic Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
207 lines
6.8 KiB
Go
207 lines
6.8 KiB
Go
package consts
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/model/id"
|
|
)
|
|
|
|
const (
|
|
AppName = "navidrome"
|
|
|
|
DefaultDbPath = "navidrome.db?cache=shared&_busy_timeout=15000&_journal_mode=WAL&_foreign_keys=on&synchronous=normal"
|
|
InitialSetupFlagKey = "InitialSetup"
|
|
FullScanAfterMigrationFlagKey = "FullScanAfterMigration"
|
|
// PlaylistsImportPendingFlagKey marks that playlist import was deferred because
|
|
// no admin user existed yet; the next scan with an admin imports them.
|
|
PlaylistsImportPendingFlagKey = "PlaylistsImportPending"
|
|
LastScanErrorKey = "LastScanError"
|
|
LastScanTypeKey = "LastScanType"
|
|
LastScanStartTimeKey = "LastScanStartTime"
|
|
|
|
UIAuthorizationHeader = "X-ND-Authorization"
|
|
UIClientUniqueIDHeader = "X-ND-Client-Unique-Id"
|
|
JWTSecretKey = "JWTSecret"
|
|
JWTIssuer = "ND"
|
|
DefaultSessionTimeout = 48 * time.Hour
|
|
CookieExpiry = 365 * 24 * 3600 // One year
|
|
|
|
OptimizeDBSchedule = "@every 24h"
|
|
|
|
// DefaultEncryptionKey This is the encryption key used if none is specified in the `PasswordEncryptionKey` option
|
|
// Never ever change this! Or it will break all Navidrome installations that don't set the config option
|
|
DefaultEncryptionKey = "just for obfuscation"
|
|
PasswordsEncryptedKey = "PasswordsEncryptedKey"
|
|
PasswordAutogenPrefix = "__NAVIDROME_AUTOGEN__" //nolint:gosec
|
|
|
|
DevInitialUserName = "admin"
|
|
DevInitialName = "Dev Admin"
|
|
|
|
URLPathUI = "/app"
|
|
URLPathNativeAPI = "/api"
|
|
URLPathSubsonicAPI = "/rest"
|
|
URLPathPublic = "/share"
|
|
URLPathPublicImages = URLPathPublic + "/img"
|
|
|
|
// DefaultUILoginBackgroundURL uses Navidrome curated background images collection,
|
|
// available at https://unsplash.com/collections/20072696/navidrome
|
|
DefaultUILoginBackgroundURL = "/backgrounds"
|
|
|
|
// DefaultUILoginBackgroundOffline Background image used in case external integrations are disabled
|
|
DefaultUILoginBackgroundOffline = "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAAABGdBTUEAALGPC/xhBQAAAiJJREFUeF7t0IEAAAAAw6D5Ux/khVBhwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDDwMDDVlwABBWcSrQAAAABJRU5ErkJggg=="
|
|
DefaultUILoginBackgroundURLOffline = "data:image/png;base64," + DefaultUILoginBackgroundOffline
|
|
DefaultMaxSidebarPlaylists = 100
|
|
|
|
RequestThrottleBacklogLimit = 100
|
|
RequestThrottleBacklogTimeout = time.Minute
|
|
|
|
ServerReadHeaderTimeout = 3 * time.Second
|
|
|
|
DefaultInfoLanguage = "en"
|
|
|
|
ArtistInfoTimeToLive = 24 * time.Hour
|
|
AlbumInfoTimeToLive = 7 * 24 * time.Hour
|
|
UpdateLastAccessFrequency = time.Minute
|
|
UpdatePlayerFrequency = time.Minute
|
|
|
|
I18nFolder = "i18n"
|
|
ScanIgnoreFile = ".ndignore"
|
|
ArtworkFolder = "artwork"
|
|
|
|
PlaceholderArtistArt = "artist-placeholder.webp"
|
|
PlaceholderAlbumArt = "album-placeholder.webp"
|
|
PlaceholderAvatar = "logo-192x192.png"
|
|
DefaultUIVolume = 100
|
|
DefaultUISearchDebounceMs = 200
|
|
DefaultUIPlaybackReportInterval = time.Minute
|
|
|
|
DefaultHttpClientTimeOut = 10 * time.Second
|
|
|
|
DefaultListenBrainzBaseURL = "https://api.listenbrainz.org/1/"
|
|
DefaultListenBrainzArtistAlgorithm = "session_based_days_9000_session_300_contribution_5_threshold_15_limit_50_skip_30"
|
|
DefaultListenBrainzTrackAlgorithm = "session_based_days_9000_session_300_contribution_5_threshold_15_limit_50_skip_30"
|
|
|
|
DefaultScannerExtractor = "taglib"
|
|
DefaultWatcherWait = 5 * time.Second
|
|
Zwsp = string('\u200b')
|
|
)
|
|
|
|
const (
|
|
DefaultUICoverArtSize = 300
|
|
DefaultMaxImageUploadSize = "10MB"
|
|
)
|
|
|
|
// Prometheus options
|
|
const (
|
|
PrometheusDefaultPath = "/metrics"
|
|
PrometheusAuthUser = "navidrome"
|
|
)
|
|
|
|
// Cache options
|
|
const (
|
|
TranscodingCacheDir = "transcoding"
|
|
DefaultTranscodingCacheMaxItems = 0 // Unlimited
|
|
|
|
ImageCacheDir = "images"
|
|
DefaultImageCacheMaxItems = 0 // Unlimited
|
|
|
|
DefaultCacheSize = 100 * 1024 * 1024 // 100MB
|
|
DefaultCacheCleanUpInterval = 10 * time.Minute
|
|
)
|
|
|
|
// Entity types
|
|
const (
|
|
EntityArtist = "artist"
|
|
EntityPlaylist = "playlist"
|
|
EntityRadio = "radio"
|
|
)
|
|
|
|
const (
|
|
AlbumPlayCountModeAbsolute = "absolute"
|
|
AlbumPlayCountModeNormalized = "normalized"
|
|
)
|
|
|
|
const (
|
|
//DefaultAlbumPID = "album_legacy"
|
|
DefaultAlbumPID = "musicbrainz_albumid|albumartistid,album,albumversion,releasedate"
|
|
DefaultTrackPID = "musicbrainz_trackid|albumid,discnumber,tracknumber,title"
|
|
PIDAlbumKey = "PIDAlbum"
|
|
PIDTrackKey = "PIDTrack"
|
|
)
|
|
|
|
const (
|
|
InsightsIDKey = "InsightsID"
|
|
InsightsEndpoint = "https://insights.navidrome.org/collect"
|
|
InsightsUpdateInterval = 24 * time.Hour
|
|
InsightsInitialDelay = 30 * time.Minute
|
|
)
|
|
|
|
const (
|
|
PurgeMissingNever = "never"
|
|
PurgeMissingAlways = "always"
|
|
PurgeMissingFull = "full"
|
|
)
|
|
|
|
var (
|
|
DefaultDownsamplingFormat = "opus"
|
|
DefaultTranscodings = []struct {
|
|
Name string
|
|
TargetFormat string
|
|
DefaultBitRate int
|
|
Command string
|
|
}{
|
|
{
|
|
Name: "mp3 audio",
|
|
TargetFormat: "mp3",
|
|
DefaultBitRate: 192,
|
|
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -b:a %bk -v 0 -f mp3 -",
|
|
},
|
|
{
|
|
Name: "opus audio",
|
|
TargetFormat: "opus",
|
|
DefaultBitRate: 128,
|
|
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -b:a %bk -v 0 -c:a libopus -f opus -",
|
|
},
|
|
{
|
|
Name: "aac audio",
|
|
TargetFormat: "aac",
|
|
DefaultBitRate: 256,
|
|
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -b:a %bk -v 0 -c:a aac -f adts -",
|
|
},
|
|
{
|
|
Name: "flac audio",
|
|
TargetFormat: "flac",
|
|
DefaultBitRate: 0,
|
|
Command: "ffmpeg -ss %t -i %s -map 0:a:0 -v 0 -c:a flac -f flac -",
|
|
},
|
|
}
|
|
)
|
|
|
|
var HTTPUserAgent = "Navidrome" + "/" + Version
|
|
|
|
var (
|
|
VariousArtists = "Various Artists"
|
|
// TODO This will be dynamic when using disambiguation
|
|
VariousArtistsID = "63sqASlAfjbGMuLP4JhnZU"
|
|
UnknownAlbum = "[Unknown Album]"
|
|
UnknownArtist = "[Unknown Artist]"
|
|
// TODO This will be dynamic when using disambiguation
|
|
UnknownArtistID = id.NewHash(strings.ToLower(UnknownArtist))
|
|
VariousArtistsMbzId = "89ad4ac3-39f7-470e-963a-56509c546377"
|
|
|
|
ArtistJoiner = " • "
|
|
)
|
|
|
|
var (
|
|
ServerStart = time.Now()
|
|
|
|
InContainer = func() bool {
|
|
// Check if the /.nddockerenv file exists
|
|
if _, err := os.Stat("/.nddockerenv"); err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}()
|
|
)
|