Compare commits
228 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f591193dda | ||
![]() |
434869900e | ||
![]() |
2aed7378cc | ||
![]() |
71cd6e6248 | ||
![]() |
c83294916a | ||
![]() |
603bbe0afd | ||
![]() |
c361e235eb | ||
![]() |
8a59493d96 | ||
![]() |
7ef86cbf9f | ||
![]() |
c9530118a4 | ||
![]() |
878d9abeb7 | ||
![]() |
2d705efe1c | ||
![]() |
aeaef85507 | ||
![]() |
ebae25d175 | ||
![]() |
5ad1a01d7a | ||
![]() |
8f84e1befd | ||
![]() |
9975905faf | ||
![]() |
233184568c | ||
![]() |
59da778009 | ||
![]() |
108ce95b7c | ||
![]() |
86e9ed5f3a | ||
![]() |
fbecb05bf4 | ||
![]() |
4983703375 | ||
![]() |
3856224df9 | ||
![]() |
6d4bedfc56 | ||
![]() |
bea821f194 | ||
![]() |
4e276256c0 | ||
![]() |
d0f9062b56 | ||
![]() |
b9cc036703 | ||
![]() |
4e9b88559b | ||
![]() |
3452682a42 | ||
![]() |
9262b24504 | ||
![]() |
a5fa43b526 | ||
![]() |
8681a3d74c | ||
![]() |
f9c4d88b12 | ||
![]() |
799032505e | ||
![]() |
c8f174ac92 | ||
![]() |
047e169f3e | ||
![]() |
687327c9e8 | ||
![]() |
26dc37bd76 | ||
![]() |
c693e4aa64 | ||
![]() |
acab731fef | ||
![]() |
7e4ba3cb72 | ||
![]() |
172c4d9c7d | ||
![]() |
bd5f6cbc7b | ||
![]() |
6fcd1c734b | ||
![]() |
eca097dbfb | ||
![]() |
51ffafa011 | ||
![]() |
8dca602346 | ||
![]() |
0ed24f3a05 | ||
![]() |
e25e0030e7 | ||
![]() |
df4b6b92f2 | ||
![]() |
1c69913eca | ||
![]() |
cb5c6259fd | ||
![]() |
bf287fefb5 | ||
![]() |
20bf1d68e6 | ||
![]() |
9bc4c168fd | ||
![]() |
3415049d1c | ||
![]() |
a45949b597 | ||
![]() |
6009d4abab | ||
![]() |
16fb843c9b | ||
![]() |
36b333459b | ||
![]() |
4d3320233e | ||
![]() |
933a1a41e6 | ||
![]() |
1ff8626716 | ||
![]() |
c30466b84a | ||
![]() |
868f1a4431 | ||
![]() |
05f529fffd | ||
![]() |
f01388559f | ||
![]() |
27edd4a610 | ||
![]() |
cc421b04cd | ||
![]() |
3f2bc325a1 | ||
![]() |
54686dfd79 | ||
![]() |
f22cf02ed8 | ||
![]() |
5b51d0f733 | ||
![]() |
e03f82636a | ||
![]() |
d53d85bd79 | ||
![]() |
4682ae0898 | ||
![]() |
fd5b195879 | ||
![]() |
bb5df9839d | ||
![]() |
be34d55291 | ||
![]() |
c13911b171 | ||
![]() |
6f83bdd6f3 | ||
![]() |
9bcd425a85 | ||
![]() |
ec917f70d2 | ||
![]() |
40ce4eeb43 | ||
![]() |
29ae84e199 | ||
![]() |
250011f016 | ||
![]() |
e08c85ae2d | ||
![]() |
dcb5ca203c | ||
![]() |
77df5a8f24 | ||
![]() |
d6bebd2507 | ||
![]() |
f74996c02f | ||
![]() |
eea2d35d3a | ||
![]() |
d94e8bd82d | ||
![]() |
b0c92e1a34 | ||
![]() |
ead5bcf048 | ||
![]() |
bdd268a524 | ||
![]() |
e783c2bd2c | ||
![]() |
837fc98638 | ||
![]() |
5deca66fdc | ||
![]() |
cfe2dd4147 | ||
![]() |
00f8d65a17 | ||
![]() |
4e0e4c00bf | ||
![]() |
a8c77a6fba | ||
![]() |
31aa6d0c4f | ||
![]() |
d051c4931d | ||
![]() |
94b0baceb0 | ||
![]() |
16feb261e2 | ||
![]() |
f084bf7872 | ||
![]() |
1112d3907a | ||
![]() |
3464497880 | ||
![]() |
651f57bced | ||
![]() |
b4e72aba6c | ||
![]() |
061dd2dfef | ||
![]() |
5f4ec7de5b | ||
![]() |
6f81bb4b09 | ||
![]() |
4ed60a5711 | ||
![]() |
c93195c94b | ||
![]() |
f30adac4bb | ||
![]() |
a4e4217204 | ||
![]() |
8754d705a1 | ||
![]() |
23d4a2d6a5 | ||
![]() |
ce77b148d9 | ||
![]() |
be3eca39e8 | ||
![]() |
3413b1aeb4 | ||
![]() |
356d13e9dd | ||
![]() |
fa34bf0aaf | ||
![]() |
5d0941476a | ||
![]() |
5ff0bbd0f8 | ||
![]() |
a3764e533c | ||
![]() |
3e05cba30e | ||
![]() |
14b3c0f0af | ||
![]() |
67aff05051 | ||
![]() |
19a101c3ac | ||
![]() |
8da17a8211 | ||
![]() |
2748929039 | ||
![]() |
0c900a4bfa | ||
![]() |
f1d5d70010 | ||
![]() |
56ebc7637d | ||
![]() |
996dd9fc8b | ||
![]() |
056514d598 | ||
![]() |
9a21bdfd6a | ||
![]() |
03f99dd26e | ||
![]() |
bfb1b641f9 | ||
![]() |
72ba98c464 | ||
![]() |
dcd19c0592 | ||
![]() |
109159e0f7 | ||
![]() |
409b877eea | ||
![]() |
c5bf7948ff | ||
![]() |
b9f7127691 | ||
![]() |
1e6f5f012c | ||
![]() |
225d85fd9b | ||
![]() |
1bb22f118d | ||
![]() |
552c30eae4 | ||
![]() |
48e8a26813 | ||
![]() |
ade847bc89 | ||
![]() |
a6173e0eae | ||
![]() |
4529bb4a83 | ||
![]() |
258ecb764f | ||
![]() |
6f595e9abb | ||
![]() |
35c4c7e8bf | ||
![]() |
293ed924d1 | ||
![]() |
c8121176b3 | ||
![]() |
ee270f9b00 | ||
![]() |
bf1d77a4d8 | ||
![]() |
a9344fafe9 | ||
![]() |
b8890726f2 | ||
![]() |
0f84332654 | ||
![]() |
46c82259f7 | ||
![]() |
2d03823283 | ||
![]() |
bba144eca5 | ||
![]() |
9af73dad93 | ||
![]() |
f0d66bf6a6 | ||
![]() |
5ad53a7554 | ||
![]() |
7b2e3331f2 | ||
![]() |
3cb44f6652 | ||
![]() |
b7fdff46f2 | ||
![]() |
e16109330d | ||
![]() |
72621531e0 | ||
![]() |
0a48146efc | ||
![]() |
0c4bf12bfd | ||
![]() |
b8e0855ef3 | ||
![]() |
6467502b9d | ||
![]() |
15b67f20e5 | ||
![]() |
0825179f00 | ||
![]() |
97211d0aad | ||
![]() |
029c499bfa | ||
![]() |
0ba867ec16 | ||
![]() |
866d147122 | ||
![]() |
32851d1bc7 | ||
![]() |
78257408b4 | ||
![]() |
f447b7615e | ||
![]() |
1f780b7209 | ||
![]() |
04bf8a6b1a | ||
![]() |
c4c64854d4 | ||
![]() |
17562dc90b | ||
![]() |
7b24316734 | ||
![]() |
5fab107fd3 | ||
![]() |
f31920e092 | ||
![]() |
eb111a10e7 | ||
![]() |
80b09360c6 | ||
![]() |
5ccf78855d | ||
![]() |
fd5a3b5880 | ||
![]() |
6120c1360c | ||
![]() |
a8087dc12c | ||
![]() |
070c03dbf7 | ||
![]() |
0a9bec3754 | ||
![]() |
fff25ac753 | ||
![]() |
4f1e79b6b8 | ||
![]() |
aa9933c0b5 | ||
![]() |
0697d1f859 | ||
![]() |
df033fa4aa | ||
![]() |
b941a7df83 | ||
![]() |
31151cec3c | ||
![]() |
07e8c338df | ||
![]() |
b22d7218aa | ||
![]() |
d5be8c74b0 | ||
![]() |
c112cb60da | ||
![]() |
677fa4f9bc | ||
![]() |
907af2ad02 | ||
![]() |
6a2e7bbc02 | ||
![]() |
771c46032f | ||
![]() |
85611aa456 | ||
![]() |
466b5cb08d | ||
![]() |
3f2f3251cb | ||
![]() |
8ae85f3991 | ||
![]() |
781fe4ff28 |
133
.github/workflows/build.yml
vendored
Normal file
133
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
---
|
||||||
|
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 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: test
|
||||||
|
directory: output/full
|
||||||
|
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||||
|
meson-version: 0.56.0
|
||||||
|
|
||||||
|
- name: Mini Build
|
||||||
|
uses: BSFishy/meson-build@v1.0.3
|
||||||
|
with:
|
||||||
|
action: test
|
||||||
|
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
|
||||||
|
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: Meson Build
|
||||||
|
uses: BSFishy/meson-build@v1.0.3
|
||||||
|
with:
|
||||||
|
action: test
|
||||||
|
directory: output
|
||||||
|
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||||
|
meson-version: 0.56.0
|
74
NEWS
74
NEWS
@@ -1,3 +1,77 @@
|
|||||||
|
ver 0.23.6 (2022/03/14)
|
||||||
|
* protocol
|
||||||
|
- support filename "cover.webp" for "albumart" command
|
||||||
|
- support "readcomments" and "readpicture" on CUE tracks
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: fix end-of-file check (update stuck at empty files)
|
||||||
|
- opus: fix "readpicture" on Opus files
|
||||||
|
* output
|
||||||
|
- pipewire: fix crash bug if setting volume before playback starts
|
||||||
|
- wasapi: fix resume after pause
|
||||||
|
|
||||||
|
ver 0.23.5 (2021/12/01)
|
||||||
|
* protocol
|
||||||
|
- support relative offsets for "searchadd"
|
||||||
|
- fix "searchaddpl" bug (bogus error "Bad position")
|
||||||
|
* database
|
||||||
|
- upnp: fix crash bug
|
||||||
|
* tags
|
||||||
|
- fix MixRamp support
|
||||||
|
* migrate to PCRE2
|
||||||
|
* GCC 12 build fixes
|
||||||
|
|
||||||
|
ver 0.23.4 (2021/11/11)
|
||||||
|
* protocol
|
||||||
|
- add optional position parameter to "searchaddpl"
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: support libavcodec 59
|
||||||
|
* output
|
||||||
|
- alsa: add option "thesycon_dsd_workaround" to work around device bug
|
||||||
|
* fix crash on debug builds if startup fails
|
||||||
|
* systemd
|
||||||
|
- remove "RuntimeDirectory" directive because it caused problems
|
||||||
|
- ignore the "pid_file" setting if started as systemd service
|
||||||
|
* Windows
|
||||||
|
- enable the "openmpt" decoder plugin
|
||||||
|
|
||||||
|
ver 0.23.3 (2021/10/31)
|
||||||
|
* protocol
|
||||||
|
- add optional position parameter to "add" and "playlistadd"
|
||||||
|
- allow range in "playlistdelete"
|
||||||
|
* database
|
||||||
|
- fix scanning files with question mark in the name
|
||||||
|
- inotify: fix use-after-free bug
|
||||||
|
* output
|
||||||
|
- alsa: add option "stop_dsd_silence" to work around DSD DAC noise
|
||||||
|
* macOS: fix libfmt related build failure
|
||||||
|
* systemd: add "RuntimeDirectory" directive
|
||||||
|
|
||||||
|
ver 0.23.2 (2021/10/22)
|
||||||
|
* protocol
|
||||||
|
- fix "albumart" timeout bug
|
||||||
|
* input
|
||||||
|
- nfs: fix playback bug
|
||||||
|
* output
|
||||||
|
- pipewire: send artist and title to PipeWire
|
||||||
|
- pipewire: DSD support
|
||||||
|
* neighbor
|
||||||
|
- mention failed plugin name in error message
|
||||||
|
* player
|
||||||
|
- fix cross-fade regression
|
||||||
|
* fix crash with libfmt versions older than 7
|
||||||
|
|
||||||
|
ver 0.23.1 (2021/10/19)
|
||||||
|
* protocol
|
||||||
|
- use decimal notation instead of scientific notation
|
||||||
|
- "load" supports relative positions
|
||||||
|
* output
|
||||||
|
- emit "mixer" idle event when replay gain changes volume
|
||||||
|
- pipewire: emit "mixer" idle events on external volume change
|
||||||
|
- pipewire: attempt to change the graph sample rate
|
||||||
|
- snapcast: fix time stamp bug which caused "Failed to get chunk"
|
||||||
|
* fix libfmt linker problems
|
||||||
|
* fix broken password authentication
|
||||||
|
|
||||||
ver 0.23 (2021/10/14)
|
ver 0.23 (2021/10/14)
|
||||||
* protocol
|
* protocol
|
||||||
- new command "getvol"
|
- new command "getvol"
|
||||||
|
@@ -2,8 +2,8 @@
|
|||||||
<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="60"
|
android:versionCode="65"
|
||||||
android:versionName="0.23">
|
android:versionName="0.23.5">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||||
|
|
||||||
@@ -17,8 +17,6 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
||||||
|
|
||||||
<application android:allowBackup="true"
|
<application android:allowBackup="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
@@ -43,7 +41,6 @@
|
|||||||
<receiver android:name=".Receiver">
|
<receiver android:name=".Receiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="android.intent.action.HEADSET_PLUG" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<service android:name=".Main" android:process=":main"/>
|
<service android:name=".Main" android:process=":main"/>
|
||||||
|
@@ -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):
|
||||||
|
@@ -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" \
|
||||||
|
@@ -24,14 +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.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothClass;
|
|
||||||
import android.content.BroadcastReceiver;
|
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.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;
|
||||||
@@ -200,24 +199,14 @@ public class Main extends Service implements Runnable {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(Intent.ACTION_HEADSET_PLUG);
|
filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
||||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
|
|
||||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
|
||||||
registerReceiver(new BroadcastReceiver() {
|
registerReceiver(new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (!mPauseOnHeadphonesDisconnect) {
|
if (!mPauseOnHeadphonesDisconnect)
|
||||||
return;
|
return;
|
||||||
}
|
if (intent.getAction() == AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||||
|
pause();
|
||||||
if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
|
|
||||||
if (intent.hasExtra("state") && intent.getIntExtra("state", 0) == 0)
|
|
||||||
pause();
|
|
||||||
} else {
|
|
||||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
|
||||||
if (device.getBluetoothClass().hasService(BluetoothClass.Service.AUDIO))
|
|
||||||
pause();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, filter);
|
}, filter);
|
||||||
|
|
||||||
|
@@ -25,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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -38,9 +38,9 @@ author = 'Max Kellermann'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.23'
|
version = '0.23.6'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version + '~git'
|
#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'
|
|||||||
# 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 = []
|
||||||
|
@@ -128,23 +128,6 @@ audio_output
|
|||||||
no audio_output section is specified, then MPD will scan for a usable audio
|
no audio_output section is specified, then MPD will scan for a usable audio
|
||||||
output.
|
output.
|
||||||
|
|
||||||
replaygain <off or album or track or auto>
|
|
||||||
If specified, mpd will adjust the volume of songs played using ReplayGain
|
|
||||||
tags (see https://wiki.hydrogenaud.io/index.php?title=Replaygain).
|
|
||||||
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.
|
|
||||||
|
|
||||||
replaygain_preamp <-15 to 15>
|
|
||||||
This is the gain (in dB) applied to songs with ReplayGain tags.
|
|
||||||
|
|
||||||
volume_normalization <yes or no>
|
|
||||||
If yes, mpd will normalize the volume of songs as they play. The default is
|
|
||||||
no.
|
|
||||||
|
|
||||||
filesystem_charset <charset>
|
filesystem_charset <charset>
|
||||||
This specifies the character set used for the filesystem. A list of supported
|
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
|
character sets can be obtained by running "iconv -l". The default is
|
||||||
|
@@ -26,22 +26,25 @@
|
|||||||
# 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
|
||||||
@@ -76,7 +79,7 @@
|
|||||||
# 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 than 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"
|
||||||
@@ -182,7 +185,7 @@
|
|||||||
# cache_directory "~/.local/share/mpd/cache"
|
# cache_directory "~/.local/share/mpd/cache"
|
||||||
#}
|
#}
|
||||||
#
|
#
|
||||||
# An example of database config for a sattelite setup
|
# An example of database config for a satellite setup
|
||||||
#
|
#
|
||||||
#music_directory "nfs://fileserver.local/srv/mp3"
|
#music_directory "nfs://fileserver.local/srv/mp3"
|
||||||
#database {
|
#database {
|
||||||
|
@@ -61,6 +61,15 @@ upnp
|
|||||||
|
|
||||||
Provides access to UPnP media servers.
|
Provides access to UPnP media servers.
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:widths: 20 80
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Setting
|
||||||
|
- Description
|
||||||
|
* - **interface**
|
||||||
|
- Interface used to discover media servers. Decided by upnp if left unconfigured.
|
||||||
|
|
||||||
Storage plugins
|
Storage plugins
|
||||||
===============
|
===============
|
||||||
|
|
||||||
@@ -836,6 +845,16 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
|
|||||||
- If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...).
|
- If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...).
|
||||||
* - **dop yes|no**
|
* - **dop yes|no**
|
||||||
- If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk.
|
- If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk.
|
||||||
|
* - **stop_dsd_silence yes|no**
|
||||||
|
- If enabled, silence is played before manually stopping playback
|
||||||
|
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
|
||||||
|
workaround for some DACs which emit noise when stopping DSD
|
||||||
|
playback.
|
||||||
|
* - **thesycon_dsd_workaround yes|no**
|
||||||
|
- If enabled, enables a workaround for a bug in Thesycon USB
|
||||||
|
audio receivers. On these devices, playing DSD512 or PCM
|
||||||
|
causes all subsequent attempts to play other DSD rates to fail,
|
||||||
|
which can be fixed by briefly playing PCM at 44.1 kHz.
|
||||||
* - **allowed_formats F1 F2 ...**
|
* - **allowed_formats F1 F2 ...**
|
||||||
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
|
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
|
||||||
|
|
||||||
@@ -1094,6 +1113,8 @@ Connect to a `PipeWire <https://pipewire.org/>`_ server. Requires
|
|||||||
* - **remote NAME**
|
* - **remote NAME**
|
||||||
- The name of the remote to connect to. The default is
|
- The name of the remote to connect to. The default is
|
||||||
``pipewire-0``.
|
``pipewire-0``.
|
||||||
|
* - **dsd yes|no**
|
||||||
|
- Enable DSD playback. This requires PipeWire 0.38.
|
||||||
|
|
||||||
.. _pulse_plugin:
|
.. _pulse_plugin:
|
||||||
|
|
||||||
@@ -1193,6 +1214,8 @@ allows MPD to act as a `Snapcast
|
|||||||
<https://github.com/badaix/snapcast>`__ server. Snapcast clients
|
<https://github.com/badaix/snapcast>`__ server. Snapcast clients
|
||||||
connect to it and receive audio data from MPD.
|
connect to it and receive audio data from MPD.
|
||||||
|
|
||||||
|
You must set a format.
|
||||||
|
|
||||||
.. list-table::
|
.. list-table::
|
||||||
:widths: 20 80
|
:widths: 20 80
|
||||||
:header-rows: 1
|
:header-rows: 1
|
||||||
|
@@ -479,7 +479,7 @@ Querying :program:`MPD`'s status
|
|||||||
current song in seconds, but with higher resolution.
|
current song in seconds, but with higher resolution.
|
||||||
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
|
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
|
||||||
- ``bitrate``: instantaneous bitrate in kbps
|
- ``bitrate``: instantaneous bitrate in kbps
|
||||||
- ``xfade``: ``crossfade`` in seconds
|
- ``xfade``: ``crossfade`` in seconds (see :ref:`crossfading`)
|
||||||
- ``mixrampdb``: ``mixramp`` threshold in dB
|
- ``mixrampdb``: ``mixramp`` threshold in dB
|
||||||
- ``mixrampdelay``: ``mixrampdelay`` in seconds
|
- ``mixrampdelay``: ``mixrampdelay`` in seconds
|
||||||
- ``audio``: The format emitted by the decoder plugin during
|
- ``audio``: The format emitted by the decoder plugin during
|
||||||
@@ -519,17 +519,19 @@ Playback options
|
|||||||
.. _command_crossfade:
|
.. _command_crossfade:
|
||||||
|
|
||||||
:command:`crossfade {SECONDS}`
|
:command:`crossfade {SECONDS}`
|
||||||
Sets crossfading between songs.
|
Sets crossfading between songs. See :ref:`crossfading`.
|
||||||
|
|
||||||
.. _command_mixrampdb:
|
.. _command_mixrampdb:
|
||||||
|
|
||||||
:command:`mixrampdb {deciBels}`
|
:command:`mixrampdb {deciBels}`
|
||||||
Sets the threshold at which songs will be overlapped. Like crossfading but doesn't fade the track volume, just overlaps. The songs need to have MixRamp tags added by an external tool. 0dB is the normalized maximum volume so use negative values, I prefer -17dB. In the absence of mixramp tags crossfading will be used. See http://sourceforge.net/projects/mixramp
|
Sets the threshold at which songs will be overlapped.
|
||||||
|
See :ref:`mixramp`.
|
||||||
|
|
||||||
.. _command_mixrampdelay:
|
.. _command_mixrampdelay:
|
||||||
|
|
||||||
:command:`mixrampdelay {SECONDS}`
|
:command:`mixrampdelay {SECONDS}`
|
||||||
Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
|
Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
|
||||||
|
See :ref:`mixramp`.
|
||||||
|
|
||||||
.. _command_random:
|
.. _command_random:
|
||||||
|
|
||||||
@@ -551,7 +553,7 @@ Playback options
|
|||||||
|
|
||||||
.. _command_getvol:
|
.. _command_getvol:
|
||||||
|
|
||||||
:command:`getvol`
|
:command:`getvol` [#since_0_23]_
|
||||||
|
|
||||||
Read the volume. The result is a ``volume:`` line like in
|
Read the volume. The result is a ``volume:`` line like in
|
||||||
:ref:`status <command_status>`. If there is no mixer, MPD will
|
:ref:`status <command_status>`. If there is no mixer, MPD will
|
||||||
@@ -689,11 +691,14 @@ Whenever possible, ids should be used.
|
|||||||
|
|
||||||
.. _command_add:
|
.. _command_add:
|
||||||
|
|
||||||
:command:`add {URI}`
|
:command:`add {URI} [POSITION]`
|
||||||
Adds the file ``URI`` to the playlist
|
Adds the file ``URI`` to the playlist
|
||||||
(directories add recursively). ``URI``
|
(directories add recursively). ``URI``
|
||||||
can also be a single file.
|
can also be a single file.
|
||||||
|
|
||||||
|
The position parameter is the same as in :ref:`addid
|
||||||
|
<command_addid>`. [#since_0_23_3]_
|
||||||
|
|
||||||
Clients that are connected via local socket may add arbitrary
|
Clients that are connected via local socket may add arbitrary
|
||||||
local files (URI is an absolute path). Example::
|
local files (URI is an absolute path). Example::
|
||||||
|
|
||||||
@@ -711,10 +716,10 @@ Whenever possible, ids should be used.
|
|||||||
|
|
||||||
If the second parameter is given, then the song is inserted at the
|
If the second parameter is given, then the song is inserted at the
|
||||||
specified position. If the parameter starts with ``+`` or ``-``,
|
specified position. If the parameter starts with ``+`` or ``-``,
|
||||||
then it is relative to the current song; e.g. ``+0`` inserts right
|
then it is relative to the current song [#since_0_23]_; e.g. ``+0``
|
||||||
after the current song and ``-0`` inserts right before the current
|
inserts right after the current song and ``-0`` inserts right
|
||||||
song (i.e. zero songs between the current song and the newly added
|
before the current song (i.e. zero songs between the current song
|
||||||
song).
|
and the newly added song).
|
||||||
|
|
||||||
.. _command_clear:
|
.. _command_clear:
|
||||||
|
|
||||||
@@ -768,8 +773,8 @@ Whenever possible, ids should be used.
|
|||||||
.. _command_playlistfind:
|
.. _command_playlistfind:
|
||||||
|
|
||||||
:command:`playlistfind {FILTER}`
|
:command:`playlistfind {FILTER}`
|
||||||
Finds songs in the queue with strict
|
Search the queue for songs matching
|
||||||
matching.
|
``FILTER`` (see :ref:`Filters <filter_syntax>`).
|
||||||
|
|
||||||
.. _command_playlistid:
|
.. _command_playlistid:
|
||||||
|
|
||||||
@@ -789,8 +794,10 @@ Whenever possible, ids should be used.
|
|||||||
.. _command_playlistsearch:
|
.. _command_playlistsearch:
|
||||||
|
|
||||||
:command:`playlistsearch {FILTER}`
|
:command:`playlistsearch {FILTER}`
|
||||||
Searches case-insensitively for partial matches in the
|
Search the queue for songs matching
|
||||||
queue.
|
``FILTER`` (see :ref:`Filters <filter_syntax>`).
|
||||||
|
Parameters have the same meaning as for :ref:`find
|
||||||
|
<command_playlistfind>`, except that search is not case sensitive.
|
||||||
|
|
||||||
.. _command_plchanges:
|
.. _command_plchanges:
|
||||||
|
|
||||||
@@ -923,18 +930,22 @@ remote playlists (absolute URI with a supported scheme).
|
|||||||
only a part of the playlist.
|
only a part of the playlist.
|
||||||
|
|
||||||
The ``POSITION`` parameter specifies where the songs will be
|
The ``POSITION`` parameter specifies where the songs will be
|
||||||
inserted into the queue. (This requires specifying the range as
|
inserted into the queue; it can be relative as described in
|
||||||
well; the special value `0:` can be used if the whole playlist
|
:ref:`addid <command_addid>`. (This requires specifying the range
|
||||||
shall be loaded at a certain queue position.)
|
as well; the special value `0:` can be used if the whole playlist
|
||||||
|
shall be loaded at a certain queue position.) [#since_0_23_1]_
|
||||||
|
|
||||||
.. _command_playlistadd:
|
.. _command_playlistadd:
|
||||||
|
|
||||||
:command:`playlistadd {NAME} {URI}`
|
:command:`playlistadd {NAME} {URI} [POSITION]`
|
||||||
Adds ``URI`` to the playlist
|
Adds ``URI`` to the playlist
|
||||||
`NAME.m3u`.
|
`NAME.m3u`.
|
||||||
`NAME.m3u` will be created if it does
|
`NAME.m3u` will be created if it does
|
||||||
not exist.
|
not exist.
|
||||||
|
|
||||||
|
The ``POSITION`` parameter specifies where the songs will be
|
||||||
|
inserted into the playlist. [#since_0_23_3]_
|
||||||
|
|
||||||
.. _command_playlistclear:
|
.. _command_playlistclear:
|
||||||
|
|
||||||
:command:`playlistclear {NAME}`
|
:command:`playlistclear {NAME}`
|
||||||
@@ -946,6 +957,8 @@ remote playlists (absolute URI with a supported scheme).
|
|||||||
Deletes ``SONGPOS`` from the
|
Deletes ``SONGPOS`` from the
|
||||||
playlist `NAME.m3u`.
|
playlist `NAME.m3u`.
|
||||||
|
|
||||||
|
The second parameter can be a range. [#since_0_23_3]_
|
||||||
|
|
||||||
.. _command_playlistmove:
|
.. _command_playlistmove:
|
||||||
|
|
||||||
:command:`playlistmove {NAME} {FROM} {TO}`
|
:command:`playlistmove {NAME} {FROM} {TO}`
|
||||||
@@ -1059,11 +1072,11 @@ The music database
|
|||||||
|
|
||||||
.. _command_findadd:
|
.. _command_findadd:
|
||||||
|
|
||||||
:command:`findadd {FILTER} [sort {TYPE}] [window {START:END}]`
|
:command:`findadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||||
Search the database for songs matching
|
Search the database for songs matching
|
||||||
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
||||||
the queue. Parameters have the same meaning as for
|
the queue. Parameters have the same meaning as for
|
||||||
:ref:`find <command_find>`.
|
:ref:`find <command_find>` and :ref:`searchadd <command_searchadd>`.
|
||||||
|
|
||||||
.. _command_list:
|
.. _command_list:
|
||||||
|
|
||||||
@@ -1196,15 +1209,12 @@ The music database
|
|||||||
|
|
||||||
.. _command_search:
|
.. _command_search:
|
||||||
|
|
||||||
:command:`search {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
:command:`search {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||||
Search the database for songs matching
|
Search the database for songs matching
|
||||||
``FILTER`` (see :ref:`Filters <filter_syntax>`). Parameters
|
``FILTER`` (see :ref:`Filters <filter_syntax>`). Parameters
|
||||||
have the same meaning as for :ref:`find <command_find>`,
|
have the same meaning as for :ref:`find <command_find>`,
|
||||||
except that search is not case sensitive.
|
except that search is not case sensitive.
|
||||||
|
|
||||||
The ``position`` parameter specifies where the songs will be
|
|
||||||
inserted.
|
|
||||||
|
|
||||||
.. _command_searchadd:
|
.. _command_searchadd:
|
||||||
|
|
||||||
:command:`searchadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
:command:`searchadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||||
@@ -1214,9 +1224,14 @@ The music database
|
|||||||
|
|
||||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||||
|
|
||||||
|
The ``position`` parameter specifies where the songs will be
|
||||||
|
inserted. [#since_0_23]_
|
||||||
|
It can be relative to the current song as in :ref:`addid
|
||||||
|
<command_addid>`. [#since_0_23_5]_
|
||||||
|
|
||||||
.. _command_searchaddpl:
|
.. _command_searchaddpl:
|
||||||
|
|
||||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}]`
|
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||||
Search the database for songs matching
|
Search the database for songs matching
|
||||||
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
||||||
the playlist named ``NAME``.
|
the playlist named ``NAME``.
|
||||||
@@ -1225,6 +1240,9 @@ The music database
|
|||||||
|
|
||||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||||
|
|
||||||
|
The ``position`` parameter specifies where the songs will be
|
||||||
|
inserted. [#since_0_23_4]_
|
||||||
|
|
||||||
.. _command_update:
|
.. _command_update:
|
||||||
|
|
||||||
:command:`update [URI]`
|
:command:`update [URI]`
|
||||||
@@ -1643,3 +1661,8 @@ client-to-client messages are local to the current partition.
|
|||||||
.. [#since_0_20] Since :program:`MPD` 0.20
|
.. [#since_0_20] Since :program:`MPD` 0.20
|
||||||
.. [#since_0_21] Since :program:`MPD` 0.21
|
.. [#since_0_21] Since :program:`MPD` 0.21
|
||||||
.. [#since_0_22_4] Since :program:`MPD` 0.22.4
|
.. [#since_0_22_4] Since :program:`MPD` 0.22.4
|
||||||
|
.. [#since_0_23] Since :program:`MPD` 0.23
|
||||||
|
.. [#since_0_23_1] Since :program:`MPD` 0.23.1
|
||||||
|
.. [#since_0_23_3] Since :program:`MPD` 0.23.3
|
||||||
|
.. [#since_0_23_4] Since :program:`MPD` 0.23.4
|
||||||
|
.. [#since_0_23_5] Since :program:`MPD` 0.23.5
|
||||||
|
113
doc/user.rst
113
doc/user.rst
@@ -64,13 +64,13 @@ In any case, you need:
|
|||||||
Each plugin usually needs a codec library, which you also need to
|
Each plugin usually needs a codec library, which you also need to
|
||||||
install. Check the :doc:`plugins` for details about required libraries
|
install. Check the :doc:`plugins` for details about required libraries
|
||||||
|
|
||||||
For example, the following installs a fairly complete list of build dependencies on Debian Buster:
|
For example, the following installs a fairly complete list of build dependencies on Debian Bullseye:
|
||||||
|
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
|
|
||||||
apt install meson g++ \
|
apt install meson g++ \
|
||||||
libfmt-dev \
|
libfmt-dev \
|
||||||
libpcre3-dev \
|
libpcre2-dev \
|
||||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||||
@@ -172,7 +172,9 @@ tarball and change into the directory. Then, instead of
|
|||||||
|
|
||||||
mkdir -p output/win64
|
mkdir -p output/win64
|
||||||
cd output/win64
|
cd output/win64
|
||||||
../../win32/build.py --64
|
../../win32/build.py --64 \
|
||||||
|
--buildtype=debugoptimized -Db_ndebug=true \
|
||||||
|
-Dwrap_mode=forcefallback
|
||||||
|
|
||||||
This downloads various library sources, and then configures and builds
|
This downloads various library sources, and then configures and builds
|
||||||
:program:`MPD` (for x64; to build a 32 bit binary, pass
|
:program:`MPD` (for x64; to build a 32 bit binary, pass
|
||||||
@@ -182,6 +184,11 @@ around. It is large, but easy to use. If you wish to have a small
|
|||||||
mpd.exe with DLLs, you need to compile manually, without the
|
mpd.exe with DLLs, you need to compile manually, without the
|
||||||
:file:`build.py` script.
|
:file:`build.py` script.
|
||||||
|
|
||||||
|
The option ``-Dwrap_mode=forcefallback`` tells Meson to download and
|
||||||
|
cross-compile several libraries used by MPD instead of looking for
|
||||||
|
them on your computer.
|
||||||
|
|
||||||
|
|
||||||
Compiling for Android
|
Compiling for Android
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
@@ -205,8 +212,10 @@ tarball and change into the directory. Then, instead of
|
|||||||
|
|
||||||
mkdir -p output/android
|
mkdir -p output/android
|
||||||
cd output/android
|
cd output/android
|
||||||
../../android/build.py SDK_PATH NDK_PATH ABI
|
../../android/build.py SDK_PATH NDK_PATH ABI \
|
||||||
meson configure -Dandroid_debug_keystore=$HOME/.android/debug.keystore
|
--buildtype=debugoptimized -Db_ndebug=true \
|
||||||
|
-Dwrap_mode=forcefallback \
|
||||||
|
-Dandroid_debug_keystore=$HOME/.android/debug.keystore
|
||||||
ninja android/apk/mpd-debug.apk
|
ninja android/apk/mpd-debug.apk
|
||||||
|
|
||||||
:envvar:`SDK_PATH` is the absolute path where you installed the
|
:envvar:`SDK_PATH` is the absolute path where you installed the
|
||||||
@@ -456,6 +465,11 @@ The following table lists the audio_output options valid for all plugins:
|
|||||||
implement an external mixer, see :ref:`external_mixer`) or no mixer
|
implement an external mixer, see :ref:`external_mixer`) or no mixer
|
||||||
(:samp:`none`). By default, the hardware mixer is used for
|
(:samp:`none`). By default, the hardware mixer is used for
|
||||||
devices which support it, and none for the others.
|
devices which support it, and none for the others.
|
||||||
|
* - **replay_gain_handler software|mixer|none**
|
||||||
|
- Specifies how :ref:`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.
|
||||||
* - **filters "name,...**"
|
* - **filters "name,...**"
|
||||||
- The specified configured filters are instantiated in the given
|
- The specified configured filters are instantiated in the given
|
||||||
order. Each filter name refers to a ``filter`` block, see
|
order. Each filter name refers to a ``filter`` block, see
|
||||||
@@ -574,6 +588,85 @@ Sometimes, music needs to be resampled before it can be played; for example, CDs
|
|||||||
Check the :ref:`resampler_plugins` reference for a list of resamplers
|
Check the :ref:`resampler_plugins` reference for a list of resamplers
|
||||||
and how to configure them.
|
and how to configure them.
|
||||||
|
|
||||||
|
Volume Normalization Settings
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. _replay_gain:
|
||||||
|
|
||||||
|
Replay Gain
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
The setting ``replaygain`` specifies whether MPD shall adjust the
|
||||||
|
volume of songs played using `ReplayGain
|
||||||
|
<https://wiki.hydrogenaud.io/index.php?title=Replaygain>`__ tags.
|
||||||
|
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.
|
||||||
|
|
||||||
|
If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
|
||||||
|
set to a value (in dB) between ``-15`` and ``15``. This is the gain
|
||||||
|
applied to songs with ReplayGain tags.
|
||||||
|
|
||||||
|
ReplayGain is usually implemented with a software volume filter (which
|
||||||
|
prevents `Bit-perfect playback`_). To use a hardware mixer, set
|
||||||
|
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
|
||||||
|
(see :ref:`config_audio_output` for details).
|
||||||
|
|
||||||
|
Simple Volume Normalization
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
MPD implements a very simple volume normalization method which can be
|
||||||
|
enabled by setting ``volume_normalization`` to ``yes``. It supports
|
||||||
|
16 bit PCM only.
|
||||||
|
|
||||||
|
|
||||||
|
.. _crossfading:
|
||||||
|
|
||||||
|
Cross-Fading
|
||||||
|
------------
|
||||||
|
|
||||||
|
If ``crossfade`` is set to a positive number, then adjacent songs are
|
||||||
|
cross-faded by this number of seconds. This is a run-time setting
|
||||||
|
:ref:`which can be controlled by clients <command_crossfade>`,
|
||||||
|
e.g. with :program:`mpc`::
|
||||||
|
|
||||||
|
mpc crossfade 10
|
||||||
|
mpc crossfade 0
|
||||||
|
|
||||||
|
Zero means cross-fading is disabled.
|
||||||
|
|
||||||
|
Cross-fading is only possible if both songs have the same audio
|
||||||
|
format. At the cost of quality loss and higher CPU usage, you can
|
||||||
|
make sure this is always given by configuring
|
||||||
|
:ref:`audio_output_format`.
|
||||||
|
|
||||||
|
.. _mixramp:
|
||||||
|
|
||||||
|
MixRamp
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
MixRamp tags describe the loudness levels at start and end of a song
|
||||||
|
and can be used by MPD to find the best time to begin cross-fading.
|
||||||
|
MPD enables MixRamp if:
|
||||||
|
|
||||||
|
- Cross-fade is enabled
|
||||||
|
- :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive
|
||||||
|
value, e.g.::
|
||||||
|
mpc mixrampdelay 1
|
||||||
|
- :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value,
|
||||||
|
e.g.::
|
||||||
|
mpc mixrampdb -17
|
||||||
|
- both songs have MixRamp tags
|
||||||
|
- both songs have the same audio format (or :ref:`audio_output_format`
|
||||||
|
is configured)
|
||||||
|
|
||||||
|
The `MixRamp <http://sourceforge.net/projects/mixramp>`__ tool can be
|
||||||
|
used to add MixRamp tags to your song files.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Client Connections
|
Client Connections
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@@ -1010,6 +1103,15 @@ See :ref:`tags` for a list of supported tags.
|
|||||||
The :ref:`metadata_to_use <metadata_to_use>` setting can be used to
|
The :ref:`metadata_to_use <metadata_to_use>` setting can be used to
|
||||||
enable or disable certain tags.
|
enable or disable certain tags.
|
||||||
|
|
||||||
|
Note that :program:`MPD` may not necessarily read metadata itself,
|
||||||
|
instead relying on data reported by the decoder that was used to read
|
||||||
|
a file. For example, this is the case for the FFmpeg decoder: both
|
||||||
|
:program:`MPD` and FFmpeg need to support a given metadata format in
|
||||||
|
order for metadata to be picked up correctly.
|
||||||
|
|
||||||
|
Only if a decoder does not have metadata support will :program:`MPD`
|
||||||
|
attempt to parse a song's metadata itself.
|
||||||
|
|
||||||
The queue
|
The queue
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@@ -1067,6 +1169,7 @@ Check list for bit-perfect playback:
|
|||||||
* Disable sound processing inside ALSA by configuring a "hardware"
|
* Disable sound processing inside ALSA by configuring a "hardware"
|
||||||
device (:samp:`hw:0,0` or similar).
|
device (:samp:`hw:0,0` or similar).
|
||||||
* Don't use software volume (setting :code:`mixer_type`).
|
* Don't use software volume (setting :code:`mixer_type`).
|
||||||
|
* Don't use :ref:`replay_gain`.
|
||||||
* Don't force :program:`MPD` to use a specific audio format (settings
|
* Don't force :program:`MPD` to use a specific audio format (settings
|
||||||
:code:`format`, :ref:`audio_output_format <audio_output_format>`).
|
:code:`format`, :ref:`audio_output_format <audio_output_format>`).
|
||||||
* Verify that you are really doing bit-perfect playback using :program:`MPD`'s verbose log and :file:`/proc/asound/card*/pcm*p/sub*/hw_params`. Some DACs can also indicate the audio format.
|
* Verify that you are really doing bit-perfect playback using :program:`MPD`'s verbose log and :file:`/proc/asound/card*/pcm*p/sub*/hw_params`. Some DACs can also indicate the audio format.
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
project(
|
project(
|
||||||
'mpd',
|
'mpd',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.23',
|
version: '0.23.6',
|
||||||
meson_version: '>= 0.56.0',
|
meson_version: '>= 0.56.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
@@ -44,7 +44,7 @@ version_conf = configuration_data()
|
|||||||
version_conf.set_quoted('PACKAGE', meson.project_name())
|
version_conf.set_quoted('PACKAGE', meson.project_name())
|
||||||
version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||||
version_conf.set_quoted('VERSION', meson.project_version())
|
version_conf.set_quoted('VERSION', meson.project_version())
|
||||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.0')
|
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.5')
|
||||||
configure_file(output: 'Version.h', configuration: version_conf)
|
configure_file(output: 'Version.h', configuration: version_conf)
|
||||||
|
|
||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
@@ -265,8 +265,8 @@ sources = [
|
|||||||
version_cxx,
|
version_cxx,
|
||||||
'src/Main.cxx',
|
'src/Main.cxx',
|
||||||
'src/protocol/ArgParser.cxx',
|
'src/protocol/ArgParser.cxx',
|
||||||
'src/protocol/Result.cxx',
|
|
||||||
'src/command/CommandError.cxx',
|
'src/command/CommandError.cxx',
|
||||||
|
'src/command/PositionArg.cxx',
|
||||||
'src/command/AllCommands.cxx',
|
'src/command/AllCommands.cxx',
|
||||||
'src/command/QueueCommands.cxx',
|
'src/command/QueueCommands.cxx',
|
||||||
'src/command/TagCommands.cxx',
|
'src/command/TagCommands.cxx',
|
||||||
|
@@ -12,14 +12,14 @@ from build.boost import BoostProject
|
|||||||
from build.jack import JackProject
|
from build.jack import JackProject
|
||||||
|
|
||||||
libmpdclient = MesonProject(
|
libmpdclient = MesonProject(
|
||||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz',
|
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.20.tar.xz',
|
||||||
'158aad4c2278ab08e76a3f2b0166c99b39fae00ee17231bd225c5a36e977a189',
|
'18793f68e939c3301e34d8fcadea1f7daa24143941263cecadb80126194e277d',
|
||||||
'lib/libmpdclient.a',
|
'lib/libmpdclient.a',
|
||||||
)
|
)
|
||||||
|
|
||||||
libogg = CmakeProject(
|
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',
|
||||||
[
|
[
|
||||||
'-DBUILD_SHARED_LIBS=OFF',
|
'-DBUILD_SHARED_LIBS=OFF',
|
||||||
@@ -43,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',
|
||||||
@@ -112,12 +112,16 @@ libmodplug = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
libopenmpt = AutotoolsProject(
|
libopenmpt = AutotoolsProject(
|
||||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.8+release.autotools.tar.gz',
|
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
|
||||||
'61de7cc0c011b10472ca16adcc123689',
|
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
|
||||||
'lib/libopenmpt.a',
|
'lib/libopenmpt.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static'
|
'--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(
|
||||||
@@ -147,8 +151,8 @@ gme = CmakeProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ffmpeg = FfmpegProject(
|
ffmpeg = FfmpegProject(
|
||||||
'http://ffmpeg.org/releases/ffmpeg-4.4.tar.xz',
|
'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz',
|
||||||
'06b10a183ce5371f915c6bb15b7b1fffbe046e8275099c96affc29e17645d909',
|
'51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298',
|
||||||
'lib/libavcodec.a',
|
'lib/libavcodec.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -376,14 +380,14 @@ ffmpeg = FfmpegProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
openssl = OpenSSLProject(
|
openssl = OpenSSLProject(
|
||||||
'https://www.openssl.org/source/openssl-3.0.0.tar.gz',
|
'https://www.openssl.org/source/openssl-3.0.1.tar.gz',
|
||||||
'59eedfcb46c25214c9bd37ed6078297b4df01d012267fe9e9eee31f61bc70536',
|
'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1',
|
||||||
'include/openssl/ossl_typ.h',
|
'include/openssl/ossl_typ.h',
|
||||||
)
|
)
|
||||||
|
|
||||||
curl = CmakeProject(
|
curl = CmakeProject(
|
||||||
'https://curl.se/download/curl-7.79.1.tar.xz',
|
'https://curl.se/download/curl-7.82.0.tar.xz',
|
||||||
'0606f74b1182ab732a17c11613cbbaf7084f2e6cca432642d0e3ad7c224c3689',
|
'0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'-DBUILD_CURL_EXE=OFF',
|
'-DBUILD_CURL_EXE=OFF',
|
||||||
@@ -411,14 +415,14 @@ curl = CmakeProject(
|
|||||||
'-DBUILD_TESTING=OFF',
|
'-DBUILD_TESTING=OFF',
|
||||||
],
|
],
|
||||||
windows_configure_args=[
|
windows_configure_args=[
|
||||||
'-DCMAKE_USE_SCHANNEL=ON',
|
'-DCURL_USE_SCHANNEL=ON',
|
||||||
],
|
],
|
||||||
patches='src/lib/curl/patches',
|
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',
|
||||||
@@ -429,8 +433,7 @@ libnfs = AutotoolsProject(
|
|||||||
|
|
||||||
'--disable-utils', '--disable-examples',
|
'--disable-utils', '--disable-examples',
|
||||||
],
|
],
|
||||||
base='libnfs-libnfs-4.0.0',
|
base='libnfs-libnfs-5.0.1',
|
||||||
patches='src/lib/nfs/patches',
|
|
||||||
autoreconf=True,
|
autoreconf=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -441,7 +444,7 @@ jack = JackProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
boost = BoostProject(
|
boost = BoostProject(
|
||||||
'https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.bz2',
|
'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2',
|
||||||
'fc9f85fc030e233142908241af7a846e60630aa7388de9a5fafb1f3a26840854',
|
'8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc',
|
||||||
'include/boost/version.hpp',
|
'include/boost/version.hpp',
|
||||||
)
|
)
|
||||||
|
@@ -53,19 +53,21 @@ pkgconfig = '{toolchain.pkg_config}'
|
|||||||
f.write(f"""
|
f.write(f"""
|
||||||
[properties]
|
[properties]
|
||||||
root = '{toolchain.install_prefix}'
|
root = '{toolchain.install_prefix}'
|
||||||
|
|
||||||
[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())}
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
if 'android' in toolchain.arch:
|
if 'android' in toolchain.arch:
|
||||||
f.write("""
|
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"""
|
f.write(f"""
|
||||||
|
@@ -86,6 +86,9 @@ 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,
|
||||||
@@ -98,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"},
|
||||||
@@ -328,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;
|
||||||
@@ -349,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;
|
||||||
|
@@ -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
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
#include "net/SocketUtil.hxx"
|
#include "net/SocketUtil.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.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/Domain.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
@@ -85,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());
|
||||||
|
|
||||||
|
@@ -48,15 +48,14 @@ LocateFileUri(const char *uri, const Client *client
|
|||||||
/* this path was relative to the music
|
/* this path was relative to the music
|
||||||
directory */
|
directory */
|
||||||
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
|
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
|
||||||
return LocatedUri(LocatedUri::Type::RELATIVE,
|
return {LocatedUri::Type::RELATIVE, suffix.data()};
|
||||||
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
|
||||||
@@ -90,8 +89,7 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
|
|||||||
const auto suffix = storage->MapToRelativeUTF8(uri);
|
const auto suffix = storage->MapToRelativeUTF8(uri);
|
||||||
if (suffix.data() != nullptr)
|
if (suffix.data() != nullptr)
|
||||||
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
|
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
|
||||||
return LocatedUri(LocatedUri::Type::RELATIVE,
|
return {LocatedUri::Type::RELATIVE, suffix.data()};
|
||||||
suffix.data());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind == UriPluginKind::STORAGE &&
|
if (kind == UriPluginKind::STORAGE &&
|
||||||
@@ -99,7 +97,7 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
|
|||||||
throw std::invalid_argument("Unsupported URI scheme");
|
throw std::invalid_argument("Unsupported URI scheme");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return LocatedUri(LocatedUri::Type::ABSOLUTE, uri);
|
return {LocatedUri::Type::ABSOLUTE, uri};
|
||||||
}
|
}
|
||||||
|
|
||||||
LocatedUri
|
LocatedUri
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#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 "Version.h"
|
||||||
@@ -110,7 +111,7 @@ chomp_length(std::string_view p) noexcept
|
|||||||
|
|
||||||
#ifdef HAVE_SYSLOG
|
#ifdef HAVE_SYSLOG
|
||||||
|
|
||||||
gcc_const
|
[[gnu::const]]
|
||||||
static int
|
static int
|
||||||
ToSysLogLevel(LogLevel log_level) noexcept
|
ToSysLogLevel(LogLevel log_level) noexcept
|
||||||
{
|
{
|
||||||
|
25
src/Main.cxx
25
src/Main.cxx
@@ -142,14 +142,24 @@ struct Config {
|
|||||||
#ifdef ENABLE_DAEMON
|
#ifdef ENABLE_DAEMON
|
||||||
|
|
||||||
static void
|
static void
|
||||||
glue_daemonize_init(const struct options *options,
|
glue_daemonize_init(const CommandLineOptions &options,
|
||||||
const ConfigData &config)
|
const ConfigData &config)
|
||||||
{
|
{
|
||||||
|
auto pid_file = config.GetPath(ConfigOption::PID_FILE);
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
if (options.systemd && pid_file != nullptr) {
|
||||||
|
pid_file = nullptr;
|
||||||
|
fprintf(stderr,
|
||||||
|
"Ignoring the 'pid_file' setting in systemd mode\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
daemonize_init(config.GetString(ConfigOption::USER),
|
daemonize_init(config.GetString(ConfigOption::USER),
|
||||||
config.GetString(ConfigOption::GROUP),
|
config.GetString(ConfigOption::GROUP),
|
||||||
config.GetPath(ConfigOption::PID_FILE));
|
std::move(pid_file));
|
||||||
|
|
||||||
if (options->kill)
|
if (options.kill)
|
||||||
daemonize_kill();
|
daemonize_kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +371,8 @@ Instance::BeginShutdownPartitions() noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
MainConfigured(const struct options &options, const ConfigData &raw_config)
|
MainConfigured(const CommandLineOptions &options,
|
||||||
|
const ConfigData &raw_config)
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_DAEMON
|
#ifdef ENABLE_DAEMON
|
||||||
daemonize_close_stdin();
|
daemonize_close_stdin();
|
||||||
@@ -384,7 +395,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
|||||||
const Config config(raw_config);
|
const Config config(raw_config);
|
||||||
|
|
||||||
#ifdef ENABLE_DAEMON
|
#ifdef ENABLE_DAEMON
|
||||||
glue_daemonize_init(&options, raw_config);
|
glue_daemonize_init(options, raw_config);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TagLoadConfig(raw_config);
|
TagLoadConfig(raw_config);
|
||||||
@@ -582,7 +593,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
|||||||
static void
|
static void
|
||||||
AndroidMain()
|
AndroidMain()
|
||||||
{
|
{
|
||||||
struct options options;
|
CommandLineOptions options;
|
||||||
ConfigData raw_config;
|
ConfigData raw_config;
|
||||||
|
|
||||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
const auto sdcard = Environment::getExternalStorageDirectory();
|
||||||
@@ -642,7 +653,7 @@ Java_org_musicpd_Bridge_pause(JNIEnv *, jclass)
|
|||||||
static inline void
|
static inline void
|
||||||
MainOrThrow(int argc, char *argv[])
|
MainOrThrow(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct options options;
|
CommandLineOptions options;
|
||||||
ConfigData raw_config;
|
ConfigData raw_config;
|
||||||
|
|
||||||
ParseCommandLine(argc, argv, options, raw_config);
|
ParseCommandLine(argc, argv, options, raw_config);
|
||||||
|
@@ -85,15 +85,15 @@ map_fs_to_utf8(Path path_fs) noexcept
|
|||||||
{
|
{
|
||||||
if (path_fs.IsAbsolute()) {
|
if (path_fs.IsAbsolute()) {
|
||||||
if (global_instance->storage == nullptr)
|
if (global_instance->storage == nullptr)
|
||||||
return std::string();
|
return {};
|
||||||
|
|
||||||
const auto music_dir_fs = global_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);
|
||||||
}
|
}
|
||||||
|
@@ -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
|
|||||||
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);
|
||||||
|
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
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)
|
||||||
@@ -41,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) {
|
||||||
@@ -81,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() ||
|
||||||
|
@@ -77,7 +77,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[gnu::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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[gnu::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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,7 +30,6 @@
|
|||||||
#include "util/StringView.hxx"
|
#include "util/StringView.hxx"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -100,18 +99,15 @@ initPermissions(const ConfigData &config)
|
|||||||
for (const auto ¶m : config.GetParamList(ConfigOption::PASSWORD)) {
|
for (const auto ¶m : config.GetParamList(ConfigOption::PASSWORD)) {
|
||||||
permission_default = 0;
|
permission_default = 0;
|
||||||
|
|
||||||
param.With([](const char *value){
|
param.With([](const StringView value){
|
||||||
const char *separator = std::strchr(value,
|
const auto [password, permissions] =
|
||||||
PERMISSION_PASSWORD_CHAR);
|
value.Split(PERMISSION_PASSWORD_CHAR);
|
||||||
|
if (permissions == nullptr)
|
||||||
if (separator == nullptr)
|
|
||||||
throw FormatRuntimeError("\"%c\" not found in password string",
|
throw FormatRuntimeError("\"%c\" not found in password string",
|
||||||
PERMISSION_PASSWORD_CHAR);
|
PERMISSION_PASSWORD_CHAR);
|
||||||
|
|
||||||
std::string password(value, separator);
|
permission_passwords.emplace(password,
|
||||||
|
parsePermissions(permissions));
|
||||||
unsigned permission = parsePermissions(separator + 1);
|
|
||||||
permission_passwords.emplace(std::move(password), permission);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,15 +157,14 @@ GetPermissionsFromAddress(SocketAddress address) noexcept
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int
|
std::optional<unsigned>
|
||||||
getPermissionFromPassword(const char *password, unsigned *permission) noexcept
|
GetPermissionFromPassword(const char *password) noexcept
|
||||||
{
|
{
|
||||||
auto i = permission_passwords.find(password);
|
auto i = permission_passwords.find(password);
|
||||||
if (i == permission_passwords.end())
|
if (i == permission_passwords.end())
|
||||||
return -1;
|
return std::nullopt;
|
||||||
|
|
||||||
*permission = i->second;
|
return i->second;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned
|
unsigned
|
||||||
|
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
struct ConfigData;
|
struct ConfigData;
|
||||||
class SocketAddress;
|
class SocketAddress;
|
||||||
|
|
||||||
@@ -32,9 +34,13 @@ static constexpr unsigned PERMISSION_CONTROL = 4;
|
|||||||
static constexpr unsigned PERMISSION_ADMIN = 8;
|
static constexpr unsigned PERMISSION_ADMIN = 8;
|
||||||
static constexpr unsigned PERMISSION_PLAYER = 16;
|
static constexpr unsigned PERMISSION_PLAYER = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the permissions for the given password or std::nullopt if
|
||||||
|
* the password is not accepted
|
||||||
|
*/
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
int
|
std::optional<unsigned>
|
||||||
getPermissionFromPassword(const char *password, unsigned *permission) noexcept;
|
GetPermissionFromPassword(const char *password) noexcept;
|
||||||
|
|
||||||
[[gnu::const]]
|
[[gnu::const]]
|
||||||
unsigned
|
unsigned
|
||||||
|
@@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
#include "PlaylistDatabase.hxx"
|
#include "PlaylistDatabase.hxx"
|
||||||
#include "db/PlaylistVector.hxx"
|
#include "db/PlaylistVector.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "io/LineReader.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "time/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/StringStrip.hxx"
|
#include "util/StringStrip.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
@@ -42,7 +42,7 @@ playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name)
|
playlist_metadata_load(LineReader &file, PlaylistVector &pv, const char *name)
|
||||||
{
|
{
|
||||||
PlaylistInfo pm(name);
|
PlaylistInfo pm(name);
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
class PlaylistVector;
|
class PlaylistVector;
|
||||||
class BufferedOutputStream;
|
class BufferedOutputStream;
|
||||||
class TextFile;
|
class LineReader;
|
||||||
|
|
||||||
void
|
void
|
||||||
playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
|
playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
|
||||||
@@ -33,6 +33,7 @@ playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
|
|||||||
* Throws #std::runtime_error on error.
|
* Throws #std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name);
|
playlist_metadata_load(LineReader &file, PlaylistVector &pv,
|
||||||
|
const char *name);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -26,15 +26,15 @@
|
|||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
#include "SongLoader.hxx"
|
#include "SongLoader.hxx"
|
||||||
#include "Mapper.hxx"
|
#include "Mapper.hxx"
|
||||||
|
#include "protocol/RangeArg.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "fs/io/TextFile.hxx"
|
||||||
#include "fs/io/FileOutputStream.hxx"
|
#include "io/FileOutputStream.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "config/Data.hxx"
|
#include "config/Data.hxx"
|
||||||
#include "config/Option.hxx"
|
#include "config/Option.hxx"
|
||||||
#include "config/Defaults.hxx"
|
#include "config/Defaults.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
#include "fs/Limits.hxx"
|
#include "fs/Limits.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "fs/FileInfo.hxx"
|
#include "fs/FileInfo.hxx"
|
||||||
@@ -173,11 +173,8 @@ ListPlaylistFiles()
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path)
|
SavePlaylistFile(Path path_fs, const PlaylistFileContents &contents)
|
||||||
{
|
{
|
||||||
assert(utf8path != nullptr);
|
|
||||||
|
|
||||||
const auto path_fs = spl_map_to_fs(utf8path);
|
|
||||||
assert(!path_fs.IsNull());
|
assert(!path_fs.IsNull());
|
||||||
|
|
||||||
FileOutputStream fos(path_fs);
|
FileOutputStream fos(path_fs);
|
||||||
@@ -191,12 +188,11 @@ SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path)
|
|||||||
fos.Commit();
|
fos.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistFileContents
|
static PlaylistFileContents
|
||||||
LoadPlaylistFile(const char *utf8path)
|
LoadPlaylistFile(Path path_fs)
|
||||||
try {
|
try {
|
||||||
PlaylistFileContents contents;
|
PlaylistFileContents contents;
|
||||||
|
|
||||||
const auto path_fs = spl_map_to_fs(utf8path);
|
|
||||||
assert(!path_fs.IsNull());
|
assert(!path_fs.IsNull());
|
||||||
|
|
||||||
TextFile file(path_fs);
|
TextFile file(path_fs);
|
||||||
@@ -251,16 +247,54 @@ try {
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static PlaylistFileContents
|
||||||
spl_move_index(const char *utf8path, unsigned src, unsigned dest)
|
MaybeLoadPlaylistFile(Path path_fs, PlaylistFileEditor::LoadMode load_mode)
|
||||||
|
try {
|
||||||
|
if (load_mode == PlaylistFileEditor::LoadMode::NO)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return LoadPlaylistFile(path_fs);
|
||||||
|
} catch (const PlaylistError &error) {
|
||||||
|
if (error.GetCode() == PlaylistResult::NO_SUCH_LIST &&
|
||||||
|
load_mode == PlaylistFileEditor::LoadMode::TRY)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaylistFileEditor::PlaylistFileEditor(const char *name_utf8,
|
||||||
|
LoadMode load_mode)
|
||||||
|
:path(spl_map_to_fs(name_utf8)),
|
||||||
|
contents(MaybeLoadPlaylistFile(path, load_mode))
|
||||||
{
|
{
|
||||||
if (src == dest)
|
}
|
||||||
/* this doesn't check whether the playlist exists, but
|
|
||||||
what the hell.. */
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto contents = LoadPlaylistFile(utf8path);
|
void
|
||||||
|
PlaylistFileEditor::Insert(std::size_t i, const char *uri)
|
||||||
|
{
|
||||||
|
if (i > size())
|
||||||
|
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad position");
|
||||||
|
|
||||||
|
if (size() >= playlist_max_length)
|
||||||
|
throw PlaylistError(PlaylistResult::TOO_LARGE,
|
||||||
|
"Stored playlist is too large");
|
||||||
|
|
||||||
|
contents.emplace(std::next(contents.begin(), i), uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlaylistFileEditor::Insert(std::size_t i, const DetachedSong &song)
|
||||||
|
{
|
||||||
|
const char *uri = playlist_saveAbsolutePaths
|
||||||
|
? song.GetRealURI()
|
||||||
|
: song.GetURI();
|
||||||
|
|
||||||
|
Insert(i, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlaylistFileEditor::MoveIndex(unsigned src, unsigned dest)
|
||||||
|
{
|
||||||
if (src >= contents.size() || dest >= contents.size())
|
if (src >= contents.size() || dest >= contents.size())
|
||||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||||
|
|
||||||
@@ -270,9 +304,31 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest)
|
|||||||
|
|
||||||
const auto dest_i = std::next(contents.begin(), dest);
|
const auto dest_i = std::next(contents.begin(), dest);
|
||||||
contents.insert(dest_i, std::move(value));
|
contents.insert(dest_i, std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
SavePlaylistFile(contents, utf8path);
|
void
|
||||||
|
PlaylistFileEditor::RemoveIndex(unsigned i)
|
||||||
|
{
|
||||||
|
if (i >= contents.size())
|
||||||
|
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||||
|
|
||||||
|
contents.erase(std::next(contents.begin(), i));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlaylistFileEditor::RemoveRange(RangeArg range)
|
||||||
|
{
|
||||||
|
if (!range.CheckClip(size()))
|
||||||
|
throw PlaylistError::BadRange();
|
||||||
|
|
||||||
|
contents.erase(std::next(contents.begin(), range.start),
|
||||||
|
std::next(contents.begin(), range.end));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlaylistFileEditor::Save()
|
||||||
|
{
|
||||||
|
SavePlaylistFile(path, contents);
|
||||||
idle_add(IDLE_STORED_PLAYLIST);
|
idle_add(IDLE_STORED_PLAYLIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,20 +370,6 @@ spl_delete(const char *name_utf8)
|
|||||||
idle_add(IDLE_STORED_PLAYLIST);
|
idle_add(IDLE_STORED_PLAYLIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
spl_remove_index(const char *utf8path, unsigned pos)
|
|
||||||
{
|
|
||||||
auto contents = LoadPlaylistFile(utf8path);
|
|
||||||
|
|
||||||
if (pos >= contents.size())
|
|
||||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
|
||||||
|
|
||||||
contents.erase(std::next(contents.begin(), pos));
|
|
||||||
|
|
||||||
SavePlaylistFile(contents, utf8path);
|
|
||||||
idle_add(IDLE_STORED_PLAYLIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
spl_append_song(const char *utf8path, const DetachedSong &song)
|
spl_append_song(const char *utf8path, const DetachedSong &song)
|
||||||
try {
|
try {
|
||||||
|
@@ -20,19 +20,55 @@
|
|||||||
#ifndef MPD_PLAYLIST_FILE_HXX
|
#ifndef MPD_PLAYLIST_FILE_HXX
|
||||||
#define MPD_PLAYLIST_FILE_HXX
|
#define MPD_PLAYLIST_FILE_HXX
|
||||||
|
|
||||||
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct ConfigData;
|
struct ConfigData;
|
||||||
|
struct RangeArg;
|
||||||
class DetachedSong;
|
class DetachedSong;
|
||||||
class SongLoader;
|
class SongLoader;
|
||||||
class PlaylistVector;
|
class PlaylistVector;
|
||||||
class AllocatedPath;
|
|
||||||
|
|
||||||
typedef std::vector<std::string> PlaylistFileContents;
|
typedef std::vector<std::string> PlaylistFileContents;
|
||||||
|
|
||||||
extern bool playlist_saveAbsolutePaths;
|
extern bool playlist_saveAbsolutePaths;
|
||||||
|
|
||||||
|
class PlaylistFileEditor {
|
||||||
|
const AllocatedPath path;
|
||||||
|
|
||||||
|
PlaylistFileContents contents;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class LoadMode {
|
||||||
|
NO,
|
||||||
|
YES,
|
||||||
|
TRY,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws on error.
|
||||||
|
*/
|
||||||
|
explicit PlaylistFileEditor(const char *name_utf8, LoadMode load_mode);
|
||||||
|
|
||||||
|
auto size() const noexcept {
|
||||||
|
return contents.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Insert(std::size_t i, const char *uri);
|
||||||
|
void Insert(std::size_t i, const DetachedSong &song);
|
||||||
|
|
||||||
|
void MoveIndex(unsigned src, unsigned dest);
|
||||||
|
void RemoveIndex(unsigned i);
|
||||||
|
void RemoveRange(RangeArg range);
|
||||||
|
|
||||||
|
void Save();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Load();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform some global initialization, e.g. load configuration values.
|
* Perform some global initialization, e.g. load configuration values.
|
||||||
*/
|
*/
|
||||||
@@ -55,21 +91,12 @@ spl_map_to_fs(const char *name_utf8);
|
|||||||
PlaylistVector
|
PlaylistVector
|
||||||
ListPlaylistFiles();
|
ListPlaylistFiles();
|
||||||
|
|
||||||
PlaylistFileContents
|
|
||||||
LoadPlaylistFile(const char *utf8path);
|
|
||||||
|
|
||||||
void
|
|
||||||
spl_move_index(const char *utf8path, unsigned src, unsigned dest);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
spl_clear(const char *utf8path);
|
spl_clear(const char *utf8path);
|
||||||
|
|
||||||
void
|
void
|
||||||
spl_delete(const char *name_utf8);
|
spl_delete(const char *name_utf8);
|
||||||
|
|
||||||
void
|
|
||||||
spl_remove_index(const char *utf8path, unsigned pos);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
spl_append_song(const char *utf8path, const DetachedSong &song);
|
spl_append_song(const char *utf8path, const DetachedSong &song);
|
||||||
|
|
||||||
|
@@ -28,8 +28,8 @@
|
|||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "fs/io/FileOutputStream.hxx"
|
#include "io/FileOutputStream.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "util/UriExtract.hxx"
|
#include "util/UriExtract.hxx"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@@ -98,7 +98,7 @@ RemoteTagCache::ItemResolved(Item &item) noexcept
|
|||||||
void
|
void
|
||||||
RemoteTagCache::InvokeHandlers() noexcept
|
RemoteTagCache::InvokeHandlers() noexcept
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> lock(mutex);
|
const std::scoped_lock<Mutex> lock(mutex);
|
||||||
|
|
||||||
while (!invoke_list.empty()) {
|
while (!invoke_list.empty()) {
|
||||||
auto &item = invoke_list.front();
|
auto &item = invoke_list.front();
|
||||||
@@ -125,7 +125,7 @@ RemoteTagCache::Item::OnRemoteTag(Tag &&_tag) noexcept
|
|||||||
|
|
||||||
scanner.reset();
|
scanner.reset();
|
||||||
|
|
||||||
const std::lock_guard<Mutex> lock(parent.mutex);
|
const std::scoped_lock<Mutex> lock(parent.mutex);
|
||||||
parent.ItemResolved(*this);
|
parent.ItemResolved(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +137,6 @@ RemoteTagCache::Item::OnRemoteTagError(std::exception_ptr e) noexcept
|
|||||||
|
|
||||||
scanner.reset();
|
scanner.reset();
|
||||||
|
|
||||||
const std::lock_guard<Mutex> lock(parent.mutex);
|
const std::scoped_lock<Mutex> lock(parent.mutex);
|
||||||
parent.ItemResolved(*this);
|
parent.ItemResolved(*this);
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ enum class SingleMode : uint8_t {
|
|||||||
/**
|
/**
|
||||||
* Return the string representation of a #SingleMode.
|
* Return the string representation of a #SingleMode.
|
||||||
*/
|
*/
|
||||||
[[gnu::pure]]
|
[[gnu::const]]
|
||||||
const char *
|
const char *
|
||||||
SingleToString(SingleMode mode) noexcept;
|
SingleToString(SingleMode mode) noexcept;
|
||||||
|
|
||||||
|
@@ -100,7 +100,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
|
|||||||
const auto duration = song.GetDuration();
|
const auto duration = song.GetDuration();
|
||||||
if (!duration.IsNegative())
|
if (!duration.IsNegative())
|
||||||
r.Fmt(FMT_STRING("Time: {}\n"
|
r.Fmt(FMT_STRING("Time: {}\n"
|
||||||
"duration: {:1.3}\n"),
|
"duration: {:1.3f}\n"),
|
||||||
duration.RoundS(),
|
duration.RoundS(),
|
||||||
duration.ToDoubleS());
|
duration.ToDoubleS());
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
|
|||||||
const auto duration = song.GetDuration();
|
const auto duration = song.GetDuration();
|
||||||
if (!duration.IsNegative())
|
if (!duration.IsNegative())
|
||||||
r.Fmt(FMT_STRING("Time: {}\n"
|
r.Fmt(FMT_STRING("Time: {}\n"
|
||||||
"duration: {:1.3}\n"),
|
"duration: {:1.3f}\n"),
|
||||||
duration.RoundS(),
|
duration.RoundS(),
|
||||||
duration.ToDoubleS());
|
duration.ToDoubleS());
|
||||||
}
|
}
|
||||||
|
@@ -22,8 +22,8 @@
|
|||||||
#include "db/plugins/simple/Song.hxx"
|
#include "db/plugins/simple/Song.hxx"
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
#include "TagSave.hxx"
|
#include "TagSave.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "io/LineReader.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "tag/ParseName.hxx"
|
#include "tag/ParseName.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
#include "tag/Builder.hxx"
|
#include "tag/Builder.hxx"
|
||||||
@@ -85,7 +85,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DetachedSong
|
DetachedSong
|
||||||
song_load(TextFile &file, const char *uri,
|
song_load(LineReader &file, const char *uri,
|
||||||
std::string *target_r)
|
std::string *target_r)
|
||||||
{
|
{
|
||||||
DetachedSong song(uri);
|
DetachedSong song(uri);
|
||||||
|
@@ -28,7 +28,7 @@ struct Song;
|
|||||||
struct AudioFormat;
|
struct AudioFormat;
|
||||||
class DetachedSong;
|
class DetachedSong;
|
||||||
class BufferedOutputStream;
|
class BufferedOutputStream;
|
||||||
class TextFile;
|
class LineReader;
|
||||||
|
|
||||||
void
|
void
|
||||||
song_save(BufferedOutputStream &os, const Song &song);
|
song_save(BufferedOutputStream &os, const Song &song);
|
||||||
@@ -43,7 +43,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
|
|||||||
* Throws on error.
|
* Throws on error.
|
||||||
*/
|
*/
|
||||||
DetachedSong
|
DetachedSong
|
||||||
song_load(TextFile &file, const char *uri,
|
song_load(LineReader &file, const char *uri,
|
||||||
std::string *target_r=nullptr);
|
std::string *target_r=nullptr);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -22,8 +22,8 @@
|
|||||||
#include "output/State.hxx"
|
#include "output/State.hxx"
|
||||||
#include "queue/PlaylistState.hxx"
|
#include "queue/PlaylistState.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "fs/io/TextFile.hxx"
|
||||||
#include "fs/io/FileOutputStream.hxx"
|
#include "io/FileOutputStream.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "storage/StorageState.hxx"
|
#include "storage/StorageState.hxx"
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
|
@@ -21,12 +21,16 @@
|
|||||||
#include "TagStream.hxx"
|
#include "TagStream.hxx"
|
||||||
#include "TagFile.hxx"
|
#include "TagFile.hxx"
|
||||||
#include "tag/Generic.hxx"
|
#include "tag/Generic.hxx"
|
||||||
|
#include "song/LightSong.hxx"
|
||||||
|
#include "db/Interface.hxx"
|
||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
#include "client/Client.hxx"
|
#include "client/Client.hxx"
|
||||||
#include "protocol/Ack.hxx"
|
#include "protocol/Ack.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
#include "util/ScopeExit.hxx"
|
||||||
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/UriExtract.hxx"
|
#include "util/UriExtract.hxx"
|
||||||
#include "LocateUri.hxx"
|
#include "LocateUri.hxx"
|
||||||
|
|
||||||
@@ -51,10 +55,67 @@ TagScanFile(const Path path_fs, TagHandler &handler)
|
|||||||
ScanGenericTags(path_fs, handler);
|
ScanGenericTags(path_fs, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_DATABASE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse "../" prefixes in a URI relative to the specified base
|
||||||
|
* URI.
|
||||||
|
*/
|
||||||
|
static std::string
|
||||||
|
ResolveUri(std::string_view base, const char *relative)
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
const char *rest = StringAfterPrefix(relative, "../");
|
||||||
|
if (rest == nullptr)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (base == ".")
|
||||||
|
throw ProtocolError(ACK_ERROR_NO_EXIST, "Bad real URI");
|
||||||
|
|
||||||
|
base = PathTraitsUTF8::GetParent(base);
|
||||||
|
relative = rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathTraitsUTF8::Build(base, relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the specified song in the database and return its
|
||||||
|
* (resolved) "real" URI.
|
||||||
|
*/
|
||||||
|
static std::string
|
||||||
|
GetRealSongUri(Client &client, std::string_view uri)
|
||||||
|
{
|
||||||
|
const auto &db = client.GetDatabaseOrThrow();
|
||||||
|
|
||||||
|
const auto *song = db.GetSong(uri);
|
||||||
|
if (song == nullptr)
|
||||||
|
throw ProtocolError(ACK_ERROR_NO_EXIST, "No such song");
|
||||||
|
|
||||||
|
AtScopeExit(&db, song) { db.ReturnSong(song); };
|
||||||
|
|
||||||
|
if (song->real_uri == nullptr)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ResolveUri(PathTraitsUTF8::GetParent(uri), song->real_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
static void
|
static void
|
||||||
TagScanDatabase(Client &client, const char *uri, TagHandler &handler)
|
TagScanDatabase(Client &client, const char *uri, TagHandler &handler)
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
|
const auto real_uri = GetRealSongUri(client, uri);
|
||||||
|
|
||||||
|
if (!real_uri.empty()) {
|
||||||
|
uri = real_uri.c_str();
|
||||||
|
|
||||||
|
// TODO: support absolute paths?
|
||||||
|
if (uri_has_scheme(uri))
|
||||||
|
return TagScanStream(uri, handler);
|
||||||
|
}
|
||||||
|
|
||||||
const Storage *storage = client.GetStorage();
|
const Storage *storage = client.GetStorage();
|
||||||
if (storage == nullptr) {
|
if (storage == nullptr) {
|
||||||
#else
|
#else
|
||||||
|
@@ -60,7 +60,7 @@ tag_print(Response &r, const Tag &tag) noexcept
|
|||||||
{
|
{
|
||||||
if (!tag.duration.IsNegative())
|
if (!tag.duration.IsNegative())
|
||||||
r.Fmt(FMT_STRING("Time: {}\n"
|
r.Fmt(FMT_STRING("Time: {}\n"
|
||||||
"duration: {:1.3}\n"),
|
"duration: {:1.3f}\n"),
|
||||||
tag.duration.RoundS(),
|
tag.duration.RoundS(),
|
||||||
tag.duration.ToDoubleS());
|
tag.duration.ToDoubleS());
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
#include "TagSave.hxx"
|
#include "TagSave.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
|
|
||||||
#define SONG_TIME "Time: "
|
#define SONG_TIME "Time: "
|
||||||
|
|
||||||
|
@@ -26,7 +26,6 @@
|
|||||||
#include "plugins/ZzipArchivePlugin.hxx"
|
#include "plugins/ZzipArchivePlugin.hxx"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <iterator>
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
@@ -162,7 +162,7 @@ class Iso9660InputStream final : public InputStream {
|
|||||||
std::array<uint8_t, ISO_BLOCKSIZE> data;
|
std::array<uint8_t, ISO_BLOCKSIZE> data;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ConstBuffer<uint8_t> Read() const noexcept {
|
[[nodiscard]] ConstBuffer<uint8_t> Read() const noexcept {
|
||||||
assert(fill <= data.size());
|
assert(fill <= data.size());
|
||||||
assert(position <= fill);
|
assert(position <= fill);
|
||||||
|
|
||||||
|
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <inttypes.h> /* for PRIoffset (PRIu64) */
|
#include <cinttypes> /* for PRIoffset (PRIu64) */
|
||||||
|
|
||||||
struct ZzipDir {
|
struct ZzipDir {
|
||||||
ZZIP_DIR *const dir;
|
ZZIP_DIR *const dir;
|
||||||
|
@@ -150,7 +150,13 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Write a null-terminated string.
|
* Write a null-terminated string.
|
||||||
*/
|
*/
|
||||||
bool Write(const char *data) noexcept;
|
bool Write(std::string_view s) noexcept {
|
||||||
|
return Write(s.data(), s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteOK() noexcept {
|
||||||
|
return Write("OK\n");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the uid of the client process, or a negative value
|
* returns the uid of the client process, or a negative value
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
#include "Client.hxx"
|
#include "Client.hxx"
|
||||||
#include "Config.hxx"
|
#include "Config.hxx"
|
||||||
#include "Domain.hxx"
|
#include "Domain.hxx"
|
||||||
#include "protocol/Result.hxx"
|
|
||||||
#include "command/AllCommands.hxx"
|
#include "command/AllCommands.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "util/StringAPI.hxx"
|
#include "util/StringAPI.hxx"
|
||||||
@@ -72,7 +71,7 @@ Client::ProcessLine(char *line) noexcept
|
|||||||
if (idle_waiting) {
|
if (idle_waiting) {
|
||||||
/* send empty idle response and leave idle mode */
|
/* send empty idle response and leave idle mode */
|
||||||
idle_waiting = false;
|
idle_waiting = false;
|
||||||
command_success(*this);
|
WriteOK();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* do nothing if the client wasn't idling: the client
|
/* do nothing if the client wasn't idling: the client
|
||||||
@@ -108,7 +107,7 @@ Client::ProcessLine(char *line) noexcept
|
|||||||
"list returned {}", id, unsigned(ret));
|
"list returned {}", id, unsigned(ret));
|
||||||
|
|
||||||
if (ret == CommandResult::OK)
|
if (ret == CommandResult::OK)
|
||||||
command_success(*this);
|
WriteOK();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
} else {
|
} else {
|
||||||
@@ -144,7 +143,7 @@ Client::ProcessLine(char *line) noexcept
|
|||||||
return CommandResult::CLOSE;
|
return CommandResult::CLOSE;
|
||||||
|
|
||||||
if (ret == CommandResult::OK)
|
if (ret == CommandResult::OK)
|
||||||
command_success(*this);
|
WriteOK();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@@ -66,7 +66,11 @@ Response::WriteBinary(ConstBuffer<void> payload) noexcept
|
|||||||
void
|
void
|
||||||
Response::Error(enum ack code, const char *msg) noexcept
|
Response::Error(enum ack code, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
FmtError(code, FMT_STRING("{}"), msg);
|
Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
|
||||||
|
(int)code, list_index, command);
|
||||||
|
|
||||||
|
Write(msg);
|
||||||
|
Write("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -76,7 +80,7 @@ Response::VFmtError(enum ack code,
|
|||||||
Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
|
Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
|
||||||
(int)code, list_index, command);
|
(int)code, list_index, command);
|
||||||
|
|
||||||
VFmt(format_str, std::move(args));
|
VFmt(format_str, args);
|
||||||
|
|
||||||
Write("\n");
|
Write("\n");
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,6 @@
|
|||||||
#include "Client.hxx"
|
#include "Client.hxx"
|
||||||
#include "Response.hxx"
|
#include "Response.hxx"
|
||||||
#include "command/CommandError.hxx"
|
#include "command/CommandError.hxx"
|
||||||
#include "protocol/Result.hxx"
|
|
||||||
|
|
||||||
ThreadBackgroundCommand::ThreadBackgroundCommand(Client &_client) noexcept
|
ThreadBackgroundCommand::ThreadBackgroundCommand(Client &_client) noexcept
|
||||||
:thread(BIND_THIS_METHOD(_Run)),
|
:thread(BIND_THIS_METHOD(_Run)),
|
||||||
@@ -57,7 +56,7 @@ ThreadBackgroundCommand::DeferredFinish() noexcept
|
|||||||
PrintError(response, error);
|
PrintError(response, error);
|
||||||
} else {
|
} else {
|
||||||
SendResponse(response);
|
SendResponse(response);
|
||||||
command_success(client);
|
client.WriteOK();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* delete this object */
|
/* delete this object */
|
||||||
|
@@ -27,9 +27,3 @@ Client::Write(const void *data, size_t length) noexcept
|
|||||||
/* if the client is going to be closed, do nothing */
|
/* if the client is going to be closed, do nothing */
|
||||||
return !IsExpired() && FullyBufferedSocket::Write(data, length);
|
return !IsExpired() && FullyBufferedSocket::Write(data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
Client::Write(const char *data) noexcept
|
|
||||||
{
|
|
||||||
return Write(data, strlen(data));
|
|
||||||
}
|
|
||||||
|
@@ -85,7 +85,7 @@ handle_not_commands(Client &client, Request request, Response &response);
|
|||||||
* This array must be sorted!
|
* This array must be sorted!
|
||||||
*/
|
*/
|
||||||
static constexpr struct command commands[] = {
|
static constexpr struct command commands[] = {
|
||||||
{ "add", PERMISSION_ADD, 1, 1, handle_add },
|
{ "add", PERMISSION_ADD, 1, 2, handle_add },
|
||||||
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
|
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
|
||||||
{ "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
|
{ "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
|
||||||
{ "albumart", PERMISSION_READ, 2, 2, handle_album_art },
|
{ "albumart", PERMISSION_READ, 2, 2, handle_album_art },
|
||||||
@@ -157,7 +157,7 @@ static constexpr struct command commands[] = {
|
|||||||
{ "play", PERMISSION_PLAYER, 0, 1, handle_play },
|
{ "play", PERMISSION_PLAYER, 0, 1, handle_play },
|
||||||
{ "playid", PERMISSION_PLAYER, 0, 1, handle_playid },
|
{ "playid", PERMISSION_PLAYER, 0, 1, handle_playid },
|
||||||
{ "playlist", PERMISSION_READ, 0, 0, handle_playlist },
|
{ "playlist", PERMISSION_READ, 0, 0, handle_playlist },
|
||||||
{ "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd },
|
{ "playlistadd", PERMISSION_CONTROL, 2, 3, handle_playlistadd },
|
||||||
{ "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
|
{ "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
|
||||||
{ "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete },
|
{ "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete },
|
||||||
{ "playlistfind", PERMISSION_READ, 1, -1, handle_playlistfind },
|
{ "playlistfind", PERMISSION_READ, 1, -1, handle_playlistfind },
|
||||||
|
@@ -58,13 +58,13 @@ handle_binary_limit(Client &client, Request args,
|
|||||||
CommandResult
|
CommandResult
|
||||||
handle_password(Client &client, Request args, Response &r)
|
handle_password(Client &client, Request args, Response &r)
|
||||||
{
|
{
|
||||||
unsigned permission = 0;
|
const auto permission = GetPermissionFromPassword(args.front());
|
||||||
if (getPermissionFromPassword(args.front(), &permission) < 0) {
|
if (!permission) {
|
||||||
r.Error(ACK_ERROR_PASSWORD, "incorrect password");
|
r.Error(ACK_ERROR_PASSWORD, "incorrect password");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.SetPermission(permission);
|
client.SetPermission(*permission);
|
||||||
|
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "DatabaseCommands.hxx"
|
#include "DatabaseCommands.hxx"
|
||||||
|
#include "PositionArg.hxx"
|
||||||
#include "Request.hxx"
|
#include "Request.hxx"
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
#include "db/DatabaseQueue.hxx"
|
#include "db/DatabaseQueue.hxx"
|
||||||
@@ -86,6 +87,20 @@ ParseQueuePosition(Request &args, unsigned queue_length)
|
|||||||
return queue_length;
|
return queue_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned
|
||||||
|
ParseInsertPosition(Request &args, const playlist &playlist)
|
||||||
|
{
|
||||||
|
if (args.size >= 2 && StringIsEqual(args[args.size - 2], "position")) {
|
||||||
|
unsigned position = ParseInsertPosition(args.back(), playlist);
|
||||||
|
args.pop_back();
|
||||||
|
args.pop_back();
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* append to the end of the queue by default */
|
||||||
|
return playlist.queue.GetLength();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert all remaining arguments to a #DatabaseSelection.
|
* Convert all remaining arguments to a #DatabaseSelection.
|
||||||
*
|
*
|
||||||
@@ -160,7 +175,8 @@ handle_match_add(Client &client, Request args, bool fold_case)
|
|||||||
{
|
{
|
||||||
auto &partition = client.GetPartition();
|
auto &partition = client.GetPartition();
|
||||||
const auto queue_length = partition.playlist.queue.GetLength();
|
const auto queue_length = partition.playlist.queue.GetLength();
|
||||||
const unsigned position = ParseQueuePosition(args, queue_length);
|
const unsigned position =
|
||||||
|
ParseInsertPosition(args, partition.playlist);
|
||||||
|
|
||||||
SongFilter filter;
|
SongFilter filter;
|
||||||
const auto selection = ParseDatabaseSelection(args, fold_case, filter);
|
const auto selection = ParseDatabaseSelection(args, fold_case, filter);
|
||||||
@@ -199,13 +215,20 @@ handle_searchaddpl(Client &client, Request args, Response &)
|
|||||||
{
|
{
|
||||||
const char *playlist = args.shift();
|
const char *playlist = args.shift();
|
||||||
|
|
||||||
|
const unsigned position = ParseQueuePosition(args, UINT_MAX);
|
||||||
|
|
||||||
SongFilter filter;
|
SongFilter filter;
|
||||||
const auto selection = ParseDatabaseSelection(args, true, filter);
|
const auto selection = ParseDatabaseSelection(args, true, filter);
|
||||||
|
|
||||||
const Database &db = client.GetDatabaseOrThrow();
|
const Database &db = client.GetDatabaseOrThrow();
|
||||||
|
|
||||||
search_add_to_playlist(db, client.GetStorage(),
|
if (position == UINT_MAX)
|
||||||
playlist, selection);
|
search_add_to_playlist(db, client.GetStorage(),
|
||||||
|
playlist, selection);
|
||||||
|
else
|
||||||
|
SearchInsertIntoPlaylist(db, client.GetStorage(), selection,
|
||||||
|
playlist, position);
|
||||||
|
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -160,8 +160,7 @@ find_stream_art(std::string_view directory, Mutex &mutex)
|
|||||||
static constexpr auto art_names = std::array {
|
static constexpr auto art_names = std::array {
|
||||||
"cover.png",
|
"cover.png",
|
||||||
"cover.jpg",
|
"cover.jpg",
|
||||||
"cover.tiff",
|
"cover.webp",
|
||||||
"cover.bmp",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for(const auto name : art_names) {
|
for(const auto name : art_names) {
|
||||||
@@ -213,7 +212,7 @@ read_stream_art(Response &r, const std::string_view art_directory,
|
|||||||
std::min<offset_type>(art_file_size - offset,
|
std::min<offset_type>(art_file_size - offset,
|
||||||
r.GetClient().binary_limit);
|
r.GetClient().binary_limit);
|
||||||
|
|
||||||
std::unique_ptr<std::byte[]> buffer(new std::byte[buffer_size]);
|
auto buffer = std::make_unique<std::byte[]>(buffer_size);
|
||||||
|
|
||||||
std::size_t read_size = 0;
|
std::size_t read_size = 0;
|
||||||
if (buffer_size > 0) {
|
if (buffer_size > 0) {
|
||||||
|
@@ -67,7 +67,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CancelThread() noexcept override {
|
void CancelThread() noexcept override {
|
||||||
const std::lock_guard<Mutex> lock(mutex);
|
const std::scoped_lock<Mutex> lock(mutex);
|
||||||
cancel = true;
|
cancel = true;
|
||||||
cond.notify_one();
|
cond.notify_one();
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ GetChromaprintCommand::DecodeFile(std::string_view suffix, InputStream &is,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
if (cancel)
|
if (cancel)
|
||||||
throw StopDecoder();
|
throw StopDecoder();
|
||||||
}
|
}
|
||||||
@@ -224,10 +224,12 @@ GetChromaprintCommand::DecodeFile(std::string_view suffix, InputStream &is,
|
|||||||
inline void
|
inline void
|
||||||
GetChromaprintCommand::DecodeFile()
|
GetChromaprintCommand::DecodeFile()
|
||||||
{
|
{
|
||||||
const auto suffix = uri_get_suffix(uri);
|
const char *_suffix = PathTraitsUTF8::GetFilenameSuffix(uri.c_str());
|
||||||
if (suffix.empty())
|
if (_suffix == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const std::string_view suffix{_suffix};
|
||||||
|
|
||||||
InputStreamPtr input_stream;
|
InputStreamPtr input_stream;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -186,7 +186,8 @@ handle_moveoutput(Client &client, Request request, Response &response)
|
|||||||
was_enabled);
|
was_enabled);
|
||||||
else
|
else
|
||||||
/* copy the AudioOutputControl and add it to the output list */
|
/* copy the AudioOutputControl and add it to the output list */
|
||||||
dest_partition.outputs.AddCopy(output,was_enabled);
|
dest_partition.outputs.AddMoveFrom(std::move(*output),
|
||||||
|
was_enabled);
|
||||||
|
|
||||||
instance.EmitIdle(IDLE_OUTPUT);
|
instance.EmitIdle(IDLE_OUTPUT);
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
|
@@ -171,7 +171,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
|||||||
|
|
||||||
if (player_status.state != PlayerState::STOP) {
|
if (player_status.state != PlayerState::STOP) {
|
||||||
r.Fmt(FMT_STRING(COMMAND_STATUS_TIME ": {}:{}\n"
|
r.Fmt(FMT_STRING(COMMAND_STATUS_TIME ": {}:{}\n"
|
||||||
"elapsed: {:1.3}\n"
|
"elapsed: {:1.3f}\n"
|
||||||
COMMAND_STATUS_BITRATE ": {}\n"),
|
COMMAND_STATUS_BITRATE ": {}\n"),
|
||||||
player_status.elapsed_time.RoundS(),
|
player_status.elapsed_time.RoundS(),
|
||||||
player_status.total_time.IsNegative()
|
player_status.total_time.IsNegative()
|
||||||
@@ -181,7 +181,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
|||||||
player_status.bit_rate);
|
player_status.bit_rate);
|
||||||
|
|
||||||
if (!player_status.total_time.IsNegative())
|
if (!player_status.total_time.IsNegative())
|
||||||
r.Fmt(FMT_STRING("duration: {:1.3}\n"),
|
r.Fmt(FMT_STRING("duration: {:1.3f}\n"),
|
||||||
player_status.total_time.ToDoubleS());
|
player_status.total_time.ToDoubleS());
|
||||||
|
|
||||||
if (player_status.audio_format.IsDefined())
|
if (player_status.audio_format.IsDefined())
|
||||||
|
@@ -19,10 +19,13 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "PlaylistCommands.hxx"
|
#include "PlaylistCommands.hxx"
|
||||||
|
#include "PositionArg.hxx"
|
||||||
#include "Request.hxx"
|
#include "Request.hxx"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
|
#include "db/Interface.hxx"
|
||||||
#include "db/Selection.hxx"
|
#include "db/Selection.hxx"
|
||||||
#include "db/DatabasePlaylist.hxx"
|
#include "db/DatabasePlaylist.hxx"
|
||||||
|
#include "db/DatabaseSong.hxx"
|
||||||
#include "PlaylistSave.hxx"
|
#include "PlaylistSave.hxx"
|
||||||
#include "PlaylistFile.hxx"
|
#include "PlaylistFile.hxx"
|
||||||
#include "PlaylistError.hxx"
|
#include "PlaylistError.hxx"
|
||||||
@@ -86,7 +89,7 @@ handle_load(Client &client, Request args, [[maybe_unused]] Response &r)
|
|||||||
const unsigned old_size = playlist.GetLength();
|
const unsigned old_size = playlist.GetLength();
|
||||||
|
|
||||||
const unsigned position = args.size > 2
|
const unsigned position = args.size > 2
|
||||||
? args.ParseUnsigned(2, old_size)
|
? ParseInsertPosition(args[2], partition.playlist)
|
||||||
: old_size;
|
: old_size;
|
||||||
|
|
||||||
const SongLoader loader(client);
|
const SongLoader loader(client);
|
||||||
@@ -172,9 +175,11 @@ handle_playlistdelete([[maybe_unused]] Client &client,
|
|||||||
Request args, [[maybe_unused]] Response &r)
|
Request args, [[maybe_unused]] Response &r)
|
||||||
{
|
{
|
||||||
const char *const name = args[0];
|
const char *const name = args[0];
|
||||||
unsigned from = args.ParseUnsigned(1);
|
const auto range = args.ParseRange(1);
|
||||||
|
|
||||||
spl_remove_index(name, from);
|
PlaylistFileEditor editor(name, PlaylistFileEditor::LoadMode::YES);
|
||||||
|
editor.RemoveRange(range);
|
||||||
|
editor.Save();
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +191,14 @@ handle_playlistmove([[maybe_unused]] Client &client,
|
|||||||
unsigned from = args.ParseUnsigned(1);
|
unsigned from = args.ParseUnsigned(1);
|
||||||
unsigned to = args.ParseUnsigned(2);
|
unsigned to = args.ParseUnsigned(2);
|
||||||
|
|
||||||
spl_move_index(name, from, to);
|
if (from == to)
|
||||||
|
/* this doesn't check whether the playlist exists, but
|
||||||
|
what the hell.. */
|
||||||
|
return CommandResult::OK;
|
||||||
|
|
||||||
|
PlaylistFileEditor editor(name, PlaylistFileEditor::LoadMode::YES);
|
||||||
|
editor.MoveIndex(from, to);
|
||||||
|
editor.Save();
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,12 +212,55 @@ handle_playlistclear([[maybe_unused]] Client &client,
|
|||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CommandResult
|
||||||
|
handle_playlistadd_position(Client &client, const char *playlist_name,
|
||||||
|
const char *uri, unsigned position,
|
||||||
|
Response &r)
|
||||||
|
{
|
||||||
|
PlaylistFileEditor editor{
|
||||||
|
playlist_name,
|
||||||
|
PlaylistFileEditor::LoadMode::TRY,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (position > editor.size()) {
|
||||||
|
r.Error(ACK_ERROR_ARG, "Bad position");
|
||||||
|
return CommandResult::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri_has_scheme(uri)) {
|
||||||
|
editor.Insert(position, uri);
|
||||||
|
} else {
|
||||||
|
#ifdef ENABLE_DATABASE
|
||||||
|
const DatabaseSelection selection(uri, true, nullptr);
|
||||||
|
|
||||||
|
if (SearchInsertIntoPlaylist(client.GetDatabaseOrThrow(),
|
||||||
|
client.GetStorage(),
|
||||||
|
selection,
|
||||||
|
editor, position) == 0)
|
||||||
|
/* no song was found, don't need to save */
|
||||||
|
return CommandResult::OK;
|
||||||
|
#else
|
||||||
|
(void)client;
|
||||||
|
r.Error(ACK_ERROR_NO_EXIST, "No database");
|
||||||
|
return CommandResult::ERROR;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.Save();
|
||||||
|
|
||||||
|
return CommandResult::OK;
|
||||||
|
}
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
handle_playlistadd(Client &client, Request args, [[maybe_unused]] Response &r)
|
handle_playlistadd(Client &client, Request args, [[maybe_unused]] Response &r)
|
||||||
{
|
{
|
||||||
const char *const playlist = args[0];
|
const char *const playlist = args[0];
|
||||||
const char *const uri = args[1];
|
const char *const uri = args[1];
|
||||||
|
|
||||||
|
if (args.size >= 3)
|
||||||
|
return handle_playlistadd_position(client, playlist, uri,
|
||||||
|
args.ParseUnsigned(2), r);
|
||||||
|
|
||||||
if (uri_has_scheme(uri)) {
|
if (uri_has_scheme(uri)) {
|
||||||
const SongLoader loader(client);
|
const SongLoader loader(client);
|
||||||
spl_append_uri(playlist, loader, uri);
|
spl_append_uri(playlist, loader, uri);
|
||||||
|
103
src/command/PositionArg.cxx
Normal file
103
src/command/PositionArg.cxx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2021 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PositionArg.hxx"
|
||||||
|
#include "protocol/Ack.hxx"
|
||||||
|
#include "protocol/ArgParser.hxx"
|
||||||
|
#include "protocol/RangeArg.hxx"
|
||||||
|
#include "queue/Playlist.hxx"
|
||||||
|
|
||||||
|
static unsigned
|
||||||
|
RequireCurrentPosition(const playlist &p)
|
||||||
|
{
|
||||||
|
int position = p.GetCurrentPosition();
|
||||||
|
if (position < 0)
|
||||||
|
throw ProtocolError(ACK_ERROR_PLAYER_SYNC,
|
||||||
|
"No current song");
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
ParseInsertPosition(const char *s, const playlist &playlist)
|
||||||
|
{
|
||||||
|
const auto queue_length = playlist.queue.GetLength();
|
||||||
|
|
||||||
|
if (*s == '+') {
|
||||||
|
/* after the current song */
|
||||||
|
|
||||||
|
const unsigned current = RequireCurrentPosition(playlist);
|
||||||
|
assert(current < queue_length);
|
||||||
|
|
||||||
|
return current + 1 +
|
||||||
|
ParseCommandArgUnsigned(s + 1,
|
||||||
|
queue_length - current - 1);
|
||||||
|
} else if (*s == '-') {
|
||||||
|
/* before the current song */
|
||||||
|
|
||||||
|
const unsigned current = RequireCurrentPosition(playlist);
|
||||||
|
assert(current < queue_length);
|
||||||
|
|
||||||
|
return current - ParseCommandArgUnsigned(s + 1, current);
|
||||||
|
} else
|
||||||
|
/* absolute position */
|
||||||
|
return ParseCommandArgUnsigned(s, queue_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
ParseMoveDestination(const char *s, const RangeArg range, const playlist &p)
|
||||||
|
{
|
||||||
|
assert(!range.IsEmpty());
|
||||||
|
assert(!range.IsOpenEnded());
|
||||||
|
|
||||||
|
const unsigned queue_length = p.queue.GetLength();
|
||||||
|
|
||||||
|
if (*s == '+') {
|
||||||
|
/* after the current song */
|
||||||
|
|
||||||
|
unsigned current = RequireCurrentPosition(p);
|
||||||
|
assert(current < queue_length);
|
||||||
|
if (range.Contains(current))
|
||||||
|
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
||||||
|
|
||||||
|
if (current >= range.end)
|
||||||
|
current -= range.Count();
|
||||||
|
|
||||||
|
return current + 1 +
|
||||||
|
ParseCommandArgUnsigned(s + 1,
|
||||||
|
queue_length - current - range.Count());
|
||||||
|
} else if (*s == '-') {
|
||||||
|
/* before the current song */
|
||||||
|
|
||||||
|
unsigned current = RequireCurrentPosition(p);
|
||||||
|
assert(current < queue_length);
|
||||||
|
if (range.Contains(current))
|
||||||
|
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
||||||
|
|
||||||
|
if (current >= range.end)
|
||||||
|
current -= range.Count();
|
||||||
|
|
||||||
|
return current -
|
||||||
|
ParseCommandArgUnsigned(s + 1,
|
||||||
|
queue_length - current - range.Count());
|
||||||
|
} else
|
||||||
|
/* absolute position */
|
||||||
|
return ParseCommandArgUnsigned(s,
|
||||||
|
queue_length - range.Count());
|
||||||
|
}
|
32
src/command/PositionArg.hxx
Normal file
32
src/command/PositionArg.hxx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2021 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct playlist;
|
||||||
|
struct RangeArg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws #ProtocolError on error.
|
||||||
|
*/
|
||||||
|
unsigned
|
||||||
|
ParseInsertPosition(const char *s, const playlist &playlist);
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
ParseMoveDestination(const char *s, const RangeArg range, const playlist &p);
|
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "QueueCommands.hxx"
|
#include "QueueCommands.hxx"
|
||||||
|
#include "PositionArg.hxx"
|
||||||
#include "Request.hxx"
|
#include "Request.hxx"
|
||||||
#include "protocol/RangeArg.hxx"
|
#include "protocol/RangeArg.hxx"
|
||||||
#include "db/DatabaseQueue.hxx"
|
#include "db/DatabaseQueue.hxx"
|
||||||
@@ -43,17 +44,6 @@
|
|||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
static unsigned
|
|
||||||
RequireCurrentPosition(const playlist &p)
|
|
||||||
{
|
|
||||||
int position = p.GetCurrentPosition();
|
|
||||||
if (position < 0)
|
|
||||||
throw ProtocolError(ACK_ERROR_PLAYER_SYNC,
|
|
||||||
"No current song");
|
|
||||||
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
AddUri(Client &client, const LocatedUri &uri)
|
AddUri(Client &client, const LocatedUri &uri)
|
||||||
{
|
{
|
||||||
@@ -62,29 +52,24 @@ AddUri(Client &client, const LocatedUri &uri)
|
|||||||
SongLoader(client).LoadSong(uri));
|
SongLoader(client).LoadSong(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
static CommandResult
|
|
||||||
AddDatabaseSelection(Client &client, const char *uri,
|
|
||||||
[[maybe_unused]] Response &r)
|
|
||||||
{
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
auto &partition = client.GetPartition();
|
|
||||||
|
static void
|
||||||
|
AddDatabaseSelection(Partition &partition, const char *uri)
|
||||||
|
{
|
||||||
const ScopeBulkEdit bulk_edit(partition);
|
const ScopeBulkEdit bulk_edit(partition);
|
||||||
|
|
||||||
const DatabaseSelection selection(uri, true);
|
const DatabaseSelection selection(uri, true);
|
||||||
AddFromDatabase(partition, selection);
|
AddFromDatabase(partition, selection);
|
||||||
return CommandResult::OK;
|
|
||||||
#else
|
|
||||||
(void)client;
|
|
||||||
(void)uri;
|
|
||||||
|
|
||||||
r.Error(ACK_ERROR_NO_EXIST, "No database");
|
|
||||||
return CommandResult::ERROR;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
handle_add(Client &client, Request args, Response &r)
|
handle_add(Client &client, Request args, [[maybe_unused]] Response &r)
|
||||||
{
|
{
|
||||||
|
auto &partition = client.GetPartition();
|
||||||
|
|
||||||
const char *uri = args.front();
|
const char *uri = args.front();
|
||||||
if (StringIsEqual(uri, "/"))
|
if (StringIsEqual(uri, "/"))
|
||||||
/* this URI is malformed, but some clients are buggy
|
/* this URI is malformed, but some clients are buggy
|
||||||
@@ -94,6 +79,11 @@ handle_add(Client &client, Request args, Response &r)
|
|||||||
here */
|
here */
|
||||||
uri = "";
|
uri = "";
|
||||||
|
|
||||||
|
const auto old_size = partition.playlist.GetLength();
|
||||||
|
const unsigned position = args.size > 1
|
||||||
|
? ParseInsertPosition(args[1], partition.playlist)
|
||||||
|
: old_size;
|
||||||
|
|
||||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
|
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
|
||||||
&client
|
&client
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
@@ -104,18 +94,34 @@ handle_add(Client &client, Request args, Response &r)
|
|||||||
case LocatedUri::Type::ABSOLUTE:
|
case LocatedUri::Type::ABSOLUTE:
|
||||||
AddUri(client, located_uri);
|
AddUri(client, located_uri);
|
||||||
client.GetInstance().LookupRemoteTag(located_uri.canonical_uri);
|
client.GetInstance().LookupRemoteTag(located_uri.canonical_uri);
|
||||||
return CommandResult::OK;
|
break;
|
||||||
|
|
||||||
case LocatedUri::Type::PATH:
|
case LocatedUri::Type::PATH:
|
||||||
AddUri(client, located_uri);
|
AddUri(client, located_uri);
|
||||||
return CommandResult::OK;
|
break;
|
||||||
|
|
||||||
case LocatedUri::Type::RELATIVE:
|
case LocatedUri::Type::RELATIVE:
|
||||||
return AddDatabaseSelection(client, located_uri.canonical_uri,
|
#ifdef ENABLE_DATABASE
|
||||||
r);
|
AddDatabaseSelection(partition, located_uri.canonical_uri);
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
r.Error(ACK_ERROR_NO_EXIST, "No database");
|
||||||
|
return CommandResult::ERROR;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_unreachable();
|
if (position < old_size) {
|
||||||
|
const unsigned new_size = partition.playlist.GetLength();
|
||||||
|
const RangeArg move_range{old_size, new_size};
|
||||||
|
|
||||||
|
try {
|
||||||
|
partition.MoveRange(move_range, position);
|
||||||
|
} catch (...) {
|
||||||
|
/* ignore - shall we handle it? */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
@@ -129,30 +135,8 @@ handle_addid(Client &client, Request args, Response &r)
|
|||||||
|
|
||||||
const auto queue_length = partition.playlist.queue.GetLength();
|
const auto queue_length = partition.playlist.queue.GetLength();
|
||||||
|
|
||||||
if (args.size > 1) {
|
if (args.size > 1)
|
||||||
const char *const s = args[1];
|
to = ParseInsertPosition(args[1], partition.playlist);
|
||||||
if (*s == '+') {
|
|
||||||
/* after the current song */
|
|
||||||
|
|
||||||
const unsigned current =
|
|
||||||
RequireCurrentPosition(partition.playlist);
|
|
||||||
assert(current < queue_length);
|
|
||||||
|
|
||||||
to = current + 1 +
|
|
||||||
ParseCommandArgUnsigned(s + 1,
|
|
||||||
queue_length - current - 1);
|
|
||||||
} else if (*s == '-') {
|
|
||||||
/* before the current song */
|
|
||||||
|
|
||||||
const unsigned current =
|
|
||||||
RequireCurrentPosition(partition.playlist);
|
|
||||||
assert(current < queue_length);
|
|
||||||
|
|
||||||
to = current - ParseCommandArgUnsigned(s + 1, current);
|
|
||||||
} else
|
|
||||||
/* absolute position */
|
|
||||||
to = args.ParseUnsigned(1, queue_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SongLoader loader(client);
|
const SongLoader loader(client);
|
||||||
const unsigned added_position = queue_length;
|
const unsigned added_position = queue_length;
|
||||||
@@ -363,49 +347,6 @@ handle_prioid(Client &client, Request args, [[maybe_unused]] Response &r)
|
|||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned
|
|
||||||
ParseMoveDestination(const char *s, const RangeArg range,
|
|
||||||
const playlist &p)
|
|
||||||
{
|
|
||||||
assert(!range.IsEmpty());
|
|
||||||
assert(!range.IsOpenEnded());
|
|
||||||
|
|
||||||
const unsigned queue_length = p.queue.GetLength();
|
|
||||||
|
|
||||||
if (*s == '+') {
|
|
||||||
/* after the current song */
|
|
||||||
|
|
||||||
unsigned current = RequireCurrentPosition(p);
|
|
||||||
assert(current < queue_length);
|
|
||||||
if (range.Contains(current))
|
|
||||||
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
|
||||||
|
|
||||||
if (current >= range.end)
|
|
||||||
current -= range.Count();
|
|
||||||
|
|
||||||
return current + 1 +
|
|
||||||
ParseCommandArgUnsigned(s + 1,
|
|
||||||
queue_length - current - range.Count());
|
|
||||||
} else if (*s == '-') {
|
|
||||||
/* before the current song */
|
|
||||||
|
|
||||||
unsigned current = RequireCurrentPosition(p);
|
|
||||||
assert(current < queue_length);
|
|
||||||
if (range.Contains(current))
|
|
||||||
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
|
||||||
|
|
||||||
if (current >= range.end)
|
|
||||||
current -= range.Count();
|
|
||||||
|
|
||||||
return current -
|
|
||||||
ParseCommandArgUnsigned(s + 1,
|
|
||||||
queue_length - current - range.Count());
|
|
||||||
} else
|
|
||||||
/* absolute position */
|
|
||||||
return ParseCommandArgUnsigned(s,
|
|
||||||
queue_length - range.Count());
|
|
||||||
}
|
|
||||||
|
|
||||||
static CommandResult
|
static CommandResult
|
||||||
handle_move(Partition &partition, RangeArg range, const char *to)
|
handle_move(Partition &partition, RangeArg range, const char *to)
|
||||||
{
|
{
|
||||||
|
@@ -31,8 +31,8 @@
|
|||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "fs/List.hxx"
|
#include "fs/List.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "fs/io/FileReader.hxx"
|
#include "io/FileReader.hxx"
|
||||||
#include "fs/io/BufferedReader.hxx"
|
#include "io/BufferedReader.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
@@ -23,11 +23,10 @@
|
|||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/StandardDirectory.hxx"
|
#include "fs/StandardDirectory.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/StringView.hxx"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
|
|
||||||
@@ -96,30 +95,18 @@ ParsePath(const char *path)
|
|||||||
if (*path == '\0')
|
if (*path == '\0')
|
||||||
return GetConfiguredHome();
|
return GetConfiguredHome();
|
||||||
|
|
||||||
AllocatedPath home = nullptr;
|
|
||||||
|
|
||||||
if (*path == '/') {
|
if (*path == '/') {
|
||||||
home = GetConfiguredHome();
|
|
||||||
|
|
||||||
++path;
|
++path;
|
||||||
|
|
||||||
|
return GetConfiguredHome() /
|
||||||
|
AllocatedPath::FromUTF8Throw(path);
|
||||||
} else {
|
} else {
|
||||||
const char *slash = std::strchr(path, '/');
|
const auto [user, rest] =
|
||||||
const char *end = slash == nullptr
|
StringView{path}.Split('/');
|
||||||
? path + strlen(path)
|
|
||||||
: slash;
|
|
||||||
const std::string user(path, end);
|
|
||||||
home = GetHome(user.c_str());
|
|
||||||
|
|
||||||
if (slash == nullptr)
|
return GetHome(std::string{user}.c_str())
|
||||||
return home;
|
/ AllocatedPath::FromUTF8Throw(rest);
|
||||||
|
|
||||||
path = slash + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (home.IsNull())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return home / AllocatedPath::FromUTF8Throw(path);
|
|
||||||
} else if (!PathTraitsUTF8::IsAbsolute(path)) {
|
} else if (!PathTraitsUTF8::IsAbsolute(path)) {
|
||||||
throw FormatRuntimeError("not an absolute path: %s", path);
|
throw FormatRuntimeError("not an absolute path: %s", path);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
#include "PlaylistFile.hxx"
|
#include "PlaylistFile.hxx"
|
||||||
#include "Interface.hxx"
|
#include "Interface.hxx"
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
|
#include "protocol/Ack.hxx"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
@@ -41,3 +42,41 @@ search_add_to_playlist(const Database &db, const Storage *storage,
|
|||||||
const auto f = [=](auto && arg1) { return AddSong(storage, playlist_path_utf8, arg1); };
|
const auto f = [=](auto && arg1) { return AddSong(storage, playlist_path_utf8, arg1); };
|
||||||
db.Visit(selection, f);
|
db.Visit(selection, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||||
|
const DatabaseSelection &selection,
|
||||||
|
PlaylistFileEditor &playlist,
|
||||||
|
unsigned position)
|
||||||
|
{
|
||||||
|
assert(position <= playlist.size());
|
||||||
|
|
||||||
|
unsigned n = 0;
|
||||||
|
|
||||||
|
db.Visit(selection, [&playlist, position, &n, storage](const auto &song){
|
||||||
|
playlist.Insert(position + n,
|
||||||
|
DatabaseDetachSong(storage, song));
|
||||||
|
++n;
|
||||||
|
});
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||||
|
const DatabaseSelection &selection,
|
||||||
|
const char *playlist_name,
|
||||||
|
unsigned position)
|
||||||
|
{
|
||||||
|
PlaylistFileEditor editor{
|
||||||
|
playlist_name,
|
||||||
|
PlaylistFileEditor::LoadMode::TRY,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (position > editor.size())
|
||||||
|
throw ProtocolError{ACK_ERROR_ARG, "Bad position"};
|
||||||
|
|
||||||
|
if (SearchInsertIntoPlaylist(db, storage, selection,
|
||||||
|
editor, position) > 0)
|
||||||
|
editor.Save();
|
||||||
|
}
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
class Database;
|
class Database;
|
||||||
class Storage;
|
class Storage;
|
||||||
struct DatabaseSelection;
|
struct DatabaseSelection;
|
||||||
|
class PlaylistFileEditor;
|
||||||
|
|
||||||
gcc_nonnull(3)
|
gcc_nonnull(3)
|
||||||
void
|
void
|
||||||
@@ -32,4 +33,19 @@ search_add_to_playlist(const Database &db, const Storage *storage,
|
|||||||
const char *playlist_path_utf8,
|
const char *playlist_path_utf8,
|
||||||
const DatabaseSelection &selection);
|
const DatabaseSelection &selection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of songs added
|
||||||
|
*/
|
||||||
|
unsigned
|
||||||
|
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||||
|
const DatabaseSelection &selection,
|
||||||
|
PlaylistFileEditor &playlist,
|
||||||
|
unsigned position);
|
||||||
|
|
||||||
|
void
|
||||||
|
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||||
|
const DatabaseSelection &selection,
|
||||||
|
const char *playlist_name,
|
||||||
|
unsigned position);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -35,6 +35,7 @@ db_plugins = static_library(
|
|||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
upnp_dep,
|
upnp_dep,
|
||||||
|
pcre_dep,
|
||||||
libmpdclient_dep,
|
libmpdclient_dep,
|
||||||
log_dep,
|
log_dep,
|
||||||
],
|
],
|
||||||
|
@@ -20,8 +20,8 @@
|
|||||||
#include "DatabaseSave.hxx"
|
#include "DatabaseSave.hxx"
|
||||||
#include "db/DatabaseLock.hxx"
|
#include "db/DatabaseLock.hxx"
|
||||||
#include "DirectorySave.hxx"
|
#include "DirectorySave.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "io/LineReader.hxx"
|
||||||
#include "tag/ParseName.hxx"
|
#include "tag/ParseName.hxx"
|
||||||
#include "tag/Settings.hxx"
|
#include "tag/Settings.hxx"
|
||||||
#include "fs/Charset.hxx"
|
#include "fs/Charset.hxx"
|
||||||
@@ -64,7 +64,7 @@ db_save_internal(BufferedOutputStream &os, const Directory &music_root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
db_load_internal(TextFile &file, Directory &music_root)
|
db_load_internal(LineReader &file, Directory &music_root)
|
||||||
{
|
{
|
||||||
char *line;
|
char *line;
|
||||||
unsigned format = 0;
|
unsigned format = 0;
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
struct Directory;
|
struct Directory;
|
||||||
class BufferedOutputStream;
|
class BufferedOutputStream;
|
||||||
class TextFile;
|
class LineReader;
|
||||||
|
|
||||||
void
|
void
|
||||||
db_save_internal(BufferedOutputStream &os, const Directory &root);
|
db_save_internal(BufferedOutputStream &os, const Directory &root);
|
||||||
@@ -31,6 +31,6 @@ db_save_internal(BufferedOutputStream &os, const Directory &root);
|
|||||||
* Throws #std::runtime_error on error.
|
* Throws #std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
db_load_internal(TextFile &file, Directory &root);
|
db_load_internal(LineReader &file, Directory &root);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -278,5 +278,5 @@ Directory::Walk(bool recursive, const SongFilter *filter,
|
|||||||
LightDirectory
|
LightDirectory
|
||||||
Directory::Export() const noexcept
|
Directory::Export() const noexcept
|
||||||
{
|
{
|
||||||
return LightDirectory(GetPath(), mtime);
|
return {GetPath(), mtime};
|
||||||
}
|
}
|
||||||
|
@@ -23,8 +23,8 @@
|
|||||||
#include "SongSave.hxx"
|
#include "SongSave.hxx"
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
#include "PlaylistDatabase.hxx"
|
#include "PlaylistDatabase.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "io/LineReader.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "time/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/StringAPI.hxx"
|
#include "util/StringAPI.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
@@ -121,7 +121,7 @@ ParseLine(Directory &directory, const char *line)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Directory *
|
static Directory *
|
||||||
directory_load_subdir(TextFile &file, Directory &parent, std::string_view name)
|
directory_load_subdir(LineReader &file, Directory &parent, std::string_view name)
|
||||||
{
|
{
|
||||||
if (parent.FindChild(name) != nullptr)
|
if (parent.FindChild(name) != nullptr)
|
||||||
throw FormatRuntimeError("Duplicate subdirectory '%.*s'",
|
throw FormatRuntimeError("Duplicate subdirectory '%.*s'",
|
||||||
@@ -152,7 +152,7 @@ directory_load_subdir(TextFile &file, Directory &parent, std::string_view name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
directory_load(TextFile &file, Directory &directory)
|
directory_load(LineReader &file, Directory &directory)
|
||||||
{
|
{
|
||||||
const char *line;
|
const char *line;
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
#define MPD_DIRECTORY_SAVE_HXX
|
#define MPD_DIRECTORY_SAVE_HXX
|
||||||
|
|
||||||
struct Directory;
|
struct Directory;
|
||||||
class TextFile;
|
class LineReader;
|
||||||
class BufferedOutputStream;
|
class BufferedOutputStream;
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -31,6 +31,6 @@ directory_save(BufferedOutputStream &os, const Directory &directory);
|
|||||||
* Throws #std::runtime_error on error.
|
* Throws #std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
directory_load(TextFile &file, Directory &directory);
|
directory_load(LineReader &file, Directory &directory);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -34,8 +34,8 @@
|
|||||||
#include "db/DatabaseLock.hxx"
|
#include "db/DatabaseLock.hxx"
|
||||||
#include "db/DatabaseError.hxx"
|
#include "db/DatabaseError.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "fs/io/TextFile.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "fs/io/FileOutputStream.hxx"
|
#include "io/FileOutputStream.hxx"
|
||||||
#include "fs/FileInfo.hxx"
|
#include "fs/FileInfo.hxx"
|
||||||
#include "config/Block.hxx"
|
#include "config/Block.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#ifdef ENABLE_ZLIB
|
#ifdef ENABLE_ZLIB
|
||||||
#include "fs/io/GzipOutputStream.hxx"
|
#include "lib/zlib/GzipOutputStream.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
@@ -316,7 +316,7 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
|
|||||||
|
|
||||||
if (r.rest.find('/') == std::string_view::npos) {
|
if (r.rest.find('/') == std::string_view::npos) {
|
||||||
if (visit_song) {
|
if (visit_song) {
|
||||||
Song *song = r.directory->FindSong(r.rest);
|
const Song *song = r.directory->FindSong(r.rest);
|
||||||
if (song != nullptr) {
|
if (song != nullptr) {
|
||||||
const auto song2 = song->Export();
|
const auto song2 = song->Export();
|
||||||
if (selection.Match(song2))
|
if (selection.Match(song2))
|
||||||
|
@@ -39,6 +39,7 @@
|
|||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/RecursiveMap.hxx"
|
#include "util/RecursiveMap.hxx"
|
||||||
#include "util/SplitString.hxx"
|
#include "util/SplitString.hxx"
|
||||||
|
#include "config/Block.hxx"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -76,10 +77,13 @@ class UpnpDatabase : public Database {
|
|||||||
UpnpClient_Handle handle;
|
UpnpClient_Handle handle;
|
||||||
UPnPDeviceDirectory *discovery;
|
UPnPDeviceDirectory *discovery;
|
||||||
|
|
||||||
|
const char* interface;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit UpnpDatabase(EventLoop &_event_loop) noexcept
|
explicit UpnpDatabase(EventLoop &_event_loop, const ConfigBlock &block) noexcept
|
||||||
:Database(upnp_db_plugin),
|
:Database(upnp_db_plugin),
|
||||||
event_loop(_event_loop) {}
|
event_loop(_event_loop),
|
||||||
|
interface(block.GetBlockValue("interface", nullptr)) {}
|
||||||
|
|
||||||
static DatabasePtr Create(EventLoop &main_event_loop,
|
static DatabasePtr Create(EventLoop &main_event_loop,
|
||||||
EventLoop &io_event_loop,
|
EventLoop &io_event_loop,
|
||||||
@@ -147,15 +151,15 @@ private:
|
|||||||
DatabasePtr
|
DatabasePtr
|
||||||
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
|
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
|
||||||
[[maybe_unused]] DatabaseListener &listener,
|
[[maybe_unused]] DatabaseListener &listener,
|
||||||
const ConfigBlock &) noexcept
|
const ConfigBlock &block) noexcept
|
||||||
{
|
{
|
||||||
return std::make_unique<UpnpDatabase>(io_event_loop);
|
return std::make_unique<UpnpDatabase>(io_event_loop, block);;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
UpnpDatabase::Open()
|
UpnpDatabase::Open()
|
||||||
{
|
{
|
||||||
handle = UpnpClientGlobalInit();
|
handle = UpnpClientGlobalInit(interface);
|
||||||
|
|
||||||
discovery = new UPnPDeviceDirectory(event_loop, handle);
|
discovery = new UPnPDeviceDirectory(event_loop, handle);
|
||||||
try {
|
try {
|
||||||
@@ -246,11 +250,11 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
|
|||||||
{
|
{
|
||||||
const SongFilter *filter = selection.filter;
|
const SongFilter *filter = selection.filter;
|
||||||
if (selection.filter == nullptr)
|
if (selection.filter == nullptr)
|
||||||
return UPnPDirContent();
|
return {};
|
||||||
|
|
||||||
const auto searchcaps = server.getSearchCapabilities(handle);
|
const auto searchcaps = server.getSearchCapabilities(handle);
|
||||||
if (searchcaps.empty())
|
if (searchcaps.empty())
|
||||||
return UPnPDirContent();
|
return {};
|
||||||
|
|
||||||
std::string cond;
|
std::string cond;
|
||||||
for (const auto &item : filter->GetItems()) {
|
for (const auto &item : filter->GetItems()) {
|
||||||
|
@@ -33,7 +33,6 @@
|
|||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@@ -282,7 +282,7 @@ InotifyUpdate::InotifyCallback(int wd, unsigned mask,
|
|||||||
(mask & IN_ISDIR) != 0) {
|
(mask & IN_ISDIR) != 0) {
|
||||||
/* a sub directory was changed: register those in
|
/* a sub directory was changed: register those in
|
||||||
inotify */
|
inotify */
|
||||||
const Path root_path = root->name;
|
const auto root_path = root->name;
|
||||||
|
|
||||||
const auto path_fs = uri_fs.IsNull()
|
const auto path_fs = uri_fs.IsNull()
|
||||||
? root_path
|
? root_path
|
||||||
|
@@ -34,7 +34,7 @@ UpdateQueueItem
|
|||||||
UpdateQueue::Pop() noexcept
|
UpdateQueue::Pop() noexcept
|
||||||
{
|
{
|
||||||
if (update_queue.empty())
|
if (update_queue.empty())
|
||||||
return UpdateQueueItem();
|
return {};
|
||||||
|
|
||||||
auto i = std::move(update_queue.front());
|
auto i = std::move(update_queue.front());
|
||||||
update_queue.pop_front();
|
update_queue.pop_front();
|
||||||
|
@@ -36,7 +36,7 @@ UpdateRemoveService::RunDeferred() noexcept
|
|||||||
std::forward_list<std::string> copy;
|
std::forward_list<std::string> copy;
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
std::swap(uris, copy);
|
std::swap(uris, copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ UpdateRemoveService::Remove(std::string &&uri)
|
|||||||
bool was_empty;
|
bool was_empty;
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
was_empty = uris.empty();
|
was_empty = uris.empty();
|
||||||
uris.emplace_front(std::move(uri));
|
uris.emplace_front(std::move(uri));
|
||||||
}
|
}
|
||||||
|
@@ -84,7 +84,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
directory_child_access(Storage &storage, const Directory &directory,
|
directory_child_access(const Storage &storage, const Directory &directory,
|
||||||
std::string_view name, int mode) noexcept
|
std::string_view name, int mode) noexcept
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@@ -55,7 +55,7 @@ directory_child_is_regular(Storage &storage, const Directory &directory,
|
|||||||
*/
|
*/
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
bool
|
bool
|
||||||
directory_child_access(Storage &storage, const Directory &directory,
|
directory_child_access(const Storage &storage, const Directory &directory,
|
||||||
std::string_view name, int mode) noexcept;
|
std::string_view name, int mode) noexcept;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -188,8 +188,8 @@ UpdateWalk::UpdateRegularFile(Directory &directory,
|
|||||||
const char *name,
|
const char *name,
|
||||||
const StorageFileInfo &info) noexcept
|
const StorageFileInfo &info) noexcept
|
||||||
{
|
{
|
||||||
const auto suffix = uri_get_suffix(name);
|
const char *suffix = PathTraitsUTF8::GetFilenameSuffix(name);
|
||||||
if (suffix.empty())
|
if (suffix == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return UpdateSongFile(directory, name, suffix, info) ||
|
return UpdateSongFile(directory, name, suffix, info) ||
|
||||||
|
@@ -152,7 +152,7 @@ DecoderBridge::FlushChunk() noexcept
|
|||||||
if (!chunk->IsEmpty())
|
if (!chunk->IsEmpty())
|
||||||
dc.pipe->Push(std::move(chunk));
|
dc.pipe->Push(std::move(chunk));
|
||||||
|
|
||||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||||
dc.client_cond.notify_one();
|
dc.client_cond.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ DecoderBridge::GetVirtualCommand() noexcept
|
|||||||
DecoderCommand
|
DecoderCommand
|
||||||
DecoderBridge::LockGetVirtualCommand() noexcept
|
DecoderBridge::LockGetVirtualCommand() noexcept
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||||
return GetVirtualCommand();
|
return GetVirtualCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
|
|||||||
seekable);
|
seekable);
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||||
dc.SetReady(audio_format, seekable, duration);
|
dc.SetReady(audio_format, seekable, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ DecoderBridge::GetCommand() noexcept
|
|||||||
void
|
void
|
||||||
DecoderBridge::CommandFinished() noexcept
|
DecoderBridge::CommandFinished() noexcept
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||||
|
|
||||||
assert(dc.command != DecoderCommand::NONE || initial_seek_running);
|
assert(dc.command != DecoderCommand::NONE || initial_seek_running);
|
||||||
assert(dc.command != DecoderCommand::SEEK ||
|
assert(dc.command != DecoderCommand::SEEK ||
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#include "Command.hxx"
|
#include "Command.hxx"
|
||||||
#include "pcm/AudioFormat.hxx"
|
#include "pcm/AudioFormat.hxx"
|
||||||
#include "MixRampInfo.hxx"
|
#include "tag/MixRampInfo.hxx"
|
||||||
#include "input/Handler.hxx"
|
#include "input/Handler.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
#include "thread/Cond.hxx"
|
#include "thread/Cond.hxx"
|
||||||
@@ -231,7 +231,7 @@ public:
|
|||||||
|
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
bool LockIsIdle() const noexcept {
|
bool LockIsIdle() const noexcept {
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
return IsIdle();
|
return IsIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ public:
|
|||||||
|
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
bool LockIsStarting() const noexcept {
|
bool LockIsStarting() const noexcept {
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
return IsStarting();
|
return IsStarting();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ public:
|
|||||||
|
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
bool LockHasFailed() const noexcept {
|
bool LockHasFailed() const noexcept {
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
return HasFailed();
|
return HasFailed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ public:
|
|||||||
* Like CheckRethrowError(), but locks and unlocks the object.
|
* Like CheckRethrowError(), but locks and unlocks the object.
|
||||||
*/
|
*/
|
||||||
void LockCheckRethrowError() const {
|
void LockCheckRethrowError() const {
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
CheckRethrowError();
|
CheckRethrowError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +360,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LockAsynchronousCommand(DecoderCommand cmd) noexcept {
|
void LockAsynchronousCommand(DecoderCommand cmd) noexcept {
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
command = cmd;
|
command = cmd;
|
||||||
Signal();
|
Signal();
|
||||||
}
|
}
|
||||||
|
@@ -35,8 +35,8 @@
|
|||||||
#include "DecoderPlugin.hxx"
|
#include "DecoderPlugin.hxx"
|
||||||
#include "ReplayGainInfo.hxx"
|
#include "ReplayGainInfo.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
|
#include "tag/MixRampInfo.hxx"
|
||||||
#include "pcm/AudioFormat.hxx"
|
#include "pcm/AudioFormat.hxx"
|
||||||
#include "MixRampInfo.hxx"
|
|
||||||
#include "config/Block.hxx"
|
#include "config/Block.hxx"
|
||||||
#include "Chrono.hxx"
|
#include "Chrono.hxx"
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#ifndef MPD_DECODER_READER_HXX
|
#ifndef MPD_DECODER_READER_HXX
|
||||||
#define MPD_DECODER_READER_HXX
|
#define MPD_DECODER_READER_HXX
|
||||||
|
|
||||||
#include "fs/io/Reader.hxx"
|
#include "io/Reader.hxx"
|
||||||
|
|
||||||
class DecoderClient;
|
class DecoderClient;
|
||||||
class InputStream;
|
class InputStream;
|
||||||
|
@@ -262,7 +262,7 @@ static void
|
|||||||
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
|
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(bridge.dc.mutex);
|
const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
|
||||||
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
|
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
|
||||||
/* ReplayGain is disabled */
|
/* ReplayGain is disabled */
|
||||||
return;
|
return;
|
||||||
@@ -337,7 +337,7 @@ TryDecoderFile(DecoderBridge &bridge, Path path_fs, std::string_view suffix,
|
|||||||
DecoderControl &dc = bridge.dc;
|
DecoderControl &dc = bridge.dc;
|
||||||
|
|
||||||
if (plugin.file_decode != nullptr) {
|
if (plugin.file_decode != nullptr) {
|
||||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||||
return decoder_file_decode(plugin, bridge, path_fs);
|
return decoder_file_decode(plugin, bridge, path_fs);
|
||||||
} else if (plugin.stream_decode != nullptr) {
|
} else if (plugin.stream_decode != nullptr) {
|
||||||
std::unique_lock<Mutex> lock(dc.mutex);
|
std::unique_lock<Mutex> lock(dc.mutex);
|
||||||
@@ -365,7 +365,7 @@ TryContainerDecoder(DecoderBridge &bridge, Path path_fs,
|
|||||||
bridge.Reset();
|
bridge.Reset();
|
||||||
|
|
||||||
DecoderControl &dc = bridge.dc;
|
DecoderControl &dc = bridge.dc;
|
||||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||||
return decoder_file_decode(plugin, bridge, path_fs);
|
return decoder_file_decode(plugin, bridge, path_fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,10 +395,12 @@ TryContainerDecoder(DecoderBridge &bridge, Path path_fs,
|
|||||||
static bool
|
static bool
|
||||||
decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs)
|
decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs)
|
||||||
{
|
{
|
||||||
const auto suffix = uri_get_suffix(uri_utf8);
|
const char *_suffix = PathTraitsUTF8::GetFilenameSuffix(uri_utf8);
|
||||||
if (suffix.empty())
|
if (_suffix == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
const std::string_view suffix{_suffix};
|
||||||
|
|
||||||
InputStreamPtr input_stream;
|
InputStreamPtr input_stream;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -83,8 +83,8 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) noexcept
|
|||||||
static AFfileoffset
|
static AFfileoffset
|
||||||
audiofile_file_length(AFvirtualfile *vfile) noexcept
|
audiofile_file_length(AFvirtualfile *vfile) noexcept
|
||||||
{
|
{
|
||||||
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||||
InputStream &is = afis.is;
|
const InputStream &is = afis.is;
|
||||||
|
|
||||||
return is.GetSize();
|
return is.GetSize();
|
||||||
}
|
}
|
||||||
@@ -92,8 +92,8 @@ audiofile_file_length(AFvirtualfile *vfile) noexcept
|
|||||||
static AFfileoffset
|
static AFfileoffset
|
||||||
audiofile_file_tell(AFvirtualfile *vfile) noexcept
|
audiofile_file_tell(AFvirtualfile *vfile) noexcept
|
||||||
{
|
{
|
||||||
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||||
InputStream &is = afis.is;
|
const InputStream &is = afis.is;
|
||||||
|
|
||||||
return is.GetOffset();
|
return is.GetOffset();
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
#include "tag/Builder.hxx"
|
#include "tag/Builder.hxx"
|
||||||
#include "tag/Handler.hxx"
|
#include "tag/Handler.hxx"
|
||||||
#include "tag/ReplayGain.hxx"
|
#include "tag/ReplayGain.hxx"
|
||||||
#include "tag/MixRamp.hxx"
|
#include "tag/MixRampParser.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "pcm/CheckAudioFormat.hxx"
|
#include "pcm/CheckAudioFormat.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
@@ -502,7 +502,7 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
|||||||
FmtDebug(ffmpeg_domain, "codec '{}'",
|
FmtDebug(ffmpeg_domain, "codec '{}'",
|
||||||
codec_descriptor->name);
|
codec_descriptor->name);
|
||||||
|
|
||||||
AVCodec *codec = avcodec_find_decoder(codec_params.codec_id);
|
const AVCodec *codec = avcodec_find_decoder(codec_params.codec_id);
|
||||||
|
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
LogError(ffmpeg_domain, "Unsupported audio codec");
|
LogError(ffmpeg_domain, "Unsupported audio codec");
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
#define __STDC_CONSTANT_MACROS
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
|
||||||
#include "FfmpegIo.hxx"
|
#include "FfmpegIo.hxx"
|
||||||
|
#include "libavutil/mem.h"
|
||||||
#include "../DecoderAPI.hxx"
|
#include "../DecoderAPI.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
|
|
||||||
@@ -35,7 +36,11 @@ AvioStream::~AvioStream()
|
|||||||
inline int
|
inline int
|
||||||
AvioStream::Read(void *dest, int size)
|
AvioStream::Read(void *dest, int size)
|
||||||
{
|
{
|
||||||
return decoder_read(client, input, dest, size);
|
const auto nbytes = decoder_read(client, input, dest, size);
|
||||||
|
if (nbytes == 0)
|
||||||
|
return AVERROR_EOF;
|
||||||
|
|
||||||
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int64_t
|
inline int64_t
|
||||||
|
@@ -78,7 +78,9 @@ FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc)
|
|||||||
if (flac_parse_replay_gain(rgi, vc))
|
if (flac_parse_replay_gain(rgi, vc))
|
||||||
GetClient()->SubmitReplayGain(&rgi);
|
GetClient()->SubmitReplayGain(&rgi);
|
||||||
|
|
||||||
GetClient()->SubmitMixRamp(flac_parse_mixramp(vc));
|
if (auto mix_ramp = flac_parse_mixramp(vc);
|
||||||
|
mix_ramp.IsDefined())
|
||||||
|
GetClient()->SubmitMixRamp(std::move(mix_ramp));
|
||||||
|
|
||||||
tag = flac_vorbis_comments_to_tag(&vc);
|
tag = flac_vorbis_comments_to_tag(&vc);
|
||||||
}
|
}
|
||||||
|
@@ -23,9 +23,10 @@
|
|||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "tag/Id3Scan.hxx"
|
#include "tag/Id3Scan.hxx"
|
||||||
#include "tag/Id3ReplayGain.hxx"
|
#include "tag/Id3ReplayGain.hxx"
|
||||||
|
#include "tag/Id3MixRamp.hxx"
|
||||||
#include "tag/Handler.hxx"
|
#include "tag/Handler.hxx"
|
||||||
#include "tag/ReplayGain.hxx"
|
#include "tag/ReplayGain.hxx"
|
||||||
#include "tag/MixRamp.hxx"
|
#include "tag/MixRampParser.hxx"
|
||||||
#include "pcm/CheckAudioFormat.hxx"
|
#include "pcm/CheckAudioFormat.hxx"
|
||||||
#include "util/Clamp.hxx"
|
#include "util/Clamp.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
@@ -268,35 +269,6 @@ MadDecoder::FillBuffer() noexcept
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_ID3TAG
|
|
||||||
gcc_pure
|
|
||||||
static MixRampInfo
|
|
||||||
parse_id3_mixramp(struct id3_tag *tag) noexcept
|
|
||||||
{
|
|
||||||
MixRampInfo result;
|
|
||||||
|
|
||||||
struct id3_frame *frame;
|
|
||||||
for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
|
|
||||||
if (frame->nfields < 3)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
char *const key = (char *)
|
|
||||||
id3_ucs4_latin1duplicate(id3_field_getstring
|
|
||||||
(&frame->fields[1]));
|
|
||||||
char *const value = (char *)
|
|
||||||
id3_ucs4_latin1duplicate(id3_field_getstring
|
|
||||||
(&frame->fields[2]));
|
|
||||||
|
|
||||||
ParseMixRampTag(result, key, value);
|
|
||||||
|
|
||||||
free(key);
|
|
||||||
free(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
||||||
{
|
{
|
||||||
@@ -310,7 +282,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
|||||||
id3_data = stream.this_frame;
|
id3_data = stream.this_frame;
|
||||||
mad_stream_skip(&(stream), tagsize);
|
mad_stream_skip(&(stream), tagsize);
|
||||||
} else {
|
} else {
|
||||||
allocated.reset(new id3_byte_t[tagsize]);
|
allocated = std::make_unique<id3_byte_t[]>(tagsize);
|
||||||
memcpy(allocated.get(), stream.this_frame, count);
|
memcpy(allocated.get(), stream.this_frame, count);
|
||||||
mad_stream_skip(&(stream), count);
|
mad_stream_skip(&(stream), count);
|
||||||
|
|
||||||
@@ -338,7 +310,9 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
|||||||
found_replay_gain = true;
|
found_replay_gain = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
client->SubmitMixRamp(parse_id3_mixramp(id3_tag.get()));
|
if (auto mix_ramp = Id3ToMixRampInfo(id3_tag.get());
|
||||||
|
mix_ramp.IsDefined())
|
||||||
|
client->SubmitMixRamp(std::move(mix_ramp));
|
||||||
}
|
}
|
||||||
|
|
||||||
#else /* !ENABLE_ID3TAG */
|
#else /* !ENABLE_ID3TAG */
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
#include "tag/Handler.hxx"
|
#include "tag/Handler.hxx"
|
||||||
#include "tag/Builder.hxx"
|
#include "tag/Builder.hxx"
|
||||||
#include "tag/ReplayGain.hxx"
|
#include "tag/ReplayGain.hxx"
|
||||||
#include "tag/MixRamp.hxx"
|
#include "tag/MixRampParser.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
|
@@ -91,7 +91,8 @@ ScanOpusTags(const void *data, size_t size,
|
|||||||
if (!r.Expect("OpusTags", 8))
|
if (!r.Expect("OpusTags", 8))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!handler.WantPair() && !handler.WantTag())
|
if (!handler.WantPair() && !handler.WantTag() &&
|
||||||
|
!handler.WantPicture())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!r.SkipString())
|
if (!r.SkipString())
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "lib/icu/Converter.hxx"
|
#include "lib/icu/Converter.hxx"
|
||||||
#ifdef HAVE_SIDPLAYFP
|
#ifdef HAVE_SIDPLAYFP
|
||||||
#include "fs/io/FileReader.hxx"
|
#include "io/FileReader.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#endif
|
#endif
|
||||||
#include "util/StringFormat.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
@@ -137,7 +137,7 @@ SidplayGlobal::SidplayGlobal(const ConfigBlock &block)
|
|||||||
const auto kernal_path = block.GetPath("kernal");
|
const auto kernal_path = block.GetPath("kernal");
|
||||||
if (!kernal_path.IsNull())
|
if (!kernal_path.IsNull())
|
||||||
{
|
{
|
||||||
kernal.reset(new uint8_t[rom_size]);
|
kernal = std::make_unique<uint8_t[]>(rom_size);
|
||||||
loadRom(kernal_path, kernal.get());
|
loadRom(kernal_path, kernal.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ SidplayGlobal::SidplayGlobal(const ConfigBlock &block)
|
|||||||
const auto basic_path = block.GetPath("basic");
|
const auto basic_path = block.GetPath("basic");
|
||||||
if (!basic_path.IsNull())
|
if (!basic_path.IsNull())
|
||||||
{
|
{
|
||||||
basic.reset(new uint8_t[rom_size]);
|
basic = std::make_unique<uint8_t[]>(rom_size);
|
||||||
loadRom(basic_path, basic.get());
|
loadRom(basic_path, basic.get());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
#include "ToOutputStream.hxx"
|
#include "ToOutputStream.hxx"
|
||||||
#include "EncoderInterface.hxx"
|
#include "EncoderInterface.hxx"
|
||||||
#include "fs/io/OutputStream.hxx"
|
#include "io/OutputStream.hxx"
|
||||||
|
|
||||||
void
|
void
|
||||||
EncoderToOutputStream(OutputStream &os, Encoder &encoder)
|
EncoderToOutputStream(OutputStream &os, Encoder &encoder)
|
||||||
|
@@ -68,7 +68,7 @@ private:
|
|||||||
exception = std::current_exception();
|
exception = std::current_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::lock_guard<Mutex> lock(mutex);
|
const std::scoped_lock<Mutex> lock(mutex);
|
||||||
done = true;
|
done = true;
|
||||||
cond.notify_one();
|
cond.notify_one();
|
||||||
}
|
}
|
||||||
|
@@ -52,6 +52,13 @@ EventLoop::EventLoop(
|
|||||||
|
|
||||||
EventLoop::~EventLoop() noexcept
|
EventLoop::~EventLoop() noexcept
|
||||||
{
|
{
|
||||||
|
#if defined(HAVE_URING) && !defined(NDEBUG)
|
||||||
|
/* if Run() was never called (maybe because startup failed and
|
||||||
|
an exception is pending), we need to destruct the
|
||||||
|
Uring::Manager here or else the assertions below fail */
|
||||||
|
uring.reset();
|
||||||
|
#endif
|
||||||
|
|
||||||
assert(defer.empty());
|
assert(defer.empty());
|
||||||
assert(idle.empty());
|
assert(idle.empty());
|
||||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||||
@@ -175,6 +182,10 @@ EventLoop::HandleTimers() noexcept
|
|||||||
void
|
void
|
||||||
EventLoop::AddDefer(DeferEvent &d) noexcept
|
EventLoop::AddDefer(DeferEvent &d) noexcept
|
||||||
{
|
{
|
||||||
|
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||||
|
assert(!IsAlive() || IsInside());
|
||||||
|
#endif
|
||||||
|
|
||||||
defer.push_back(d);
|
defer.push_back(d);
|
||||||
again = true;
|
again = true;
|
||||||
}
|
}
|
||||||
@@ -312,7 +323,7 @@ EventLoop::Run() noexcept
|
|||||||
/* try to handle DeferEvents without WakeFD
|
/* try to handle DeferEvents without WakeFD
|
||||||
overhead */
|
overhead */
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> lock(mutex);
|
const std::scoped_lock<Mutex> lock(mutex);
|
||||||
HandleInject();
|
HandleInject();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -335,7 +346,7 @@ EventLoop::Run() noexcept
|
|||||||
|
|
||||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> lock(mutex);
|
const std::scoped_lock<Mutex> lock(mutex);
|
||||||
busy = true;
|
busy = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -367,7 +378,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
|
|||||||
bool must_wake;
|
bool must_wake;
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> lock(mutex);
|
const std::scoped_lock<Mutex> lock(mutex);
|
||||||
if (d.IsPending())
|
if (d.IsPending())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -386,7 +397,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
|
|||||||
void
|
void
|
||||||
EventLoop::RemoveInject(InjectEvent &d) noexcept
|
EventLoop::RemoveInject(InjectEvent &d) noexcept
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
|
|
||||||
if (d.IsPending())
|
if (d.IsPending())
|
||||||
inject.erase(inject.iterator_to(d));
|
inject.erase(inject.iterator_to(d));
|
||||||
@@ -413,7 +424,7 @@ EventLoop::OnSocketReady([[maybe_unused]] unsigned flags) noexcept
|
|||||||
|
|
||||||
wake_fd.Read();
|
wake_fd.Read();
|
||||||
|
|
||||||
const std::lock_guard<Mutex> lock(mutex);
|
const std::scoped_lock<Mutex> lock(mutex);
|
||||||
HandleInject();
|
HandleInject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,7 +91,7 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool IsDefined() const noexcept {
|
[[nodiscard]] bool IsDefined() const noexcept {
|
||||||
return event.IsDefined();
|
return event.IsDefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,6 +37,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &GetEventLoop() const noexcept {
|
[[nodiscard]] auto &GetEventLoop() const noexcept {
|
||||||
return event.GetEventLoop();
|
return event.GetEventLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,12 +91,12 @@ private:
|
|||||||
/* this should be enough - is it? */
|
/* this should be enough - is it? */
|
||||||
static constexpr unsigned MAX_SIGNAL = 64;
|
static constexpr unsigned MAX_SIGNAL = 64;
|
||||||
|
|
||||||
static SignalHandler signal_handlers[MAX_SIGNAL];
|
static std::array<SignalHandler, MAX_SIGNAL> signal_handlers;
|
||||||
|
|
||||||
#ifdef USE_SIGNALFD
|
#ifdef USE_SIGNALFD
|
||||||
static sigset_t signal_mask;
|
static sigset_t signal_mask;
|
||||||
#else
|
#else
|
||||||
static std::atomic_bool signal_pending[MAX_SIGNAL];
|
static std::array<std::atomic_bool, MAX_SIGNAL> signal_pending;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static Manual<SignalMonitor> monitor;
|
static Manual<SignalMonitor> monitor;
|
||||||
@@ -153,7 +154,7 @@ void
|
|||||||
SignalMonitorFinish() noexcept
|
SignalMonitorFinish() noexcept
|
||||||
{
|
{
|
||||||
#ifdef USE_SIGNALFD
|
#ifdef USE_SIGNALFD
|
||||||
std::fill_n(signal_handlers, MAX_SIGNAL, nullptr);
|
signal_handlers = {};
|
||||||
#else
|
#else
|
||||||
struct sigaction sa;
|
struct sigaction sa;
|
||||||
sa.sa_flags = 0;
|
sa.sa_flags = 0;
|
||||||
@@ -167,7 +168,7 @@ SignalMonitorFinish() noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::fill_n(signal_pending, MAX_SIGNAL, false);
|
std::fill(signal_pending.begin(), signal_pending.end(), false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
monitor.Destruct();
|
monitor.Destruct();
|
||||||
|
@@ -62,7 +62,7 @@ EventThread::Run() noexcept
|
|||||||
SetThreadRealtime();
|
SetThreadRealtime();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
FmtInfo(event_domain,
|
FmtInfo(event_domain,
|
||||||
"RTIOThread could not get realtime scheduling, continuing anyway: %s",
|
"RTIOThread could not get realtime scheduling, continuing anyway: {}",
|
||||||
std::current_exception());
|
std::current_exception());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user