diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..6675e72b --- /dev/null +++ b/.clang-format @@ -0,0 +1,245 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: DontAlign +AlignArrayOfStructures: Left +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: All +AlwaysBreakAfterReturnType: All +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: false + AfterExternBlock: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: false + AfterUnion: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: true + IndentBraces: true + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: All +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Custom +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: ForIndentation +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/.dev/Makefile b/.dev/Makefile new file mode 100644 index 00000000..050c7c82 --- /dev/null +++ b/.dev/Makefile @@ -0,0 +1,7 @@ +.PHONY: vscode + +vscode: + mkdir -p ../.vscode + cp -rT ./vscode ../.vscode + mkdir -p ../.devcontainer + cp -rT ./devcontainer ../.devcontainer diff --git a/.dev/devcontainer/.scripts/init-devcontainer-cli.sh b/.dev/devcontainer/.scripts/init-devcontainer-cli.sh new file mode 100755 index 00000000..0e17335b --- /dev/null +++ b/.dev/devcontainer/.scripts/init-devcontainer-cli.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env sh + +# cd aliases +alias ..='cd ..' +alias ...='cd ../..' +alias -- -='cd -' + +# bat aliases +alias bat='batcat' + +if [ "$ENABLE_ESA" = "1" ]; then + if [ "$(command -v eza)" ]; then + alias l='eza -la --icons=auto --group-directories-first' + alias la='eza -la --icons=auto --group-directories-first' + alias ll='eza -l --icons=auto --group-directories-first' + alias l.='eza -d .*' + alias ls='eza' + alias l1='eza -1' + fi +fi diff --git a/.dev/devcontainer/.scripts/install-devcontainer-tools.sh b/.dev/devcontainer/.scripts/install-devcontainer-tools.sh new file mode 100755 index 00000000..64a9953d --- /dev/null +++ b/.dev/devcontainer/.scripts/install-devcontainer-tools.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# Install mkdocs with mkdocs-material theme +pipx install --include-deps mkdocs-material +pipx inject mkdocs-material mkdocs-minify-plugin + +# Starfish (https://starship.rs/) - shell prompt +if [ "$ENABLE_STARSHIP" = "1" ] +then + curl -sS https://starship.rs/install.sh | sh -s -- -y + echo 'eval "$(starship init bash)"' >> ~/.bashrc +fi + +# Atuin (https://atuin.sh/) - shell history +if [ "$ENABLE_ATUIN" = "1" ] +then + curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh + curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o ~/.bash-preexec.sh +fi + +# zoxide (https://github.com/ajeetdsouza/zoxide) - replacement for cd +if [ "$ENABLE_ZOXIDE" = "1" ] +then + curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh + echo 'eval "$(zoxide init bash)"' >> ~/.bashrc +fi + +pipx install harlequin +pipx install toolong +pipx install posting diff --git a/.dev/devcontainer/data/devcontainer-owntone.conf b/.dev/devcontainer/data/devcontainer-owntone.conf new file mode 100644 index 00000000..c7f7b399 --- /dev/null +++ b/.dev/devcontainer/data/devcontainer-owntone.conf @@ -0,0 +1,479 @@ +# A quick guide to configuring OwnTone: +# +# For regular use, the most important setting to configure is "directories", +# which should be the location of your media. Whatever user you have set as +# "uid" must have read access to this location. If the location is a network +# mount, please see the README. +# +# In all likelihood, that's all you need to do! + +general { + # Username + # Make sure the user has read access to the library directories you set + # below, and full access to the databases, log and local audio + uid = "vscode" + + # Database location + db_path = "/data/cache/songs3.db" + + # Database backup location + # Uncomment and specify a full path to enable abilty to use REST endpoint + # to initiate backup of songs3.db +# db_backup_path = "/usr/local/var/cache/owntone/songs3.bak" + + # Log file and level + # Available levels: fatal, log, warning, info, debug, spam + logfile = "/data/logs/owntone.log" + loglevel = debug + + # Admin password for the web interface + # Note that access to the web interface from computers in + # "trusted_network" (see below) does not require password +# admin_password = "" + + # Websocket port for the web interface. +# websocket_port = 0 + + # Websocket interface to bind listener to (e.g. "eth0"). Default is + # disabled, which means listen on all interfaces. +# websocket_interface = "" + + # Sets who is allowed to connect without authorisation. This applies to + # client types like Remotes, DAAP clients (iTunes) and to the web + # interface. Options are "any", "localhost" or the prefix to one or + # more ipv4/6 networks. The default is { "localhost", "192.168", "fd" } +# trusted_networks = { "localhost", "192.168", "fd" } + + # Enable/disable IPv6 +# ipv6 = no + + # Set this if you want the server to bind to a specific IP address. Can + # be ipv6 or ipv4. Default (commented out or "::") is to listen on all + # IP addresses. +# bind_address = "::" + + # Location of cache database + cache_path = "/data/cache/cache.db" + + # DAAP requests that take longer than this threshold (in msec) get their + # replies cached for next time. Set to 0 to disable caching. +# cache_daap_threshold = 1000 + + # When starting playback, autoselect speaker (if none of the previously + # selected speakers/outputs are available) +# speaker_autoselect = no + + # Most modern systems have a high-resolution clock, but if you are on an + # unusual platform and experience audio drop-outs, you can try changing + # this option +# high_resolution_clock = yes +} + +# Library configuration +library { + # Name of the library as displayed by the clients (%h: hostname). If you + # change the name after pairing with Remote you may have to re-pair. + name = "My Music on %h" + + # TCP port to listen on. Default port is 3689 (daap) + port = 3689 + + # Password for the library. Optional. +# password = "" + + # Directories to index + directories = { "/data/music" } + + # Follow symlinks. Default: true. +# follow_symlinks = true + + # Directories containing podcasts + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # podcasts. Eg. if you index /srv/music, and your podcasts are in + # /srv/music/Podcasts, you can set this to "/Podcasts". + # (changing this setting only takes effect after rescan, see the README) + podcasts = { "/Podcasts" } + + # Directories containing audiobooks + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # audiobooks. + # (changing this setting only takes effect after rescan, see the README) + audiobooks = { "/Audiobooks" } + + # Directories containing compilations (eg soundtracks) + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # compilations. + # (changing this setting only takes effect after rescan, see the README) + compilations = { "/Compilations" } + + # Compilations usually have many artists, and sometimes no album artist. + # If you don't want every artist to be listed in artist views, you can + # set a single name which will be used for all compilation tracks + # without an album artist, and for all tracks in the compilation + # directories. + # (changing this setting only takes effect after rescan, see the README) + compilation_artist = "Various Artists" + + # If your album and artist lists are cluttered, you can choose to hide + # albums and artists with only one track. The tracks will still be + # visible in other lists, e.g. songs and playlists. This setting + # currently only works in some remotes. +# hide_singles = false + + # Internet streams in your playlists will by default be shown in the + # "Radio" library, like iTunes does. However, some clients (like + # TunesRemote+) won't show the "Radio" library. If you would also like + # to have them shown like normal playlists, you can enable this option. +# radio_playlists = false + + # These are the default playlists. If you want them to have other names, + # you can set it here. +# name_library = "Library" +# name_music = "Music" +# name_movies = "Movies" +# name_tvshows = "TV Shows" +# name_podcasts = "Podcasts" +# name_audiobooks = "Audiobooks" +# name_radio = "Radio" + + # Artwork file names (without file type extension) + # OwnTone will look for jpg and png files with these base names +# artwork_basenames = { "artwork", "cover", "Folder" } + + # Enable searching for artwork corresponding to each individual media + # file instead of only looking for album artwork. This is disabled by + # default to reduce cache size. +# artwork_individual = false + + # File types the scanner should ignore + # Non-audio files will never be added to the database, but here you + # can prevent the scanner from even probing them. This might improve + # scan time. By default .db, .ini, .db-journal, .pdf and .metadata are + # ignored. +# filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf", ".metadata" } + + # File paths the scanner should ignore + # If you want to exclude files on a more advanced basis you can enter + # one or more POSIX regular expressions, and any file with a matching + # path will be ignored. +# filepath_ignore = { "myregex" } + + # Disable startup file scanning + # When OwnTone starts it will do an initial file scan of your + # library (and then watch it for changes). If you are sure your library + # never changes while OwnTone is not running, you can disable the + # initial file scan and save some system ressources. Disabling this scan + # may lead to OwnTone's database coming out of sync with the + # library. If that happens read the instructions in the README on how + # to trigger a rescan. +# filescan_disable = false + + # Only use the first genre found in metadata + # Some tracks have multiple genres semicolon-separated in the same tag, + # e.g. 'Pop;Rock'. If you don't want them listed like this, you can + # enable this option and only the first genre will be used (i.e. 'Pop'). +# only_first_genre = false + + # Should metadata from m3u playlists, e.g. artist and title in EXTINF, + # override the metadata we get from radio streams? +# m3u_overrides = false + + # Should iTunes metadata override ours? +# itunes_overrides = false + + # Should we import the content of iTunes smart playlists? +# itunes_smartpl = false + + # Decoding options for DAAP and RSP clients + # Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav, + # such files will be sent as they are. Any other formats will be decoded + # to raw wav. If OwnTone detects a non-iTunes DAAP client, it is + # assumed to only support mpeg and wav, other formats will be decoded. + # Here you can change when to decode. Note that these settings only + # affect serving media to DAAP and RSP clients, they have no effect on + # direct AirPlay, Chromecast and local audio playback. + # Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav + # Formats that should never be decoded +# no_decode = { "format", "format" } + # Formats that should always be decoded +# force_decode = { "format", "format" } + + # Set ffmpeg filters (similar to 'ffmpeg -af xxx') that you want the + # server to use when decoding files from your library. Examples: + # { 'volume=replaygain=track' } -> use REPLAYGAIN_TRACK_GAIN metadata + # { 'loudnorm=I=-16:LRA=11:TP=-1.5' } -> normalize volume +# decode_audio_filters = { } + + # Watch named pipes in the library for data and autostart playback when + # there is data to be read. To exclude specific pipes from watching, + # consider using the above _ignore options. +# pipe_autostart = true + + # Enable automatic rating updates + # If enabled, rating is automatically updated after a song has either been + # played or skipped (only skipping to the next song is taken into account). + # The calculation is taken from the beets plugin "mpdstats" (see + # https://beets.readthedocs.io/en/latest/plugins/mpdstats.html). + # It consist of calculating a stable rating based only on the play- and + # skipcount and a rolling rating based on the current rating and the action + # (played or skipped). Both results are combined with a mix-factor of 0.75: + # new rating = 0.75 * stable rating + 0.25 * rolling rating) +# rating_updates = false + + # By default, ratings are only saved in the server's database. Enable + # the below to make the server also read ratings from file metadata and + # write on update (requires write access). To avoid excessive writing to + # the library, automatic rating updates are not written, even with the + # write_rating option enabled. +# read_rating = false +# write_rating = false + # The scale used when reading/writing ratings to files +# max_rating = 100 + + # Allows creating, deleting and modifying m3u playlists in the library directories. + # Only supported by the player web interface and some mpd clients + # Defaults to being disabled. +# allow_modifying_stored_playlists = false + + # A directory in one of the library directories that will be used as the default + # playlist directory. OwnTone creates new playlists in this directory if only + # a playlist name is provided (requires "allow_modify_stored_playlists" set to true). +# default_playlist_directory = "" + + # By default OwnTone will - like iTunes - clear the playqueue if + # playback stops. Setting clear_queue_on_stop_disable to true will keep + # the playlist like MPD does. Note that some dacp clients do not show + # the playqueue if playback is stopped. +# clear_queue_on_stop_disable = false +} + +# Local audio output +audio { + # Name - used in the speaker list in Remote + nickname = "Computer" + + # Type of the output (alsa, pulseaudio, dummy or disabled) +# type = "alsa" + + # For pulseaudio output, an optional server hostname or IP can be + # specified (e.g. "localhost"). If not set, connection is made via local + # socket. +# server = "" + + # Audio PCM device name for local audio output - ALSA only +# card = "default" + + # Mixer channel to use for volume control - ALSA only + # If not set, PCM will be used if available, otherwise Master. +# mixer = "" + + # Mixer device to use for volume control - ALSA only + # If not set, the value for "card" will be used. +# mixer_device = "" + + # Enable or disable audio resampling to keep local audio in sync with + # e.g. Airplay. This feature relies on accurate ALSA measurements of + # delay, and some devices don't provide that. If that is the case you + # are better off disabling the feature. +# sync_disable = false + + # Here you can adjust when local audio is started relative to other + # speakers, e.g. Airplay. Negative values correspond to moving local + # audio ahead, positive correspond to delaying it. The unit is + # milliseconds. The offset must be between -1000 and 1000 (+/- 1 sec). +# offset_ms = 0 + + # To calculate what and if resampling is required, local audio delay is + # measured each second. After a period the collected measurements are + # used to estimate drift and latency, which determines if corrections + # are required. This setting sets the length of that period in seconds. +# adjust_period_seconds = 100 +} + +# ALSA device settings +# If you have multiple ALSA devices you can configure them individually via +# sections like the below. Make sure to set the "card name" correctly. See the +# README about ALSA for details. Note that these settings will override the ALSA +# settings in the "audio" section above. +#alsa "card name" { + # Name used in the speaker list. If not set, the card name will be used. +# nickname = "Computer" + + # Mixer channel to use for volume control + # If not set, PCM will be used if available, otherwise Master +# mixer = "" + + # Mixer device to use for volume control + # If not set, the card name will be used +# mixer_device = "" +#} + +# Pipe output +# Allows OwnTone to output audio data to a named pipe +#fifo { +# nickname = "fifo" +# path = "/path/to/fifo" +#} + +# AirPlay settings common to all devices +#airplay_shared { + # UDP ports used when airplay devices make connections back to + # OwnTone (choosing specific ports may be helpful when running + # OwnTone behind a firewall) +# control_port = 0 +# timing_port = 0 + + # Switch Airplay 1 streams to uncompressed ALAC (as opposed to regular, + # compressed ALAC). Reduces CPU use at the cost of network bandwidth. +# uncompressed_alac = false +#} + +# AirPlay per device settings +# (make sure you get the capitalization of the device name right) +#airplay "My AirPlay device" { + # OwnTone's volume goes to 11! If that's more than you can handle + # you can set a lower value here +# max_volume = 11 + + # Enable this option to exclude a particular AirPlay device from the + # speaker list +# exclude = false + + # Enable this option to keep a particular AirPlay device in the speaker + # list and thus ignore mdns notifications about it no longer being + # present. The speaker will remain until restart of OwnTone. +# permanent = false + + # Some devices spuriously disconnect during playback, and based on the + # device type OwnTone may attempt to reconnect. Setting this option + # overrides this so reconnecting is either always enabled or disabled. +# reconnect = false + + # AirPlay password +# password = "s1kr3t" + + # Disable AirPlay 1 (RAOP) +# raop_disable = false + + # Name used in the speaker list, overrides name from the device +# nickname = "My speaker name" +#} + +# Chromecast settings +# (make sure you get the capitalization of the device name right) +#chromecast "My Chromecast device" { + # OwnTone's volume goes to 11! If that's more than you can handle + # you can set a lower value here +# max_volume = 11 + + # Enable this option to exclude a particular device from the speaker + # list +# exclude = false + + # Name used in the speaker list, overrides name from the device +# nickname = "My speaker name" +#} + +# Spotify settings (only have effect if Spotify enabled - see README/INSTALL) +spotify { + # Set preferred bitrate for music streaming + # 0: No preference (default), 1: 96kbps, 2: 160kbps, 3: 320kbps +# bitrate = 0 + + # Your Spotify playlists will by default be put in a "Spotify" playlist + # folder. If you would rather have them together with your other + # playlists you can set this option to true. +# base_playlist_disable = false + + # Spotify playlists usually have many artist, and if you don't want + # every artist to be listed when artist browsing in Remote, you can set + # the artist_override flag to true. This will use the compilation_artist + # as album artist for Spotify items. +# artist_override = false + + # Similar to the different artists in Spotify playlists, the playlist + # items belong to different albums, and if you do not want every album + # to be listed when browsing in Remote, you can set the album_override + # flag to true. This will use the playlist name as album name for + # Spotify items. Notice that if an item is in more than one playlist, + # it will only appear in one album when browsing (in which album is + # random). +# album_override = false +} + +# RCP/Roku Soundbridge output settings +# (make sure you get the capitalization of the device name right) +#rcp "My SoundBridge device" { + # Enable this option to exclude a particular device from the speaker + # list +# exclude = false + + # A Roku/SoundBridge can power up in 2 modes: (default) reconnect to the + # previously used library (ie OwnTone) or in a 'cleared library' mode. + # The Roku power up behaviour is affected by how OwnTone disconnects + # from the Roku device. + # + # Set to false to maintain default Roku power on behaviour +# clear_on_close = false +#} + + +# MPD configuration (only have effect if MPD enabled - see README/INSTALL) +mpd { + # TCP port to listen on for MPD client requests. + # Default port is 6600, set to 0 to disable MPD support. +# port = 6600 + + # HTTP port to listen for artwork requests (only supported by some MPD + # clients and will need additional configuration in the MPD client to + # work). Set to 0 to disable serving artwork over http. +# http_port = 0 +} + +# SQLite configuration (allows to modify the operation of the SQLite databases) +# Make sure to read the SQLite documentation for the corresponding PRAGMA +# statements as changing them from the defaults may increase the possibility of +# database corruptions! By default the SQLite default values are used. +sqlite { + # Cache size in number of db pages for the library database + # (SQLite default page size is 1024 bytes and cache size is 2000 pages) +# pragma_cache_size_library = 2000 + + # Cache size in number of db pages for the daap cache database + # (SQLite default page size is 1024 bytes and cache size is 2000 pages) +# pragma_cache_size_cache = 2000 + + # Sets the journal mode for the database + # DELETE (default), TRUNCATE, PERSIST, MEMORY, WAL, OFF +# pragma_journal_mode = DELETE + + # Change the setting of the "synchronous" flag + # 0: OFF, 1: NORMAL, 2: FULL (default) +# pragma_synchronous = 2 + + # Number of bytes set aside for memory-mapped I/O for the library database + # (requires sqlite 3.7.17 or later) + # 0: disables mmap (default), any other value > 0: number of bytes for mmap +# pragma_mmap_size_library = 0 + + # Number of bytes set aside for memory-mapped I/O for the cache database + # (requires sqlite 3.7.17 or later) + # 0: disables mmap (default), any other value > 0: number of bytes for mmap +# pragma_mmap_size_cache = 0 + + # Should the database be vacuumed on startup? (increases startup time, + # but may reduce database size). Default is yes. +# vacuum = yes +} + +# Streaming audio settings for remote connections (ie stream.mp3) +streaming { + # Sample rate, typically 44100 or 48000 +# sample_rate = 44100 + + # Set the MP3 streaming bit rate (in kbps), valid options: 64 / 96 / 128 / 192 / 320 +# bit_rate = 192 +} diff --git a/.dev/devcontainer/devcontainer.env b/.dev/devcontainer/devcontainer.env new file mode 100644 index 00000000..715c87b9 --- /dev/null +++ b/.dev/devcontainer/devcontainer.env @@ -0,0 +1,11 @@ +# Starfish (https://starship.rs/) - shell prompt +ENABLE_STARSHIP=1 + +# Atuin (https://atuin.sh/) - shell history +ENABLE_ATUIN=1 + +# zoxide (https://github.com/ajeetdsouza/zoxide) - replacement for cd +ENABLE_ZOXIDE=1 + +# eza (https://eza.rocks/) - replacement for ls +ENABLE_ESA=1 diff --git a/.dev/devcontainer/ubuntu/Dockerfile b/.dev/devcontainer/ubuntu/Dockerfile new file mode 100644 index 00000000..15f4852e --- /dev/null +++ b/.dev/devcontainer/ubuntu/Dockerfile @@ -0,0 +1,73 @@ +FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 + +ARG USERNAME=vscode + +# Workaround for bug: https://github.com/devcontainers/images/issues/1056 +RUN userdel -r ubuntu; usermod -u 1000 $USERNAME; groupmod -g 1000 $USERNAME + +RUN apt-get -y update \ + && apt-get install -y \ + # Build tools and dependencies for OwnTone + autoconf \ + automake \ + autotools-dev \ + bison \ + build-essential \ + flex \ + gawk \ + gettext \ + git \ + gperf \ + libasound2-dev \ + libavahi-client-dev \ + libavcodec-dev \ + libavfilter-dev \ + libavformat-dev \ + libavutil-dev \ + libconfuse-dev \ + libcurl4-openssl-dev \ + libevent-dev \ + libgcrypt20-dev \ + libjson-c-dev \ + libmxml-dev \ + libplist-dev \ + libprotobuf-c-dev \ + libsodium-dev \ + libsqlite3-dev \ + libswscale-dev \ + libtool \ + libunistring-dev \ + libwebsockets-dev \ + libxml2-dev \ + zlib1g-dev \ + # Build tools for mmkdocs (OwnTone documentation) + python3-pip \ + # Additional runtime dependencies for dev container + avahi-daemon \ + # Additional debug and devtools for dev container + clang \ + clang-format \ + clang-tools \ + gdb \ + valgrind \ + # Additional terminal utility applications + pipx \ + # bat - replacement for cat + bat \ + # eza (https://eza.rocks/) - replacement for ls + eza \ + # fuzzy search + fzf \ + # Create folders and set ownership for folders that might be mounted as volumes + && mkdir -p /home/$USERNAME/.local/share \ + && chown -R $USERNAME /home/$USERNAME/.local \ + && mkdir /commandhistory \ + && touch /commandhistory/.bash_history \ + && chown -R $USERNAME /commandhistory \ + && echo "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" >> "/home/$USERNAME/.bashrc" \ + && echo '[[ -f /scripts/init-devcontainer-cli.sh ]] && source /scripts/init-devcontainer-cli.sh' >> "/home/$USERNAME/.bashrc" \ + # Create folders for owntone-server data + && mkdir -p /data/logs /data/music /data/cache /data/conf \ + && chown -R $USERNAME /data \ + # Clean up + && apt-get clean -y && rm -rf /var/lib/apt/lists/* diff --git a/.dev/devcontainer/ubuntu/devcontainer.json b/.dev/devcontainer/ubuntu/devcontainer.json new file mode 100644 index 00000000..6f5c6bab --- /dev/null +++ b/.dev/devcontainer/ubuntu/devcontainer.json @@ -0,0 +1,55 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu +{ + "name": "Ubuntu", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + // "image": "mcr.microsoft.com/devcontainers/base:jammy" + "build": { + "dockerfile": "Dockerfile" + }, + + "runArgs": [ + // Use host network to be able to connect to remote speakers + "--network=host", + "--env-file", ".devcontainer/devcontainer.env" + ], + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "Vue.volar", + "ms-vscode.cpptools-extension-pack", + "ms-azuretools.vscode-docker", + "lokalise.i18n-ally", + "esbenp.prettier-vscode" + ] + } + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers/features/node:1": {} + }, + + // Mounts volumes to keep files / state between container rebuilds + "mounts": [ + // Map script folder to install and init additional tools (see "postCreateCommand" and Dockerfile / .bashrc) + "source=${localWorkspaceFolder}/.devcontainer/.scripts,target=/scripts,type=bind,consistency=cached", + // Persist ~/.bash_history + "source=owntone-bashhistory,target=/commandhistory,type=volume", + // Persist ~/.local/share to persist state of additionally installed tools (e. g. atuin, zoxide) + "source=owntone-localshare,target=/home/vscode/.local/share,type=volume", + // Bind mounts for owntone config file and logs, cache, music directories + //"source=,target=/data/logs,type=bind,consistency=cached", + //"source=,target=/data/cache,type=bind,consistency=cached", + //"source=,target=/data/music,type=bind,consistency=cached", + "source=${localWorkspaceFolder}/.devcontainer/data/devcontainer-owntone.conf,target=/data/conf/owntone.conf,type=bind,consistency=cached" + ], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "bash /scripts/install-devcontainer-tools.sh", + + // Start dbus and avahi, required when running owntone-server + "postStartCommand": "sudo service dbus start ; sudo avahi-daemon -D" +} diff --git a/.dev/vscode/c_cpp_properties.json b/.dev/vscode/c_cpp_properties.json new file mode 100644 index 00000000..8c4dc691 --- /dev/null +++ b/.dev/vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/usr/include/json-c" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "linux-clang-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.dev/vscode/launch.json b/.dev/vscode/launch.json new file mode 100644 index 00000000..16d728c1 --- /dev/null +++ b/.dev/vscode/launch.json @@ -0,0 +1,28 @@ +{ + "configurations": [ + { + "name": "OwnTone", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/src/owntone", + "args": ["-f", "-c", "/data/conf/owntone.conf", "-w", "${workspaceFolder}/htdocs", "-s", "${workspaceFolder}/sqlext/.libs/owntone-sqlext.so"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/.dev/vscode/settings.json b/.dev/vscode/settings.json new file mode 100644 index 00000000..0510f4c3 --- /dev/null +++ b/.dev/vscode/settings.json @@ -0,0 +1,12 @@ +{ + "C_Cpp.default.forcedInclude": [ + "${workspaceFolder}/config.h" + ], + "[vue]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[c]": { + "editor.detectIndentation": false, + "editor.tabSize": 8 + } +} \ No newline at end of file diff --git a/.dev/vscode/tasks.json b/.dev/vscode/tasks.json new file mode 100644 index 00000000..d5f94c41 --- /dev/null +++ b/.dev/vscode/tasks.json @@ -0,0 +1,106 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "[server] autoreconf", + "type": "shell", + "command": "autoreconf -i", + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "[server] configure", + "type": "shell", + "command": "./configure", + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "[server] make", + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "[server] scan-build", + "type": "shell", + "command": "make clean && scan-build --status-bugs -disable-checker deadcode.DeadStores --exclude src/parsers make", + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "[server] clean", + "type": "shell", + "command": "make clean", + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "[web] install", + "type": "npm", + "script": "install", + "options": { + "cwd": "${workspaceFolder}/web-src" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "[web] build", + "type": "npm", + "script": "build", + "options": { + "cwd": "${workspaceFolder}/web-src" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "[web] serve", + "type": "npm", + "script": "serve", + "options": { + "cwd": "${workspaceFolder}/web-src" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "[docs] serve", + "type": "shell", + "command": "mkdocs serve", + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "[docs] build", + "type": "shell", + "command": "mkdocs build", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 432c91bd..d4ddb4c7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,11 @@ owntone@.service /.cproject /.project /.autotools -/.vscode # ignore MkDocs generated documentation /site/ +/test/ + +/.vscode/ +/.devcontainer/ +!/.dev/Makefile diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 00000000..acb03033 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,88 @@ +# Development + +## Dev Containers and VSCode + +To set up a development environment for OwnTone, the project includes an example Dev Containers configuration. + +!!! tip "Dev Containers" + To learn more about Dev Containers and how to use them check out the documentation at: + + - + - + +Dev Containers config for OwnTone includes all the necessary and some nice to have tooling: + +- C-tools to build and develop for owntone-server, including autotools, dependencies, etc. +- Javascript-tools to build and develop the OwnTone web interface. +- Python-tools to build and run the OwnTone documentation with mkdocs. + +### Prerquisites + +1. Install [Docker](https://www.docker.com/get-started). +2. Install [Visual Studio Code](https://code.visualstudio.com/). +3. Install the [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension for Visual Studio Code. + +### Initial setup + +The Dev Container and VSCode example configuration files are located in the project folder `.dev/devcontainer` and `.dev/vscode`. + +To make use of them follow these steps: + +1. Copy the directories or run `make vscode` from inside the `.dev` folder. +2. Open your project in Visual Studio Code. +3. Open the Command Palette (`Ctrl+Shift+P`) and select `Dev Containers: Reopen in Container`. +4. VSCode will build the container and reopen the project inside the container. + Be patient, the first run will take several minutes to complete. + +### Usage + +Inside the container you can follow the build instructions (see [Building](building.md)): + +- Build owntone-server + + ```bash + autoreconf -i + ./configure + make + ``` + +- Build web interface + + ```bash + cd web-src + npm run build + ``` + +Running `owntone-server` from inside the container with the predefined run/debug configuration will +use the conf file `.devcontainer/data/devcontainer-owntone.conf` as `owntone.conf`. + +Configure the mount configuration in `.devcontainer/devcontainer.json` to use a different music folder +or mount the log folder to a local directory. + +```json + // Mounts volumes to keep files / state between container rebuilds + "mounts": [ + //... + + // Bind mounts for owntone config file and logs, cache, music directories + //"source=,target=/data/logs,type=bind,consistency=cached", + //"source=,target=/data/cache,type=bind,consistency=cached", + //"source=,target=/data/music,type=bind,consistency=cached", + "source=${localWorkspaceFolder}/.devcontainer/data/devcontainer-owntone.conf,target=/data/conf/owntone.conf,type=bind,consistency=cached" + ], +``` + +### Dev Container configuration + +The Dev Container example uses an Ubuntu image as base. It contains some additional (opinionated) tools to customize the shell prompt and some terminal niceties: + +- [Starship](https://starship.rs/) to customize the shell prompt. +- [eza](https://eza.rocks/) as `ls` replacement. +- [Atuin](https://atuin.sh/) for the shell history. + +Take a look at `.devcontainer/devcontainer.env` if you want to disable any of those. + +Additional terminal tools installed are: + +- [zoxide](https://github.com/ajeetdsouza/zoxide) - a smarter `cd` +- [bat](https://github.com/sharkdp/bat) - a `cat` clone with syntax highlighting diff --git a/mkdocs.yml b/mkdocs.yml index e0f54b60..51f13862 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -158,4 +158,5 @@ nav: - Radio Streams: advanced/radio-streams.md - Remote Access: advanced/remote-access.md - Multiple Instances: advanced/multiple-instances.md + - Development: development.md - JSON API: json-api.md diff --git a/src/db.c b/src/db.c index f8e8618d..2b209b95 100644 --- a/src/db.c +++ b/src/db.c @@ -652,6 +652,7 @@ db_pl_type_label(enum pl_type pl_type) struct rng_ctx shuffle_rng; static char *db_path; +static char *db_sqlite_ext_path; static bool db_rating_updates; static __thread sqlite3 *hdl; @@ -6957,7 +6958,7 @@ db_open(void) return -1; } - ret = sqlite3_load_extension(hdl, PKGLIBDIR "/" PACKAGE_NAME "-sqlext.so", NULL, &errmsg); + ret = sqlite3_load_extension(hdl, db_sqlite_ext_path, NULL, &errmsg); if (ret != SQLITE_OK) { DPRINTF(E_LOG, L_DB, "Could not load SQLite extension: %s\n", errmsg); @@ -7371,7 +7372,7 @@ db_check_version(void) } int -db_init(void) +db_init(char *sqlite_ext_path) { uint32_t files; uint32_t pls; @@ -7388,6 +7389,7 @@ db_init(void) } db_path = cfg_getstr(cfg_getsec(cfg, "general"), "db_path"); + db_sqlite_ext_path = sqlite_ext_path; db_rating_updates = cfg_getbool(cfg_getsec(cfg, "library"), "rating_updates"); DPRINTF(E_INFO, L_DB, "Configured to use database file '%s'\n", db_path); diff --git a/src/db.h b/src/db.h index ef0663f3..e024df68 100644 --- a/src/db.h +++ b/src/db.h @@ -1029,7 +1029,7 @@ void db_perthread_deinit(void); int -db_init(void); +db_init(char *sqlite_ext_path); void db_deinit(void); diff --git a/src/main.c b/src/main.c index 3a16d083..659b18f5 100644 --- a/src/main.c +++ b/src/main.c @@ -73,8 +73,9 @@ # include "lastfm.h" #endif -#define PIDFILE STATEDIR "/run/" PACKAGE ".pid" -#define WEB_ROOT DATADIR "/htdocs" +#define PIDFILE STATEDIR "/run/" PACKAGE ".pid" +#define WEB_ROOT DATADIR "/htdocs" +#define SQLITE_EXT_PATH PKGLIBDIR "/" PACKAGE_NAME "-sqlext.so" struct event_base *evbase_main; @@ -106,6 +107,7 @@ usage(char *program) printf(" --mdns-no-daap Don't announce DAAP service via mDNS\n"); printf(" --mdns-no-cname Don't register owntone.local as CNAME via mDNS\n"); printf(" --mdns-no-web Don't announce web interface via mDNS\n"); + printf(" -s Use as the path for the OwnTone sqlite extension (owntone-sqlext.so)\n"); printf("\n\n"); printf("Available log domains:\n"); logger_domains(); @@ -495,6 +497,7 @@ main(int argc, char **argv) char *ffid = NULL; char *pidfile = PIDFILE; char *webroot = WEB_ROOT; + char *sqlite_extension_path = SQLITE_EXT_PATH; char **buildopts; const char *av_version; const char *gcry_version; @@ -516,6 +519,7 @@ main(int argc, char **argv) { "pidfile", 1, NULL, 'P' }, { "version", 0, NULL, 'v' }, { "webroot", 1, NULL, 'w' }, + { "sqliteext", 1, NULL, 's' }, { "testrun", 0, NULL, 't' }, // Used for CI, not documented to user { "mdns-no-rsp", 0, NULL, 512 }, @@ -526,7 +530,7 @@ main(int argc, char **argv) { NULL, 0, NULL, 0 } }; - while ((option = getopt_long(argc, argv, "D:d:c:P:ftb:vw:", option_map, NULL)) != -1) + while ((option = getopt_long(argc, argv, "D:d:c:P:ftb:vw:s:", option_map, NULL)) != -1) { switch (option) { @@ -587,6 +591,10 @@ main(int argc, char **argv) webroot = optarg; break; + case 's': + sqlite_extension_path = optarg; + break; + default: usage(argv[0]); return EXIT_FAILURE; @@ -739,7 +747,7 @@ main(int argc, char **argv) /* Initialize the database before starting */ DPRINTF(E_INFO, L_MAIN, "Initializing database\n"); - ret = db_init(); + ret = db_init(sqlite_extension_path); if (ret < 0) { DPRINTF(E_FATAL, L_MAIN, "Database init failed\n"); diff --git a/web-src/package.json b/web-src/package.json index 6563f3f2..08fe4983 100644 --- a/web-src/package.json +++ b/web-src/package.json @@ -3,7 +3,7 @@ "version": "2.0.1", "type": "module", "scripts": { - "serve": "vite --port 3000", + "serve": "vite --port 3000 --host", "build": "vite build --base='./'", "lint": "eslint", "dev": "vite",