Commit d1e51243 authored by Konstantin A. Lepikhov's avatar Konstantin A. Lepikhov

Merge tag 'v0.23.8' of https://github.com/MusicPlayerDaemon/MPD into sisyphus

release v0.23.8
parents 1c467988 1f287904

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

BasedOnStyle: LLVM
AccessModifierOffset: -8
AllowAllConstructorInitializersOnNextLine: false
AlwaysBreakTemplateDeclarations: Yes
BraceWrapping:
AfterFunction: true
SplitEmptyFunction: true
BreakConstructorInitializers: BeforeColon
ColumnLimit: 90
ConstructorInitializerIndentWidth: 0
ContinuationIndentWidth: 8
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 8
Standard: c++17
TabWidth: 8
UseTab: Always
---
name: Bug report
about: Create a bug report
---
<!-- See https://www.musicpd.org/help/ -->
## Bug report
### Describe the bug
## Expected Behavior
## Actual Behavior
## Version
<!-- Paste the output of "mpd --version" here -->
## Configuration
<!-- Paste your MPD configuration here -->
## Log
<!-- Paste relevant portions of the log file here (--verbose) -->
---
name: Feature request
about: Create a feature request
---
## Feature request
---
on:
workflow_dispatch:
push:
paths-ignore:
- 'android/**'
- 'build/**'
- 'doc/**'
- 'python/**'
- 'subprojects/**'
- 'systemd/**'
- 'win32/**'
branches:
- master
- v0.23.x
pull_request:
paths-ignore:
- 'android/**'
- 'build/**'
- 'doc/**'
- 'python/**'
- 'subprojects/**'
- 'systemd/**'
- 'win32/**'
branches:
- master
- v0.23.x
jobs:
build-linux:
runs-on: ubuntu-latest
env:
CC: 'ccache gcc-10'
CXX: 'ccache g++-10'
steps:
- id: checkout
uses: actions/checkout@v2
- id: cache-ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: linux
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
g++-10 libfmt-dev libboost-dev \
libgtest-dev \
libpcre2-dev \
libsystemd-dev libdbus-1-dev \
libicu-dev \
libcurl4-gnutls-dev \
libpcre2-dev \
libavahi-client-dev \
libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev libogg-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
libmpcdec-dev libwavpack-dev libwildmidi-dev \
libsidplay2-dev libsidutils-dev libresid-builder-dev \
libavcodec-dev libavformat-dev \
libmp3lame-dev libtwolame-dev libshine-dev \
libsamplerate0-dev libsoxr-dev \
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
libzzip-dev \
libyajl-dev libexpat-dev \
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
libpulse-dev libshout3-dev \
libsndio-dev \
libmpdclient-dev \
libnfs-dev \
libupnp-dev \
libsqlite3-dev \
libchromaprint-dev \
libgcrypt20-dev
- name: Full Build
uses: BSFishy/meson-build@v1.0.3
with:
action: build
directory: output/full
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
options: --verbose
meson-version: 0.56.0
- name: Unit Tests
uses: BSFishy/meson-build@v1.0.3
with:
action: test
directory: output/full
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
options: --verbose
meson-version: 0.56.0
- name: Mini Build
uses: BSFishy/meson-build@v1.0.3
with:
action: build
directory: output/mini
setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
options: --verbose
meson-version: 0.56.0
build-macos:
runs-on: macos-latest
steps:
- id: checkout
uses: actions/checkout@v2
- id: cache-ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: macos
- uses: actions/setup-python@v1
- name: Install dependencies
run: |
brew install \
meson ninja \
fmt \
boost \
googletest \
icu4c \
ffmpeg \
libnfs \
yajl \
libupnp \
libid3tag \
chromaprint \
libsamplerate \
libsoxr \
flac \
opus \
libvorbis \
faad2 \
wavpack \
libmpdclient
- name: Build
uses: BSFishy/meson-build@v1.0.3
with:
action: build
directory: output
setup-options: -Ddocumentation=disabled -Dtest=true
options: --verbose
meson-version: 0.56.0
- name: Unit Tests
uses: BSFishy/meson-build@v1.0.3
with:
action: test
directory: output
setup-options: -Ddocumentation=disabled -Dtest=true
options: --verbose
meson-version: 0.56.0
...@@ -2,96 +2,40 @@ language: cpp ...@@ -2,96 +2,40 @@ language: cpp
jobs: jobs:
include: include:
# Ubuntu Bionic (18.04) with GCC 7 # Ubuntu Focal (20.04) with GCC 9.3
- os: linux - os: linux
dist: bionic dist: focal
addons: addons:
apt: apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages: packages:
- meson
- libgtest-dev - libgtest-dev
- libboost-dev - libboost-dev
- python3.6 - libfmt-dev
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Bionic (18.04) with GCC 7 on big-endian # Ubuntu Focal (20.04) with GCC 9.3 on big-endian
- os: linux - os: linux
arch: s390x arch: s390x
dist: bionic dist: focal
addons: addons:
apt: apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages: packages:
- meson
- libgtest-dev - libgtest-dev
- libboost-dev - libboost-dev
- python3.6 - libfmt-dev
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Bionic (18.04) with GCC 7 on ARM64 # Ubuntu Focal (20.04) with GCC 9.3 on ARM64
- os: linux - os: linux
arch: arm64 arch: arm64
dist: bionic dist: focal
addons: addons:
apt: apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages: packages:
- meson
- libgtest-dev - libgtest-dev
- libboost-dev - libboost-dev
- python3.6 - libfmt-dev
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Trusty (16.04) with GCC 6
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:mhier/libboost-latest'
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- g++-6
- libgtest-dev
- boost1.67
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Trusty (16.04) with GCC 8 # Ubuntu Trusty (16.04) with GCC 8
- os: linux - os: linux
...@@ -101,7 +45,7 @@ jobs: ...@@ -101,7 +45,7 @@ jobs:
sources: sources:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
- sourceline: 'ppa:mhier/libboost-latest' - sourceline: 'ppa:mhier/libboost-latest'
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2' - sourceline: 'ppa:ricotz/toolchain'
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson) - sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages: packages:
- g++-8 - g++-8
...@@ -120,12 +64,14 @@ jobs: ...@@ -120,12 +64,14 @@ jobs:
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH" - MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
- os: osx - os: osx
osx_image: xcode9.4 osx_image: xcode11.6
addons: addons:
homebrew: homebrew:
packages: packages:
- ccache - ccache
- meson - meson
- fmt
- googletest
- icu4c - icu4c
- ffmpeg - ffmpeg
- libnfs - libnfs
...@@ -143,7 +89,6 @@ jobs: ...@@ -143,7 +89,6 @@ jobs:
- faad2 - faad2
- wavpack - wavpack
- libmpdclient - libmpdclient
update: true
env: env:
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1" - MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
...@@ -160,13 +105,6 @@ before_install: ...@@ -160,13 +105,6 @@ before_install:
- eval "${MATRIX_EVAL}" - eval "${MATRIX_EVAL}"
install: install:
# C++14
# Work around "Target /usr/local/lib/libgtest.a is a symlink
# belonging to nss. You can unlink it" during gtest install
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
- test "$TRAVIS_OS_NAME" != "osx" || brew install https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
before_script: before_script:
- ccache -s - ccache -s
......
Music Player Daemon - http://www.musicpd.org Music Player Daemon - http://www.musicpd.org
Copyright 2003-2018 The Music Player Daemon Project Copyright 2003-2021 The Music Player Daemon Project
The following people have contributed code to MPD: The following people have contributed code to MPD:
......
...@@ -14,7 +14,7 @@ For basic installation instructions ...@@ -14,7 +14,7 @@ For basic installation instructions
- [Manual](http://www.musicpd.org/doc/user/) - [Manual](http://www.musicpd.org/doc/user/)
- [Forum](http://forum.musicpd.org/) - [Forum](http://forum.musicpd.org/)
- [IRC](irc://chat.freenode.net/#mpd) - [IRC](ircs://irc.libera.chat:6697/#mpd)
- [Bug tracker](https://github.com/MusicPlayerDaemon/MPD/issues/) - [Bug tracker](https://github.com/MusicPlayerDaemon/MPD/issues/)
# Developers # Developers
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd" package="org.musicpd"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="47" android:versionCode="66"
android:versionName="0.21.24"> android:versionName="0.23.7">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
<uses-feature android:name="android.software.leanback" <uses-feature android:name="android.software.leanback"
android:required="false" /> android:required="false" />
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true" <application android:allowBackup="true"
android:debuggable="true"
android:requestLegacyExternalStorage="true"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:banner="@drawable/icon" android:banner="@drawable/icon"
android:label="@string/app_name"> android:label="@string/app_name">
......
...@@ -13,7 +13,7 @@ android_abi = sys.argv[3] ...@@ -13,7 +13,7 @@ android_abi = sys.argv[3]
configure_args = sys.argv[4:] configure_args = sys.argv[4:]
if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')): if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
print("SDK not found in", ndk_path, file=sys.stderr) print("SDK not found in", sdk_path, file=sys.stderr)
sys.exit(1) sys.exit(1)
if not os.path.isdir(ndk_path): if not os.path.isdir(ndk_path):
...@@ -24,15 +24,13 @@ android_abis = { ...@@ -24,15 +24,13 @@ android_abis = {
'armeabi-v7a': { 'armeabi-v7a': {
'arch': 'arm-linux-androideabi', 'arch': 'arm-linux-androideabi',
'ndk_arch': 'arm', 'ndk_arch': 'arm',
'toolchain_arch': 'arm-linux-androideabi',
'llvm_triple': 'armv7-linux-androideabi', 'llvm_triple': 'armv7-linux-androideabi',
'cflags': '-fpic -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp', 'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
}, },
'arm64-v8a': { 'arm64-v8a': {
'arch': 'aarch64-linux-android', 'arch': 'aarch64-linux-android',
'ndk_arch': 'arm64', 'ndk_arch': 'arm64',
'toolchain_arch': 'aarch64-linux-android',
'llvm_triple': 'aarch64-linux-android', 'llvm_triple': 'aarch64-linux-android',
'cflags': '-fpic', 'cflags': '-fpic',
}, },
...@@ -40,7 +38,6 @@ android_abis = { ...@@ -40,7 +38,6 @@ android_abis = {
'x86': { 'x86': {
'arch': 'i686-linux-android', 'arch': 'i686-linux-android',
'ndk_arch': 'x86', 'ndk_arch': 'x86',
'toolchain_arch': 'x86',
'llvm_triple': 'i686-linux-android', 'llvm_triple': 'i686-linux-android',
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32', 'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
}, },
...@@ -48,7 +45,6 @@ android_abis = { ...@@ -48,7 +45,6 @@ android_abis = {
'x86_64': { 'x86_64': {
'arch': 'x86_64-linux-android', 'arch': 'x86_64-linux-android',
'ndk_arch': 'x86_64', 'ndk_arch': 'x86_64',
'toolchain_arch': 'x86_64',
'llvm_triple': 'x86_64-linux-android', 'llvm_triple': 'x86_64-linux-android',
'cflags': '-fPIC -m64', 'cflags': '-fPIC -m64',
}, },
...@@ -84,36 +80,29 @@ class AndroidNdkToolchain: ...@@ -84,36 +80,29 @@ class AndroidNdkToolchain:
ndk_arch = abi_info['ndk_arch'] ndk_arch = abi_info['ndk_arch']
android_api_level = '21' android_api_level = '21'
# select the NDK compiler
gcc_version = '4.9'
install_prefix = os.path.join(arch_path, 'root') install_prefix = os.path.join(arch_path, 'root')
self.arch = arch self.arch = arch
self.actual_arch = arch
self.install_prefix = install_prefix self.install_prefix = install_prefix
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch) llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = abi_info['llvm_triple'] + android_api_level llvm_triple = abi_info['llvm_triple'] + android_api_level
common_flags = '-Os -g' common_flags = '-Os -g'
common_flags += ' ' + abi_info['cflags'] common_flags += ' ' + abi_info['cflags']
toolchain_bin = os.path.join(toolchain_path, 'bin')
llvm_bin = os.path.join(llvm_path, 'bin') llvm_bin = os.path.join(llvm_path, 'bin')
self.cc = os.path.join(llvm_bin, 'clang') self.cc = os.path.join(llvm_bin, 'clang')
self.cxx = os.path.join(llvm_bin, 'clang++') self.cxx = os.path.join(llvm_bin, 'clang++')
common_flags += ' -target ' + llvm_triple + ' -integrated-as -gcc-toolchain ' + toolchain_path common_flags += ' -target ' + llvm_triple
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections' common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
# required flags from https://android.googlesource.com/platform/ndk/+/ndk-release-r20/docs/BuildSystemMaintainers.md#additional-required-arguments self.ar = os.path.join(llvm_bin, 'llvm-ar')
common_flags += ' -fno-addrsig' self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
self.nm = os.path.join(llvm_bin, 'llvm-nm')
self.ar = os.path.join(toolchain_bin, arch + '-ar') self.strip = os.path.join(llvm_bin, 'llvm-strip')
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
self.nm = os.path.join(toolchain_bin, arch + '-nm')
self.strip = os.path.join(toolchain_bin, arch + '-strip')
self.cflags = common_flags self.cflags = common_flags
self.cxxflags = common_flags self.cxxflags = common_flags
...@@ -151,10 +140,7 @@ class AndroidNdkToolchain: ...@@ -151,10 +140,7 @@ class AndroidNdkToolchain:
# default one on the build host # default one on the build host
import shutil import shutil
bin_dir = os.path.join(install_prefix, 'bin') bin_dir = os.path.join(install_prefix, 'bin')
try: os.makedirs(bin_dir, exist_ok=True)
os.makedirs(bin_dir)
except:
pass
self.pkg_config = shutil.copy(os.path.join(mpd_path, 'build', 'pkg-config.sh'), self.pkg_config = shutil.copy(os.path.join(mpd_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config')) os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config self.env['PKG_CONFIG'] = self.pkg_config
...@@ -164,7 +150,6 @@ from build.libs import * ...@@ -164,7 +150,6 @@ from build.libs import *
thirdparty_libs = [ thirdparty_libs = [
libmpdclient, libmpdclient,
libogg, libogg,
libvorbis,
opus, opus,
flac, flac,
libid3tag, libid3tag,
...@@ -172,8 +157,8 @@ thirdparty_libs = [ ...@@ -172,8 +157,8 @@ thirdparty_libs = [
wildmidi, wildmidi,
gme, gme,
ffmpeg, ffmpeg,
openssl,
curl, curl,
libexpat,
libnfs, libnfs,
boost, boost,
] ]
......
#!/bin/sh
# This script need the following modification in ANDROID_NDK in order to attach
# to the good :main pid
#--- a/prebuilt/linux-x86_64/bin/ndk-gdb.py
#+++ b/prebuilt/linux-x86_64/bin/ndk-gdb.py
#@@ -669,7 +669,7 @@
# log("Sleeping for {} seconds.".format(args.delay))
# time.sleep(args.delay)
#
#- pids = gdbrunner.get_pids(device, pkg_name)
#+ pids = gdbrunner.get_pids(device, pkg_name + ":main")
# if len(pids) == 0:
# error("Failed to find running process '{}'".format(pkg_name))
# if len(pids) > 1:
SCRIPT_PATH=$(dirname $0)
BUILD_PATH="`pwd`"
TMP_PATH="$BUILD_PATH/gdb"
NDK_GDB_ARGS="--force"
ANDROID_NDK=$1
if [ ! -f $ANDROID_NDK/source.properties ];then
echo "usage: $0 ANDROID_NDK"
exit 1
fi
if [ ! -f $BUILD_PATH/libmpd.so ];then
echo "This script need to be executed from the android build directory"
exit 1
fi
rm -rf "$TMP_PATH"
mkdir -p "$TMP_PATH"
ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml"
ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1`
if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then
echo "Invalid manifest/ABI, did you try building first ?"
exit 1
fi
mkdir -p "$TMP_PATH"/jni
touch "$TMP_PATH"/jni/Android.mk
echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk
DEST=obj/local/$ABI
mkdir -p "$TMP_PATH/$DEST"
cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST"
cp "$ANDROID_MANIFEST" "$TMP_PATH"
(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS)
...@@ -5,8 +5,8 @@ android_ndk = get_option('android_ndk') ...@@ -5,8 +5,8 @@ android_ndk = get_option('android_ndk')
android_sdk = get_option('android_sdk') android_sdk = get_option('android_sdk')
android_abi = get_option('android_abi') android_abi = get_option('android_abi')
android_sdk_build_tools_version = '27.0.0' android_sdk_build_tools_version = '29.0.3'
android_sdk_platform = 'android-23' android_sdk_platform = 'android-29'
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version) android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform) android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)
......
...@@ -23,6 +23,12 @@ ...@@ -23,6 +23,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/checkbox_wakelock" /> android:text="@string/checkbox_wakelock" />
<CheckBox
android:id="@+id/pause_on_headphones_disconnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/checkbox_pause_on_headphones_disconnect" />
<TextView <TextView
android:id="@+id/status" android:id="@+id/status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
......
...@@ -8,4 +8,5 @@ ...@@ -8,4 +8,5 @@
<string name="toggle_button_run_off">MPD is not running</string> <string name="toggle_button_run_off">MPD is not running</string>
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string> <string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string> <string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
</resources> </resources>
...@@ -13,7 +13,7 @@ GENCLASS="$D/classes" ...@@ -13,7 +13,7 @@ GENCLASS="$D/classes"
GENINCLUDE="$D/include" GENINCLUDE="$D/include"
mkdir -p "$GENSRC/$JAVA_PKG_PATH" mkdir -p "$GENSRC/$JAVA_PKG_PATH"
"$JAVAC" -source 1.6 -target 1.6 -Xlint:-options \ "$JAVAC" -source 1.7 -target 1.7 -Xlint:-options \
-cp "$CLASSPATH" \ -cp "$CLASSPATH" \
-h "$GENINCLUDE" \ -h "$GENINCLUDE" \
-d "$GENCLASS" \ -d "$GENCLASS" \
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -33,4 +33,5 @@ public class Bridge { ...@@ -33,4 +33,5 @@ public class Bridge {
public static native void run(Context context, LogListener logListener); public static native void run(Context context, LogListener logListener);
public static native void shutdown(); public static native void shutdown();
public static native void pause();
} }
...@@ -5,6 +5,7 @@ interface IMain ...@@ -5,6 +5,7 @@ interface IMain
{ {
void start(); void start();
void stop(); void stop();
void setPauseOnHeadphonesDisconnect(boolean enabled);
void setWakelockEnabled(boolean enabled); void setWakelockEnabled(boolean enabled);
boolean isRunning(); boolean isRunning();
void registerCallback(IMainCallback cb); void registerCallback(IMainCallback cb);
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -24,10 +24,13 @@ import android.app.Notification; ...@@ -24,10 +24,13 @@ import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
...@@ -55,6 +58,7 @@ public class Main extends Service implements Runnable { ...@@ -55,6 +58,7 @@ public class Main extends Service implements Runnable {
private String mError = null; private String mError = null;
private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>(); private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>();
private final IBinder mBinder = new MainStub(this); private final IBinder mBinder = new MainStub(this);
private boolean mPauseOnHeadphonesDisconnect = false;
private PowerManager.WakeLock mWakelock = null; private PowerManager.WakeLock mWakelock = null;
static class MainStub extends IMain.Stub { static class MainStub extends IMain.Stub {
...@@ -68,6 +72,9 @@ public class Main extends Service implements Runnable { ...@@ -68,6 +72,9 @@ public class Main extends Service implements Runnable {
public void stop() { public void stop() {
mService.stop(); mService.stop();
} }
public void setPauseOnHeadphonesDisconnect(boolean enabled) {
mService.setPauseOnHeadphonesDisconnect(enabled);
}
public void setWakelockEnabled(boolean enabled) { public void setWakelockEnabled(boolean enabled) {
mService.setWakelockEnabled(enabled); mService.setWakelockEnabled(enabled);
} }
...@@ -191,6 +198,18 @@ public class Main extends Service implements Runnable { ...@@ -191,6 +198,18 @@ public class Main extends Service implements Runnable {
if (mThread != null) if (mThread != null)
return; return;
IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mPauseOnHeadphonesDisconnect)
return;
if (intent.getAction() == AudioManager.ACTION_AUDIO_BECOMING_NOISY)
pause();
}
}, filter);
final Intent mainIntent = new Intent(this, Settings.class); final Intent mainIntent = new Intent(this, Settings.class);
mainIntent.setAction("android.intent.action.MAIN"); mainIntent.setAction("android.intent.action.MAIN");
mainIntent.addCategory("android.intent.category.LAUNCHER"); mainIntent.addCategory("android.intent.category.LAUNCHER");
...@@ -241,6 +260,21 @@ public class Main extends Service implements Runnable { ...@@ -241,6 +260,21 @@ public class Main extends Service implements Runnable {
stopSelf(); stopSelf();
} }
private void pause() {
if (mThread != null) {
if (mThread.isAlive()) {
synchronized (this) {
if (mStatus == MAIN_STATUS_STARTED)
Bridge.pause();
}
}
}
}
private void setPauseOnHeadphonesDisconnect(boolean enabled) {
mPauseOnHeadphonesDisconnect = enabled;
}
private void setWakelockEnabled(boolean enabled) { private void setWakelockEnabled(boolean enabled) {
if (enabled && mWakelock == null) { if (enabled && mWakelock == null) {
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
...@@ -368,6 +402,19 @@ public class Main extends Service implements Runnable { ...@@ -368,6 +402,19 @@ public class Main extends Service implements Runnable {
} }
} }
public boolean setPauseOnHeadphonesDisconnect(boolean enabled) {
synchronized (this) {
if (mIMain != null) {
try {
mIMain.setPauseOnHeadphonesDisconnect(enabled);
return true;
} catch (RemoteException e) {
}
}
return false;
}
}
public boolean setWakelockEnabled(boolean enabled) { public boolean setWakelockEnabled(boolean enabled) {
synchronized (this) { synchronized (this) {
if (mIMain != null) { if (mIMain != null) {
...@@ -414,6 +461,15 @@ public class Main extends Service implements Runnable { ...@@ -414,6 +461,15 @@ public class Main extends Service implements Runnable {
* start Main service without any callback * start Main service without any callback
*/ */
public static void start(Context context, boolean wakelock) { public static void start(Context context, boolean wakelock) {
context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock)); Intent intent = new Intent(context, Main.class)
.putExtra("wakelock", wakelock);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
/* in Android 8+, we need to use this method
or else we'll get "IllegalStateException:
app is in background" */
context.startForegroundService(intent);
else
context.startService(intent);
} }
} }
/* /*
* Copyright (C) 2003-2014 The Music Player Daemon Project * Copyright (C) 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -26,16 +25,18 @@ import android.content.Intent; ...@@ -26,16 +25,18 @@ import android.content.Intent;
import android.util.Log; import android.util.Log;
public class Receiver extends BroadcastReceiver { public class Receiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.d("Receiver", "onReceive: " + intent); Log.d("Receiver", "onReceive: " + intent);
if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") { if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") {
if (Settings.Preferences.getBoolean(context, if (Settings.Preferences.getBoolean(context,
Settings.Preferences.KEY_RUN_ON_BOOT, false)) { Settings.Preferences.KEY_RUN_ON_BOOT,
final boolean wakelock = Settings.Preferences.getBoolean(context, false)) {
Settings.Preferences.KEY_WAKELOCK, false); final boolean wakelock =
Main.start(context, wakelock); Settings.Preferences.getBoolean(context,
} Settings.Preferences.KEY_WAKELOCK, false);
} Main.start(context, wakelock);
} }
}
}
} }
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -59,6 +59,7 @@ public class Settings extends Activity { ...@@ -59,6 +59,7 @@ public class Settings extends Activity {
public static class Preferences { public static class Preferences {
public static final String KEY_RUN_ON_BOOT ="run_on_boot"; public static final String KEY_RUN_ON_BOOT ="run_on_boot";
public static final String KEY_WAKELOCK ="wakelock"; public static final String KEY_WAKELOCK ="wakelock";
public static final String KEY_PAUSE_ON_HEADPHONES_DISCONNECT ="pause_on_headphones_disconnect";
public static SharedPreferences get(Context context) { public static SharedPreferences get(Context context) {
return context.getSharedPreferences(TAG, MODE_PRIVATE); return context.getSharedPreferences(TAG, MODE_PRIVATE);
...@@ -154,6 +155,9 @@ public class Settings extends Activity { ...@@ -154,6 +155,9 @@ public class Settings extends Activity {
if (Preferences.getBoolean(Settings.this, if (Preferences.getBoolean(Settings.this,
Preferences.KEY_WAKELOCK, false)) Preferences.KEY_WAKELOCK, false))
mClient.setWakelockEnabled(true); mClient.setWakelockEnabled(true);
if (Preferences.getBoolean(Settings.this,
Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, false))
mClient.setPauseOnHeadphonesDisconnect(true);
} else { } else {
mClient.stop(); mClient.stop();
} }
...@@ -179,6 +183,15 @@ public class Settings extends Activity { ...@@ -179,6 +183,15 @@ public class Settings extends Activity {
} }
}; };
private final OnCheckedChangeListener mOnPauseOnHeadphonesDisconnectChangeListener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Preferences.putBoolean(Settings.this, Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, isChecked);
if (mClient != null && mClient.isRunning())
mClient.setPauseOnHeadphonesDisconnect(isChecked);
}
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
/* TODO: this sure is the wrong place to request /* TODO: this sure is the wrong place to request
...@@ -211,6 +224,11 @@ public class Settings extends Activity { ...@@ -211,6 +224,11 @@ public class Settings extends Activity {
if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false)) if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false))
checkbox.setChecked(true); checkbox.setChecked(true);
checkbox = (CheckBox) findViewById(R.id.pause_on_headphones_disconnect);
checkbox.setOnCheckedChangeListener(mOnPauseOnHeadphonesDisconnectChangeListener);
if (Preferences.getBoolean(this, Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, false))
checkbox.setChecked(true);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
......
Client Developer's Manual
#########################
Introduction
************
MPD is a music player without a user interface. The user interface
will be provided by independent clients, which connect to MPD over
socket connections (TCP or local sockets).
This chapter describes how to develop a client.
Before you develop a new client, consider joining an existing client
project. There are many clients, but few are mature; we need fewer,
but better clients.
Client Libraries
****************
There are many libraries which help with connecting to MPD. If you
develop a MPD client, use a library instead of reinventing the wheel.
The MPD website has a list of libraries: https://www.musicpd.org/libs/
Connecting to MPD
*****************
Do not hard-code your client to connect to ``localhost:6600``.
Instead, use the defaults of the client library. For example, with
:program:`libmpdclient`, don't do::
c = mpd_connection_new("localhost", 6600, 30000);
Instead, do::
c = mpd_connection_new(NULL, 0, 0);
This way, the library can choose the best defaults, maybe derived from
environment variables, so all MPD clients use the same settings.
If you need to reimplement those defaults (or if you are developing a
client library), this is a good set of addresses to attempt to connect
to:
- if the environment variable :envvar:`MPD_HOST` is set:
``$MPD_HOST:$MPD_PORT`` (:envvar:`MPD_PORT` defaulting to 6600)
- if the environment variable :envvar:`XDG_RUNTIME_DIR` is set:
``$XDG_RUNTIME_DIR/mpd/socket``
- :file:`/run/mpd/socket`
- ``localhost:$MPD_PORT`` (:envvar:`MPD_PORT` defaulting to 6600)
Environment Variables
*********************
The following environment variables should be obeyed by all clients
(preferably by the client library):
- :envvar:`MPD_HOST`: the host (or local socket path) to connect to;
on Linux, this may start with a ``@`` to connect to an abstract
socket. To use a password with MPD, set :envvar:`MPD_HOST` to
``password@host`` (then abstract socket requires double ``@``:
``password@@socket``).
- :envvar:`MPD_PORT`: the port number; defaults to 6600.
- :envvar:`MPD_TIMEOUT`: timeout for connecting to MPD and for waiting
for MPD's response in seconds. A good default is 30 seconds.
...@@ -30,7 +30,7 @@ master_doc = 'index' ...@@ -30,7 +30,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = 'Music Player Daemon' project = 'Music Player Daemon'
copyright = '2003-2018 The Music Player Daemon Project' copyright = '2003-2021 The Music Player Daemon Project'
author = 'Max Kellermann' author = 'Max Kellermann'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
...@@ -38,9 +38,9 @@ author = 'Max Kellermann' ...@@ -38,9 +38,9 @@ author = 'Max Kellermann'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.21.24' version = '0.23.8'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version #release = version + '~git'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
...@@ -107,6 +107,7 @@ html_theme = 'classic' ...@@ -107,6 +107,7 @@ html_theme = 'classic'
# documentation. # documentation.
# #
# html_theme_options = {} # html_theme_options = {}
html_theme_options = {"sidebarwidth": "300px"}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = [] # html_theme_path = []
...@@ -212,3 +213,7 @@ html_static_path = ['_static'] ...@@ -212,3 +213,7 @@ html_static_path = ['_static']
# implements a search results scorer. If empty, the default will be used. # implements a search results scorer. If empty, the default will be used.
# #
# html_search_scorer = 'scorer.js' # html_search_scorer = 'scorer.js'
man_pages = [
('mpd.1', 'mpd', 'MPD documentation', [author], 1),
('mpd.conf.5', 'mpd.conf', 'mpd.conf documentation', [author], 5)
]
...@@ -12,8 +12,7 @@ Code Style ...@@ -12,8 +12,7 @@ Code Style
* indent with tabs (width 8) * indent with tabs (width 8)
* don't write CPP when you can write C++: use inline functions and constexpr instead of macros * don't write CPP when you can write C++: use inline functions and constexpr instead of macros
* comment your code, document your APIs * comment your code, document your APIs
* the code should be C++14 compliant, and must compile with :program:`GCC` 6.0 and :program:`clang` 3.4 * the code should be C++17 compliant, and must compile with :program:`GCC` 8 and :program:`clang` 5
* report error conditions with C++ exceptions, preferable derived from :envvar:`std::runtime_error`
* all code must be exception-safe * all code must be exception-safe
* classes and functions names use CamelCase; variables are lower-case with words separated by underscore * classes and functions names use CamelCase; variables are lower-case with words separated by underscore
...@@ -31,6 +30,56 @@ Some example code: ...@@ -31,6 +30,56 @@ Some example code:
return xyz; return xyz;
} }
Error handling
==============
If an error occurs, throw a C++ exception, preferably derived from
:code:`std::runtime_error`. The function's API documentation should
mention that. If a function cannot throw exceptions, add
:code:`noexcept` to its prototype.
Some parts of MPD use callbacks to report completion; the handler
classes usually have an "error" callback which receives a
:code:`std::exception_ptr`
(e.g. :code:`BufferedSocket::OnSocketError()`). Wrapping errors in
:code:`std::exception_ptr` allows propagating details about the error
across thread boundaries to the entity which is interested in handling
it (e.g. giving the MPD client details about an I/O error caught by
the decoder thread).
Out-of-memory errors (i.e. :code:`std::bad_alloc`) do not need to be
handled. Some operating systems such as Linux do not report
out-of-memory to userspace, and instead kill a process to recover.
Even if we know we are out of memory, there is little we can do except
for aborting the process quickly. Any other attempts to give back
memory may cause page faults on the way which make the situation
worse.
Error conditions which are caused by a bug do not need to be handled
at runtime; instead, use :code:`assert()` to detect them in debug
builds.
git Branches
************
There are two active branches in the git repository:
- the "unstable" branch called ``master`` where new features are
merged. This will become the next major release eventually.
- the "stable" branch (currently called ``v0.22.x``) where only bug
fixes are merged.
Once :program:`MPD` 0.23 is released, a new branch called ``v0.23.x``
will be created for 0.23 bug-fix releases; after that, ``v0.22.x``
will eventually cease to be maintained.
After bug fixes have been added to the "stable" branch, it will be
merged into ``master``. This ensures that all known bugs are fixed in
all active branches.
Hacking The Source Hacking The Source
****************** ******************
......
...@@ -8,6 +8,7 @@ Music Player Daemon ...@@ -8,6 +8,7 @@ Music Player Daemon
user user
plugins plugins
developer developer
client
protocol protocol
......
install_man(['mpd.1', 'mpd.conf.5']) if not get_option('html_manual') and not get_option('manpages')
subdir_done()
endif
sphinx = find_program('sphinx-build') sphinx = find_program('sphinx-build', required: get_option('documentation'))
sphinx_output = custom_target( if not sphinx.found()
'HTML documentation', subdir_done()
output: 'html', endif
input: [
'index.rst', 'user.rst', 'developer.rst',
'plugins.rst',
'protocol.rst',
'conf.py',
],
command: [sphinx, '-q', '-b', 'html', '-d', '@OUTDIR@/doctrees', meson.current_source_dir(), '@OUTPUT@'],
build_by_default: true,
install: true,
install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()),
)
custom_target( if get_option('html_manual')
'upload', sphinx_output = custom_target(
input: sphinx_output, 'HTML documentation',
output: 'upload', output: 'html',
build_always_stale: true, input: [
command: [ 'index.rst', 'user.rst', 'developer.rst',
'rsync', '-vpruz', '--delete', meson.current_build_dir() + '/', 'plugins.rst',
'www.musicpd.org:/var/www/mpd/doc/', 'client.rst',
'--chmod=Dug+rwx,Do+rx,Fug+rw,Fo+r', 'protocol.rst',
'--include=html', '--include=html/**', 'conf.py',
'--exclude=*', ],
], command: [sphinx, '-q', '-b', 'html', '-d', '@OUTDIR@/doctrees', meson.current_source_dir(), '@OUTPUT@'],
) build_by_default: true,
install: true,
install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()),
)
endif
if get_option('manpages')
custom_target(
'Manpage documentation',
output: ['mpd.1', 'mpd.conf.5'],
input: ['mpd.1.rst', 'conf.py'],
command: [sphinx, '-q', '-b', 'man', '-d', '@OUTDIR@/man_doctrees', meson.current_source_dir(), '@OUTDIR@'],
build_by_default: true,
install: true,
install_dir: [join_paths(get_option('mandir'), 'man1'), join_paths(get_option('mandir'), 'man5')],
)
endif
.TH "Music Player Daemon" 1
.SH NAME
MPD \- A daemon for playing music
.SH SYNOPSIS
.B mpd
.RI [ options ]
.RI [ CONF_FILE ]
.SH DESCRIPTION
MPD is a daemon for playing music. Music is played through the configured
audio output(s) (which are generally local, but can be remote). The daemon
stores info about all available music, and this info can be easily searched and
retrieved. Player control, info retrieval, and playlist management can all be
managed remotely.
MPD searches for a config file in \fB$XDG_CONFIG_HOME/mpd/mpd.conf\fP then
\fB~/.mpdconf\fP then \fB/etc/mpd.conf\fP or uses CONF_FILE.
Read more about MPD at <\fBhttp://www.musicpd.org/\fP>.
.SH OPTIONS
.TP
.BI \-\-help
Output a brief help message.
.TP
.BI \-\-kill
Kill the currently running mpd session. The pid_file parameter must be
specified in the config file for this to work.
.TP
.BI \-\-no\-daemon
Don't detach from console.
.TP
.BI \-\-stderr
Print messages stderr.
.TP
.BI \-\-verbose
Verbose logging.
.TP
.BI \-\-version
Print version information.
.SH FILES
.TP
.BI ~/.mpdconf
User configuration file.
.TP
.BI /etc/mpd.conf
Global configuration file.
.SH SEE ALSO
mpd.conf(5), mpc(1)
.SH BUGS
If you find a bug, please report it at
.br
<\fBhttps://github.com/MusicPlayerDaemon/MPD/issues/\fP>.
.SH AUTHORS
Max Kellermann <max.kellermann@gmail.com>
Special thanks to all the people that provided feedback and patches.
===
mpd
===
SYNOPSIS
--------
``mpd`` [options] [CONF_FILE]
DESCRIPTION
------------
MPD is a daemon for playing music. Music is played through the configured audio output(s) (which are generally local, but can be remote). The daemon stores info about all available music, and this info can be easily searched and retrieved. Player control, info retrieval, and playlist management can all be managed remotely.
MPD searches for a config file in ``$XDG_CONFIG_HOME/mpd/mpd.conf``
then ``~/.mpdconf`` then ``~/.mpd/mpd.conf`` then ``/etc/mpd.conf`` or uses ``CONF_FILE``.
Read more about MPD at http://www.musicpd.org/
OPTIONS
-------
.. program:: mpd
.. option:: --help
Output a brief help message.
.. option:: --kill
Kill the currently running mpd session. The pid_file parameter must be specified in the config file for this to work.
.. option:: --no-config
Don't read from the configuration file.
.. option:: --no-daemon
Don't detach from console.
.. option:: --stderr
Print messages to stderr.
.. option:: --verbose
Verbose logging.
.. option:: --version
Print version information.
FILES
-----
:file:`$XDG_CONFIG_HOME/mpd/mpd.conf`
User configuration file (usually :file:`~/.config/mpd/mpd.conf`).
:file:`/etc/mpd.conf`
Global configuration file.
SEE ALSO
--------
:manpage:`mpd.conf(5)`, :manpage:`mpc(1)`
BUGS
----
If you find a bug, please report it at https://github.com/MusicPlayerDaemon/MPD/issues/
.TH mpd.conf 5
.SH NAME
mpd.conf \- Music Player Daemon configuration file
.SH DESCRIPTION
\fBmpd.conf\fP is the configuration file for mpd(1). If not specified on the
command line, MPD first searches for it at \fB$XDG_CONFIG_HOME/mpd/mpd.conf\fP
then at \fB~/.mpdconf\fP then at \fB~/.mpd/mpd.conf\fP and then in
\fB/etc/mpd.conf\fP.
Lines beginning with a "#" character are comments. All other non-empty lines
specify parameters and their values. These lines contain the parameter name
and parameter value (surrounded by double quotes) separated by whitespace
(either tabs or spaces). For example:
parameter "value"
The exception to this rule is the audio_output parameter, which is of the form:
audio_output {
.br
parameter1 "value"
parameter2 "value"
.br
}
Parameters that take a file or directory as an argument should use absolute
paths.
See \fBdocs/mpdconf.example\fP in the source tarball for an example
configuration file.
This manual is not complete, it lists only the most important options.
Please read the MPD user manual for a complete configuration guide:
<\fBhttp://www.musicpd.org/doc/user/\fP>
.SH REQUIRED PARAMETERS
.TP
.B db_file <file>
This specifies where the db file will be stored.
.TP
.B log_file <file>
This specifies where the log file should be located.
The special value "syslog" makes MPD use the local syslog daemon.
.SH OPTIONAL PARAMETERS
.TP
.B sticker_file <file>
The location of the sticker database. This is a database which
manages dynamic information attached to songs.
.TP
.B pid_file <file>
This specifies the file to save mpd's process ID in.
.TP
.B music_directory <directory>
This specifies the directory where music is located.
If you do not configure this, you can only play streams.
.TP
.B playlist_directory <directory>
This specifies the directory where saved playlists are stored.
If you do not configure this, you cannot save playlists.
.TP
.B state_file <file>
This specifies if a state file is used and where it is located. The state of
mpd will be saved to this file when mpd is terminated by a TERM signal or by
the "kill" command. When mpd is restarted, it will read the state file and
restore the state of mpd (including the playlist).
.TP
.B restore_paused <yes or no>
Put MPD into pause mode instead of starting playback after startup.
.TP
.B user <username>
This specifies the user that MPD will run as, if set. MPD should
never run as root, and you may use this option to make MPD change its
user id after initialization. Do not use this option if you start MPD
as an unprivileged user.
.TP
.B port <port>
This specifies the port that mpd listens on. The default is 6600.
.TP
.B log_level <default, secure, or verbose>
This specifies how verbose logs are. "default" is minimal logging, "secure"
reports from what address a connection is opened, and when it is closed, and
"verbose" records excessive amounts of information for debugging purposes. The
default is "default".
.TP
.B follow_outside_symlinks <yes or no>
Control if MPD will follow symbolic links pointing outside the music dir.
You must recreate the database after changing this option.
The default is "yes".
.TP
.B follow_inside_symlinks <yes or no>
Control if MPD will follow symbolic links pointing inside the music dir,
potentially adding duplicates to the database.
You must recreate the database after changing this option.
The default is "yes".
.TP
.B zeroconf_enabled <yes or no>
If yes, and MPD has been compiled with support for Avahi or Bonjour, service
information will be published with Zeroconf. The default is yes.
.TP
.B zeroconf_name <name>
If Zeroconf is enabled, this is the service name to publish. This name should
be unique to your local network, but name collisions will be properly dealt
with. The default is "Music Player @ %h", where %h will be replaced with the
hostname of the machine running MPD.
.TP
.B audio_output
See \fBDESCRIPTION\fP and the various \fBAUDIO OUTPUT PARAMETERS\fP sections
for the format of this parameter. Multiple audio_output sections may be
specified. If no audio_output section is specified, then MPD will scan for a
usable audio output.
.TP
.B replaygain <off or album or track or auto>
If specified, mpd will adjust the volume of songs played using ReplayGain tags
(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust
volume using the album's ReplayGain tags, while setting it to "track" will
adjust it using the track ReplayGain tags. "auto" uses the track ReplayGain
tags if random play is activated otherwise the album ReplayGain tags. Currently
only FLAC, Ogg Vorbis, Musepack, and MP3 (through ID3v2 ReplayGain tags, not
APEv2) are supported.
.TP
.B replaygain_preamp <\-15 to 15>
This is the gain (in dB) applied to songs with ReplayGain tags.
.TP
.B volume_normalization <yes or no>
If yes, mpd will normalize the volume of songs as they play. The default is no.
.TP
.B filesystem_charset <charset>
This specifies the character set used for the filesystem. A list of supported
character sets can be obtained by running "iconv \-l". The default is
determined from the locale when the db was originally created.
.TP
.B save_absolute_paths_in_playlists <yes or no>
This specifies whether relative or absolute paths for song filenames are used
when saving playlists. The default is "no".
.TP
.B auto_update <yes or no>
This specifies the whether to support automatic update of music database when
files are changed in music_directory. The default is to disable autoupdate
of database.
.TP
.B auto_update_depth <N>
Limit the depth of the directories being watched, 0 means only watch
the music directory itself. There is no limit by default.
.SH REQUIRED AUDIO OUTPUT PARAMETERS
.TP
.B type <type>
This specifies the audio output type. See the list of supported outputs in mpd
\-\-version for possible values.
.TP
.B name <name>
This specifies a unique name for the audio output.
.SH OPTIONAL AUDIO OUTPUT PARAMETERS
.TP
.B format <sample_rate:bits:channels>
This specifies the sample rate, bits per sample, and number of channels of
audio that is sent to the audio output device. See documentation for the
\fBaudio_output_format\fP parameter for more details. The default is to use
whatever audio format is passed to the audio output.
Any of the three attributes may be an asterisk to specify that this
attribute should not be enforced
.TP
.B replay_gain_handler <software, mixer or none>
Specifies how replay gain is applied. The default is "software",
which uses an internal software volume control. "mixer" uses the
configured (hardware) mixer control. "none" disables replay gain on
this audio output.
.TP
.B mixer_type <hardware, software or none>
Specifies which mixer should be used for this audio output: the
hardware mixer (available for ALSA, OSS and PulseAudio), the software
mixer or no mixer ("none"). By default, the hardware mixer is used
for devices which support it, and none for the others.
.SH FILES
.TP
.BI ~/.mpdconf
User configuration file.
.TP
.BI /etc/mpd.conf
Global configuration file.
.SH SEE ALSO
mpd(1), mpc(1)
========
mpd.conf
========
DESCRIPTION
------------
:file:`mpd.conf` is the configuration file for :manpage:`mpd(1)`. If
not specified on the command line, MPD first searches for it at
:file:`$XDG_CONFIG_HOME/mpd/mpd.conf` then at :file:`~/.mpdconf` then
at :file:`~/.mpd/mpd.conf` and then in :file:`/etc/mpd.conf`.
Each line in the configuration file contains a setting name and its value, e.g.:
:code:`connection_timeout "5"`
For settings which specify a filesystem path, the tilde is expanded:
:code:`music_directory "~/Music"`
Some of the settings are grouped in blocks with curly braces, e.g. per-plugin settings:
.. code-block:: none
audio_output {
type "alsa"
name "My ALSA output"
device "iec958:CARD=Intel,DEV=0"
mixer_control "PCM"
}
The :code:`include` directive can be used to include settings from
another file; the given file name is relative to the current file:
:code:`include "other.conf"`
You can use include_optional instead if you want the included file to be
optional; the directive will be ignored if the file does not exist:
:code:`include_optional "may_not_exist.conf"`
See :file:`docs/mpdconf.example` in the source tarball for an example
configuration file.
This manual is not complete, it lists only the most important options.
Please read the MPD user manual for a complete configuration guide:
http://www.musicpd.org/doc/user/
OPTIONAL PARAMETERS
-------------------
db_file <file>
This specifies where the db file will be stored.
log_file <file>
This specifies where the log file should be located. The special value "syslog" makes MPD use the local syslog daemon.
sticker_file <file>
The location of the sticker database. This is a database which manages
dynamic information attached to songs.
pid_file <file>
This specifies the file to save mpd's process ID in.
music_directory <directory>
This specifies the directory where music is located. If you do not configure
this, you can only play streams.
playlist_directory <directory>
This specifies the directory where saved playlists are stored. If
you do not configure this, you cannot save playlists.
state_file <file>
This specifies if a state file is used and where it is located. The state of
mpd will be saved to this file when mpd is terminated by a TERM signal or by
the :program:`kill` command. When mpd is restarted, it will read the state file and
restore the state of mpd (including the playlist).
restore_paused <yes or no>
Put MPD into pause mode instead of starting playback after startup.
user <username>
This specifies the user that MPD will run as, if set. MPD should never run
as root, and you may use this option to make MPD change its user id after
initialization. Do not use this option if you start MPD as an unprivileged
user.
port <port>
This specifies the port that mpd listens on. The default is 6600.
log_level <level>
Suppress all messages below the given threshold. The following
log levels are available:
- :samp:`error`: errors
- :samp:`warning`: warnings
- :samp:`notice`: interesting informational messages
- :samp:`info`: unimportant informational messages
- :samp:`verbose`: debug messages (for developers and for
troubleshooting)
The default is :samp:`notice`.
follow_outside_symlinks <yes or no>
Control if MPD will follow symbolic links pointing outside the music dir. You
must recreate the database after changing this option. The default is "yes".
follow_inside_symlinks <yes or no>
Control if MPD will follow symbolic links pointing inside the music dir,
potentially adding duplicates to the database. You must recreate the
database after changing this option. The default is "yes".
zeroconf_enabled <yes or no>
If yes, and MPD has been compiled with support for Avahi or Bonjour, service
information will be published with Zeroconf. The default is yes.
zeroconf_name <name>
If Zeroconf is enabled, this is the service name to publish. This name should
be unique to your local network, but name collisions will be properly dealt
with. The default is "Music Player @ %h", where %h will be replaced with the
hostname of the machine running MPD.
audio_output
See DESCRIPTION and the various ``AUDIO OUTPUT PARAMETERS`` sections for the
format of this parameter. Multiple audio_output sections may be specified. If
no audio_output section is specified, then MPD will scan for a usable audio
output.
filesystem_charset <charset>
This specifies the character set used for the filesystem. A list of supported
character sets can be obtained by running "iconv -l". The default is
determined from the locale when the db was originally created.
save_absolute_paths_in_playlists <yes or no>
This specifies whether relative or absolute paths for song filenames are used
when saving playlists. The default is "no".
auto_update <yes or no>
This specifies the whether to support automatic update of music database
when files are changed in music_directory. The default is to disable
autoupdate of database.
auto_update_depth <N>
Limit the depth of the directories being watched, 0 means only watch the
music directory itself. There is no limit by default.
REQUIRED AUDIO OUTPUT PARAMETERS
--------------------------------
type <type>
This specifies the audio output type. See the list of supported outputs in
``mpd --version`` for possible values.
name <name>
This specifies a unique name for the audio output.
OPTIONAL AUDIO OUTPUT PARAMETERS
--------------------------------
format <sample_rate:bits:channels>
This specifies the sample rate, bits per sample, and number of channels of
audio that is sent to the audio output device. See documentation for the
``audio_output_format`` parameter for more details. The default is to use
whatever audio format is passed to the audio output. Any of the three
attributes may be an asterisk to specify that this attribute should not be
enforced
replay_gain_handler <software, mixer or none>
Specifies how replay gain is applied. The default is "software", which uses
an internal software volume control. "mixer" uses the configured (hardware)
mixer control. "none" disables replay gain on this audio output.
mixer_type <hardware, software or none>
Specifies which mixer should be used for this audio output: the hardware
mixer (available for ALSA, OSS and PulseAudio), the software mixer or no
mixer ("none"). By default, the hardware mixer is used for devices which
support it, and none for the others.
FILES
-----
:file:`$XDG_CONFIG_HOME/mpd/mpd.conf`
User configuration file (usually :file:`~/.config/mpd/mpd.conf`).
:file:`/etc/mpd.conf`
Global configuration file.
SEE ALSO
--------
:manpage:`mpd(1)`, :manpage:`mpc(1)`
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
# Files and directories ####################################################### # Files and directories #######################################################
# #
# This setting controls the top directory which MPD will search to discover the # This setting controls the top directory which MPD will search to discover the
# available audio files and add them to the daemon's online database. This # available audio files and add them to the daemon's online database. This
# setting defaults to the XDG directory, otherwise the music directory will be # setting defaults to the XDG directory, otherwise the music directory will be
# be disabled and audio files will only be accepted over ipc socket (using # be disabled and audio files will only be accepted over ipc socket (using
# file:// protocol) or streaming files over an accepted protocol. # file:// protocol) or streaming files over an accepted protocol.
...@@ -13,38 +13,41 @@ ...@@ -13,38 +13,41 @@
#music_directory "~/music" #music_directory "~/music"
# #
# This setting sets the MPD internal playlist directory. The purpose of this # This setting sets the MPD internal playlist directory. The purpose of this
# directory is storage for playlists created by MPD. The server will use # directory is storage for playlists created by MPD. The server will use
# playlist files not created by the server but only if they are in the MPD # playlist files not created by the server but only if they are in the MPD
# format. This setting defaults to playlist saving being disabled. # format. This setting defaults to playlist saving being disabled.
# #
#playlist_directory "~/.mpd/playlists" #playlist_directory "~/.mpd/playlists"
# #
# This setting sets the location of the MPD database. This file is used to # This setting sets the location of the MPD database. This file is used to
# load the database at server start up and store the database while the # load the database at server start up and store the database while the
# server is not up. This setting defaults to disabled which will allow # server is not up. This setting defaults to disabled which will allow
# MPD to accept files over ipc socket (using file:// protocol) or streaming # MPD to accept files over ipc socket (using file:// protocol) or streaming
# files over an accepted protocol. # files over an accepted protocol.
# #
#db_file "~/.mpd/database" #db_file "~/.mpd/database"
#
# These settings are the locations for the daemon log files for the daemon. # These settings are the locations for the daemon log files for the daemon.
# These logs are great for troubleshooting, depending on your log_level
# settings.
# #
# The special value "syslog" makes MPD use the local syslog daemon. This # The special value "syslog" makes MPD use the local syslog daemon. This
# setting defaults to logging to syslog. # setting defaults to logging to syslog.
# #
#log_file "~/.mpd/log" # If you use systemd, do not configure a log_file. With systemd, MPD
# defaults to the systemd journal, which is fine.
# #
#log_file "~/.mpd/log"
# This setting sets the location of the file which stores the process ID # This setting sets the location of the file which stores the process ID
# for use of mpd --kill and some init scripts. This setting is disabled by # for use of mpd --kill and some init scripts. This setting is disabled by
# default and the pid file will not be stored. # default and the pid file will not be stored.
# #
#pid_file "~/.mpd/pid" # If you use systemd, do not configure a pid_file.
# #
#pid_file "~/.mpd/pid"
# This setting sets the location of the file which contains information about # This setting sets the location of the file which contains information about
# most variables to get MPD back into the same general shape it was in before # most variables to get MPD back into the same general shape it was in before
# it was brought down. This setting is disabled by default and the server # it was brought down. This setting is disabled by default and the server
# state will be reset on server start up. # state will be reset on server start up.
# #
#state_file "~/.mpd/state" #state_file "~/.mpd/state"
...@@ -74,9 +77,9 @@ ...@@ -74,9 +77,9 @@
#group "nogroup" #group "nogroup"
# #
# This setting sets the address for the daemon to listen on. Careful attention # This setting sets the address for the daemon to listen on. Careful attention
# should be paid if this is assigned to anything other then the default, any. # should be paid if this is assigned to anything other than the default, any.
# This setting can deny access to control of the daemon. Not effective if # This setting can deny access to control of the daemon. Not effective if
# systemd socket activiation is in use. # systemd socket activation is in use.
# #
# For network # For network
#bind_to_address "any" #bind_to_address "any"
...@@ -89,12 +92,11 @@ ...@@ -89,12 +92,11 @@
# #
#port "6600" #port "6600"
# #
# This setting controls the type of information which is logged. Available # Suppress all messages below the given threshold. Use "verbose" for
# setting arguments are "default", "secure" or "verbose". The "verbose" setting # troubleshooting. Available setting arguments are "notice", "info", "verbose",
# argument is recommended for troubleshooting, though can quickly stretch # "warning" and "error".
# available resources on limited hardware storage.
# #
#log_level "default" #log_level "notice"
# #
# Setting "restore_paused" to "yes" puts MPD into pause mode instead # Setting "restore_paused" to "yes" puts MPD into pause mode instead
# of starting playback after startup. # of starting playback after startup.
...@@ -115,7 +117,7 @@ ...@@ -115,7 +117,7 @@
# the other supported tags: # the other supported tags:
#metadata_to_use "+comment" #metadata_to_use "+comment"
# #
# This setting enables automatic update of MPD's database when files in # This setting enables automatic update of MPD's database when files in
# music_directory are changed. # music_directory are changed.
# #
#auto_update "yes" #auto_update "yes"
...@@ -130,7 +132,7 @@ ...@@ -130,7 +132,7 @@
# Symbolic link behavior ###################################################### # Symbolic link behavior ######################################################
# #
# If this setting is set to "yes", MPD will discover audio files by following # If this setting is set to "yes", MPD will discover audio files by following
# symbolic links outside of the configured music_directory. # symbolic links outside of the configured music_directory.
# #
#follow_outside_symlinks "yes" #follow_outside_symlinks "yes"
...@@ -165,7 +167,7 @@ ...@@ -165,7 +167,7 @@
# #
#password "password@read,add,control,admin" #password "password@read,add,control,admin"
# #
# This setting specifies the permissions a user has who has not yet logged in. # This setting specifies the permissions a user has who has not yet logged in.
# #
#default_permissions "read,add,control,admin" #default_permissions "read,add,control,admin"
# #
...@@ -174,7 +176,18 @@ ...@@ -174,7 +176,18 @@
# Database ####################################################################### # Database #######################################################################
# #
# An example of a database section instead of the old 'db_file' setting.
# It enables mounting other storages into the music directory.
#
#database {
# plugin "simple"
# path "~/.local/share/mpd/db
# cache_directory "~/.local/share/mpd/cache"
#}
#
# An example of database config for a satellite setup
#
#music_directory "nfs://fileserver.local/srv/mp3"
#database { #database {
# plugin "proxy" # plugin "proxy"
# host "other.mpd.host" # host "other.mpd.host"
...@@ -183,7 +196,6 @@ ...@@ -183,7 +196,6 @@
# Input ####################################################################### # Input #######################################################################
# #
input { input {
plugin "curl" plugin "curl"
# proxy "proxy.isp.com:8080" # proxy "proxy.isp.com:8080"
...@@ -196,8 +208,8 @@ input { ...@@ -196,8 +208,8 @@ input {
# Audio Output ################################################################ # Audio Output ################################################################
# #
# MPD supports various audio output types, as well as playing through multiple # MPD supports various audio output types, as well as playing through multiple
# audio outputs at the same time, through multiple audio_output settings # audio outputs at the same time, through multiple audio_output settings
# blocks. Setting this block is optional, though the server will only attempt # blocks. Setting this block is optional, though the server will only attempt
# autodetection for one sound card. # autodetection for one sound card.
# #
...@@ -280,6 +292,7 @@ input { ...@@ -280,6 +292,7 @@ input {
# name "My Pulse Output" # name "My Pulse Output"
## server "remote_server" # optional ## server "remote_server" # optional
## sink "remote_server_sink" # optional ## sink "remote_server_sink" # optional
## media_role "media_role" #optional
#} #}
# #
# An example of a winmm output (Windows multimedia API). # An example of a winmm output (Windows multimedia API).
...@@ -293,6 +306,20 @@ input { ...@@ -293,6 +306,20 @@ input {
## mixer_type "hardware" # optional ## mixer_type "hardware" # optional
#} #}
# #
# An example of a wasapi output (Windows multimedia API).
#
#audio_output {
# type "wasapi"
# name "My WASAPI output"
## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
# or
## device "0" # optional
## Exclusive mode blocks all other audio source, and get best audio quality without resampling.
## exclusive "no" # optional
## Enumerate all devices in log.
## enumerate "no" # optional
#}
#
# An example of an openal output. # An example of an openal output.
# #
#audio_output { #audio_output {
...@@ -348,7 +375,8 @@ input { ...@@ -348,7 +375,8 @@ input {
# the argument "off", "album", "track" or "auto". "auto" is a special mode that # the argument "off", "album", "track" or "auto". "auto" is a special mode that
# chooses between "track" and "album" depending on the current state of # chooses between "track" and "album" depending on the current state of
# random playback. If random playback is enabled then "track" mode is used. # random playback. If random playback is enabled then "track" mode is used.
# See <http://www.replaygain.org> for more details about ReplayGain. # See <https://wiki.hydrogenaud.io/index.php?title=Replaygain> for
# more details about ReplayGain.
# This setting is off by default. # This setting is off by default.
# #
#replaygain "album" #replaygain "album"
...@@ -373,7 +401,7 @@ input { ...@@ -373,7 +401,7 @@ input {
#replaygain_limit "yes" #replaygain_limit "yes"
# #
# This setting enables on-the-fly normalization volume adjustment. This will # This setting enables on-the-fly normalization volume adjustment. This will
# result in the volume of all playing audio to be adjusted so the output has # result in the volume of all playing audio to be adjusted so the output has
# equal "loudness". This setting is disabled by default. # equal "loudness". This setting is disabled by default.
# #
#volume_normalization "no" #volume_normalization "no"
...@@ -382,7 +410,7 @@ input { ...@@ -382,7 +410,7 @@ input {
# Character Encoding ########################################################## # Character Encoding ##########################################################
# #
# If file or directory names do not display correctly for your locale then you # If file or directory names do not display correctly for your locale then you
# may need to modify this setting. # may need to modify this setting.
# #
#filesystem_charset "UTF-8" #filesystem_charset "UTF-8"
......
option('documentation', type: 'boolean', value: false, description: 'Build documentation') option('documentation', type: 'feature', description: 'Build documentation')
option('html_manual', type: 'boolean', value: true, description: 'Build the HTML manual')
option('test', type: 'boolean', value: false, description: 'Build the unit tests and debug programs') option('manpages', type: 'boolean', value: true, description: 'Build manual pages')
option('syslog', type: 'feature', description: 'syslog support') option('syslog', type: 'feature', description: 'syslog support')
option('inotify', type: 'boolean', value: true, description: 'inotify support (for automatic database update)') option('inotify', type: 'boolean', value: true, description: 'inotify support (for automatic database update)')
option('io_uring', type: 'feature', description: 'Linux io_uring support using liburing')
option('daemon', type: 'boolean', value: true, description: 'enable daemonization') option('daemon', type: 'boolean', value: true, description: 'enable daemonization')
option('systemd', type: 'feature', description: 'systemd support') option('systemd', type: 'feature', description: 'systemd support')
...@@ -12,6 +13,13 @@ option('systemd_system_unit_dir', type: 'string', description: 'systemd system s ...@@ -12,6 +13,13 @@ option('systemd_system_unit_dir', type: 'string', description: 'systemd system s
option('systemd_user_unit_dir', type: 'string', description: 'systemd user service directory') option('systemd_user_unit_dir', type: 'string', description: 'systemd user service directory')
# #
# Options for developers
#
option('test', type: 'boolean', value: false, description: 'Build the unit tests and debug programs')
option('fuzzer', type: 'boolean', value: false, description: 'Build fuzzers (requires libFuzzer)')
#
# Android # Android
# #
...@@ -54,7 +62,10 @@ option('dsd', type: 'boolean', value: true, description: 'Support the DSD audio ...@@ -54,7 +62,10 @@ option('dsd', type: 'boolean', value: true, description: 'Support the DSD audio
# #
option('database', type: 'boolean', value: true, description: 'enable support for the music database') option('database', type: 'boolean', value: true, description: 'enable support for the music database')
option('upnp', type: 'feature', description: 'UPnP client support') option('upnp', type: 'combo',
choices: ['auto', 'pupnp', 'npupnp', 'disabled'],
value: 'auto',
description: 'UPnP client support')
option('libmpdclient', type: 'feature', description: 'libmpdclient support (for the proxy database plugin)') option('libmpdclient', type: 'feature', description: 'libmpdclient support (for the proxy database plugin)')
# #
...@@ -84,7 +95,11 @@ option('cdio_paranoia', type: 'feature', description: 'libcdio_paranoia input pl ...@@ -84,7 +95,11 @@ option('cdio_paranoia', type: 'feature', description: 'libcdio_paranoia input pl
option('curl', type: 'feature', description: 'HTTP client using CURL') option('curl', type: 'feature', description: 'HTTP client using CURL')
option('mms', type: 'feature', description: 'MMS protocol support using libmms') option('mms', type: 'feature', description: 'MMS protocol support using libmms')
option('nfs', type: 'feature', description: 'NFS protocol support using libnfs') option('nfs', type: 'feature', description: 'NFS protocol support using libnfs')
option('smbclient', type: 'feature', description: 'SMB support using libsmbclient')
# The "smbclient" plugin is disabled by default because libsmbclient
# has a serious bug which crashes MPD very quickly:
# https://bugzilla.samba.org/show_bug.cgi?id=11413
option('smbclient', type: 'feature', value: 'disabled', description: 'SMB support using libsmbclient')
# #
# Commercial services # Commercial services
...@@ -92,7 +107,6 @@ option('smbclient', type: 'feature', description: 'SMB support using libsmbclien ...@@ -92,7 +107,6 @@ option('smbclient', type: 'feature', description: 'SMB support using libsmbclien
option('qobuz', type: 'feature', description: 'Qobuz client') option('qobuz', type: 'feature', description: 'Qobuz client')
option('soundcloud', type: 'feature', description: 'SoundCloud client') option('soundcloud', type: 'feature', description: 'SoundCloud client')
option('tidal', type: 'feature', description: 'Tidal client')
# #
# Archive plugins # Archive plugins
...@@ -123,6 +137,7 @@ option('gme', type: 'feature', description: 'Game Music Emulator decoder plugin' ...@@ -123,6 +137,7 @@ option('gme', type: 'feature', description: 'Game Music Emulator decoder plugin'
option('mad', type: 'feature', description: 'MP3 decoder using libmad') option('mad', type: 'feature', description: 'MP3 decoder using libmad')
option('mikmod', type: 'feature', description: 'MikMod decoder plugin') option('mikmod', type: 'feature', description: 'MikMod decoder plugin')
option('modplug', type: 'feature', description: 'Modplug decoder plugin') option('modplug', type: 'feature', description: 'Modplug decoder plugin')
option('openmpt', type: 'feature', description: 'OpenMPT decoder plugin')
option('mpcdec', type: 'feature', description: 'Musepack decoder plugin') option('mpcdec', type: 'feature', description: 'Musepack decoder plugin')
option('mpg123', type: 'feature', description: 'MP3 decoder using libmpg123') option('mpg123', type: 'feature', description: 'MP3 decoder using libmpg123')
option('opus', type: 'feature', description: 'Opus decoder plugin') option('opus', type: 'feature', description: 'Opus decoder plugin')
...@@ -162,9 +177,11 @@ option('jack', type: 'feature', description: 'JACK output plugin') ...@@ -162,9 +177,11 @@ option('jack', type: 'feature', description: 'JACK output plugin')
option('openal', type: 'feature', description: 'OpenAL output plugin') option('openal', type: 'feature', description: 'OpenAL output plugin')
option('oss', type: 'feature', description: 'Open Sound System support') option('oss', type: 'feature', description: 'Open Sound System support')
option('pipe', type: 'boolean', value: true, description: 'Pipe output plugin') option('pipe', type: 'boolean', value: true, description: 'Pipe output plugin')
option('pipewire', type: 'feature', description: 'PipeWire support')
option('pulse', type: 'feature', description: 'PulseAudio support') option('pulse', type: 'feature', description: 'PulseAudio support')
option('recorder', type: 'boolean', value: true, description: 'Recorder output plugin') option('recorder', type: 'boolean', value: true, description: 'Recorder output plugin')
option('shout', type: 'feature', description: 'Shoutcast streaming support using libshout') option('shout', type: 'feature', description: 'Shoutcast streaming support using libshout')
option('snapcast', type: 'boolean', value: true, description: 'Snapcast output plugin')
option('sndio', type: 'feature', description: 'sndio output plugin') option('sndio', type: 'feature', description: 'sndio output plugin')
option('solaris_output', type: 'feature', description: 'Solaris /dev/audio support') option('solaris_output', type: 'feature', description: 'Solaris /dev/audio support')
......
...@@ -55,10 +55,10 @@ class AutotoolsProject(MakeProject): ...@@ -55,10 +55,10 @@ class AutotoolsProject(MakeProject):
subprocess.check_call(configure, cwd=build, env=toolchain.env) subprocess.check_call(configure, cwd=build, env=toolchain.env)
return build return build
def build(self, toolchain): def _build(self, toolchain):
build = self.configure(toolchain) build = self.configure(toolchain)
if self.subdirs is not None: if self.subdirs is not None:
for subdir in self.subdirs: for subdir in self.subdirs:
MakeProject.build(self, toolchain, os.path.join(build, subdir)) self.build_make(toolchain, os.path.join(build, subdir))
else: else:
MakeProject.build(self, toolchain, build) self.build_make(toolchain, build)
...@@ -12,7 +12,7 @@ class BoostProject(Project): ...@@ -12,7 +12,7 @@ class BoostProject(Project):
name='boost', version=version, name='boost', version=version,
**kwargs) **kwargs)
def build(self, toolchain): def _build(self, toolchain):
src = self.unpack(toolchain) src = self.unpack(toolchain)
# install the headers manually; don't build any library # install the headers manually; don't build any library
...@@ -21,3 +21,8 @@ class BoostProject(Project): ...@@ -21,3 +21,8 @@ class BoostProject(Project):
dest = os.path.join(includedir, 'boost') dest = os.path.join(includedir, 'boost')
shutil.rmtree(dest, ignore_errors=True) shutil.rmtree(dest, ignore_errors=True)
shutil.copytree(os.path.join(src, 'boost'), dest) shutil.copytree(os.path.join(src, 'boost'), dest)
# touch the boost/version.hpp file to ensure it's newer than
# the downloaded Boost tarball, to avoid reinstalling Boost on
# every run
os.utime(os.path.join(toolchain.install_prefix, self.installed))
import os
import subprocess import subprocess
from build.project import Project from build.project import Project
def __write_cmake_compiler(f, language, compiler):
s = compiler.split(' ', 1)
if len(s) == 2:
print(f'set(CMAKE_{language}_COMPILER_LAUNCHER {s[0]})', file=f)
compiler = s[1]
print(f'set(CMAKE_{language}_COMPILER {compiler})', file=f)
def __write_cmake_toolchain_file(f, toolchain):
if '-darwin' in toolchain.actual_arch:
cmake_system_name = 'Darwin'
elif toolchain.is_windows:
cmake_system_name = 'Windows'
else:
cmake_system_name = 'Linux'
f.write(f"""
set(CMAKE_SYSTEM_NAME {cmake_system_name})
set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]})
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}")
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}")
""")
__write_cmake_compiler(f, 'C', toolchain.cc)
__write_cmake_compiler(f, 'CXX', toolchain.cxx)
def configure(toolchain, src, build, args=()): def configure(toolchain, src, build, args=()):
cross_args = [] cross_args = []
if toolchain.is_windows: if toolchain.is_windows:
cross_args.append('-DCMAKE_SYSTEM_NAME=Windows')
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres) cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres)
# Several targets need a sysroot to prevent pkg-config from
# looking for libraries on the build host (TODO: fix this
# properly); but we must not do that on Android because the NDK
# has a sysroot already
if '-android' not in toolchain.actual_arch and '-darwin' not in toolchain.actual_arch:
cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
os.makedirs(build, exist_ok=True)
cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
with open(cmake_toolchain_file, 'w') as f:
__write_cmake_toolchain_file(f, toolchain)
configure = [ configure = [
'cmake', 'cmake',
src, src,
'-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file,
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix, '-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
'-DCMAKE_BUILD_TYPE=release', '-DCMAKE_BUILD_TYPE=release',
'-DCMAKE_C_COMPILER=' + toolchain.cc,
'-DCMAKE_CXX_COMPILER=' + toolchain.cxx,
'-DCMAKE_C_FLAGS=' + toolchain.cflags + ' ' + toolchain.cppflags,
'-DCMAKE_CXX_FLAGS=' + toolchain.cxxflags + ' ' + toolchain.cppflags,
'-GNinja', '-GNinja',
] + cross_args + args ] + cross_args + args
...@@ -29,17 +65,22 @@ def configure(toolchain, src, build, args=()): ...@@ -29,17 +65,22 @@ def configure(toolchain, src, build, args=()):
class CmakeProject(Project): class CmakeProject(Project):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url, md5, installed, configure_args=[],
windows_configure_args=[],
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
self.windows_configure_args = windows_configure_args
def configure(self, toolchain): def configure(self, toolchain):
src = self.unpack(toolchain) src = self.unpack(toolchain)
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
configure(toolchain, src, build, self.configure_args) configure_args = self.configure_args
if toolchain.is_windows:
configure_args = configure_args + self.windows_configure_args
configure(toolchain, src, build, configure_args)
return build return build
def build(self, toolchain): def _build(self, toolchain):
build = self.configure(toolchain) build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'], subprocess.check_call(['ninja', 'install'],
cwd=build, env=toolchain.env) cwd=build, env=toolchain.env)
...@@ -10,12 +10,7 @@ class FfmpegProject(Project): ...@@ -10,12 +10,7 @@ class FfmpegProject(Project):
self.configure_args = configure_args self.configure_args = configure_args
self.cppflags = cppflags self.cppflags = cppflags
def _filter_cflags(self, flags): def _build(self, toolchain):
# FFmpeg expects the GNU as syntax
flags = flags.replace(' -integrated-as ', ' -no-integrated-as ')
return flags
def build(self, toolchain):
src = self.unpack(toolchain) src = self.unpack(toolchain)
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
...@@ -36,8 +31,8 @@ class FfmpegProject(Project): ...@@ -36,8 +31,8 @@ class FfmpegProject(Project):
'--cc=' + toolchain.cc, '--cc=' + toolchain.cc,
'--cxx=' + toolchain.cxx, '--cxx=' + toolchain.cxx,
'--nm=' + toolchain.nm, '--nm=' + toolchain.nm,
'--extra-cflags=' + self._filter_cflags(toolchain.cflags) + ' ' + toolchain.cppflags + ' ' + self.cppflags, '--extra-cflags=' + toolchain.cflags + ' ' + toolchain.cppflags + ' ' + self.cppflags,
'--extra-cxxflags=' + self._filter_cflags(toolchain.cxxflags) + ' ' + toolchain.cppflags + ' ' + self.cppflags, '--extra-cxxflags=' + toolchain.cxxflags + ' ' + toolchain.cppflags + ' ' + self.cppflags,
'--extra-ldflags=' + toolchain.ldflags, '--extra-ldflags=' + toolchain.ldflags,
'--extra-libs=' + toolchain.libs, '--extra-libs=' + toolchain.libs,
'--ar=' + toolchain.ar, '--ar=' + toolchain.ar,
......
import os, shutil
import re
from .project import Project
# This class installs just the public headers and a fake pkg-config
# file which defines the macro "DYNAMIC_JACK". This tells MPD's JACK
# output plugin to load the libjack64.dll dynamically using
# LoadLibrary(). This kludge avoids the runtime DLL dependency for
# users who don't use JACK, but still allows using the system JACK
# client library.
#
# The problem with JACK is that it uses an extremely fragile shared
# memory protocol to communicate with the daemon. One needs to use
# daemon and client library from the same build. That's why we don't
# build libjack statically here; it would probably not be compatible
# with the user's JACK daemon.
class JackProject(Project):
def __init__(self, url, md5, installed,
**kwargs):
m = re.match(r'.*/v([\d.]+)\.tar\.gz$', url)
self.version = m.group(1)
Project.__init__(self, url, md5, installed,
name='jack2', version=self.version,
base='jack2-' + self.version,
**kwargs)
def _build(self, toolchain):
src = self.unpack(toolchain)
includes = ['jack.h', 'ringbuffer.h', 'systemdeps.h', 'transport.h', 'types.h', 'weakmacros.h']
includedir = os.path.join(toolchain.install_prefix, 'include', 'jack')
os.makedirs(includedir, exist_ok=True)
for i in includes:
shutil.copyfile(os.path.join(src, 'common', 'jack', i),
os.path.join(includedir, i))
with open(os.path.join(toolchain.install_prefix, 'lib', 'pkgconfig', 'jack.pc'), 'w') as f:
print("prefix=" + toolchain.install_prefix, file=f)
print("", file=f)
print("Name: jack", file=f)
print("Description: dummy", file=f)
print("Version: " + self.version, file=f)
print("Libs: ", file=f)
print("Cflags: -DDYNAMIC_JACK", file=f)
...@@ -7,37 +7,27 @@ from build.meson import MesonProject ...@@ -7,37 +7,27 @@ from build.meson import MesonProject
from build.cmake import CmakeProject from build.cmake import CmakeProject
from build.autotools import AutotoolsProject from build.autotools import AutotoolsProject
from build.ffmpeg import FfmpegProject from build.ffmpeg import FfmpegProject
from build.openssl import OpenSSLProject
from build.boost import BoostProject from build.boost import BoostProject
from build.jack import JackProject
libmpdclient = MesonProject( libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz', 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.20.tar.xz',
'4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa', '18793f68e939c3301e34d8fcadea1f7daa24143941263cecadb80126194e277d',
'lib/libmpdclient.a', 'lib/libmpdclient.a',
) )
libogg = AutotoolsProject( libogg = CmakeProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz', 'http://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz',
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe', 'c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705',
'lib/libogg.a', 'lib/libogg.a',
[ [
'--disable-shared', '--enable-static', '-DBUILD_SHARED_LIBS=OFF',
'-DINSTALL_DOCS=OFF',
'-DINSTALL_CMAKE_PACKAGE_MODULE=OFF',
], ],
) )
libvorbis = AutotoolsProject(
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
'lib/libvorbis.a',
[
'--disable-shared', '--enable-static',
],
edits={
# this option is not understood by clang
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
}
)
opus = AutotoolsProject( opus = AutotoolsProject(
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz', 'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d', '65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
...@@ -53,8 +43,8 @@ opus = AutotoolsProject( ...@@ -53,8 +43,8 @@ opus = AutotoolsProject(
) )
flac = AutotoolsProject( flac = AutotoolsProject(
'http://downloads.xiph.org/releases/flac/flac-1.3.3.tar.xz', 'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
'213e82bd716c9de6db2f98bcadbc4c24c7e2efe8c75939a1a84e28539c4e1748', '8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
'lib/libFLAC.a', 'lib/libFLAC.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -65,8 +55,8 @@ flac = AutotoolsProject( ...@@ -65,8 +55,8 @@ flac = AutotoolsProject(
) )
zlib = ZlibProject( zlib = ZlibProject(
'http://zlib.net/zlib-1.2.11.tar.xz', 'http://zlib.net/zlib-1.2.12.tar.xz',
'4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066', '7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18',
'lib/libz.a', 'lib/libz.a',
) )
...@@ -121,18 +111,31 @@ libmodplug = AutotoolsProject( ...@@ -121,18 +111,31 @@ libmodplug = AutotoolsProject(
], ],
) )
libopenmpt = AutotoolsProject(
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
'lib/libopenmpt.a',
[
'--disable-shared', '--enable-static',
'--disable-openmpt123',
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
],
base='libopenmpt-0.5.12+release.autotools',
)
wildmidi = CmakeProject( wildmidi = CmakeProject(
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3', 'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4',
'498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5', '6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430',
'lib/libWildMidi.a', 'lib/libWildMidi.a',
[ [
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DWANT_PLAYER=OFF', '-DWANT_PLAYER=OFF',
'-DWANT_STATIC=ON', '-DWANT_STATIC=ON',
], ],
base='wildmidi-wildmidi-0.4.3', base='wildmidi-wildmidi-0.4.4',
name='wildmidi', name='wildmidi',
version='0.4.3', version='0.4.4',
) )
gme = CmakeProject( gme = CmakeProject(
...@@ -148,8 +151,8 @@ gme = CmakeProject( ...@@ -148,8 +151,8 @@ gme = CmakeProject(
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.2.3.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz',
'9df6c90aed1337634c1fb026fb01c154c29c82a64ea71291ff2da9aacb9aad31', 'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -174,6 +177,8 @@ ffmpeg = FfmpegProject( ...@@ -174,6 +177,8 @@ ffmpeg = FfmpegProject(
'--disable-filters', '--disable-filters',
'--disable-v4l2_m2m', '--disable-v4l2_m2m',
'--disable-vulkan',
'--disable-parser=bmp', '--disable-parser=bmp',
'--disable-parser=cavsvideo', '--disable-parser=cavsvideo',
'--disable-parser=dvbsub', '--disable-parser=dvbsub',
...@@ -376,48 +381,50 @@ ffmpeg = FfmpegProject( ...@@ -376,48 +381,50 @@ ffmpeg = FfmpegProject(
], ],
) )
curl = AutotoolsProject( openssl = OpenSSLProject(
'http://curl.haxx.se/download/curl-7.70.0.tar.xz', 'https://www.openssl.org/source/openssl-3.0.5.tar.gz',
'032f43f2674008c761af19bf536374128c16241fb234699a55f9fb603fcfbae7', 'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a',
'lib/libcurl.a', 'include/openssl/ossl_typ.h',
[
'--disable-shared', '--enable-static',
'--disable-debug',
'--enable-http',
'--enable-ipv6',
'--disable-ftp', '--disable-file',
'--disable-ldap', '--disable-ldaps',
'--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
'--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
'--disable-smb',
'--disable-gopher',
'--disable-manual',
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
'--disable-doh',
'--disable-mime',
'--disable-netrc',
'--disable-progress-meter',
'--disable-alt-svc',
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
],
patches='src/lib/curl/patches',
) )
libexpat = AutotoolsProject( curl = CmakeProject(
'https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.bz2', 'https://curl.se/download/curl-7.84.0.tar.xz',
'f1063084dc4302a427dabcca499c8312b3a32a29b7d2506653ecc8f950a9a237', '2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8',
'lib/libexpat.a', 'lib/libcurl.a',
[ [
'--disable-shared', '--enable-static', '-DBUILD_CURL_EXE=OFF',
'--without-docbook', '-DBUILD_SHARED_LIBS=OFF',
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
'-DCURL_DISABLE_LDAP=ON',
'-DCURL_DISABLE_TELNET=ON',
'-DCURL_DISABLE_DICT=ON',
'-DCURL_DISABLE_FILE=ON',
'-DCURL_DISABLE_FTP=ON',
'-DCURL_DISABLE_TFTP=ON',
'-DCURL_DISABLE_LDAPS=ON',
'-DCURL_DISABLE_RTSP=ON',
'-DCURL_DISABLE_PROXY=ON',
'-DCURL_DISABLE_POP3=ON',
'-DCURL_DISABLE_IMAP=ON',
'-DCURL_DISABLE_SMTP=ON',
'-DCURL_DISABLE_GOPHER=ON',
'-DCURL_DISABLE_COOKIES=ON',
'-DCURL_DISABLE_CRYPTO_AUTH=ON',
'-DCURL_DISABLE_ALTSVC=ON',
'-DCMAKE_USE_LIBSSH2=OFF',
'-DCURL_WINDOWS_SSPI=OFF',
'-DCURL_DISABLE_NTLM=ON',
'-DBUILD_TESTING=OFF',
],
windows_configure_args=[
'-DCURL_USE_SCHANNEL=ON',
], ],
patches='src/lib/curl/patches',
) )
libnfs = AutotoolsProject( libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-4.0.0.tar.gz', 'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
'6ee77e9fe220e2d3e3b1f53cfea04fb319828cc7dbb97dd9df09e46e901d797d', '7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
'lib/libnfs.a', 'lib/libnfs.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -428,12 +435,18 @@ libnfs = AutotoolsProject( ...@@ -428,12 +435,18 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples', '--disable-utils', '--disable-examples',
], ],
base='libnfs-libnfs-4.0.0', base='libnfs-libnfs-5.0.1',
autoreconf=True, autoreconf=True,
) )
jack = JackProject(
'https://github.com/jackaudio/jack2/archive/v1.9.17.tar.gz',
'38f674bbc57852a8eb3d9faa1f96a0912d26f7d5df14c11005ad499c8ae352f2',
'lib/pkgconfig/jack.pc',
)
boost = BoostProject( boost = BoostProject(
'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2', 'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2',
'4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402', '475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39',
'include/boost/version.hpp', 'include/boost/version.hpp',
) )
...@@ -22,7 +22,7 @@ class MakeProject(Project): ...@@ -22,7 +22,7 @@ class MakeProject(Project):
subprocess.check_call(['/usr/bin/make'] + args, subprocess.check_call(['/usr/bin/make'] + args,
cwd=wd, env=toolchain.env) cwd=wd, env=toolchain.env)
def build(self, toolchain, wd, install=True): def build_make(self, toolchain, wd, install=True):
self.make(toolchain, wd, self.get_make_args(toolchain)) self.make(toolchain, wd, self.get_make_args(toolchain))
if install: if install:
self.make(toolchain, wd, self.get_make_install_args(toolchain)) self.make(toolchain, wd, self.get_make_install_args(toolchain))
import os.path, subprocess, sys import os.path, subprocess, sys
import platform
from build.project import Project from build.project import Project
...@@ -34,41 +35,48 @@ def make_cross_file(toolchain): ...@@ -34,41 +35,48 @@ def make_cross_file(toolchain):
path = os.path.join(toolchain.build_path, 'meson.cross') path = os.path.join(toolchain.build_path, 'meson.cross')
os.makedirs(toolchain.build_path, exist_ok=True) os.makedirs(toolchain.build_path, exist_ok=True)
with open(path, 'w') as f: with open(path, 'w') as f:
f.write(""" f.write(f"""
[binaries] [binaries]
c = '%s' c = '{toolchain.cc}'
cpp = '%s' cpp = '{toolchain.cxx}'
ar = '%s' ar = '{toolchain.ar}'
strip = '%s' strip = '{toolchain.strip}'
pkgconfig = '%s' pkgconfig = '{toolchain.pkg_config}'
%s """)
[properties] if toolchain.is_windows and platform.system() != 'Windows':
root = '%s' f.write(f"windres = '{toolchain.windres}'\n")
c_args = %s # Run unit tests with WINE when cross-building for Windows
c_link_args = %s print("exe_wrapper = 'wine'", file=f)
cpp_args = %s f.write(f"""
cpp_link_args = %s [properties]
root = '{toolchain.install_prefix}'
""")
if 'android' in toolchain.arch:
f.write("""
# Keep Meson from executing Android-x86 test binariees # Keep Meson from executing Android-x86 test binariees
needs_exe_wrapper = true needs_exe_wrapper = true
""")
f.write(f"""
[built-in options]
c_args = {repr((toolchain.cppflags + ' ' + toolchain.cflags).split())}
c_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
cpp_args = {repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split())}
cpp_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
""")
f.write(f"""
[host_machine] [host_machine]
system = '%s' system = '{system}'
cpu_family = '%s' cpu_family = '{cpu_family}'
cpu = '%s' cpu = '{cpu}'
endian = '%s' endian = '{endian}'
""" % (toolchain.cc, toolchain.cxx, toolchain.ar, toolchain.strip, """)
toolchain.pkg_config,
windres,
toolchain.install_prefix,
repr((toolchain.cppflags + ' ' + toolchain.cflags).split()),
repr(toolchain.ldflags.split() + toolchain.libs.split()),
repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split()),
repr(toolchain.ldflags.split() + toolchain.libs.split()),
system, cpu_family, cpu, endian))
return path return path
def configure(toolchain, src, build, args=()): def configure(toolchain, src, build, args=()):
...@@ -79,11 +87,6 @@ def configure(toolchain, src, build, args=()): ...@@ -79,11 +87,6 @@ def configure(toolchain, src, build, args=()):
'--prefix', toolchain.install_prefix, '--prefix', toolchain.install_prefix,
# this is necessary because Meson uses Debian's build machine
# MultiArch path (e.g. "lib/x86_64-linux-gnu") for cross
# builds, which is obviously wrong
'--libdir', 'lib',
'--buildtype', 'plain', '--buildtype', 'plain',
'--default-library=static', '--default-library=static',
...@@ -91,7 +94,12 @@ def configure(toolchain, src, build, args=()): ...@@ -91,7 +94,12 @@ def configure(toolchain, src, build, args=()):
'--cross-file', cross_file, '--cross-file', cross_file,
] + args ] + args
subprocess.check_call(configure, env=toolchain.env) env = toolchain.env.copy()
# Meson 0.54 requires the BOOST_ROOT environment variable
env['BOOST_ROOT'] = toolchain.install_prefix
subprocess.check_call(configure, env=env)
class MesonProject(Project): class MesonProject(Project):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url, md5, installed, configure_args=[],
...@@ -105,7 +113,7 @@ class MesonProject(Project): ...@@ -105,7 +113,7 @@ class MesonProject(Project):
configure(toolchain, src, build, self.configure_args) configure(toolchain, src, build, self.configure_args)
return build return build
def build(self, toolchain): def _build(self, toolchain):
build = self.configure(toolchain) build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'], subprocess.check_call(['ninja', 'install'],
cwd=build, env=toolchain.env) cwd=build, env=toolchain.env)
import subprocess
from build.makeproject import MakeProject
class OpenSSLProject(MakeProject):
def __init__(self, url, md5, installed,
**kwargs):
MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs)
def get_make_args(self, toolchain):
return MakeProject.get_make_args(self, toolchain) + [
'CC=' + toolchain.cc,
'CFLAGS=' + toolchain.cflags,
'CPPFLAGS=' + toolchain.cppflags,
'AR=' + toolchain.ar,
'RANLIB=' + toolchain.ranlib,
'build_libs',
]
def get_make_install_args(self, toolchain):
# OpenSSL's Makefile runs "ranlib" during installation
return MakeProject.get_make_install_args(self, toolchain) + [
'RANLIB=' + toolchain.ranlib,
]
def _build(self, toolchain):
src = self.unpack(toolchain, out_of_tree=False)
# OpenSSL has a weird target architecture scheme with lots of
# hard-coded architectures; this table translates between our
# "toolchain_arch" (HOST_TRIPLET) and the OpenSSL target
openssl_archs = {
# not using "android-*" because those OpenSSL targets want
# to know where the SDK is, but our own build scripts
# prepared everything already to look like a regular Linux
# build
'arm-linux-androideabi': 'linux-generic32',
'aarch64-linux-android': 'linux-aarch64',
'i686-linux-android': 'linux-x86-clang',
'x86_64-linux-android': 'linux-x86_64-clang',
# Kobo
'arm-linux-gnueabihf': 'linux-generic32',
# Windows
'i686-w64-mingw32': 'mingw',
'x86_64-w64-mingw32': 'mingw64',
}
openssl_arch = openssl_archs[toolchain.arch]
subprocess.check_call(['./Configure',
'no-shared',
'no-module', 'no-engine', 'no-static-engine',
'no-async',
'no-tests',
'no-asm', # "asm" causes build failures on Windows
openssl_arch,
'--prefix=' + toolchain.install_prefix],
cwd=src, env=toolchain.env)
self.build_make(toolchain, src)
...@@ -20,7 +20,7 @@ class Project: ...@@ -20,7 +20,7 @@ class Project:
self.base = base self.base = base
if name is None or version is None: if name is None or version is None:
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*)$', self.base) m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
if name is None: name = m.group(1) if name is None: name = m.group(1)
if version is None: version = m.group(2) if version is None: version = m.group(2)
...@@ -79,3 +79,6 @@ class Project: ...@@ -79,3 +79,6 @@ class Project:
pass pass
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
return path return path
def build(self, toolchain):
self._build(toolchain)
...@@ -7,5 +7,11 @@ def untar(tarball_path, parent_path, base): ...@@ -7,5 +7,11 @@ def untar(tarball_path, parent_path, base):
except FileNotFoundError: except FileNotFoundError:
pass pass
os.makedirs(parent_path, exist_ok=True) os.makedirs(parent_path, exist_ok=True)
subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path]) try:
subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path])
except FileNotFoundError:
import tarfile
tar = tarfile.open(tarball_path)
tar.extractall(path=parent_path)
tar.close()
return path return path
...@@ -31,6 +31,8 @@ def guess_digest_algorithm(digest): ...@@ -31,6 +31,8 @@ def guess_digest_algorithm(digest):
return hashlib.sha1 return hashlib.sha1
elif l == 64: elif l == 64:
return hashlib.sha256 return hashlib.sha256
elif l == 128:
return hashlib.sha512
else: else:
return None return None
......
...@@ -7,7 +7,7 @@ class ZlibProject(Project): ...@@ -7,7 +7,7 @@ class ZlibProject(Project):
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
def build(self, toolchain): def _build(self, toolchain):
src = self.unpack(toolchain, out_of_tree=False) src = self.unpack(toolchain, out_of_tree=False)
subprocess.check_call(['/usr/bin/make', '--quiet', subprocess.check_call(['/usr/bin/make', '--quiet',
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -95,7 +95,7 @@ public: ...@@ -95,7 +95,7 @@ public:
constexpr double ToDoubleS() const { constexpr double ToDoubleS() const {
return double(count()) / 1000.; return double(count()) / 1000.;
}; }
constexpr bool IsZero() const { constexpr bool IsZero() const {
return count() == 0; return count() == 0;
...@@ -199,7 +199,7 @@ public: ...@@ -199,7 +199,7 @@ public:
constexpr double ToDoubleS() const { constexpr double ToDoubleS() const {
return double(count()) / 1000.; return double(count()) / 1000.;
}; }
constexpr bool IsZero() const { constexpr bool IsZero() const {
return count() == 0; return count() == 0;
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -37,11 +37,16 @@ ...@@ -37,11 +37,16 @@
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
#include "fs/StandardDirectory.hxx" #include "fs/StandardDirectory.hxx"
#include "system/Error.hxx" #include "event/Features.h"
#include "util/RuntimeError.hxx" #include "io/uring/Features.h"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/OptionDef.hxx" #include "util/OptionDef.hxx"
#include "util/OptionParser.hxx" #include "util/OptionParser.hxx"
#include "Version.h"
#ifdef _WIN32
#include "system/Error.hxx"
#endif
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
#include "db/Registry.hxx" #include "db/Registry.hxx"
...@@ -55,6 +60,7 @@ ...@@ -55,6 +60,7 @@
#include "neighbor/NeighborPlugin.hxx" #include "neighbor/NeighborPlugin.hxx"
#endif #endif
#include "encoder/Features.h"
#ifdef ENABLE_ENCODER #ifdef ENABLE_ENCODER
#include "encoder/EncoderList.hxx" #include "encoder/EncoderList.hxx"
#include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderPlugin.hxx"
...@@ -65,9 +71,6 @@ ...@@ -65,9 +71,6 @@
#include "archive/ArchivePlugin.hxx" #include "archive/ArchivePlugin.hxx"
#endif #endif
#include <stdio.h>
#include <stdlib.h>
namespace { namespace {
#ifdef _WIN32 #ifdef _WIN32
constexpr auto CONFIG_FILE_LOCATION = Path::FromFS(PATH_LITERAL("mpd\\mpd.conf")); constexpr auto CONFIG_FILE_LOCATION = Path::FromFS(PATH_LITERAL("mpd\\mpd.conf"));
...@@ -77,12 +80,15 @@ constexpr auto USER_CONFIG_FILE_LOCATION1 = Path::FromFS(PATH_LITERAL(".mpdconf" ...@@ -77,12 +80,15 @@ constexpr auto USER_CONFIG_FILE_LOCATION1 = Path::FromFS(PATH_LITERAL(".mpdconf"
constexpr auto USER_CONFIG_FILE_LOCATION2 = Path::FromFS(PATH_LITERAL(".mpd/mpd.conf")); constexpr auto USER_CONFIG_FILE_LOCATION2 = Path::FromFS(PATH_LITERAL(".mpd/mpd.conf"));
constexpr auto USER_CONFIG_FILE_LOCATION_XDG = Path::FromFS(PATH_LITERAL("mpd/mpd.conf")); constexpr auto USER_CONFIG_FILE_LOCATION_XDG = Path::FromFS(PATH_LITERAL("mpd/mpd.conf"));
#endif #endif
} } // namespace
enum Option { enum Option {
OPTION_KILL, OPTION_KILL,
OPTION_NO_CONFIG, OPTION_NO_CONFIG,
OPTION_NO_DAEMON, OPTION_NO_DAEMON,
#ifdef __linux__
OPTION_SYSTEMD,
#endif
OPTION_STDOUT, OPTION_STDOUT,
OPTION_STDERR, OPTION_STDERR,
OPTION_VERBOSE, OPTION_VERBOSE,
...@@ -95,6 +101,9 @@ static constexpr OptionDef option_defs[] = { ...@@ -95,6 +101,9 @@ static constexpr OptionDef option_defs[] = {
{"kill", "kill the currently running mpd session"}, {"kill", "kill the currently running mpd session"},
{"no-config", "don't read from config"}, {"no-config", "don't read from config"},
{"no-daemon", "don't detach from console"}, {"no-daemon", "don't detach from console"},
#ifdef __linux__
{"systemd", "systemd service mode"},
#endif
{"stdout", nullptr}, // hidden, compatibility with old versions {"stdout", nullptr}, // hidden, compatibility with old versions
{"stderr", "print messages to stderr"}, {"stderr", "print messages to stderr"},
{"verbose", 'v', "verbose logging"}, {"verbose", 'v', "verbose logging"},
...@@ -105,13 +114,13 @@ static constexpr OptionDef option_defs[] = { ...@@ -105,13 +114,13 @@ static constexpr OptionDef option_defs[] = {
static constexpr Domain cmdline_domain("cmdline"); static constexpr Domain cmdline_domain("cmdline");
gcc_noreturn [[noreturn]]
static void version(void) static void version()
{ {
printf("Music Player Daemon " VERSION " (%s)" printf("Music Player Daemon " VERSION " (%s)"
"\n" "\n"
"Copyright 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" "Copyright 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
"Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>\n" "Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>\n"
"This is free software; see the source for copying conditions. There is NO\n" "This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
GIT_VERSION); GIT_VERSION);
...@@ -145,15 +154,19 @@ static void version(void) ...@@ -145,15 +154,19 @@ static void version(void)
"Decoders plugins:\n"); "Decoders plugins:\n");
decoder_plugins_for_each([](const DecoderPlugin &plugin){ decoder_plugins_for_each([](const DecoderPlugin &plugin){
printf(" [%s]", plugin.name); printf(" [%s]", plugin.name);
const char *const*suffixes = plugin.suffixes; const char *const*suffixes = plugin.suffixes;
if (suffixes != nullptr) if (suffixes != nullptr)
for (; *suffixes != nullptr; ++suffixes) for (; *suffixes != nullptr; ++suffixes)
printf(" %s", *suffixes); printf(" %s", *suffixes);
printf("\n"); if (plugin.protocols != nullptr)
}); for (const auto &i : plugin.protocols())
printf(" %s", i.c_str());
printf("\n");
});
printf("\n" printf("\n"
"Filters:\n" "Filters:\n"
...@@ -202,6 +215,9 @@ static void version(void) ...@@ -202,6 +215,9 @@ static void version(void)
"\n" "\n"
"Input plugins:\n" "Input plugins:\n"
" file" " file"
#ifdef HAVE_URING
" io_uring"
#endif
#ifdef ENABLE_ARCHIVE #ifdef ENABLE_ARCHIVE
" archive" " archive"
#endif #endif
...@@ -255,7 +271,7 @@ static void version(void) ...@@ -255,7 +271,7 @@ static void version(void)
#endif #endif
"\n"); "\n");
exit(EXIT_SUCCESS); std::exit(EXIT_SUCCESS);
} }
static void PrintOption(const OptionDef &opt) static void PrintOption(const OptionDef &opt)
...@@ -271,8 +287,8 @@ static void PrintOption(const OptionDef &opt) ...@@ -271,8 +287,8 @@ static void PrintOption(const OptionDef &opt)
opt.GetDescription()); opt.GetDescription());
} }
gcc_noreturn [[noreturn]]
static void help(void) static void help()
{ {
printf("Usage:\n" printf("Usage:\n"
" mpd [OPTION...] [path/to/mpd.conf]\n" " mpd [OPTION...] [path/to/mpd.conf]\n"
...@@ -282,10 +298,10 @@ static void help(void) ...@@ -282,10 +298,10 @@ static void help(void)
"Options:\n"); "Options:\n");
for (const auto &i : option_defs) for (const auto &i : option_defs)
if(i.HasDescription() == true) // hide hidden options from help print if(i.HasDescription()) // hide hidden options from help print
PrintOption(i); PrintOption(i);
exit(EXIT_SUCCESS); std::exit(EXIT_SUCCESS);
} }
class ConfigLoader class ConfigLoader
...@@ -296,7 +312,7 @@ public: ...@@ -296,7 +312,7 @@ public:
explicit ConfigLoader(ConfigData &_config) noexcept explicit ConfigLoader(ConfigData &_config) noexcept
:config(_config) {} :config(_config) {}
bool TryFile(const Path path); bool TryFile(Path path);
bool TryFile(const AllocatedPath &base_path, Path path); bool TryFile(const AllocatedPath &base_path, Path path);
}; };
...@@ -318,7 +334,7 @@ bool ConfigLoader::TryFile(const AllocatedPath &base_path, Path path) ...@@ -318,7 +334,7 @@ bool ConfigLoader::TryFile(const AllocatedPath &base_path, Path path)
} }
void void
ParseCommandLine(int argc, char **argv, struct options &options, ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
ConfigData &config) ConfigData &config)
{ {
bool use_config_file = true; bool use_config_file = true;
...@@ -339,6 +355,13 @@ ParseCommandLine(int argc, char **argv, struct options &options, ...@@ -339,6 +355,13 @@ ParseCommandLine(int argc, char **argv, struct options &options,
options.daemon = false; options.daemon = false;
break; break;
#ifdef __linux__
case OPTION_SYSTEMD:
options.daemon = false;
options.systemd = true;
break;
#endif
case OPTION_STDOUT: case OPTION_STDOUT:
case OPTION_STDERR: case OPTION_STDERR:
options.log_stderr = true; options.log_stderr = true;
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -22,15 +22,20 @@ ...@@ -22,15 +22,20 @@
struct ConfigData; struct ConfigData;
struct options { struct CommandLineOptions {
bool kill = false; bool kill = false;
bool daemon = true; bool daemon = true;
#ifdef __linux__
bool systemd = false;
#endif
bool log_stderr = false; bool log_stderr = false;
bool verbose = false; bool verbose = false;
}; };
void void
ParseCommandLine(int argc, char **argv, struct options &options, ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
ConfigData &config); ConfigData &config);
#endif #endif
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -19,4 +19,4 @@ ...@@ -19,4 +19,4 @@
#include "GitVersion.hxx" #include "GitVersion.hxx"
char GIT_VERSION[] = "@VCS_TAG@"; const char GIT_VERSION[] = "@VCS_TAG@";
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -20,6 +20,6 @@ ...@@ -20,6 +20,6 @@
#ifndef MPD_GIT_VERSION_HXX #ifndef MPD_GIT_VERSION_HXX
#define MPD_GIT_VERSION_HXX #define MPD_GIT_VERSION_HXX
extern char GIT_VERSION[]; extern const char GIT_VERSION[];
#endif #endif
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -26,12 +26,12 @@ ...@@ -26,12 +26,12 @@
#include "Main.hxx" #include "Main.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include <assert.h> #include <cassert>
void void
idle_add(unsigned flags) idle_add(unsigned flags)
{ {
assert(flags != 0); assert(flags != 0);
instance->EmitIdle(flags); global_instance->EmitIdle(flags);
} }
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -25,9 +25,9 @@ ...@@ -25,9 +25,9 @@
#include "IdleFlags.hxx" #include "IdleFlags.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include <assert.h> #include <cassert>
static const char *const idle_names[] = { static constexpr const char * idle_names[] = {
"database", "database",
"stored_playlist", "stored_playlist",
"playlist", "playlist",
...@@ -42,7 +42,7 @@ static const char *const idle_names[] = { ...@@ -42,7 +42,7 @@ static const char *const idle_names[] = {
"neighbor", "neighbor",
"mount", "mount",
"partition", "partition",
nullptr nullptr,
}; };
const char*const* const char*const*
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
#ifndef MPD_IDLE_FLAGS_HXX #ifndef MPD_IDLE_FLAGS_HXX
#define MPD_IDLE_FLAGS_HXX #define MPD_IDLE_FLAGS_HXX
#include "util/Compiler.h"
/** song database has been updated*/ /** song database has been updated*/
static constexpr unsigned IDLE_DATABASE = 0x1; static constexpr unsigned IDLE_DATABASE = 0x1;
...@@ -73,7 +71,7 @@ static constexpr unsigned IDLE_PARTITION = 0x2000; ...@@ -73,7 +71,7 @@ static constexpr unsigned IDLE_PARTITION = 0x2000;
/** /**
* Get idle names * Get idle names
*/ */
gcc_const [[gnu::const]]
const char*const* const char*const*
idle_get_names() noexcept; idle_get_names() noexcept;
...@@ -81,7 +79,7 @@ idle_get_names() noexcept; ...@@ -81,7 +79,7 @@ idle_get_names() noexcept;
* Parse an idle name and return its mask. Returns 0 if the given * Parse an idle name and return its mask. Returns 0 if the given
* name is unknown. * name is unknown.
*/ */
gcc_nonnull_all gcc_pure [[gnu::nonnull]] [[gnu::pure]]
unsigned unsigned
idle_parse_name(const char *name) noexcept; idle_parse_name(const char *name) noexcept;
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -20,12 +20,15 @@ ...@@ -20,12 +20,15 @@
#include "config.h" #include "config.h"
#include "Instance.hxx" #include "Instance.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Idle.hxx" #include "IdleFlags.hxx"
#include "StateFile.hxx"
#include "Stats.hxx" #include "Stats.hxx"
#include "client/List.hxx"
#include "input/cache/Manager.hxx"
#ifdef ENABLE_CURL #ifdef ENABLE_CURL
#include "RemoteTagCache.hxx" #include "RemoteTagCache.hxx"
#include "util/UriUtil.hxx" #include "util/UriExtract.hxx"
#endif #endif
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
...@@ -34,14 +37,20 @@ ...@@ -34,14 +37,20 @@
#include "db/update/Service.hxx" #include "db/update/Service.hxx"
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#ifdef ENABLE_INOTIFY
#include "db/update/InotifyUpdate.hxx"
#endif
#ifdef ENABLE_NEIGHBOR_PLUGINS
#include "neighbor/Glue.hxx"
#endif
#ifdef ENABLE_SQLITE #ifdef ENABLE_SQLITE
#include "sticker/StickerDatabase.hxx" #include "sticker/Database.hxx"
#include "sticker/SongSticker.hxx" #include "sticker/SongSticker.hxx"
#endif #endif
#endif #endif
#include <exception>
Instance::Instance() Instance::Instance()
:rtio_thread(true), :rtio_thread(true),
#ifdef ENABLE_SYSTEMD_DAEMON #ifdef ENABLE_SYSTEMD_DAEMON
...@@ -65,6 +74,13 @@ Instance::~Instance() noexcept ...@@ -65,6 +74,13 @@ Instance::~Instance() noexcept
#endif #endif
} }
void
Instance::OnStateModified() noexcept
{
if (state_file)
state_file->CheckModified();
}
Partition * Partition *
Instance::FindPartition(const char *name) noexcept Instance::FindPartition(const char *name) noexcept
{ {
...@@ -75,6 +91,20 @@ Instance::FindPartition(const char *name) noexcept ...@@ -75,6 +91,20 @@ Instance::FindPartition(const char *name) noexcept
return nullptr; return nullptr;
} }
void
Instance::DeletePartition(Partition &partition) noexcept
{
// TODO: use boost::intrusive::list to avoid this loop
for (auto i = partitions.begin();; ++i) {
assert(i != partitions.end());
if (&*i == &partition) {
partitions.erase(i);
break;
}
}
}
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
const Database & const Database &
...@@ -88,7 +118,7 @@ Instance::GetDatabaseOrThrow() const ...@@ -88,7 +118,7 @@ Instance::GetDatabaseOrThrow() const
} }
void void
Instance::OnDatabaseModified() Instance::OnDatabaseModified() noexcept
{ {
assert(database != nullptr); assert(database != nullptr);
...@@ -101,15 +131,15 @@ Instance::OnDatabaseModified() ...@@ -101,15 +131,15 @@ Instance::OnDatabaseModified()
} }
void void
Instance::OnDatabaseSongRemoved(const char *uri) Instance::OnDatabaseSongRemoved(const char *uri) noexcept
{ {
assert(database != nullptr); assert(database != nullptr);
#ifdef ENABLE_SQLITE #ifdef ENABLE_SQLITE
/* if the song has a sticker, remove it */ /* if the song has a sticker, remove it */
if (sticker_enabled()) { if (HasStickerDatabase()) {
try { try {
sticker_song_delete(uri); sticker_song_delete(*sticker_database, uri);
} catch (...) { } catch (...) {
} }
} }
...@@ -124,17 +154,15 @@ Instance::OnDatabaseSongRemoved(const char *uri) ...@@ -124,17 +154,15 @@ Instance::OnDatabaseSongRemoved(const char *uri)
#ifdef ENABLE_NEIGHBOR_PLUGINS #ifdef ENABLE_NEIGHBOR_PLUGINS
void void
Instance::FoundNeighbor(gcc_unused const NeighborInfo &info) noexcept Instance::FoundNeighbor([[maybe_unused]] const NeighborInfo &info) noexcept
{ {
for (auto &partition : partitions) EmitIdle(IDLE_NEIGHBOR);
partition.EmitIdle(IDLE_NEIGHBOR);
} }
void void
Instance::LostNeighbor(gcc_unused const NeighborInfo &info) noexcept Instance::LostNeighbor([[maybe_unused]] const NeighborInfo &info) noexcept
{ {
for (auto &partition : partitions) EmitIdle(IDLE_NEIGHBOR);
partition.EmitIdle(IDLE_NEIGHBOR);
} }
#endif #endif
...@@ -166,3 +194,18 @@ Instance::OnRemoteTag(const char *uri, const Tag &tag) noexcept ...@@ -166,3 +194,18 @@ Instance::OnRemoteTag(const char *uri, const Tag &tag) noexcept
} }
#endif #endif
void
Instance::OnIdle(unsigned flags) noexcept
{
/* broadcast to all partitions */
for (auto &partition : partitions)
partition.EmitIdle(flags);
}
void
Instance::FlushCaches() noexcept
{
if (input_cache)
input_cache->Flush();
}
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
#include "event/Loop.hxx" #include "event/Loop.hxx"
#include "event/Thread.hxx" #include "event/Thread.hxx"
#include "event/MaskMonitor.hxx" #include "event/MaskMonitor.hxx"
#include "util/Compiler.h"
#ifdef ENABLE_SYSTEMD_DAEMON #ifdef ENABLE_SYSTEMD_DAEMON
#include "lib/systemd/Watchdog.hxx" #include "lib/systemd/Watchdog.hxx"
...@@ -44,6 +43,9 @@ class NeighborGlue; ...@@ -44,6 +43,9 @@ class NeighborGlue;
#include "db/Ptr.hxx" #include "db/Ptr.hxx"
class Storage; class Storage;
class UpdateService; class UpdateService;
#ifdef ENABLE_INOTIFY
class InotifyUpdate;
#endif
#endif #endif
#include <memory> #include <memory>
...@@ -53,6 +55,8 @@ class ClientList; ...@@ -53,6 +55,8 @@ class ClientList;
struct Partition; struct Partition;
class StateFile; class StateFile;
class RemoteTagCache; class RemoteTagCache;
class StickerDatabase;
class InputCacheManager;
/** /**
* A utility class which, when used as the first base class, ensures * A utility class which, when used as the first base class, ensures
...@@ -97,10 +101,16 @@ struct Instance final ...@@ -97,10 +101,16 @@ struct Instance final
Systemd::Watchdog systemd_watchdog; Systemd::Watchdog systemd_watchdog;
#endif #endif
std::unique_ptr<InputCacheManager> input_cache;
/**
* Monitor for global idle events to be broadcasted to all
* partitions.
*/
MaskMonitor idle_monitor; MaskMonitor idle_monitor;
#ifdef ENABLE_NEIGHBOR_PLUGINS #ifdef ENABLE_NEIGHBOR_PLUGINS
NeighborGlue *neighbors; std::unique_ptr<NeighborGlue> neighbors;
#endif #endif
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
...@@ -113,17 +123,25 @@ struct Instance final ...@@ -113,17 +123,25 @@ struct Instance final
Storage *storage = nullptr; Storage *storage = nullptr;
UpdateService *update = nullptr; UpdateService *update = nullptr;
#ifdef ENABLE_INOTIFY
std::unique_ptr<InotifyUpdate> inotify_update;
#endif
#endif #endif
#ifdef ENABLE_CURL #ifdef ENABLE_CURL
std::unique_ptr<RemoteTagCache> remote_tag_cache; std::unique_ptr<RemoteTagCache> remote_tag_cache;
#endif #endif
ClientList *client_list; std::unique_ptr<ClientList> client_list;
std::list<Partition> partitions; std::list<Partition> partitions;
StateFile *state_file = nullptr; std::unique_ptr<StateFile> state_file;
#ifdef ENABLE_SQLITE
std::unique_ptr<StickerDatabase> sticker_database;
#endif
Instance(); Instance();
~Instance() noexcept; ~Instance() noexcept;
...@@ -131,21 +149,36 @@ struct Instance final ...@@ -131,21 +149,36 @@ struct Instance final
/** /**
* Wrapper for EventLoop::Break(). Call to initiate shutdown. * Wrapper for EventLoop::Break(). Call to initiate shutdown.
*/ */
void Break() { void Break() noexcept {
event_loop.Break(); event_loop.Break();
} }
void EmitIdle(unsigned mask) { /**
* Emit an "idle" event to all clients of all partitions.
*
* This method can be called from any thread.
*/
void EmitIdle(unsigned mask) noexcept {
idle_monitor.OrMask(mask); idle_monitor.OrMask(mask);
} }
/** /**
* Notify the #Instance that the state has been modified, and
* the #StateFile may need to be saved.
*
* This method must be called from the main thread.
*/
void OnStateModified() noexcept;
/**
* Find a #Partition with the given name. Returns nullptr if * Find a #Partition with the given name. Returns nullptr if
* no such partition was found. * no such partition was found.
*/ */
gcc_pure [[gnu::pure]]
Partition *FindPartition(const char *name) noexcept; Partition *FindPartition(const char *name) noexcept;
void DeletePartition(Partition &partition) noexcept;
void BeginShutdownPartitions() noexcept; void BeginShutdownPartitions() noexcept;
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
...@@ -154,7 +187,7 @@ struct Instance final ...@@ -154,7 +187,7 @@ struct Instance final
* if this MPD configuration has no database (no * if this MPD configuration has no database (no
* music_directory was configured). * music_directory was configured).
*/ */
Database *GetDatabase() { Database *GetDatabase() noexcept {
return database.get(); return database.get();
} }
...@@ -166,6 +199,12 @@ struct Instance final ...@@ -166,6 +199,12 @@ struct Instance final
const Database &GetDatabaseOrThrow() const; const Database &GetDatabaseOrThrow() const;
#endif #endif
#ifdef ENABLE_SQLITE
bool HasStickerDatabase() const noexcept {
return sticker_database != nullptr;
}
#endif
void BeginShutdownUpdate() noexcept; void BeginShutdownUpdate() noexcept;
#ifdef ENABLE_CURL #ifdef ENABLE_CURL
...@@ -176,10 +215,13 @@ struct Instance final ...@@ -176,10 +215,13 @@ struct Instance final
} }
#endif #endif
void FlushCaches() noexcept;
private: private:
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
void OnDatabaseModified() override; /* virtual methods from class DatabaseListener */
void OnDatabaseSongRemoved(const char *uri) override; void OnDatabaseModified() noexcept override;
void OnDatabaseSongRemoved(const char *uri) noexcept override;
#endif #endif
#ifdef ENABLE_NEIGHBOR_PLUGINS #ifdef ENABLE_NEIGHBOR_PLUGINS
...@@ -194,7 +236,7 @@ private: ...@@ -194,7 +236,7 @@ private:
#endif #endif
/* callback for #idle_monitor */ /* callback for #idle_monitor */
void OnIdle(unsigned mask); void OnIdle(unsigned mask) noexcept;
}; };
#endif #endif
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -25,17 +25,19 @@ ...@@ -25,17 +25,19 @@
#include "config/Data.hxx" #include "config/Data.hxx"
#include "config/Option.hxx" #include "config/Option.hxx"
#include "config/Net.hxx" #include "config/Net.hxx"
#include "lib/fmt/ExceptionFormatter.hxx"
#include "lib/fmt/PathFormatter.hxx"
#include "net/AllocatedSocketAddress.hxx" #include "net/AllocatedSocketAddress.hxx"
#include "net/UniqueSocketDescriptor.hxx" #include "net/UniqueSocketDescriptor.hxx"
#include "net/SocketUtil.hxx" #include "net/SocketUtil.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include "util/RuntimeError.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "fs/StandardDirectory.hxx"
#include "fs/XDG.hxx" #include "fs/XDG.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include <sys/stat.h> #include <sys/stat.h>
#include <string.h>
#include <assert.h>
#ifdef ENABLE_SYSTEMD_DAEMON #ifdef ENABLE_SYSTEMD_DAEMON
#include <systemd/sd-daemon.h> #include <systemd/sd-daemon.h>
...@@ -43,6 +45,10 @@ ...@@ -43,6 +45,10 @@
#define DEFAULT_PORT 6600 #define DEFAULT_PORT 6600
#if defined(USE_XDG) && defined(HAVE_UN)
static constexpr Domain listen_domain("listen");
#endif
int listen_port; int listen_port;
#ifdef ENABLE_SYSTEMD_DAEMON #ifdef ENABLE_SYSTEMD_DAEMON
...@@ -80,13 +86,10 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept ...@@ -80,13 +86,10 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept
use $XDG_RUNTIME_DIR */ use $XDG_RUNTIME_DIR */
return false; return false;
Path xdg_runtime_dir = Path::FromFS(getenv("XDG_RUNTIME_DIR")); const auto mpd_runtime_dir = GetAppRuntimeDir();
if (xdg_runtime_dir.IsNull()) if (mpd_runtime_dir.IsNull())
return false; return false;
const auto mpd_runtime_dir = xdg_runtime_dir / Path::FromFS("mpd");
mkdir(mpd_runtime_dir.c_str(), 0700);
const auto socket_path = mpd_runtime_dir / Path::FromFS("socket"); const auto socket_path = mpd_runtime_dir / Path::FromFS("socket");
unlink(socket_path.c_str()); unlink(socket_path.c_str());
...@@ -100,9 +103,9 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept ...@@ -100,9 +103,9 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept
listener.AddFD(std::move(fd), std::move(address)); listener.AddFD(std::move(fd), std::move(address));
return true; return true;
} catch (...) { } catch (...) {
FormatError(std::current_exception(), FmtError(listen_domain,
"Failed to listen on '%s' (not fatal)", "Failed to listen on '{}' (not fatal): {}",
socket_path.c_str()); socket_path, std::current_exception());
return false; return false;
} }
#else #else
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -22,8 +22,9 @@ ...@@ -22,8 +22,9 @@
#include "client/Client.hxx" #include "client/Client.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "ls.hxx" #include "ls.hxx"
#include "util/UriUtil.hxx" #include "storage/Registry.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/UriExtract.hxx"
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
...@@ -42,18 +43,19 @@ LocateFileUri(const char *uri, const Client *client ...@@ -42,18 +43,19 @@ LocateFileUri(const char *uri, const Client *client
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
if (storage != nullptr) { if (storage != nullptr) {
const char *suffix = storage->MapToRelativeUTF8(uri); const auto suffix = storage->MapToRelativeUTF8(uri);
if (suffix != nullptr) if (suffix.data() != nullptr)
/* this path was relative to the music /* this path was relative to the music
directory */ directory */
return LocatedUri(LocatedUri::Type::RELATIVE, suffix); // TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
return {LocatedUri::Type::RELATIVE, suffix.data()};
} }
#endif #endif
if (client != nullptr) if (client != nullptr)
client->AllowFile(path); client->AllowFile(path);
return LocatedUri(LocatedUri::Type::PATH, uri, std::move(path)); return {LocatedUri::Type::PATH, uri, std::move(path)};
} }
static LocatedUri static LocatedUri
...@@ -65,9 +67,13 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri ...@@ -65,9 +67,13 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
{ {
switch (kind) { switch (kind) {
case UriPluginKind::INPUT: case UriPluginKind::INPUT:
case UriPluginKind::STORAGE: // TODO: separate check for storage plugins
if (!uri_supported_scheme(uri)) if (!uri_supported_scheme(uri))
throw std::runtime_error("Unsupported URI scheme"); throw std::invalid_argument("Unsupported URI scheme");
break;
case UriPluginKind::STORAGE:
/* plugin support will be checked after the
Storage::MapToRelativeUTF8() call */
break; break;
case UriPluginKind::PLAYLIST: case UriPluginKind::PLAYLIST:
...@@ -80,13 +86,18 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri ...@@ -80,13 +86,18 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
if (storage != nullptr) { if (storage != nullptr) {
const char *suffix = storage->MapToRelativeUTF8(uri); const auto suffix = storage->MapToRelativeUTF8(uri);
if (suffix != nullptr) if (suffix.data() != nullptr)
return LocatedUri(LocatedUri::Type::RELATIVE, suffix); // TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
return {LocatedUri::Type::RELATIVE, suffix.data()};
} }
if (kind == UriPluginKind::STORAGE &&
GetStoragePluginByUri(uri) == nullptr)
throw std::invalid_argument("Unsupported URI scheme");
#endif #endif
return LocatedUri(LocatedUri::Type::ABSOLUTE, uri); return {LocatedUri::Type::ABSOLUTE, uri};
} }
LocatedUri LocatedUri
...@@ -101,7 +112,7 @@ LocateUri(UriPluginKind kind, ...@@ -101,7 +112,7 @@ LocateUri(UriPluginKind kind,
const char *path_utf8 = StringAfterPrefixCaseASCII(uri, "file://"); const char *path_utf8 = StringAfterPrefixCaseASCII(uri, "file://");
if (path_utf8 != nullptr) { if (path_utf8 != nullptr) {
if (!PathTraitsUTF8::IsAbsolute(path_utf8)) if (!PathTraitsUTF8::IsAbsolute(path_utf8))
throw std::runtime_error("Malformed file:// URI"); throw std::invalid_argument("Malformed file:// URI");
return LocateFileUri(path_utf8, client return LocateFileUri(path_utf8, client
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
#define MPD_LOCATE_URI_HXX #define MPD_LOCATE_URI_HXX
#include "config.h" #include "config.h"
#include "util/Compiler.h"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#ifdef _WIN32 #ifdef _WIN32
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -17,197 +17,35 @@ ...@@ -17,197 +17,35 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "LogV.hxx" #include "Log.hxx"
#include "lib/fmt/ExceptionFormatter.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include <exception> #include <fmt/format.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
static constexpr Domain exception_domain("exception"); static constexpr Domain exception_domain("exception");
void void
LogFormatV(const Domain &domain, LogLevel level, LogVFmt(LogLevel level, const Domain &domain,
const char *fmt, va_list ap) noexcept fmt::string_view format_str, fmt::format_args args) noexcept
{
char msg[1024];
vsnprintf(msg, sizeof(msg), fmt, ap);
Log(domain, level, msg);
}
void
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, level, fmt, ap);
va_end(ap);
}
void
FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
va_end(ap);
}
void
FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::INFO, fmt, ap);
va_end(ap);
}
void
FormatDefault(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::DEFAULT, fmt, ap);
va_end(ap);
}
void
FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::WARNING, fmt, ap);
va_end(ap);
}
void
FormatError(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::ERROR, fmt, ap);
va_end(ap);
}
void
LogError(const std::exception &e) noexcept
{
Log(exception_domain, LogLevel::ERROR, e.what());
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nested) {
LogError(nested, "nested");
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized nested exception");
}
}
void
LogError(const std::exception &e, const char *msg) noexcept
{ {
FormatError(exception_domain, "%s: %s", msg, e.what()); fmt::memory_buffer buffer;
#if FMT_VERSION >= 80000
try { fmt::vformat_to(std::back_inserter(buffer), format_str, args);
std::rethrow_if_nested(e); #else
} catch (const std::exception &nested) { fmt::vformat_to(buffer, format_str, args);
LogError(nested); #endif
} catch (...) { Log(level, domain, {buffer.data(), buffer.size()});
Log(exception_domain, LogLevel::ERROR,
"Unrecognized nested exception");
}
} }
void void
FormatError(const std::exception &e, const char *fmt, ...) noexcept Log(LogLevel level, const std::exception_ptr &ep) noexcept
{ {
char msg[1024]; Log(level, exception_domain, GetFullMessage(ep));
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
LogError(e, msg);
} }
void void
LogError(const std::exception_ptr &ep) noexcept Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
{ {
try { LogFmt(level, exception_domain, "{}: {}", msg, ep);
std::rethrow_exception(ep);
} catch (const std::exception &e) {
LogError(e);
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized exception");
}
}
void
LogError(const std::exception_ptr &ep, const char *msg) noexcept
{
try {
std::rethrow_exception(ep);
} catch (const std::exception &e) {
LogError(e, msg);
} catch (...) {
FormatError(exception_domain,
"%s: Unrecognized exception", msg);
}
}
void
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
{
char msg[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
LogError(ep, msg);
}
void
LogErrno(const Domain &domain, int e, const char *msg) noexcept
{
LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
}
void
LogErrno(const Domain &domain, const char *msg) noexcept
{
LogErrno(domain, errno, msg);
}
static void
FormatErrnoV(const Domain &domain, int e, const char *fmt, va_list ap) noexcept
{
char msg[1024];
vsnprintf(msg, sizeof(msg), fmt, ap);
LogErrno(domain, e, msg);
}
void
FormatErrno(const Domain &domain, int e, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
FormatErrnoV(domain, e, fmt, ap);
va_end(ap);
}
void
FormatErrno(const Domain &domain, const char *fmt, ...) noexcept
{
const int e = errno;
va_list ap;
va_start(ap, fmt);
FormatErrnoV(domain, e, fmt, ap);
va_end(ap);
} }
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -21,101 +21,130 @@ ...@@ -21,101 +21,130 @@
#define MPD_LOG_HXX #define MPD_LOG_HXX
#include "LogLevel.hxx" #include "LogLevel.hxx"
#include "util/Compiler.h"
#include <fmt/core.h>
#if FMT_VERSION < 70000 || FMT_VERSION >= 80000
#include <fmt/format.h>
#endif
#include <exception> #include <exception>
#include <string_view>
#include <utility>
class Domain; class Domain;
void void
Log(const Domain &domain, LogLevel level, const char *msg) noexcept; Log(LogLevel level, const Domain &domain, std::string_view msg) noexcept;
gcc_printf(3,4)
void void
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept; LogVFmt(LogLevel level, const Domain &domain,
fmt::string_view format_str, fmt::format_args args) noexcept;
static inline void template<typename S, typename... Args>
LogDebug(const Domain &domain, const char *msg) noexcept void
LogFmt(LogLevel level, const Domain &domain,
const S &format_str, Args&&... args) noexcept
{ {
Log(domain, LogLevel::DEBUG, msg); #if FMT_VERSION >= 90000
return LogVFmt(level, domain, format_str,
fmt::make_format_args(args...));
#elif FMT_VERSION >= 70000
return LogVFmt(level, domain, fmt::to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str,
args...));
#else
/* expensive fallback for older libfmt versions */
const auto result = fmt::format(format_str, args...);
return Log(level, domain, result);
#endif
} }
gcc_printf(2,3) template<typename S, typename... Args>
void void
FormatDebug(const Domain &domain, const char *fmt, ...) noexcept; FmtDebug(const Domain &domain,
const S &format_str, Args&&... args) noexcept
static inline void
LogInfo(const Domain &domain, const char *msg) noexcept
{ {
Log(domain, LogLevel::INFO, msg); LogFmt(LogLevel::DEBUG, domain, format_str, args...);
} }
gcc_printf(2,3) template<typename S, typename... Args>
void void
FormatInfo(const Domain &domain, const char *fmt, ...) noexcept; FmtInfo(const Domain &domain,
const S &format_str, Args&&... args) noexcept
static inline void
LogDefault(const Domain &domain, const char *msg) noexcept
{ {
Log(domain, LogLevel::DEFAULT, msg); LogFmt(LogLevel::INFO, domain, format_str, args...);
} }
gcc_printf(2,3) template<typename S, typename... Args>
void void
FormatDefault(const Domain &domain, const char *fmt, ...) noexcept; FmtNotice(const Domain &domain,
const S &format_str, Args&&... args) noexcept
static inline void
LogWarning(const Domain &domain, const char *msg) noexcept
{ {
Log(domain, LogLevel::WARNING, msg); LogFmt(LogLevel::NOTICE, domain, format_str, args...);
} }
gcc_printf(2,3) template<typename S, typename... Args>
void void
FormatWarning(const Domain &domain, const char *fmt, ...) noexcept; FmtWarning(const Domain &domain,
const S &format_str, Args&&... args) noexcept
static inline void
LogError(const Domain &domain, const char *msg) noexcept
{ {
Log(domain, LogLevel::ERROR, msg); LogFmt(LogLevel::WARNING, domain, format_str, args...);
} }
template<typename S, typename... Args>
void void
LogError(const std::exception &e) noexcept; FmtError(const Domain &domain,
const S &format_str, Args&&... args) noexcept
void {
LogError(const std::exception &e, const char *msg) noexcept; LogFmt(LogLevel::ERROR, domain, format_str, args...);
}
gcc_printf(2,3)
void void
FormatError(const std::exception &e, const char *fmt, ...) noexcept; Log(LogLevel level, const std::exception_ptr &ep) noexcept;
void void
LogError(const std::exception_ptr &ep) noexcept; Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept;
void static inline void
LogError(const std::exception_ptr &ep, const char *msg) noexcept; LogDebug(const Domain &domain, const char *msg) noexcept
{
Log(LogLevel::DEBUG, domain, msg);
}
gcc_printf(2,3) static inline void
void LogInfo(const Domain &domain, const char *msg) noexcept
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept; {
Log(LogLevel::INFO, domain, msg);
}
gcc_printf(2,3) static inline void
void LogNotice(const Domain &domain, const char *msg) noexcept
FormatError(const Domain &domain, const char *fmt, ...) noexcept; {
Log(LogLevel::NOTICE, domain, msg);
}
void static inline void
LogErrno(const Domain &domain, int e, const char *msg) noexcept; LogWarning(const Domain &domain, const char *msg) noexcept
{
Log(LogLevel::WARNING, domain, msg);
}
void static inline void
LogErrno(const Domain &domain, const char *msg) noexcept; LogError(const Domain &domain, const char *msg) noexcept
{
Log(LogLevel::ERROR, domain, msg);
}
gcc_printf(3,4) inline void
void LogError(const std::exception_ptr &ep) noexcept
FormatErrno(const Domain &domain, int e, const char *fmt, ...) noexcept; {
Log(LogLevel::ERROR, ep);
}
gcc_printf(2,3) inline void
void LogError(const std::exception_ptr &ep, const char *msg) noexcept
FormatErrno(const Domain &domain, const char *fmt, ...) noexcept; {
Log(LogLevel::ERROR, ep, msg);
}
#endif /* LOG_H */ #endif /* LOG_H */
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -19,11 +19,14 @@ ...@@ -19,11 +19,14 @@
#include "LogBackend.hxx" #include "LogBackend.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "util/Compiler.h"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/StringStrip.hxx" #include "util/StringStrip.hxx"
#include "Version.h"
#include "config.h" #include "config.h"
#include <assert.h> #include <cassert>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
...@@ -45,7 +48,7 @@ ToAndroidLogLevel(LogLevel log_level) noexcept ...@@ -45,7 +48,7 @@ ToAndroidLogLevel(LogLevel log_level) noexcept
return ANDROID_LOG_DEBUG; return ANDROID_LOG_DEBUG;
case LogLevel::INFO: case LogLevel::INFO:
case LogLevel::DEFAULT: case LogLevel::NOTICE:
return ANDROID_LOG_INFO; return ANDROID_LOG_INFO;
case LogLevel::WARNING: case LogLevel::WARNING:
...@@ -61,7 +64,7 @@ ToAndroidLogLevel(LogLevel log_level) noexcept ...@@ -61,7 +64,7 @@ ToAndroidLogLevel(LogLevel log_level) noexcept
#else #else
static LogLevel log_threshold = LogLevel::INFO; static LogLevel log_threshold = LogLevel::NOTICE;
static bool enable_timestamp; static bool enable_timestamp;
...@@ -101,15 +104,14 @@ log_date() noexcept ...@@ -101,15 +104,14 @@ log_date() noexcept
* characters. * characters.
*/ */
static int static int
chomp_length(const char *p) noexcept chomp_length(std::string_view p) noexcept
{ {
size_t length = strlen(p); return StripRight(p.data(), p.size());
return StripRight(p, length);
} }
#ifdef HAVE_SYSLOG #ifdef HAVE_SYSLOG
gcc_const [[gnu::const]]
static int static int
ToSysLogLevel(LogLevel log_level) noexcept ToSysLogLevel(LogLevel log_level) noexcept
{ {
...@@ -120,7 +122,7 @@ ToSysLogLevel(LogLevel log_level) noexcept ...@@ -120,7 +122,7 @@ ToSysLogLevel(LogLevel log_level) noexcept
case LogLevel::INFO: case LogLevel::INFO:
return LOG_INFO; return LOG_INFO;
case LogLevel::DEFAULT: case LogLevel::NOTICE:
return LOG_NOTICE; return LOG_NOTICE;
case LogLevel::WARNING: case LogLevel::WARNING:
...@@ -135,11 +137,11 @@ ToSysLogLevel(LogLevel log_level) noexcept ...@@ -135,11 +137,11 @@ ToSysLogLevel(LogLevel log_level) noexcept
} }
static void static void
SysLog(const Domain &domain, LogLevel log_level, const char *message) noexcept SysLog(const Domain &domain, LogLevel log_level, std::string_view message) noexcept
{ {
syslog(ToSysLogLevel(log_level), "%s: %.*s", syslog(ToSysLogLevel(log_level), "%s: %.*s",
domain.GetName(), domain.GetName(),
chomp_length(message), message); chomp_length(message), message.data());
} }
void void
...@@ -159,12 +161,12 @@ LogFinishSysLog() noexcept ...@@ -159,12 +161,12 @@ LogFinishSysLog() noexcept
#endif #endif
static void static void
FileLog(const Domain &domain, const char *message) noexcept FileLog(const Domain &domain, std::string_view message) noexcept
{ {
fprintf(stderr, "%s%s: %.*s\n", fprintf(stderr, "%s%s: %.*s\n",
enable_timestamp ? log_date() : "", enable_timestamp ? log_date() : "",
domain.GetName(), domain.GetName(),
chomp_length(message), message); chomp_length(message), message.data());
#ifdef _WIN32 #ifdef _WIN32
/* force-flush the log file, because setvbuf() does not seem /* force-flush the log file, because setvbuf() does not seem
...@@ -176,14 +178,20 @@ FileLog(const Domain &domain, const char *message) noexcept ...@@ -176,14 +178,20 @@ FileLog(const Domain &domain, const char *message) noexcept
#endif /* !ANDROID */ #endif /* !ANDROID */
void void
Log(const Domain &domain, LogLevel level, const char *msg) noexcept Log(LogLevel level, const Domain &domain, std::string_view msg) noexcept
{ {
#ifdef ANDROID #ifdef ANDROID
__android_log_print(ToAndroidLogLevel(level), "MPD", __android_log_print(ToAndroidLogLevel(level), "MPD",
"%s: %s", domain.GetName(), msg); "%s: %.*s", domain.GetName(),
if (logListener != nullptr) (int)msg.size(), msg.data());
if (logListener != nullptr) {
char buffer[1024];
snprintf(buffer, sizeof(buffer), "%s: %.*s",
domain.GetName(), (int)msg.size(), msg.data());
logListener->OnLog(Java::GetEnv(), ToAndroidLogLevel(level), logListener->OnLog(Java::GetEnv(), ToAndroidLogLevel(level),
"%s: %s", domain.GetName(), msg); buffer);
}
#else #else
if (level < log_threshold) if (level < log_threshold)
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -28,24 +28,24 @@ ...@@ -28,24 +28,24 @@
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include <cassert>
#ifdef ENABLE_SYSTEMD_DAEMON #ifdef ENABLE_SYSTEMD_DAEMON
#include <systemd/sd-daemon.h> #include <systemd/sd-daemon.h>
#endif #endif
#include <assert.h>
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#define LOG_LEVEL_SECURE LogLevel::INFO
#define LOG_DATE_BUF_SIZE 16 #define LOG_DATE_BUF_SIZE 16
#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1) #define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1)
gcc_unused [[maybe_unused]]
static constexpr Domain log_domain("log"); static constexpr Domain log_domain("log");
#ifndef ANDROID #ifndef ANDROID
...@@ -63,7 +63,7 @@ static void redirect_logs(int fd) ...@@ -63,7 +63,7 @@ static void redirect_logs(int fd)
} }
static int static int
open_log_file(void) open_log_file()
{ {
assert(!out_path.IsNull()); assert(!out_path.IsNull());
...@@ -93,17 +93,24 @@ log_init_file(int line) ...@@ -93,17 +93,24 @@ log_init_file(int line)
} }
static inline LogLevel static inline LogLevel
parse_log_level(const char *value, int line) parse_log_level(const char *value)
{ {
if (0 == strcmp(value, "default")) if (StringIsEqual(value, "notice") ||
return LogLevel::DEFAULT; /* deprecated name: */
if (0 == strcmp(value, "secure")) StringIsEqual(value, "default"))
return LOG_LEVEL_SECURE; return LogLevel::NOTICE;
else if (0 == strcmp(value, "verbose")) else if (StringIsEqual(value, "info") ||
/* deprecated since MPD 0.22: */
StringIsEqual(value, "secure"))
return LogLevel::INFO;
else if (StringIsEqual(value, "verbose"))
return LogLevel::DEBUG; return LogLevel::DEBUG;
else if (StringIsEqual(value, "warning"))
return LogLevel::WARNING;
else if (StringIsEqual(value, "error"))
return LogLevel::ERROR;
else else
throw FormatRuntimeError("unknown log level \"%s\" at line %d", throw FormatRuntimeError("unknown log level \"%s\"", value);
value, line);
} }
#endif #endif
...@@ -132,9 +139,12 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout) ...@@ -132,9 +139,12 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
#else #else
if (verbose) if (verbose)
SetLogThreshold(LogLevel::DEBUG); SetLogThreshold(LogLevel::DEBUG);
else if (const auto &param = config.GetParam(ConfigOption::LOG_LEVEL)) else
SetLogThreshold(parse_log_level(param->value.c_str(), SetLogThreshold(config.With(ConfigOption::LOG_LEVEL, [](const char *s){
param->line)); return s != nullptr
? parse_log_level(s)
: LogLevel::NOTICE;
}));
if (use_stdout) { if (use_stdout) {
out_fd = STDOUT_FILENO; out_fd = STDOUT_FILENO;
...@@ -157,7 +167,7 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout) ...@@ -157,7 +167,7 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
throw std::runtime_error("config parameter 'log_file' not found"); throw std::runtime_error("config parameter 'log_file' not found");
#endif #endif
#ifdef HAVE_SYSLOG #ifdef HAVE_SYSLOG
} else if (strcmp(param->value.c_str(), "syslog") == 0) { } else if (StringIsEqual(param->value.c_str(), "syslog")) {
LogInitSysLog(); LogInitSysLog();
#endif #endif
} else { } else {
...@@ -224,22 +234,22 @@ cycle_log_files() noexcept ...@@ -224,22 +234,22 @@ cycle_log_files() noexcept
if (out_path.IsNull()) if (out_path.IsNull())
return 0; return 0;
FormatDebug(log_domain, "Cycling log files"); LogDebug(log_domain, "Cycling log files");
close_log_files(); close_log_files();
fd = open_log_file(); fd = open_log_file();
if (fd < 0) { if (fd < 0) {
const std::string out_path_utf8 = out_path.ToUTF8(); const std::string out_path_utf8 = out_path.ToUTF8();
FormatError(log_domain, FmtError(log_domain,
"error re-opening log file: %s", "error re-opening log file: {}",
out_path_utf8.c_str()); out_path_utf8);
return -1; return -1;
} }
redirect_logs(fd); redirect_logs(fd);
close(fd); close(fd);
FormatDebug(log_domain, "Done cycling log files"); LogDebug(log_domain, "Done cycling log files");
return 0; return 0;
#endif #endif
} }
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -42,7 +42,7 @@ enum class LogLevel { ...@@ -42,7 +42,7 @@ enum class LogLevel {
/** /**
* Interesting informational message. * Interesting informational message.
*/ */
DEFAULT, NOTICE,
/** /**
* Warning: something may be wrong. * Warning: something may be wrong.
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -20,18 +20,16 @@ ...@@ -20,18 +20,16 @@
#ifndef MPD_MAIN_HXX #ifndef MPD_MAIN_HXX
#define MPD_MAIN_HXX #define MPD_MAIN_HXX
class EventLoop;
class Context;
struct Instance; struct Instance;
#ifdef ANDROID #ifdef ANDROID
#include "android/LogListener.hxx" #include "android/LogListener.hxx"
extern Context *context; extern class Context *context;
extern LogListener *logListener; extern LogListener *logListener;
#endif #endif
extern Instance *instance; extern Instance *global_instance;
#ifndef ANDROID #ifndef ANDROID
...@@ -42,7 +40,7 @@ extern Instance *instance; ...@@ -42,7 +40,7 @@ extern Instance *instance;
* after doing some initialization. * after doing some initialization.
*/ */
int int
mpd_main(int argc, char *argv[]) noexcept; mpd_main(int argc, char *argv[]);
#endif #endif
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
#include "Main.hxx" #include "Main.hxx"
#endif #endif
#include <assert.h> #include <cassert>
/** /**
* The absolute path of the playlist directory encoded in the * The absolute path of the playlist directory encoded in the
...@@ -66,10 +66,10 @@ map_uri_fs(const char *uri) noexcept ...@@ -66,10 +66,10 @@ map_uri_fs(const char *uri) noexcept
assert(uri != nullptr); assert(uri != nullptr);
assert(*uri != '/'); assert(*uri != '/');
if (instance->storage == nullptr) if (global_instance->storage == nullptr)
return nullptr; return nullptr;
const auto music_dir_fs = instance->storage->MapFS(""); const auto music_dir_fs = global_instance->storage->MapFS("");
if (music_dir_fs.IsNull()) if (music_dir_fs.IsNull())
return nullptr; return nullptr;
...@@ -84,16 +84,16 @@ std::string ...@@ -84,16 +84,16 @@ std::string
map_fs_to_utf8(Path path_fs) noexcept map_fs_to_utf8(Path path_fs) noexcept
{ {
if (path_fs.IsAbsolute()) { if (path_fs.IsAbsolute()) {
if (instance->storage == nullptr) if (global_instance->storage == nullptr)
return std::string(); return {};
const auto music_dir_fs = instance->storage->MapFS(""); const auto music_dir_fs = global_instance->storage->MapFS("");
if (music_dir_fs.IsNull()) if (music_dir_fs.IsNull())
return std::string(); return {};
auto relative = music_dir_fs.Relative(path_fs); auto relative = music_dir_fs.Relative(path_fs);
if (relative == nullptr || StringIsEmpty(relative)) if (relative == nullptr || StringIsEmpty(relative))
return std::string(); return {};
path_fs = Path::FromFS(relative); path_fs = Path::FromFS(relative);
} }
...@@ -119,7 +119,7 @@ map_spl_utf8_to_fs(const char *name) noexcept ...@@ -119,7 +119,7 @@ map_spl_utf8_to_fs(const char *name) noexcept
filename_utf8.append(PLAYLIST_FILE_SUFFIX); filename_utf8.append(PLAYLIST_FILE_SUFFIX);
const auto filename_fs = const auto filename_fs =
AllocatedPath::FromUTF8(filename_utf8.c_str()); AllocatedPath::FromUTF8(filename_utf8);
if (filename_fs.IsNull()) if (filename_fs.IsNull())
return nullptr; return nullptr;
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
#ifndef MPD_MAPPER_HXX #ifndef MPD_MAPPER_HXX
#define MPD_MAPPER_HXX #define MPD_MAPPER_HXX
#include "util/Compiler.h"
#include "config.h" #include "config.h"
#include <string> #include <string>
...@@ -44,7 +43,7 @@ mapper_init(AllocatedPath &&playlist_dir); ...@@ -44,7 +43,7 @@ mapper_init(AllocatedPath &&playlist_dir);
* is basically done by converting the URI to the file system charset * is basically done by converting the URI to the file system charset
* and prepending the music directory. * and prepending the music directory.
*/ */
gcc_pure [[gnu::pure]]
AllocatedPath AllocatedPath
map_uri_fs(const char *uri) noexcept; map_uri_fs(const char *uri) noexcept;
...@@ -56,7 +55,7 @@ map_uri_fs(const char *uri) noexcept; ...@@ -56,7 +55,7 @@ map_uri_fs(const char *uri) noexcept;
* @return the relative path in UTF-8, or an empty string if mapping * @return the relative path in UTF-8, or an empty string if mapping
* failed * failed
*/ */
gcc_pure [[gnu::pure]]
std::string std::string
map_fs_to_utf8(Path path_fs) noexcept; map_fs_to_utf8(Path path_fs) noexcept;
...@@ -65,7 +64,7 @@ map_fs_to_utf8(Path path_fs) noexcept; ...@@ -65,7 +64,7 @@ map_fs_to_utf8(Path path_fs) noexcept;
/** /**
* Returns the playlist directory. * Returns the playlist directory.
*/ */
gcc_const [[gnu::const]]
const AllocatedPath & const AllocatedPath &
map_spl_path() noexcept; map_spl_path() noexcept;
...@@ -75,7 +74,7 @@ map_spl_path() noexcept; ...@@ -75,7 +74,7 @@ map_spl_path() noexcept;
* *
* @return the path in file system encoding, or nullptr if mapping failed * @return the path in file system encoding, or nullptr if mapping failed
*/ */
gcc_pure [[gnu::pure]]
AllocatedPath AllocatedPath
map_spl_utf8_to_fs(const char *name) noexcept; map_spl_utf8_to_fs(const char *name) noexcept;
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
#include "MusicBuffer.hxx" #include "MusicBuffer.hxx"
#include "MusicChunk.hxx" #include "MusicChunk.hxx"
#include <assert.h> #include <cassert>
MusicBuffer::MusicBuffer(unsigned num_chunks) MusicBuffer::MusicBuffer(unsigned num_chunks)
:buffer(num_chunks) { :buffer(num_chunks) {
...@@ -29,8 +29,8 @@ MusicBuffer::MusicBuffer(unsigned num_chunks) ...@@ -29,8 +29,8 @@ MusicBuffer::MusicBuffer(unsigned num_chunks)
MusicChunkPtr MusicChunkPtr
MusicBuffer::Allocate() noexcept MusicBuffer::Allocate() noexcept
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return MusicChunkPtr(buffer.Allocate(), MusicChunkDeleter(*this)); return {buffer.Allocate(), MusicChunkDeleter(*this)};
} }
void void
...@@ -44,7 +44,7 @@ MusicBuffer::Return(MusicChunk *chunk) noexcept ...@@ -44,7 +44,7 @@ MusicBuffer::Return(MusicChunk *chunk) noexcept
chunk->next.reset(); chunk->next.reset();
chunk->other.reset(); chunk->other.reset();
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
assert(!chunk->other || !chunk->other->other); assert(!chunk->other || !chunk->other->other);
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -54,7 +54,7 @@ public: ...@@ -54,7 +54,7 @@ public:
#endif #endif
bool IsFull() const noexcept { bool IsFull() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return buffer.IsFull(); return buffer.IsFull();
} }
...@@ -63,7 +63,7 @@ public: ...@@ -63,7 +63,7 @@ public:
* is the same value which was passed to the constructor * is the same value which was passed to the constructor
* music_buffer_new(). * music_buffer_new().
*/ */
gcc_pure [[gnu::pure]]
unsigned GetSize() const noexcept { unsigned GetSize() const noexcept {
return buffer.GetCapacity(); return buffer.GetCapacity();
} }
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -18,10 +18,10 @@ ...@@ -18,10 +18,10 @@
*/ */
#include "MusicChunk.hxx" #include "MusicChunk.hxx"
#include "AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include <assert.h> #include <cassert>
MusicChunkInfo::MusicChunkInfo() noexcept = default; MusicChunkInfo::MusicChunkInfo() noexcept = default;
MusicChunkInfo::~MusicChunkInfo() noexcept = default; MusicChunkInfo::~MusicChunkInfo() noexcept = default;
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -26,14 +26,13 @@ ...@@ -26,14 +26,13 @@
#include "util/WritableBuffer.hxx" #include "util/WritableBuffer.hxx"
#ifndef NDEBUG #ifndef NDEBUG
#include "AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#endif #endif
#include <cstddef>
#include <cstdint>
#include <memory> #include <memory>
#include <stdint.h>
#include <stddef.h>
static constexpr size_t CHUNK_SIZE = 4096; static constexpr size_t CHUNK_SIZE = 4096;
struct AudioFormat; struct AudioFormat;
...@@ -43,15 +42,7 @@ struct MusicChunk; ...@@ -43,15 +42,7 @@ struct MusicChunk;
/** /**
* Meta information for #MusicChunk. * Meta information for #MusicChunk.
*/ */
struct alignas(8) MusicChunkInfo { struct MusicChunkInfo {
/* align to multiple of 8 bytes, which adds padding at the
end, so the size of MusicChunk::data is also a multiple of
8 bytes; this is a workaround for a bug in the DSD_U32 and
DoP converters which require processing 8 bytes at a time,
discarding the remainder */
/* TODO: once all converters have been fixed, we should remove
this workaround */
/** the next chunk in a linked list */ /** the next chunk in a linked list */
MusicChunkPtr next; MusicChunkPtr next;
...@@ -114,7 +105,7 @@ struct alignas(8) MusicChunkInfo { ...@@ -114,7 +105,7 @@ struct alignas(8) MusicChunkInfo {
* Checks if the audio format if the chunk is equal to the * Checks if the audio format if the chunk is equal to the
* specified audio_format. * specified audio_format.
*/ */
gcc_pure [[gnu::pure]]
bool CheckFormat(AudioFormat audio_format) const noexcept; bool CheckFormat(AudioFormat audio_format) const noexcept;
#endif #endif
}; };
...@@ -127,10 +118,6 @@ struct MusicChunk : MusicChunkInfo { ...@@ -127,10 +118,6 @@ struct MusicChunk : MusicChunkInfo {
/** the data (probably PCM) */ /** the data (probably PCM) */
uint8_t data[CHUNK_SIZE - sizeof(MusicChunkInfo)]; uint8_t data[CHUNK_SIZE - sizeof(MusicChunkInfo)];
/* TODO: remove this check once all converters have been fixed
(see comment in struct MusicChunkInfo for details) */
static_assert(sizeof(data) % 8 == 0, "Wrong alignment");
/** /**
* Prepares appending to the music chunk. Returns a buffer * Prepares appending to the music chunk. Returns a buffer
* where you may write into. After you are finished, call * where you may write into. After you are finished, call
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -20,12 +20,14 @@ ...@@ -20,12 +20,14 @@
#include "MusicPipe.hxx" #include "MusicPipe.hxx"
#include "MusicChunk.hxx" #include "MusicChunk.hxx"
#include <cassert>
#ifndef NDEBUG #ifndef NDEBUG
bool bool
MusicPipe::Contains(const MusicChunk *chunk) const noexcept MusicPipe::Contains(const MusicChunk *chunk) const noexcept
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
for (const MusicChunk *i = head.get(); i != nullptr; i = i->next.get()) for (const MusicChunk *i = head.get(); i != nullptr; i = i->next.get())
if (i == chunk) if (i == chunk)
...@@ -39,7 +41,7 @@ MusicPipe::Contains(const MusicChunk *chunk) const noexcept ...@@ -39,7 +41,7 @@ MusicPipe::Contains(const MusicChunk *chunk) const noexcept
MusicChunkPtr MusicChunkPtr
MusicPipe::Shift() noexcept MusicPipe::Shift() noexcept
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
auto chunk = std::move(head); auto chunk = std::move(head);
if (chunk != nullptr) { if (chunk != nullptr) {
...@@ -79,7 +81,7 @@ MusicPipe::Push(MusicChunkPtr chunk) noexcept ...@@ -79,7 +81,7 @@ MusicPipe::Push(MusicChunkPtr chunk) noexcept
assert(!chunk->IsEmpty()); assert(!chunk->IsEmpty());
assert(chunk->length == 0 || chunk->audio_format.IsValid()); assert(chunk->length == 0 || chunk->audio_format.IsValid());
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
assert(size > 0 || !audio_format.IsDefined()); assert(size > 0 || !audio_format.IsDefined());
assert(!audio_format.IsDefined() || assert(!audio_format.IsDefined() ||
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -22,14 +22,11 @@ ...@@ -22,14 +22,11 @@
#include "MusicChunkPtr.hxx" #include "MusicChunkPtr.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "util/Compiler.h"
#ifndef NDEBUG #ifndef NDEBUG
#include "AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#endif #endif
#include <assert.h>
/** /**
* A queue of #MusicChunk objects. One party appends chunks at the * A queue of #MusicChunk objects. One party appends chunks at the
* tail, and the other consumes them from the head. * tail, and the other consumes them from the head.
...@@ -61,7 +58,7 @@ public: ...@@ -61,7 +58,7 @@ public:
* Checks if the audio format if the chunk is equal to the specified * Checks if the audio format if the chunk is equal to the specified
* audio_format. * audio_format.
*/ */
gcc_pure [[gnu::pure]]
bool CheckFormat(AudioFormat other) const noexcept { bool CheckFormat(AudioFormat other) const noexcept {
return !audio_format.IsDefined() || return !audio_format.IsDefined() ||
audio_format == other; audio_format == other;
...@@ -70,7 +67,7 @@ public: ...@@ -70,7 +67,7 @@ public:
/** /**
* Checks if the specified chunk is enqueued in the music pipe. * Checks if the specified chunk is enqueued in the music pipe.
*/ */
gcc_pure [[gnu::pure]]
bool Contains(const MusicChunk *chunk) const noexcept; bool Contains(const MusicChunk *chunk) const noexcept;
#endif #endif
...@@ -78,9 +75,9 @@ public: ...@@ -78,9 +75,9 @@ public:
* Returns the first #MusicChunk from the pipe. Returns * Returns the first #MusicChunk from the pipe. Returns
* nullptr if the pipe is empty. * nullptr if the pipe is empty.
*/ */
gcc_pure [[gnu::pure]]
const MusicChunk *Peek() const noexcept { const MusicChunk *Peek() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return head.get(); return head.get();
} }
...@@ -102,13 +99,13 @@ public: ...@@ -102,13 +99,13 @@ public:
/** /**
* Returns the number of chunks currently in this pipe. * Returns the number of chunks currently in this pipe.
*/ */
gcc_pure [[gnu::pure]]
unsigned GetSize() const noexcept { unsigned GetSize() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return size; return size;
} }
gcc_pure [[gnu::pure]]
bool IsEmpty() const noexcept { bool IsEmpty() const noexcept {
return GetSize() == 0; return GetSize() == 0;
} }
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -20,24 +20,34 @@ ...@@ -20,24 +20,34 @@
#include "config.h" #include "config.h"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "Log.hxx"
#include "lib/fmt/ExceptionFormatter.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "mixer/Volume.hxx" #include "mixer/Volume.hxx"
#include "IdleFlags.hxx" #include "IdleFlags.hxx"
#include "client/Listener.hxx" #include "client/Listener.hxx"
#include "client/Client.hxx"
#include "input/cache/Manager.hxx"
#include "util/Domain.hxx"
static constexpr Domain cache_domain("cache");
Partition::Partition(Instance &_instance, Partition::Partition(Instance &_instance,
const char *_name, const char *_name,
unsigned max_length, unsigned max_length,
unsigned buffer_chunks, unsigned buffer_chunks,
AudioFormat configured_audio_format, AudioFormat configured_audio_format,
const ReplayGainConfig &replay_gain_config) const ReplayGainConfig &replay_gain_config) noexcept
:instance(_instance), :instance(_instance),
name(_name), name(_name),
listener(new ClientListener(instance.event_loop, *this)), listener(new ClientListener(instance.event_loop, *this)),
idle_monitor(instance.event_loop, BIND_THIS_METHOD(OnIdleMonitor)),
global_events(instance.event_loop, BIND_THIS_METHOD(OnGlobalEvent)), global_events(instance.event_loop, BIND_THIS_METHOD(OnGlobalEvent)),
playlist(max_length, *this), playlist(max_length, *this),
outputs(*this), outputs(pc, *this),
pc(*this, outputs, buffer_chunks, pc(*this, outputs,
instance.input_cache.get(),
buffer_chunks,
configured_audio_format, replay_gain_config) configured_audio_format, replay_gain_config)
{ {
UpdateEffectiveReplayGainMode(); UpdateEffectiveReplayGainMode();
...@@ -46,13 +56,52 @@ Partition::Partition(Instance &_instance, ...@@ -46,13 +56,52 @@ Partition::Partition(Instance &_instance,
Partition::~Partition() noexcept = default; Partition::~Partition() noexcept = default;
void void
Partition::EmitIdle(unsigned mask) Partition::BeginShutdown() noexcept
{
pc.Kill();
listener.reset();
}
static void
PrefetchSong(InputCacheManager &cache, const char *uri) noexcept
{ {
instance.EmitIdle(mask); if (cache.Contains(uri))
return;
FmtDebug(cache_domain, "Prefetch '{}'", uri);
try {
cache.Prefetch(uri);
} catch (...) {
FmtError(cache_domain,
"Prefetch '{}' failed: {}",
uri, std::current_exception());
}
}
static void
PrefetchSong(InputCacheManager &cache, const DetachedSong &song) noexcept
{
PrefetchSong(cache, song.GetURI());
}
inline void
Partition::PrefetchQueue() noexcept
{
if (!instance.input_cache)
return;
auto &cache = *instance.input_cache;
int next = playlist.GetNextPosition();
if (next >= 0)
PrefetchSong(cache, playlist.queue.Get(next));
// TODO: prefetch more songs
} }
void void
Partition::UpdateEffectiveReplayGainMode() Partition::UpdateEffectiveReplayGainMode() noexcept
{ {
auto mode = replay_gain_mode; auto mode = replay_gain_mode;
if (mode == ReplayGainMode::AUTO) if (mode == ReplayGainMode::AUTO)
...@@ -68,7 +117,7 @@ Partition::UpdateEffectiveReplayGainMode() ...@@ -68,7 +117,7 @@ Partition::UpdateEffectiveReplayGainMode()
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
const Database * const Database *
Partition::GetDatabase() const Partition::GetDatabase() const noexcept
{ {
return instance.GetDatabase(); return instance.GetDatabase();
} }
...@@ -80,7 +129,7 @@ Partition::GetDatabaseOrThrow() const ...@@ -80,7 +129,7 @@ Partition::GetDatabaseOrThrow() const
} }
void void
Partition::DatabaseModified(const Database &db) Partition::DatabaseModified(const Database &db) noexcept
{ {
playlist.DatabaseModified(db); playlist.DatabaseModified(db);
EmitIdle(IDLE_DATABASE); EmitIdle(IDLE_DATABASE);
...@@ -89,7 +138,7 @@ Partition::DatabaseModified(const Database &db) ...@@ -89,7 +138,7 @@ Partition::DatabaseModified(const Database &db)
#endif #endif
void void
Partition::TagModified() Partition::TagModified() noexcept
{ {
auto song = pc.LockReadTaggedSong(); auto song = pc.LockReadTaggedSong();
if (song) if (song)
...@@ -103,31 +152,35 @@ Partition::TagModified(const char *uri, const Tag &tag) noexcept ...@@ -103,31 +152,35 @@ Partition::TagModified(const char *uri, const Tag &tag) noexcept
} }
void void
Partition::SyncWithPlayer() Partition::SyncWithPlayer() noexcept
{ {
playlist.SyncWithPlayer(pc); playlist.SyncWithPlayer(pc);
/* TODO: invoke this function in batches, to let the hard disk
spin down in between */
PrefetchQueue();
} }
void void
Partition::BorderPause() Partition::BorderPause() noexcept
{ {
playlist.BorderPause(pc); playlist.BorderPause(pc);
} }
void void
Partition::OnQueueModified() Partition::OnQueueModified() noexcept
{ {
EmitIdle(IDLE_PLAYLIST); EmitIdle(IDLE_PLAYLIST);
} }
void void
Partition::OnQueueOptionsChanged() Partition::OnQueueOptionsChanged() noexcept
{ {
EmitIdle(IDLE_OPTIONS); EmitIdle(IDLE_OPTIONS);
} }
void void
Partition::OnQueueSongStarted() Partition::OnQueueSongStarted() noexcept
{ {
EmitIdle(IDLE_PLAYER); EmitIdle(IDLE_PLAYER);
} }
...@@ -151,7 +204,7 @@ Partition::OnBorderPause() noexcept ...@@ -151,7 +204,7 @@ Partition::OnBorderPause() noexcept
} }
void void
Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume) Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
{ {
InvalidateHardwareVolume(); InvalidateHardwareVolume();
...@@ -160,7 +213,19 @@ Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume) ...@@ -160,7 +213,19 @@ Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume)
} }
void void
Partition::OnGlobalEvent(unsigned mask) Partition::OnIdleMonitor(unsigned mask) noexcept
{
/* send "idle" notifications to all subscribed
clients */
for (auto &client : clients)
client.IdleAdd(mask);
if (mask & (IDLE_PLAYLIST|IDLE_PLAYER|IDLE_MIXER|IDLE_OUTPUT))
instance.OnStateModified();
}
void
Partition::OnGlobalEvent(unsigned mask) noexcept
{ {
if ((mask & SYNC_WITH_PLAYER) != 0) if ((mask & SYNC_WITH_PLAYER) != 0)
SyncWithPlayer(); SyncWithPlayer();
......
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -27,19 +27,23 @@ ...@@ -27,19 +27,23 @@
#include "mixer/Listener.hxx" #include "mixer/Listener.hxx"
#include "player/Control.hxx" #include "player/Control.hxx"
#include "player/Listener.hxx" #include "player/Listener.hxx"
#include "protocol/RangeArg.hxx"
#include "ReplayGainMode.hxx" #include "ReplayGainMode.hxx"
#include "SingleMode.hxx" #include "SingleMode.hxx"
#include "Chrono.hxx" #include "Chrono.hxx"
#include "util/Compiler.h"
#include "config.h" #include "config.h"
#include <boost/intrusive/list.hpp>
#include <string> #include <string>
#include <memory> #include <memory>
struct Instance; struct Instance;
struct RangeArg;
class MultipleOutputs; class MultipleOutputs;
class SongLoader; class SongLoader;
class ClientListener; class ClientListener;
class Client;
/** /**
* A partition of the Music Player Daemon. It is a separate unit with * A partition of the Music Player Daemon. It is a separate unit with
...@@ -56,6 +60,16 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -56,6 +60,16 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
std::unique_ptr<ClientListener> listener; std::unique_ptr<ClientListener> listener;
boost::intrusive::list<Client,
boost::intrusive::base_hook<boost::intrusive::list_base_hook<boost::intrusive::tag<Partition>,
boost::intrusive::link_mode<boost::intrusive::normal_link>>>,
boost::intrusive::constant_time_size<false>> clients;
/**
* Monitor for idle events local to this partition.
*/
MaskMonitor idle_monitor;
MaskMonitor global_events; MaskMonitor global_events;
struct playlist playlist; struct playlist playlist;
...@@ -71,17 +85,34 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -71,17 +85,34 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
unsigned max_length, unsigned max_length,
unsigned buffer_chunks, unsigned buffer_chunks,
AudioFormat configured_audio_format, AudioFormat configured_audio_format,
const ReplayGainConfig &replay_gain_config); const ReplayGainConfig &replay_gain_config) noexcept;
~Partition() noexcept; ~Partition() noexcept;
void EmitGlobalEvent(unsigned mask) { void BeginShutdown() noexcept;
void EmitGlobalEvent(unsigned mask) noexcept {
global_events.OrMask(mask); global_events.OrMask(mask);
} }
void EmitIdle(unsigned mask); /**
* Emit an "idle" event to all clients of this partition.
*
* This method can be called from any thread.
*/
void EmitIdle(unsigned mask) noexcept {
idle_monitor.OrMask(mask);
}
/**
* Populate the #InputCacheManager with soon-to-be-played song
* files.
*
* Errors will be logged.
*/
void PrefetchQueue() noexcept;
void ClearQueue() { void ClearQueue() noexcept {
playlist.Clear(pc); playlist.Clear(pc);
} }
...@@ -104,24 +135,20 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -104,24 +135,20 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
* @param start the position of the first song to delete * @param start the position of the first song to delete
* @param end the position after the last song to delete * @param end the position after the last song to delete
*/ */
void DeleteRange(unsigned start, unsigned end) { void DeleteRange(RangeArg range) {
playlist.DeleteRange(pc, start, end); playlist.DeleteRange(pc, range);
} }
void StaleSong(const char *uri) { void StaleSong(const char *uri) noexcept {
playlist.StaleSong(pc, uri); playlist.StaleSong(pc, uri);
} }
void Shuffle(unsigned start, unsigned end) { void Shuffle(RangeArg range) {
playlist.Shuffle(pc, start, end); playlist.Shuffle(pc, range);
}
void MoveRange(unsigned start, unsigned end, int to) {
playlist.MoveRange(pc, start, end, to);
} }
void MoveId(unsigned id, int to) { void MoveRange(RangeArg range, unsigned to) {
playlist.MoveId(pc, id, to); playlist.MoveRange(pc, range, to);
} }
void SwapPositions(unsigned song1, unsigned song2) { void SwapPositions(unsigned song1, unsigned song2) {
...@@ -132,17 +159,15 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -132,17 +159,15 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
playlist.SwapIds(pc, id1, id2); playlist.SwapIds(pc, id1, id2);
} }
void SetPriorityRange(unsigned start_position, unsigned end_position, void SetPriorityRange(RangeArg position_range, uint8_t priority) {
uint8_t priority) { playlist.SetPriorityRange(pc, position_range, priority);
playlist.SetPriorityRange(pc, start_position, end_position,
priority);
} }
void SetPriorityId(unsigned song_id, uint8_t priority) { void SetPriorityId(unsigned song_id, uint8_t priority) {
playlist.SetPriorityId(pc, song_id, priority); playlist.SetPriorityId(pc, song_id, priority);
} }
void Stop() { void Stop() noexcept {
playlist.Stop(pc); playlist.Stop(pc);
} }
...@@ -174,27 +199,27 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -174,27 +199,27 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
playlist.SeekCurrent(pc, seek_time, relative); playlist.SeekCurrent(pc, seek_time, relative);
} }
void SetRepeat(bool new_value) { void SetRepeat(bool new_value) noexcept {
playlist.SetRepeat(pc, new_value); playlist.SetRepeat(pc, new_value);
} }
bool GetRandom() const { bool GetRandom() const noexcept {
return playlist.GetRandom(); return playlist.GetRandom();
} }
void SetRandom(bool new_value) { void SetRandom(bool new_value) noexcept {
playlist.SetRandom(pc, new_value); playlist.SetRandom(pc, new_value);
} }
void SetSingle(SingleMode new_value) { void SetSingle(SingleMode new_value) noexcept {
playlist.SetSingle(pc, new_value); playlist.SetSingle(pc, new_value);
} }
void SetConsume(bool new_value) { void SetConsume(bool new_value) noexcept {
playlist.SetConsume(new_value); playlist.SetConsume(new_value);
} }
void SetReplayGainMode(ReplayGainMode mode) { void SetReplayGainMode(ReplayGainMode mode) noexcept {
replay_gain_mode = mode; replay_gain_mode = mode;
UpdateEffectiveReplayGainMode(); UpdateEffectiveReplayGainMode();
} }
...@@ -203,7 +228,7 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -203,7 +228,7 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
* Publishes the effective #ReplayGainMode to all subsystems. * Publishes the effective #ReplayGainMode to all subsystems.
* #ReplayGainMode::AUTO is substituted. * #ReplayGainMode::AUTO is substituted.
*/ */
void UpdateEffectiveReplayGainMode(); void UpdateEffectiveReplayGainMode() noexcept;
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
/** /**
...@@ -211,7 +236,7 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -211,7 +236,7 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
* if this MPD configuration has no database (no * if this MPD configuration has no database (no
* music_directory was configured). * music_directory was configured).
*/ */
const Database *GetDatabase() const; const Database *GetDatabase() const noexcept;
const Database &GetDatabaseOrThrow() const; const Database &GetDatabaseOrThrow() const;
...@@ -219,14 +244,14 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -219,14 +244,14 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
* The database has been modified. Propagate the change to * The database has been modified. Propagate the change to
* all subsystems. * all subsystems.
*/ */
void DatabaseModified(const Database &db); void DatabaseModified(const Database &db) noexcept;
#endif #endif
/** /**
* A tag in the play queue has been modified by the player * A tag in the play queue has been modified by the player
* thread. Propagate the change to all subsystems. * thread. Propagate the change to all subsystems.
*/ */
void TagModified(); void TagModified() noexcept;
/** /**
* The tag of the given song has been modified. Propagate the * The tag of the given song has been modified. Propagate the
...@@ -237,19 +262,19 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -237,19 +262,19 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
/** /**
* Synchronize the player with the play queue. * Synchronize the player with the play queue.
*/ */
void SyncWithPlayer(); void SyncWithPlayer() noexcept;
/** /**
* Border pause has just been enabled. Change single mode to off * Border pause has just been enabled. Change single mode to off
* if it was one-shot. * if it was one-shot.
*/ */
void BorderPause(); void BorderPause() noexcept;
private: private:
/* virtual methods from class QueueListener */ /* virtual methods from class QueueListener */
void OnQueueModified() override; void OnQueueModified() noexcept override;
void OnQueueOptionsChanged() override; void OnQueueOptionsChanged() noexcept override;
void OnQueueSongStarted() override; void OnQueueSongStarted() noexcept override;
/* virtual methods from class PlayerListener */ /* virtual methods from class PlayerListener */
void OnPlayerSync() noexcept override; void OnPlayerSync() noexcept override;
...@@ -257,10 +282,13 @@ private: ...@@ -257,10 +282,13 @@ private:
void OnBorderPause() noexcept override; void OnBorderPause() noexcept override;
/* virtual methods from class MixerListener */ /* virtual methods from class MixerListener */
void OnMixerVolumeChanged(Mixer &mixer, int volume) override; void OnMixerVolumeChanged(Mixer &mixer, int volume) noexcept override;
/* callback for #idle_monitor */
void OnIdleMonitor(unsigned mask) noexcept;
/* callback for #global_events */ /* callback for #global_events */
void OnGlobalEvent(unsigned mask); void OnGlobalEvent(unsigned mask) noexcept;
}; };
#endif #endif
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment