Compare commits

...

111 Commits

Author SHA1 Message Date
Max Kellermann
66a8fac25e release v0.21.9 2019-05-20 17:10:58 +02:00
Max Kellermann
1b902e00b4 doc/protocol.rst: several clarifications
Closes https://github.com/MusicPlayerDaemon/MPD/issues/340
2019-05-20 17:06:20 +02:00
Max Kellermann
923e66738c player/Thread: fix "single" mode race condition
If the decoder finishes decoding the current song between the two
IsIdle() checks, MPD stops playback instead of starting the decoder
for the next song.

This is usually not visible problem, because the main thread restarts
it via playlist::ResumePlayback(), but that way it, ignores "single"
mode.

As a workaround, this commit adds another "queued" check which
re-enters the player loop and checks again whether to start the
decoder.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/556
2019-05-20 16:22:01 +02:00
Max Kellermann
ff3e2c0514 player/Thread: remove unnecessary "pipe" check
The "queued" flag can only possibly be set if the decoder is still
decoding the current song or if the decoder is stopped.  This is also
what the following assert() checks.  This check was not necessary.
2019-05-20 16:20:59 +02:00
Max Kellermann
6922a2f55e input/buffered: check error in IsAvailable() 2019-05-17 12:43:45 +02:00
Max Kellermann
ca5a400dbe input/buffered: rethrow read_error in Check() 2019-05-16 22:08:33 +02:00
Max Kellermann
63fe4d1d17 input/buffered: wake up client thread on seek error 2019-05-16 22:05:25 +02:00
Max Kellermann
ca06d9d3bf input/buffered: fix deadlock bug 2019-05-16 21:11:03 +02:00
Max Kellermann
ed2db04f43 doc/mpd.conf.5: remove ALSA specific documentation
ALSA is just one out of many output plugins, and detailed plugin
documentation should only live in the user manual, without having
duplicates in the (brief) manpage.

Also move "mixer_type" to the "optional audio output parameters"
section; it is a generic option, not specific to ALSA.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/552
2019-05-13 22:51:48 +02:00
Max Kellermann
de0afa0e08 doc/mpd.conf.5: fix section indent 2019-05-13 22:51:45 +02:00
Max Kellermann
f0d3227d7b doc/protocol.rst: add references to audio_output_format 2019-05-13 22:46:23 +02:00
Max Kellermann
fb07a7cecc doc/user.rst: move audio format spec to section "Global Audio Format" 2019-05-13 22:39:49 +02:00
Max Kellermann
c6b08a4d48 doc/user.rst: add reference to audio_output_format 2019-05-13 22:39:44 +02:00
Max Kellermann
040e87ad8d doc/user.rst: more markup 2019-05-13 22:36:19 +02:00
Max Kellermann
d5521ead56 doc/user.rst: add missing space 2019-05-13 22:36:19 +02:00
Max Kellermann
f8468451c9 android/AndroidManifest.xml: increment versionCode after hotfix upload 2019-05-04 13:25:05 +02:00
Max Kellermann
65df6ca14e android/Settings: request READ_EXTERNAL_STORAGE permission
Using this API function requires SDK level 23.
2019-05-04 07:29:41 +02:00
Max Kellermann
36dec47bf7 android/build.py: link ARMv7 binary with libunwind
Fixes nullptr dereference when an exception gets thrown because there
is no ".eh_frame" section for unwinding.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/543
2019-05-03 20:15:50 +02:00
Max Kellermann
478cedcadf increment version number to 0.21.9 2019-05-03 20:15:33 +02:00
Max Kellermann
cabcbb059d release v0.21.8 2019-04-23 14:35:14 +02:00
Max Kellermann
5e21b2db3c doc/protocol.rst: "list file" is deprecated
Closes https://github.com/MusicPlayerDaemon/MPD/issues/526
2019-04-23 14:29:42 +02:00
Max Kellermann
3a0d6d96c1 input/smbclient: wrap in MaybeBufferedInputStream
This enables the input buffer for remote files and caches file
contents in MPD.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/376
2019-04-23 14:08:27 +02:00
Max Kellermann
f39d2d33c0 python/build/libs.py: upgrade Boost to 1.70.0 2019-04-23 14:08:27 +02:00
Max Kellermann
ead3dc6a92 LocateUri: pass URI plugin kind, optionally disables plugin verify
Commit b3a458338a added a LocateUri()
call to several playlist commands, which applied InputPlugin URI
scheme verification to playlist URIs.  This broke the SoundCloud
playlist plugin which uses "soundcloud://" URIs for which no input
plugin exists.

This commit allows the caller to specify the kind of plugin which
shall be used to verify the URI.  Right now, only "input" is
implemented; "storage" uses the "input" verification for now; and
"playlist" has no verification at all (for now).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/528
2019-04-18 10:03:15 +02:00
Max Kellermann
7d814cc899 neighbor/smbclient: fix double smbc_closedir() call
There is already one call in ReadServers(), which is the correct place
to do it.
2019-04-18 09:40:56 +02:00
Max Kellermann
f5b4606c09 .travis.yml: switch to another PPA for a newer ninja version
Fixes Travis failure with Meson 0.50:

 ERROR: Could not detect Ninja v1.5 or newer
2019-04-18 09:40:30 +02:00
Max Kellermann
d6dbf64efb CommandLine: fix another build failure with -Ddatabase=false
Split several printf() calls to make it easier to deal with all those
#ifdefs.
2019-04-18 09:20:12 +02:00
Eugene Gorodinsky
8d18b4c24b Fix meson.build to work properly with '-Ddatabase=false' 2019-04-18 08:55:13 +02:00
Max Kellermann
fe8621906d systemd: add user socket unit
Copy the system socket unit to the "user" directory.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/530
2019-04-10 16:37:13 +02:00
Max Kellermann
b4fcbdb235 systemd/socket: use %t instead of hard-coding /run
This allows using the file as a user unit, where "%t" maps to
"$XDG_RUNTIME_DIR".

Proposed in https://github.com/MusicPlayerDaemon/MPD/issues/530
2019-04-10 16:34:40 +02:00
Max Kellermann
f4b5a28596 doc/protocol: mention that stickers are only implemented for songs
Closes https://github.com/MusicPlayerDaemon/MPD/issues/524
2019-04-10 16:33:17 +02:00
Max Kellermann
6cbd77fc57 doc/protocol.rst: mention "in seconds" where it was missing
Closes https://github.com/MusicPlayerDaemon/MPD/issues/523
2019-04-10 16:30:26 +02:00
cotko
1bc78e9f2c Fid move doc args 2019-04-10 13:16:58 +02:00
Max Kellermann
cb6282e0a7 doc/developer.rst: remove mailing list, refer to GitHub instead 2019-04-10 11:36:03 +02:00
Max Kellermann
f6941f9a44 event/SocketMonitor: don't cancel if OnSocketReady() returns false
Expect OnSocketReady() to cancel events.  If it returns false, the
SocketMonitor may be destructed already.  This fixes a use-after-free
bug in the "httpd" output plugin.
2019-04-04 10:24:58 +02:00
Max Kellermann
d2eb4df8fc event/{Fully,}BufferedSocket: add more API documentation 2019-04-04 10:24:58 +02:00
Max Kellermann
df33a898d7 zeroconf/Bonjour: fix OnSocketReady() return value
Keep the SocketMonitor registered.  This wrong return value was added
6 years ago in commit 72cf8dd8a0, andd
apparently, nobody ever noticed.
2019-04-04 10:24:29 +02:00
Max Kellermann
325c7b8e8b output/httpd: close client connection on error
This missing piece probably never really hurt, because
HttpdClient::OnSocketClosed() would be called right after a socket
error, but it's better to be explicit about closing on error.
2019-04-04 09:39:22 +02:00
Max Kellermann
380656d8c9 output/httpd: add missing mutex lock 2019-04-03 22:53:03 +02:00
Max Kellermann
9111bc2c21 output/httpd: add more API documentation about locking 2019-04-03 22:49:25 +02:00
Max Kellermann
37b54179d8 net/IPv[46]Address: add cast to void* to fix GCC9 build failure
Fixes:

 src/net/IPv4Address.hxx: In member function 'constexpr IPv4Address::operator SocketAddress() const':
 src/net/IPv4Address.hxx:171:24: error: a reinterpret_cast is not a constant expression
   171 |   return SocketAddress((const struct sockaddr *)&address,
       |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 src/net/IPv6Address.hxx: In member function 'constexpr IPv6Address::operator SocketAddress() const':
 src/net/IPv6Address.hxx:138:24: error: a reinterpret_cast is not a constant expression
   138 |   return SocketAddress((const struct sockaddr *)&address,
       |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Closes https://github.com/MusicPlayerDaemon/MPD/issues/522
2019-04-03 16:59:53 +02:00
Max Kellermann
511826763a increment version number to 0.21.8 2019-04-03 12:27:18 +02:00
Max Kellermann
ef10354d06 release v0.21.7 2019-04-03 12:18:29 +02:00
Max Kellermann
158458db5f python/build/libs.py: upgrade libnfs to 4.0.0 2019-04-03 11:37:33 +02:00
Max Kellermann
e183ab5cf8 python/build/libs.py: upgrade CURL to 7.64.1 2019-04-03 11:35:13 +02:00
Max Kellermann
fef839e2a9 python/build/libs.py: upgrade FFmpeg to 4.1.3 2019-04-03 11:34:32 +02:00
Max Kellermann
9776e43bbe android/AndroidManifest.xml: update version number 2019-04-03 11:28:59 +02:00
Max Kellermann
5201147ab1 input/curl: use std::throw_with_nested() instead of logging the exception
Let the caller decide what to do with the original exception.
2019-03-29 17:34:51 +01:00
Max Kellermann
fb7daa0d05 input/smbclient: use std::throw_with_nested() to construct PluginUnavailable
Preserve the original exception.
2019-03-29 17:32:23 +01:00
Max Kellermann
2e9f3d8b9f decoder/HybridDSD: downgrade log message to "debug"
This plugin is interesting only for a tiny fraction of MPD users, so
let's not spam everybody else's log with it.
2019-03-29 17:15:48 +01:00
Max Kellermann
976731ab6c command/playlist: invoke the RemoteTagScanner on all newly added songs
Closes https://github.com/MusicPlayerDaemon/MPD/issues/234
2019-03-29 17:01:31 +01:00
François Revol
0d8942e64a Haiku: remove redundant calls to delete_sem()
Fixes #184.

Semaphores are kernel-managed objects, calling delete_sem() twice is not more
dangerous than calling close() twice on an fd though, it would just return
an error.
2019-03-29 14:33:49 +01:00
François Revol
37a0f04712 Haiku: add version info to the resources like win32 does 2019-03-29 14:33:27 +01:00
François Revol
cde9348009 Haiku: fix adding resources
The custom_command was run in src/haiku/ and created a file with only resources inside.

Since xres edits the file in-place and meson doesn't like it, we have to run a shell script for now.
Maybe later I'll add proper support in meson.
2019-03-29 14:32:59 +01:00
François Revol
095e6e6ad4 Haiku: meson.build: fix linking (missing libs) 2019-03-29 14:32:19 +01:00
François Revol
9d0bf5e95c Haiku: fix build 2019-03-29 14:32:06 +01:00
Max Kellermann
8b327f1d9b filter/AutoConvert: implement Flush() 2019-03-24 22:42:06 +01:00
Max Kellermann
aef0507abb filter/Filter: fix typo in API doc 2019-03-24 22:34:11 +01:00
Max Kellermann
6bab3bcfea test/RunChromaprint: add missing override 2019-03-20 13:30:13 +01:00
Max Kellermann
a854595886 event/ServerSocket: runtime error if abstract sockets are unavailable 2019-03-20 13:09:16 +01:00
Max Kellermann
8fc3c5c612 event/ServerSocket: add HAVE_UN check to AddAbstract()
Closes https://github.com/MusicPlayerDaemon/MPD/issues/510
2019-03-20 13:06:09 +01:00
Max Kellermann
4f408bd952 event/ServerSocket, doc, ...: refer to AF_LOCAL as "local socket"
.. and not "UNIX domain socket.  Be consistent about the naming.
2019-03-20 12:57:26 +01:00
Max Kellermann
7de8fd04a4 doc/plugins.rst: add the Haiku plugin and mark it as unmaintained 2019-03-18 18:24:51 +01:00
Max Kellermann
8158bd218c doc/plugins.rst: add filter plugin reference 2019-03-18 18:05:18 +01:00
Max Kellermann
aa1d867b72 doc/user.rst: document the "filters" setting 2019-03-18 17:05:23 +01:00
Max Kellermann
34c8242133 doc/user.rst: add more links 2019-03-18 17:01:55 +01:00
Max Kellermann
e22bdee808 win32/res/meson.build: drop tilde suffix from version number before splitting
MPD sometimes uses version numbers like "0.22~git" to mark unreleased
versions.  That makes the win32 resource compiler unhappy, because it
expects numbers only.
2019-03-18 09:58:40 +01:00
Jörg Krause
7f87de783f src/lib/gcrypt/meson.build: use dependency() for quering linker flags
Since version 0.49.0 the Meson build system has native support for
finding and using the gcrypt library using the `dependency()` function.

`dependency()` has the advantage over `find_library()` as it queries the
required linker flags for proper linking with external libraries, e.g.
libgpg-error.

As the latest released version 1.8.4 of libgcrypt does not
provide a .pc file, using `libgcrypt-config` is the only way to query
the required linker flags.

Unfortunately, there is an issue when cross compiling mpd and the user does not
define `libgcrypt-config` in the cross file. If the user sets the qobuz feature
to `auto` and the target does not have libgcrypt installed, the Meson
build system will falsly assume libgcrypt is available for the target as
it uses the native `libgcrypt-config` on the host and pretend is has
found the library.

Therefore, we still rely on `find_library()` to workaround this buggy
behavior. This way, if qobuz feature detection is set to `auto`, the
feature is disabled in case there is no target libgcrypt available.

Fixes building mpd statically with the qobuz feature enabled. Otherwise
the build fails with undefined references because of the missing libgpg-error
dependency:

```
/sysroot/usr/lib/libgcrypt.a(libgcrypt_la-visibility.o): In function `gcry_strerror':
visibility.c:(.text+0x14): undefined reference to `gpg_strerror'
```
2019-03-18 09:12:19 +01:00
Jörg Krause
c66389a453 meson.build: require Meson 0.49.0
Meson 0.49.0 adds native support for `libgcrypt-config` which is
necessary for detecting libgcrypt dependencies, as the latest
version 1.8.4 of libgcrypt does not provide a .pc file.
2019-03-18 09:11:46 +01:00
Max Kellermann
b63c1a2144 increment version number to 0.21.7 2019-03-18 09:11:16 +01:00
Max Kellermann
808dd7cc54 release v0.21.6 2019-03-17 23:52:13 +01:00
Max Kellermann
62a129c18f PlaylistFile: ignore empty playlist names
Closes https://github.com/MusicPlayerDaemon/MPD/issues/465 and
https://github.com/MusicPlayerDaemon/MPD/pull/466
2019-03-17 23:46:36 +01:00
Max Kellermann
c18cd941aa lib/xiph: disable Tremor detection if libvorbis was found
And disable libvorbis detection if Tremor was explicitly enabled.

This fixes a crash bug caused by libvorbis/Tremor ABI conflict caused
by commit 4f7d52dbf2
2019-03-17 23:36:52 +01:00
Max Kellermann
6d12c22653 decoder/ogg: ignore the BOS packet after seek to the beginning of song
Previously, MPD would skip the current song after attempting to seek
to its beginnig, because that was a seek to offset 0.  At offset 0,
MPD will see the BOS packet again, which results in throwing
StopDecoder in MPDOpusDecoder::OnOggEnd().

Closes https://github.com/MusicPlayerDaemon/MPD/issues/470
2019-03-17 23:14:59 +01:00
Max Kellermann
b76d78e6ae output/sles: enable power saving mode 2019-03-17 18:04:40 +01:00
Jacob Vosmaer
0a6e484b1a output/plugins/OSXOutputPlugin: add boost meson dependency 2019-03-17 16:59:24 +01:00
Max Kellermann
0bb71f1f20 output/pulse: use pa_channel_map_init_extend() instead of _auto()
Unlike pa_channel_map_init_auto(), pa_channel_map_init_extend() does
not fail if there is no valid mapping for the given channel count, but
instead maps additional "AUX" channels.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/493
2019-03-16 14:03:10 +01:00
Max Kellermann
1aa7cdd602 decoder/opus: fix replay gain when there are no other tags
The `tag_builder.empty()` check was wrong for the SubmitReplayGain()
call.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/497
2019-03-16 13:55:19 +01:00
Max Kellermann
a4b8a0d801 doc/protocol.rst: clarify filter expressions with multiple tag values
Clarification for https://github.com/MusicPlayerDaemon/MPD/issues/505
2019-03-16 13:23:44 +01:00
Max Kellermann
3bf521d5ca song/TagSongFilter: apply negation properly to multiple tag values
The old implementation didn't make a lot of sense; the "!=" operator
was not actually the opposite of "==".

Closes https://github.com/MusicPlayerDaemon/MPD/issues/505
2019-03-16 13:23:02 +01:00
Max Kellermann
0acb55cde5 song/StringFilter: remove obsolete #if 2019-03-16 13:23:02 +01:00
Max Kellermann
6b89fd6100 song/StringFilter: make MatchWithoutNegation() public 2019-03-16 13:23:02 +01:00
Max Kellermann
52ce39dc3e test/TestSongFilter: unit test for song filters
A few of those tests fail due to bugs.
2019-03-16 13:23:02 +01:00
Max Kellermann
7a3e15d8e5 test/meson.build: add section for filter tests 2019-03-16 13:23:02 +01:00
Max Kellermann
cf66a60c60 test/MakeTag: add noexcept 2019-03-16 13:23:02 +01:00
Max Kellermann
9b26d451e4 test/MakeTag: remove static 2019-03-16 13:23:02 +01:00
Max Kellermann
137ffba1b4 test/test_translate_song: move MakeTag() to header 2019-03-16 13:23:02 +01:00
Max Kellermann
5c5dc1b7c0 meson.build: increase protocol version to 0.21.6
There is a minor new feature (commit 713c1f2ba9) and clients might be
interested in detecting it by the protocol version.
2019-03-16 13:23:02 +01:00
Max Kellermann
9e9418294a song/TagSongFilter: eliminate Match(TagItem) 2019-03-15 20:28:27 +01:00
Max Kellermann
b850eb74b7 song/TagSongFilter: add code comments 2019-03-15 19:54:29 +01:00
Max Kellermann
67d73a2aee song/TagSongFilter: improve lambda indent 2019-03-15 19:54:16 +01:00
Max Kellermann
fde9a470dd song/TagSongFilter: eliminate the std::fill_n() call 2019-03-15 19:35:58 +01:00
Max Kellermann
8d1f30e55b tag/Fallback: add API documentation 2019-03-15 19:23:10 +01:00
Max Kellermann
ddd2b60489 doc/protocol.rst: add missing operators to example expressions 2019-03-15 19:14:06 +01:00
Max Kellermann
8777737861 doc/protocol.rst: use double backticks for tag names 2019-03-15 19:11:30 +01:00
Max Kellermann
cb71f6dd04 doc/protocol.rst: clarify the meaning of the any tag type 2019-03-15 19:09:55 +01:00
Max Kellermann
1881b0e975 song/TagSongFilter: rename MatchNN() to Match()
The "NN" suffix used to mean "no negation", but that's not how it's
implemented today.
2019-03-15 19:06:56 +01:00
Max Kellermann
98b29f6d1c meson.build: remove the libwinpthread-1.dll dependency on Windows
Closes https://github.com/MusicPlayerDaemon/MPD/issues/507
2019-03-14 20:07:06 +01:00
Max Kellermann
59fdfd25cb command/database: fix "list" with filter expression
Disable the 0.11 compatibility mode if the only argument is a filter
expression.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/506
2019-03-14 19:50:09 +01:00
Max Kellermann
0d98677212 playlist/flac: copy the URI to fix use-after-free bug
Closes https://github.com/MusicPlayerDaemon/MPD/issues/508
2019-03-14 19:30:33 +01:00
Max Kellermann
38f0c16904 system/UniqueFileDescriptor: add CreatePipeNonBlock() 2019-02-27 23:30:56 +01:00
Max Kellermann
4fbf6b6c95 net/StaticSocketAddress: remove GetAddress() 2019-02-27 23:26:59 +01:00
Max Kellermann
1f8ff48168 net/StaticSocketAddress: add GetLocalRaw() 2019-02-27 23:26:00 +01:00
Max Kellermann
20b6e0d684 net/SocketDescriptor: add SetTcpUserTimeout() 2019-02-27 23:22:12 +01:00
Max Kellermann
713c1f2ba9 Merge branch 'feature/playlist' of git://github.com/miccoli/MPD 2019-02-27 13:49:22 +01:00
Stefano Miccoli
a149bc4c5d update protocol documentation for new semantics of playlist abs. name 2019-02-26 00:12:09 +01:00
Stefano Miccoli
b3a458338a allow loading playlists specified as absolute filesystem paths
implement for the "load" command the same logic used for the "add"
command: local clients can load playlist specified as absolute paths.

For relative paths the old logic is preserved: first look for a stored
playlist, then look in the music directory.
2019-02-26 00:12:09 +01:00
Max Kellermann
44422b2b2f event/ServerSocket, config/Net: abstract socket support 2019-02-25 13:08:33 +01:00
Max Kellermann
f10afd38b5 NEWS: mention the cdio_paranoia build failure fix 2019-02-25 13:08:33 +01:00
Thomas Zander
4c50a5e0b3 Ensure SEEK_SET is set on systems where stdio.h is not pulled in by accident. 2019-02-23 18:04:00 +01:00
Max Kellermann
f255a485b7 increment version number to 0.21.6 2019-02-22 15:28:03 +01:00
90 changed files with 990 additions and 301 deletions

View File

@@ -9,7 +9,7 @@ matrix:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:mhier/libboost-latest'
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- g++-6
@@ -34,7 +34,7 @@ matrix:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:mhier/libboost-latest'
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- g++-8

54
NEWS
View File

@@ -1,3 +1,57 @@
ver 0.21.9 (2019/05/20)
* input
- buffer: fix deadlock bug
* Android
- fix crash on ARMv7
- request storage permission on Android 6+
* fix spurious "single" mode bug
ver 0.21.8 (2019/04/23)
* input
- smbclient: download to buffer instead of throttling transfer
* output
- httpd: add missing mutex lock
- httpd: fix use-after-free bug
* playlist
- soundcloud: fix "Unsupported URI scheme" (0.21.6 regression)
* fix Bonjour bug
* fix build failure with GCC 9
* fix build failure with -Ddatabase=false
* systemd: add user socket unit
* doc: "list file" is deprecated
ver 0.21.7 (2019/04/03)
* input
- qobuz/tidal: scan tags when loading a playlist
* require Meson 0.49.0 for native libgcrypt-config support
* fix build failure with -Dlocal_socket=false
* Haiku
- fix build
- add version info
ver 0.21.6 (2019/03/17)
* protocol
- allow loading playlists specified as absolute filesystem paths
- fix negated filter expressions with multiple tag values
- fix "list" with filter expression
- omit empty playlist names in "listplaylists"
* input
- cdio_paranoia: fix build failure due to missing #include
* decoder
- opus: fix replay gain when there are no other tags
- opus: fix seeking to beginning of song
- vorbis: fix Tremor conflict resulting in crash
* output
- pulse: work around error with unusual channel count
- osx: fix build failure
* playlist
- flac: fix use-after-free bug
* support abstract sockets on Linux
* Windows
- remove the unused libwinpthread-1.dll dependency
* Android
- enable SLES power saving mode
ver 0.21.5 (2019/02/22)
* protocol
- fix deadlock in "albumart" command

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="27"
android:versionName="0.21.5">
android:versionCode="32"
android:versionName="0.21.9">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>

View File

@@ -138,6 +138,12 @@ class AndroidNdkToolchain:
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
libstdcxx_libs = '-lc++_static -lc++abi'
if self.is_armv7:
# On 32 bit ARM, clang generates no ".eh_frame" section;
# instead, the LLVM unwinder library is used for unwinding
# the stack after a C++ exception was thrown
libstdcxx_libs += ' -lunwind'
if use_cxx:
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags

View File

@@ -6,7 +6,7 @@ android_sdk = get_option('android_sdk')
android_abi = get_option('android_abi')
android_sdk_build_tools_version = '27.0.0'
android_sdk_platform = 'android-21'
android_sdk_platform = 'android-23'
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)

View File

@@ -21,10 +21,12 @@ package org.musicpd;
import java.util.LinkedList;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -178,6 +180,14 @@ public class Settings extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
/* TODO: this sure is the wrong place to request
permissions - it will cause MPD to quit
immediately; we should request permissions when we
need them, but implementing that is complicated, so
for now, we do it here to give users a quick
solution for the problem */
requestAllPermissions();
setContentView(R.layout.settings);
mRunButton = (ToggleButton) findViewById(R.id.run);
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
@@ -203,6 +213,31 @@ public class Settings extends Activity {
super.onCreate(savedInstanceState);
}
private void checkRequestPermission(String permission) {
if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED)
return;
try {
this.requestPermissions(new String[]{permission}, 0);
} catch (Exception e) {
Log.e(TAG, "requestPermissions(" + permission + ") failed",
e);
}
}
private void requestAllPermissions() {
if (android.os.Build.VERSION.SDK_INT < 23)
/* we don't need to request permissions on
this old Android version */
return;
/* starting with Android 6.0, we need to explicitly
request all permissions before using them;
mentioning them in the manifest is not enough */
checkRequestPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
}
private void connectClient() {
mClient = new Main.Client(this, new Main.Client.Callback() {

View File

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.5'
version = '0.21.9'
# The full version, including alpha/beta/rc tags.
release = version

View File

@@ -96,10 +96,8 @@ When the whole patch series is finished, convert stgit patches to git commits:
Submitting Patches
==================
Send your patches to the mailing list:
Email: `mpd-devel <mpd-devel@musicpd.org>`_
:program:`git pull` requests are preferred.
Submit pull requests on GitHub:
https://github.com/MusicPlayerDaemon/MPD/pulls
Development Tools
=================

View File

@@ -140,7 +140,6 @@ of database.
.B auto_update_depth <N>
Limit the depth of the directories being watched, 0 means only watch
the music directory itself. There is no limit by default.
.TP
.SH REQUIRED AUDIO OUTPUT PARAMETERS
.TP
.B type <type>
@@ -164,57 +163,12 @@ Specifies how replay gain is applied. The default is "software",
which uses an internal software volume control. "mixer" uses the
configured (hardware) mixer control. "none" disables replay gain on
this audio output.
.SH OPTIONAL ALSA OUTPUT PARAMETERS
.TP
.B device <dev>
This specifies the device to use for audio output. The default is "default".
.TP
.B mixer_type <hardware, software or none>
Specifies which mixer should be used for this audio output: the
hardware mixer (available for ALSA, OSS and PulseAudio), the software
mixer or no mixer ("none"). By default, the hardware mixer is used
for devices which support it, and none for the others.
.TP
.B mixer_device <mixer dev>
This specifies which mixer to use. The default is "default". To use
the second sound card in a system, use "hw:1".
.TP
.B mixer_control <mixer ctrl>
This specifies which mixer control to use (sometimes referred to as
the "device"). The default is "PCM". Use "amixer scontrols" to see
the list of possible controls.
.TP
.B mixer_index <mixer index>
A number identifying the index of the named mixer control. This is
probably only useful if your alsa device has more than one
identically\-named mixer control. The default is "0". Use "amixer
scontrols" to see the list of controls with their indexes.
.TP
.B auto_resample <yes or no>
Setting this to "no" disables ALSA's software resampling, if the
hardware does not support a specific sample rate. This lets MPD do
the resampling. "yes" is the default and allows ALSA to resample.
.TP
.B auto_channels <yes or no>
Setting this to "no" disables ALSA's channel conversion, if the
hardware does not support a specific number of channels. Default: "yes".
.TP
.B auto_format <yes or no>
Setting this to "no" disables ALSA's sample format conversion, if the
hardware does not support a specific sample format. Default: "yes".
.TP
.B buffer_time <time in microseconds>
This sets the length of the hardware sample buffer in microseconds. Increasing
it may help to reduce or eliminate skipping on certain setups. Most users do
not need to change this. The default is 500000 microseconds (0.5 seconds).
.TP
.B period_time <time in microseconds>
This sets the time between hardware sample transfers in microseconds.
Increasing this can reduce CPU usage while lowering it can reduce underrun
errors on bandwidth-limited devices. Some users have reported good results
with this set to 50000, but not all devices support values this high. Most
users do not need to change this. The default is 256000000 / sample_rate(kHz),
or 5804 microseconds for CD-quality audio.
.SH FILES
.TP
.BI ~/.mpdconf

View File

@@ -690,6 +690,8 @@ Valid quality values for libsoxr:
* "low"
* "quick"
.. _output_plugins:
Output plugins
--------------
@@ -800,6 +802,15 @@ The fifo plugin writes raw PCM data to a FIFO (First In, First Out) file. The da
* - **path P**
- This specifies the path of the FIFO to write to. Must be an absolute path. If the path does not exist, it will be created when MPD is started, and removed when MPD is stopped. The FIFO will be created with the same user and group as MPD is running as. Default permissions can be modified by using the builtin shell command umask. If a FIFO already exists at the specified path it will be reused, and will not be removed when MPD is stopped. You can use the "mkfifo" command to create this, and then you may modify the permissions to your liking.
haiku
~~~~~
Use the SoundPlayer API on the Haiku operating system.
This plugin is unmaintained and contains known bugs. It will be
removed soon, unless there is a new maintainer.
jack
~~~~
The jack plugin connects to a `JACK server <http://jackaudio.org/>`_.
@@ -838,7 +849,7 @@ It is highly recommended to configure a fixed format, because a stream cannot sw
* - **port P**
- Binds the HTTP server to the specified port.
* - **bind_to_address ADDR**
- Binds the HTTP server to the specified address (IPv4, IPv6 or UNIX socket). Multiple addresses in parallel are not supported.
- Binds the HTTP server to the specified address (IPv4, IPv6 or local socket). Multiple addresses in parallel are not supported.
* - **encoder NAME**
- Chooses an encoder plugin. A list of encoder plugins can be found in the encoder plugin reference :ref:`encoder_plugins`.
* - **max_clients MC**
@@ -1037,8 +1048,41 @@ The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
* - **device PATH**
- Sets the path of the audio device, defaults to /dev/audio.
.. _filter_plugins:
Filter plugins
--------------
normalize
~~~~~~~~~
Normalize the volume during playback (at the expensve of quality).
null
~~~~
A no-op filter. Audio data is returned as-is.
route
~~~~~
Reroute channels.
.. list-table::
:widths: 20 80
:header-rows: 1
* - Setting
- Description
* - **routes "0>0, 1>1, ..."**
- Specifies the channel mapping.
.. _playlist_plugins:
Playlist plugins
----------------

View File

@@ -14,6 +14,9 @@ Once the client is connected to the server, they conduct a
conversation until the client closes the connection. The
conversation flow is always initiated by the client.
All data between the client and the server is encoded in
UTF-8.
The client transmits a command sequence, terminated by the
newline character ``\n``. The server will
respond with one or more lines, the last of which will be a
@@ -42,9 +45,6 @@ quotation marks.
Argument strings are separated from the command and any other
arguments by linear white-space (' ' or '\\t').
All data between the client and the server is encoded in
UTF-8.
Responses
=========
@@ -52,6 +52,28 @@ A command returns ``OK`` on completion or
``ACK some error`` on failure. These
denote the end of command execution.
Some commands return more data before the response ends with ``OK``.
Each line is usually in the form ``NAME: VALUE``. Example::
foo: bar
OK
.. _binary:
Binary Responses
----------------
Some commands can return binary data. This is initiated by a line
containing ``binary: 1234`` (followed as usual by a newline). After
that, the specified number of bytes of binary data follows (without an
extra newline, because this binary data is not a text line), and
finally the ``OK`` line. Example::
foo: bar
binary: 42
<42 bytes>OK
Failure responses
-----------------
@@ -112,9 +134,9 @@ list begins with `command_list_begin` or
`command_list_ok_begin` and ends with
`command_list_end`.
It does not execute any commands until the list has ended.
The return value is whatever the return for a list of commands
is. On success for all commands,
It does not execute any commands until the list has ended. The
response is a concatentation of all individual responses.
On success for all commands,
``OK`` is returned. If a command
fails, no more commands are executed and the appropriate
``ACK`` error is returned. If
@@ -144,15 +166,20 @@ syntax::
``EXPRESSION`` is a string enclosed in parantheses which can be one
of:
- ``(TAG == 'VALUE')``: match a tag value.
``(TAG != 'VALUE')``: mismatch a tag value.
The special tag "*any*" checks all
tag values.
*albumartist* looks for
- ``(TAG == 'VALUE')``: match a tag value; if there are multiple
values of the given type, at least one must match.
``(TAG != 'VALUE')``: mismatch a tag value; if there are multiple
values of the given type, none of them must match.
The special tag ``any`` checks all
tag types.
``AlbumArtist`` looks for
``VALUE`` in ``AlbumArtist``
and falls back to ``Artist`` tags if
``AlbumArtist`` does not exist.
``VALUE`` is what to find.
An empty value string means: match only if the given tag type does
not exist at all; this implies that negation with an empty value
checks for the existence of the given tag type.
- ``(TAG contains 'VALUE')`` checks if the given value is a substring
of the tag value.
@@ -173,12 +200,13 @@ of:
file's time stamp with the given value (ISO 8601 or UNIX
time stamp).
- ``(AudioFormat == 'SAMPLERATE:BITS:CHANNELS')``:
compares the audio format with the given value.
- ``(AudioFormat == 'SAMPLERATE:BITS:CHANNELS')``: compares the audio
format with the given value. See :ref:`audio_output_format` for a
detailed explanation.
- ``(AudioFormat =~ 'SAMPLERATE:BITS:CHANNELS')``:
matches the audio format with the given mask (i.e. one
or more attributes may be "*").
or more attributes may be ``*``).
- ``(!EXPRESSION)``: negate an expression. Note that each expression
must be enclosed in parantheses, e.g. :code:`(!(artist == 'VALUE'))`
@@ -207,11 +235,11 @@ backslash.
Example expression which matches an artist named ``foo'bar"``::
(artist "foo\'bar\"")
(Artist == "foo\'bar\"")
At the protocol level, the command must look like this::
find "(artist \"foo\\'bar\\\"\")"
find "(Artist == \"foo\\'bar\\\"\")"
The double quotes enclosing the artist name must be escaped because
they are inside a double-quoted ``find`` parameter. The single quote
@@ -409,15 +437,18 @@ Querying :program:`MPD`'s status
- ``songid``: playlist songid of the current song stopped on or playing
- ``nextsong`` [#since_0_15]_: playlist song number of the next song to be played
- ``nextsongid`` [#since_0_15]_: playlist songid of the next song to be played
- ``time``: total time elapsed (of current playing/paused song)
- ``time``: total time elapsed (of current playing/paused song) in seconds
(deprecated, use ``elapsed`` instead)
- ``elapsed`` [#since_0_16]_: Total time elapsed within the current song, but with higher resolution.
- ``elapsed`` [#since_0_16]_: Total time elapsed within the
current song in seconds, but with higher resolution.
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
- ``bitrate``: instantaneous bitrate in kbps
- ``xfade``: ``crossfade`` in seconds
- ``mixrampdb``: ``mixramp`` threshold in dB
- ``mixrampdelay``: ``mixrampdelay`` in seconds
- ``audio``: The format emitted by the decoder plugin during playback, format: ``*samplerate:bits:channels*``. Check the user manual for a detailed explanation.
- ``audio``: The format emitted by the decoder plugin during
playback, format: ``samplerate:bits:channels``. See
:ref:`audio_output_format` for a detailed explanation.
- ``updating_db``: ``job id``
- ``error``: if there is an error, returns message here
@@ -432,7 +463,7 @@ Querying :program:`MPD`'s status
- ``albums``: number of albums
- ``songs``: number of songs
- ``uptime``: daemon uptime in seconds
- ``db_playtime``: sum of all song times in the db
- ``db_playtime``: sum of all song times in the database in seconds
- ``db_update``: last db update in UNIX time
- ``playtime``: time length of music played
@@ -594,7 +625,7 @@ Whenever possible, ids should be used.
Deletes the song ``SONGID`` from the
playlist
:command:`move {FROM} [{START:END} | {TO}]`
:command:`move [{FROM} | {START:END}] {TO}`
Moves the song at ``FROM`` or range of songs
at ``START:END`` [#since_0_15]_ to ``TO``
in the playlist.
@@ -714,7 +745,9 @@ and without the `.m3u` suffix).
Some of the commands described in this section can be used to
run playlist plugins instead of the hard-coded simple
`m3u` parser. They can access playlists in
the music directory (relative path including the suffix) or
the music directory (relative path including the suffix),
playlists in arbitrary location (absolute path including the suffix;
allowed only for clients that are connected via local socket), or
remote playlists (absolute URI with a supported scheme).
:command:`listplaylist {NAME}`
@@ -783,7 +816,7 @@ The music database
Returns the file size and actual number
of bytes read at the requested offset, followed
by the chunk requested as raw bytes, then a
by the chunk requested as raw bytes (see :ref:`binary`), then a
newline and the completion code.
Example::
@@ -791,8 +824,7 @@ The music database
albumart
size: 1024768
binary: 8192
<8192 bytes>
OK
<8192 bytes>OK
:command:`count {FILTER} [group {GROUPTYPE}]`
Count the number of songs and their total playtime in
@@ -853,8 +885,7 @@ The music database
:command:`list {TYPE} {FILTER} [group {GROUPTYPE}]`
Lists unique tags values of the specified type.
``TYPE`` can be any tag supported by
:program:`MPD` or
*file*.
:program:`MPD`.
Additional arguments may specify a :ref:`filter <filter_syntax>`.
The *group* keyword may be used
@@ -865,6 +896,10 @@ The music database
list album group albumartist
``list file`` was implemented in an early :program:`MPD` version,
but does not appear to make a lot of sense. It still works (to
avoid breaking compatibility), but is deprecated.
.. _command_listall:
:command:`listall [URI]`
@@ -924,7 +959,7 @@ The music database
This command may be used to list metadata of remote
files (e.g. URI beginning with "http://" or "smb://").
Clients that are connected via UNIX domain socket may
Clients that are connected via local socket may
use this command to read the tags of an arbitrary local
file (URI is an absolute path).
@@ -1046,7 +1081,8 @@ Stickers
"Stickers" [#since_0_15]_ are pieces of
information attached to existing
:program:`MPD` objects (e.g. song files,
directories, albums). Clients can create arbitrary name/value
directories, albums; but currently, they are only implemented for
song). Clients can create arbitrary name/value
pairs. :program:`MPD` itself does not assume
any special meaning in them.
@@ -1215,7 +1251,7 @@ Reflection
:command:`config`
Dumps configuration values that may be interesting for
the client. This command is only permitted to "local"
clients (connected via UNIX domain socket).
clients (connected via local socket).
The following response attributes are available:

View File

@@ -54,7 +54,7 @@ Download the source tarball from the `MPD home page <https://musicpd.org>`_ and
In any case, you need:
* a C++14 compiler (e.g. gcc 6.0 or clang 3.9)
* `Meson 0.47.2 <http://mesonbuild.com/>`__ and `Ninja
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* Boost 1.58
* pkg-config
@@ -365,10 +365,14 @@ More information can be found in the :ref:`decoder_plugins` reference.
Configuring encoder plugins
---------------------------
Encoders are used by some of the output plugins (such as shout). The encoder settings are included in the audio_output section.
Encoders are used by some of the output plugins (such as shout). The
encoder settings are included in the ``audio_output`` section, see :ref:`config_audio_output`.
More information can be found in the :ref:`encoder_plugins` reference.
.. _config_audio_output:
Configuring audio outputs
-------------------------
@@ -398,14 +402,9 @@ The following table lists the audio_output options valid for all plugins:
- The name of the plugin
* - **name**
- The name of the audio output. It is visible to the client. Some plugins also use it internally, e.g. as a name registered in the PULSE server.
* - **format**
- Always open the audio output with the specified audio format samplerate:bits:channels), regardless of the format of the input file. This is optional for most plugins.
Any of the three attributes may be an asterisk to specify that this attribute should not be enforced, example: 48000:16:*. *:*:* is equal to not having a format specification.
The following values are valid for bits: 8 (signed 8 bit integer samples), 16, 24 (signed 24 bit integer samples padded to 32 bit), 32 (signed 32 bit integer samples), f (32 bit floating point, -1.0 to 1.0), "dsd" means DSD (Direct Stream Digital). For DSD, there are special cases such as "dsd64", which allows you to omit the sample rate (e.g. dsd512:2 for stereo DSD512, i.e. 22.5792 MHz).
The sample rate is special for DSD: :program:`MPD` counts the number of bytes, not bits. Thus, a DSD "bit" rate of 22.5792 MHz (DSD512) is 2822400 from :program:`MPD`'s point of view (44100*512/8).
* - **format samplerate:bits:channels**
- Always open the audio output with the specified audio format, regardless of the format of the input file. This is optional for most plugins.
See :ref:`audio_output_format` for a detailed description of the value.
* - **enabed yes|no**
- Specifies whether this audio output is enabled when :program:`MPD` is started. By default, all audio outputs are enabled. This is just the default setting when there is no state file; with a state file, the previous state is restored.
* - **tags yes|no**
@@ -421,6 +420,15 @@ The following table lists the audio_output options valid for all plugins:
implement an external mixer :ref:`external_mixer`) or no mixer
(:samp:`none`). By default, the hardware mixer is used for
devices which support it, and none for the others.
* - **filters "name,...**"
- The specified configured filters are instantiated in the given
order. Each filter name refers to a ``filter`` block, see
:ref:`config_filter`.
More information can be found in the :ref:`output_plugins` reference.
.. _config_filter:
Configuring filters
-------------------
@@ -436,6 +444,9 @@ To configure a filter, add a :code:`filter` block to :file:`mpd.conf`:
name "software volume"
}
Configured filters may then be added to the ``filters`` setting of an
``audio_output`` section, see :ref:`config_audio_output`.
The following table lists the filter options valid for all plugins:
.. list-table::
@@ -449,6 +460,9 @@ The following table lists the filter options valid for all plugins:
* - **name**
- The name of the filter
More information can be found in the :ref:`filter_plugins` reference.
Configuring playlist plugins
----------------------------
@@ -485,10 +499,31 @@ reference.
Audio Format Settings
---------------------
.. _audio_output_format:
Global Audio Format
~~~~~~~~~~~~~~~~~~~
The setting audio_output_format forces :program:`MPD` to use one audio format for all outputs. Doing that is usually not a good idea. The values are the same as in format in the audio_output section.
The setting ``audio_output_format`` forces :program:`MPD` to use one
audio format for all outputs. Doing that is usually not a good idea.
The value is specified as ``samplerate:bits:channels``.
Any of the three attributes may be an asterisk to specify that this
attribute should not be enforced, example: ``48000:16:*``.
``*:*:*`` is equal to not having a format specification.
The following values are valid for bits: ``8`` (signed 8 bit integer
samples), ``16``, ``24`` (signed 24 bit integer samples padded to 32
bit), ``32`` (signed 32 bit integer samples), ``f`` (32 bit floating
point, -1.0 to 1.0), ``dsd`` means DSD (Direct Stream Digital). For
DSD, there are special cases such as ``dsd64``, which allows you to
omit the sample rate (e.g. ``dsd512:2`` for stereo DSD512,
i.e. 22.5792 MHz).
The sample rate is special for DSD: :program:`MPD` counts the number
of bytes, not bits. Thus, a DSD "bit" rate of 22.5792 MHz (DSD512) is
2822400 from :program:`MPD`'s point of view (44100*512/8).
Resampler
~~~~~~~~~
@@ -531,6 +566,12 @@ choice::
bind_to_address "/var/run/mpd/socket"
On Linux, local sockets can be bound to a name without a socket inode
on the filesystem; MPD implements this by prepending ``@`` to the
address::
bind_to_address "@mpd"
If no port is specified, the default port is 6600. This default can
be changed with the port setting::
@@ -860,7 +901,7 @@ To verify if :program:`MPD` converts the audio format, enable verbose logging, a
.. code-block:: none
decoder: audio_format=44100:24:2, seekable=true
output: opened plugin=alsa name="An ALSA output"audio_format=44100:16:2
output: opened plugin=alsa name="An ALSA output" audio_format=44100:16:2
output: converting from 44100:24:2
This example shows that a 24 bit file is being played, but the sound chip cannot play 24 bit. It falls back to 16 bit, discarding 8 bit.
@@ -887,7 +928,7 @@ Check list for bit-perfect playback:
device (:samp:`hw:0,0` or similar).
* Don't use software volume (setting :code:`mixer_type`).
* Don't force :program:`MPD` to use a specific audio format (settings
:code:`format`, :code:`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.
Direct Stream Digital (DSD)

View File

@@ -1,8 +1,8 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.5',
meson_version: '>= 0.47.2',
version: '0.21.9',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',
'cpp_std=c++14'
@@ -20,7 +20,7 @@ conf.set_quoted('PACKAGE', meson.project_name())
conf.set_quoted('PACKAGE_NAME', meson.project_name())
conf.set_quoted('PACKAGE_VERSION', meson.project_version())
conf.set_quoted('VERSION', meson.project_version())
conf.set_quoted('PROTOCOL_VERSION', '0.21.4')
conf.set_quoted('PROTOCOL_VERSION', '0.21.6')
conf.set_quoted('SYSTEM_CONFIG_FILE_LOCATION', join_paths(get_option('prefix'), get_option('sysconfdir'), 'mpd.conf'))
common_cppflags = [
@@ -367,8 +367,10 @@ basic_dep = declare_dependency(
if enable_database
subdir('src/storage')
subdir('src/db')
else
storage_glue_dep = dependency('', required: false)
endif
subdir('src/db')
if neighbor_glue_dep.found()
sources += 'src/command/NeighborCommands.cxx'
@@ -390,6 +392,7 @@ more_deps = []
if is_android
subdir('src/java')
target_type = 'shared_library'
target_name = 'mpd'
link_args += [
'-Wl,--no-undefined,-shared,-Bsymbolic',
'-llog',
@@ -399,12 +402,20 @@ if is_android
declare_dependency(sources: [classes_jar]),
java_dep,
]
elif is_haiku
target_type = 'executable'
target_name = 'mpd.nores'
link_args += [
'-lnetwork',
'-lbe',
]
else
target_type = 'executable'
target_name = 'mpd'
endif
mpd = build_target(
'mpd',
target_name,
sources,
target_type: target_type,
include_directories: inc,
@@ -443,6 +454,14 @@ endif
if is_haiku
subdir('src/haiku')
custom_target(
'mpd',
output: 'mpd',
input: [mpd, rsrc],
command: [addres, '@OUTPUT@', '@INPUT0@', '@INPUT1@'],
install: true,
install_dir: get_option('bindir'),
)
endif
configure_file(output: 'config.h', configuration: conf)

View File

@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.1.1.tar.xz',
'373749824dfd334d84e55dff406729edfd1606575ee44dd485d97d45ea4d2d86',
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.64.0.tar.xz',
'2f2f13fa34d44aa29cb444077ad7dc4dc6d189584ad552e0aaeb06e608af6001',
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -375,8 +375,8 @@ libexpat = AutotoolsProject(
)
libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-3.0.0.tar.gz',
'445d92c5fc55e4a5b115e358e60486cf8f87ee50e0103d46a02e7fb4618566a5',
'https://github.com/sahlberg/libnfs/archive/libnfs-4.0.0.tar.gz',
'6ee77e9fe220e2d3e3b1f53cfea04fb319828cc7dbb97dd9df09e46e901d797d',
'lib/libnfs.a',
[
'--disable-shared', '--enable-static',
@@ -387,12 +387,12 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples',
],
base='libnfs-libnfs-3.0.0',
base='libnfs-libnfs-4.0.0',
autoreconf=True,
)
boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.69.0/boost_1_69_0.tar.bz2',
'8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406',
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
'include/boost/version.hpp',
)

View File

@@ -108,17 +108,17 @@ static constexpr Domain cmdline_domain("cmdline");
gcc_noreturn
static void version(void)
{
printf("Music Player Daemon " VERSION " (%s)\n"
printf("Music Player Daemon " VERSION " (%s)"
"\n"
"Copyright 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
"Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
GIT_VERSION);
#ifdef ENABLE_DATABASE
"\n"
"Database plugins:\n",
GIT_VERSION);
printf("\n"
"Database plugins:\n");
for (auto i = database_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
@@ -129,18 +129,18 @@ static void version(void)
for (auto i = storage_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
printf("\n"
printf("\n");
#endif
#ifdef ENABLE_NEIGHBOR_PLUGINS
"\n"
printf("\n"
"Neighbor plugins:\n");
for (auto i = neighbor_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
printf("\n"
#endif
printf("\n"
"\n"
"Decoders plugins:\n");

View File

@@ -55,14 +55,26 @@ LocateFileUri(const char *uri, const Client *client
}
static LocatedUri
LocateAbsoluteUri(const char *uri
LocateAbsoluteUri(UriPluginKind kind, const char *uri
#ifdef ENABLE_DATABASE
, const Storage *storage
#endif
)
{
if (!uri_supported_scheme(uri))
throw std::runtime_error("Unsupported URI scheme");
switch (kind) {
case UriPluginKind::INPUT:
case UriPluginKind::STORAGE: // TODO: separate check for storage plugins
if (!uri_supported_scheme(uri))
throw std::runtime_error("Unsupported URI scheme");
break;
case UriPluginKind::PLAYLIST:
/* for now, no validation for playlist URIs; this is
more complicated because there are three ways to
identify which plugin to use: URI scheme, filename
suffix and MIME type */
break;
}
#ifdef ENABLE_DATABASE
if (storage != nullptr) {
@@ -76,7 +88,8 @@ LocateAbsoluteUri(const char *uri
}
LocatedUri
LocateUri(const char *uri, const Client *client
LocateUri(UriPluginKind kind,
const char *uri, const Client *client
#ifdef ENABLE_DATABASE
, const Storage *storage
#endif
@@ -100,7 +113,7 @@ LocateUri(const char *uri, const Client *client
#endif
);
else if (uri_has_scheme(uri))
return LocateAbsoluteUri(uri
return LocateAbsoluteUri(kind, uri
#ifdef ENABLE_DATABASE
, storage
#endif

View File

@@ -41,6 +41,12 @@ class Client;
class Storage;
#endif
enum class UriPluginKind {
INPUT,
STORAGE,
PLAYLIST,
};
struct LocatedUri {
enum class Type {
/**
@@ -84,7 +90,8 @@ struct LocatedUri {
* that feature is disabled if this parameter is nullptr
*/
LocatedUri
LocateUri(const char *uri, const Client *client
LocateUri(UriPluginKind kind,
const char *uri, const Client *client
#ifdef ENABLE_DATABASE
, const Storage *storage
#endif

View File

@@ -134,7 +134,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info,
const auto *const name_fs_end =
FindStringSuffix(name_fs_str,
PATH_LITERAL(PLAYLIST_FILE_SUFFIX));
if (name_fs_end == nullptr)
if (name_fs_end == nullptr ||
/* no empty playlist names (raw file name = ".m3u") */
name_fs_end == name_fs_str)
return false;
FileInfo fi;

View File

@@ -94,7 +94,8 @@ SongLoader::LoadSong(const char *uri_utf8) const
assert(uri_utf8 != nullptr);
#endif
const auto located_uri = LocateUri(uri_utf8, client
const auto located_uri = LocateUri(UriPluginKind::INPUT,
uri_utf8, client
#ifdef ENABLE_DATABASE
, storage
#endif

View File

@@ -268,7 +268,10 @@ handle_list(Client &client, Request args, Response &r)
std::unique_ptr<SongFilter> filter;
TagType group = TAG_NUM_OF_ITEM_TYPES;
if (args.size == 1) {
if (args.size == 1 &&
/* parantheses are the syntax for filter expressions: no
compatibility mode */
args.front()[0] != '(') {
/* for compatibility with < 0.12.0 */
if (tagType != TAG_ALBUM) {
r.FormatError(ACK_ERROR_ARG,

View File

@@ -218,7 +218,7 @@ handle_read_comments(Client &client, Request args, Response &r)
const char *const uri = args.front();
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
@@ -331,7 +331,7 @@ handle_album_art(Client &client, Request args, Response &r)
const char *uri = args.front();
size_t offset = args.ParseUnsigned(1);
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif

View File

@@ -99,7 +99,7 @@ handle_listfiles(Client &client, Request args, Response &r)
/* default is root directory */
const auto uri = args.GetOptional(0, "");
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::STORAGE, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
@@ -219,7 +219,7 @@ handle_lsinfo(Client &client, Request args, Response &r)
compatibility, work around this here */
uri = "";
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif

View File

@@ -20,6 +20,7 @@
#include "config.h"
#include "PlaylistCommands.hxx"
#include "Request.hxx"
#include "Instance.hxx"
#include "db/DatabasePlaylist.hxx"
#include "CommandError.hxx"
#include "PlaylistSave.hxx"
@@ -27,6 +28,7 @@
#include "PlaylistError.hxx"
#include "db/PlaylistVector.hxx"
#include "SongLoader.hxx"
#include "song/DetachedSong.hxx"
#include "BulkEdit.hxx"
#include "playlist/PlaylistQueue.hxx"
#include "playlist/Print.hxx"
@@ -38,6 +40,7 @@
#include "util/UriUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/ChronoUtil.hxx"
#include "LocateUri.hxx"
bool
playlist_commands_available() noexcept
@@ -66,22 +69,43 @@ handle_save(Client &client, Request args, gcc_unused Response &r)
CommandResult
handle_load(Client &client, Request args, gcc_unused Response &r)
{
const auto uri = LocateUri(UriPluginKind::PLAYLIST, args.front(),
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
RangeArg range = args.ParseOptional(1, RangeArg::All());
const ScopeBulkEdit bulk_edit(client.GetPartition());
auto &playlist = client.GetPlaylist();
const unsigned old_size = playlist.GetLength();
const SongLoader loader(client);
playlist_open_into_queue(args.front(),
playlist_open_into_queue(uri,
range.start, range.end,
client.GetPlaylist(),
playlist,
client.GetPlayerControl(), loader);
/* invoke the RemoteTagScanner on all newly added songs */
auto &instance = client.GetInstance();
const unsigned new_size = playlist.GetLength();
for (unsigned i = old_size; i < new_size; ++i)
instance.LookupRemoteTag(playlist.queue.Get(i).GetURI());
return CommandResult::OK;
}
CommandResult
handle_listplaylist(Client &client, Request args, Response &r)
{
const char *const name = args.front();
const auto name = LocateUri(UriPluginKind::PLAYLIST, args.front(),
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
name, false))
@@ -93,7 +117,12 @@ handle_listplaylist(Client &client, Request args, Response &r)
CommandResult
handle_listplaylistinfo(Client &client, Request args, Response &r)
{
const char *const name = args.front();
const auto name = LocateUri(UriPluginKind::PLAYLIST, args.front(),
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
name, true))

View File

@@ -83,7 +83,8 @@ handle_add(Client &client, Request args, Response &r)
here */
uri = "";
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif

View File

@@ -29,6 +29,8 @@ ServerSocketAddGeneric(ServerSocket &server_socket, const char *address, unsigne
server_socket.AddPort(port);
} else if (address[0] == '/' || address[0] == '~') {
server_socket.AddPath(ParsePath(address));
} else if (address[0] == '@') {
server_socket.AddAbstract(address);
} else {
server_socket.AddHost(address, port);
}

View File

@@ -23,14 +23,14 @@
class ServerSocket;
/**
* Sets the address or unix socket of a ServerSocket instance
* Sets the address or local socket of a ServerSocket instance
* There are three possible ways
* 1) Set address to a valid ip address and specify port.
* server_socket will listen on this address/port tuple.
* 2) Set address to null and specify port.
* server_socket will listen on ANY address on that port.
* 3) Set address to a path of a unix socket. port is ignored.
* server_socket will listen on this unix socket.
* 3) Set address to a path of a local socket. port is ignored.
* server_socket will listen on this local socket.
*
* Throws #std::runtime_error on error.
*

View File

@@ -9,6 +9,11 @@ db_api_dep = declare_dependency(
link_with: db_api,
)
if not enable_database
db_glue_dep = db_api_dep
subdir_done()
endif
subdir('plugins')
db_glue_sources = [

View File

@@ -568,7 +568,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
if (!is_idle) {
// TODO: can this happen?
IdleMonitor::Schedule();
return false;
SocketMonitor::Cancel();
return true;
}
unsigned idle = (unsigned)mpd_recv_idle(connection, false);
@@ -586,7 +587,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
idle_received |= idle;
is_idle = false;
IdleMonitor::Schedule();
return false;
SocketMonitor::Cancel();
return true;
}
void

View File

@@ -39,8 +39,8 @@ InitHybridDsdDecoder(const ConfigBlock &block)
without a DSD DAC, the PCM (=ALAC) part of the file is
better */
if (block.GetBlockParam("enabled") == nullptr) {
LogInfo(hybrid_dsd_domain,
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
LogDebug(hybrid_dsd_domain,
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
return false;
}

View File

@@ -208,10 +208,12 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
TagBuilder tag_builder;
AddTagHandler h(tag_builder);
if (ScanOpusTags(packet.packet, packet.bytes, &rgi, h) &&
!tag_builder.empty()) {
client.SubmitReplayGain(&rgi);
if (!ScanOpusTags(packet.packet, packet.bytes, &rgi, h))
return;
client.SubmitReplayGain(&rgi);
if (!tag_builder.empty()) {
Tag tag = tag_builder.Commit();
auto cmd = client.SubmitTag(input_stream, std::move(tag));
if (cmd != DecoderCommand::NONE)

View File

@@ -110,15 +110,9 @@ BufferedSocket::OnSocketReady(unsigned flags) noexcept
if (flags & READ) {
assert(!input.IsFull());
if (!ReadToBuffer())
if (!ReadToBuffer() || !ResumeInput())
return false;
if (!ResumeInput())
/* we must return "true" here or
SocketMonitor::Dispatch() will call
Cancel() on a freed object */
return true;
if (!input.IsFull())
ScheduleRead();
}

View File

@@ -46,6 +46,11 @@ public:
using SocketMonitor::Close;
private:
/**
* @return the number of bytes read from the socket, 0 if the
* socket isn't ready for reading, -1 on error (the socket has
* been closed and probably destructed)
*/
ssize_t DirectRead(void *data, size_t length) noexcept;
/**

View File

@@ -45,6 +45,11 @@ public:
}
private:
/**
* @return the number of bytes written to the socket, 0 if the
* socket isn't ready for writing, -1 on error (the socket has
* been closed and probably destructed)
*/
ssize_t DirectWrite(const void *data, size_t length) noexcept;
protected:

View File

@@ -392,7 +392,29 @@ ServerSocket::AddPath(AllocatedPath &&path)
#else /* !HAVE_UN */
(void)path;
throw std::runtime_error("UNIX domain socket support is disabled");
throw std::runtime_error("Local socket support is disabled");
#endif /* !HAVE_UN */
}
void
ServerSocket::AddAbstract(const char *name)
{
#if !defined(__linux__)
(void)name;
throw std::runtime_error("Abstract sockets are only available on Linux");
#elif !defined(HAVE_UN)
(void)name;
throw std::runtime_error("Local socket support is disabled");
#else
assert(name != nullptr);
assert(*name == '@');
AllocatedSocketAddress address;
address.SetLocal(name);
AddAddress(std::move(address));
#endif
}

View File

@@ -90,7 +90,7 @@ public:
void AddHost(const char *hostname, unsigned port);
/**
* Add a listener on a Unix domain socket.
* Add a listener on a local socket.
*
* Throws #std::runtime_error on error.
*
@@ -99,6 +99,16 @@ public:
*/
void AddPath(AllocatedPath &&path);
/**
* Add a listener on an abstract local socket (Linux specific).
*
* Throws on error.
*
* @param name the abstract socket name, starting with a '@'
* instead of a null byte
*/
void AddAbstract(const char *name);
/**
* Add a socket descriptor that is accepting connections. After this
* has been called, don't call server_socket_open(), because the

View File

@@ -33,8 +33,8 @@ SocketMonitor::Dispatch(unsigned flags) noexcept
{
flags &= GetScheduledFlags();
if (flags != 0 && !OnSocketReady(flags) && IsDefined())
Cancel();
if (flags != 0)
OnSocketReady(flags);
}
SocketMonitor::~SocketMonitor() noexcept

View File

@@ -65,7 +65,7 @@ public:
/**
* Flush pending data and return it. This should be called
* repepatedly until it returns nullptr.
* repeatedly until it returns nullptr.
*/
virtual ConstBuffer<void> Flush();
};

View File

@@ -56,6 +56,7 @@ public:
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
ConstBuffer<void> Flush() override;
};
class PreparedAutoConvertFilter final : public PreparedFilter {
@@ -104,6 +105,18 @@ AutoConvertFilter::FilterPCM(ConstBuffer<void> src)
return filter->FilterPCM(src);
}
ConstBuffer<void>
AutoConvertFilter::Flush()
{
if (convert != nullptr) {
auto result = convert->Flush();
if (!result.IsNull())
return filter->FilterPCM(result);
}
return filter->Flush();
}
std::unique_ptr<PreparedFilter>
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept
{

3
src/haiku/add_resources.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
cp "$2" "$1" && xres -o "$1" -- "$3" && mimeset -f "$1" || (rm -f "$1"; exit 1)

View File

@@ -1,18 +1,26 @@
rc = meson.find_program('rc')
xres = meson.find_program('xres')
haiku_conf = configuration_data()
haiku_conf.set('VERSION', meson.project_version())
splitted_version = meson.project_version().split('~')[0].split('.')
haiku_conf.set('VERSION_MAJOR', splitted_version[0])
haiku_conf.set('VERSION_MINOR', splitted_version.get(1, '0'))
haiku_conf.set('VERSION_REVISION', splitted_version.get(2, '0'))
haiku_conf.set('VERSION_EXTRA', splitted_version.get(3, '0'))
mpd_rdef = configure_file(
input: 'mpd.rdef.in',
output: 'mpd.rdef',
configuration: haiku_conf,
)
rc = find_program('rc')
xres = find_program('xres')
rsrc = custom_target(
'mpd.rsrc',
output: 'mpd.rsrc',
input: 'mpd.rdef',
input: mpd_rdef,
command: [rc, '-o', '@OUTPUT@', '@INPUT@'],
)
custom_target(
'mpd.rsrc',
output: 'mpd',
input: [mpd, rsrc],
command: [xres, '-o', '@OUTPUT@', '--', '@INPUT@'],
install: true,
install_dir: get_option('bindir'),
)
addres = files('add_resources.sh')

View File

@@ -2,7 +2,15 @@ resource app_signature "application/x-vnd.MusicPD";
resource app_flags B_BACKGROUND_APP;
// TODO: resource app_version {};
resource app_version {
major = @VERSION_MAJOR@,
middle = @VERSION_MINOR@,
minor = @VERSION_REVISION@,
variety = B_APPV_ALPHA,
internal = @VERSION_EXTRA@,
short_info = "Music Player Daemon @VERSION@",
long_info = "Music Player Daemon @VERSION@ ©The Music Player Daemon Project"
};
resource vector_icon {
$"6E6369661F050102031604BEE29BBEC5403EC540BEE29B4A10004A10000001C6"

View File

@@ -59,6 +59,9 @@ BufferedInputStream::~BufferedInputStream() noexcept
void
BufferedInputStream::Check()
{
if (read_error)
std::rethrow_exception(read_error);
if (input)
input->Check();
}
@@ -101,7 +104,7 @@ BufferedInputStream::IsEOF() noexcept
bool
BufferedInputStream::IsAvailable() noexcept
{
return IsEOF() || buffer.Read(offset).HasData();
return IsEOF() || buffer.Read(offset).HasData() || read_error;
}
size_t
@@ -164,6 +167,32 @@ BufferedInputStream::RunThread() noexcept
idle = false;
seek = false;
client_cond.signal();
} else if (!idle && !read_error &&
offset != input->GetOffset() &&
!IsAvailable()) {
/* a past Seek() call was a no-op because data
was already available at that position, but
now we've reached a new position where
there is no more data in the buffer, and
our input is reading somewhere else (maybe
stuck at the end of the file); to find a
way out, we now seek our input to our
reading position to be able to fill our
buffer */
try {
input->Seek(offset);
} catch (...) {
/* this is really a seek error, but we
register it as a read_error,
because seek_error is only checked
by Seek(), and at our frontend (our
own InputStream interface) is in
"read" mode */
read_error = std::current_exception();
client_cond.signal();
InvokeOnAvailable();
}
} else if (!idle && !read_error &&
input->IsAvailable() && !input->IsEOF()) {
const auto read_offset = input->GetOffset();

View File

@@ -306,9 +306,8 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block)
{
try {
curl_init = new CurlInit(event_loop);
} catch (const std::runtime_error &e) {
LogError(e);
throw PluginUnavailable(e.what());
} catch (...) {
std::throw_with_nested(PluginUnavailable("CURL initialization failed"));
}
const auto version_info = curl_version_info(CURLVERSION_FIRST);

View File

@@ -22,14 +22,13 @@
#include "lib/smbclient/Mutex.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "../MaybeBufferedInputStream.hxx"
#include "PluginUnavailable.hxx"
#include "system/Error.hxx"
#include "util/ASCII.hxx"
#include <libsmbclient.h>
#include <stdexcept>
class SmbclientInputStream final : public InputStream {
SMBCCTX *ctx;
int fd;
@@ -72,9 +71,8 @@ input_smbclient_init(EventLoop &, const ConfigBlock &)
{
try {
SmbclientInit();
} catch (const std::runtime_error &e) {
// TODO: use std::throw_with_nested()?
throw PluginUnavailable(e.what());
} catch (...) {
std::throw_with_nested(PluginUnavailable("libsmbclient initialization failed"));
}
// TODO: create one global SMBCCTX here?
@@ -115,8 +113,9 @@ input_smbclient_open(const char *uri,
throw MakeErrno(e, "smbc_fstat() failed");
}
return std::make_unique<SmbclientInputStream>(uri, mutex,
ctx, fd, st);
return std::make_unique<MaybeBufferedInputStream>
(std::make_unique<SmbclientInputStream>(uri, mutex,
ctx, fd, st));
}
size_t

View File

@@ -43,6 +43,8 @@
#include <stdexcept>
#include <utility>
#include <cstdio>
class CdromDrive {
cdrom_drive_t *drv = nullptr;

View File

@@ -1,4 +1,17 @@
# Since version 0.49.0 Meson has native libgcrypt dependency support, which has
# the advantage over find_library() as it uses libgcrypt-config to query the
# required linker flags.
# However, we still need to use find_library() first, to prevent Meson
# falsly assuming a target libgcrypt is available in case there is no
# libgcrypt-config entry in the cross file and libgcrypt is installed on the
# host. In this case, Meson will falsly use the native libgcrypt-config and
# will falsly assume it has found the gcrypt library for the target.
#
# See: https://github.com/MusicPlayerDaemon/MPD/pull/495
gcrypt_dep = c_compiler.find_library('gcrypt', required: get_option('qobuz'))
if gcrypt_dep.found()
gcrypt_dep = dependency('libgcrypt')
endif
if not gcrypt_dep.found()
subdir_done()
endif

View File

@@ -20,6 +20,7 @@
#include "OggVisitor.hxx"
#include <stdexcept>
#include <utility>
void
OggVisitor::EndStream()
@@ -51,7 +52,13 @@ OggVisitor::ReadNextPage()
inline void
OggVisitor::HandlePacket(const ogg_packet &packet)
{
const bool _post_seek = std::exchange(post_seek, false);
if (packet.b_o_s) {
if (_post_seek)
/* ignore the BOS packet after seeking */
return;
EndStream();
has_stream = true;
OnOggBeginning(packet);
@@ -97,4 +104,6 @@ OggVisitor::PostSeek()
/* find the next Ogg page and feed it into the stream */
sync.ExpectPageSeekIn(stream);
post_seek = true;
}

View File

@@ -39,6 +39,14 @@ class OggVisitor {
bool has_stream = false;
/**
* This is true after seeking; its one-time effect is to
* ignore the BOS packet, just in case we have been seeking to
* the beginning of the file, because that would disrupt
* playback.
*/
bool post_seek = false;
public:
explicit OggVisitor(Reader &reader)
:sync(reader), stream(0) {}

View File

@@ -1,7 +1,20 @@
libflac_dep = dependency('flac', version: '>= 1.2', required: get_option('flac'))
libopus_dep = dependency('opus', required: get_option('opus'))
libvorbis_dep = dependency('vorbis', required: get_option('vorbis'))
libvorbisidec_dep = dependency('vorbisidec', required: get_option('tremor'))
if get_option('tremor').enabled()
# no libvorbis if Tremor was explicitly enabled
libvorbis_dep = dependency('', required: false)
else
libvorbis_dep = dependency('vorbis', required: get_option('vorbis'))
endif
if libvorbis_dep.found()
# no Tremor if libvorbis is used
libvorbisidec_dep = dependency('', required: false)
else
# attempt to auto-detect Tremor only if libvorbis was disabled or not found
libvorbisidec_dep = dependency('vorbisidec', required: get_option('tremor'))
endif
if get_option('vorbis').enabled() and get_option('tremor').enabled()
error('Cannot build both, the Vorbis decoder AND the Tremor (Vorbis fixed-point) decoder')

View File

@@ -150,8 +150,6 @@ ReadServers(NeighborExplorer::List &list, int fd)
smbc_dirent *e;
while ((e = smbc_readdir(fd)) != nullptr)
ReadEntry(list, *e);
smbc_closedir(fd);
}
static void

View File

@@ -168,7 +168,7 @@ public:
}
constexpr operator SocketAddress() const noexcept {
return SocketAddress((const struct sockaddr *)&address,
return SocketAddress((const struct sockaddr *)(const void *)&address,
sizeof(address));
}

View File

@@ -135,7 +135,7 @@ public:
}
constexpr operator SocketAddress() const noexcept {
return SocketAddress((const struct sockaddr *)&address,
return SocketAddress((const struct sockaddr *)(const void *)&address,
sizeof(address));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -315,6 +315,13 @@ SocketDescriptor::SetTcpDeferAccept(const int &seconds) noexcept
return SetOption(IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds));
}
bool
SocketDescriptor::SetTcpUserTimeout(const unsigned &milliseconds) noexcept
{
return SetOption(IPPROTO_TCP, TCP_USER_TIMEOUT,
&milliseconds, sizeof(milliseconds));
}
bool
SocketDescriptor::SetV6Only(bool value) noexcept
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -188,6 +188,12 @@ public:
bool SetCork(bool value=true) noexcept;
bool SetTcpDeferAccept(const int &seconds) noexcept;
/**
* Setter for TCP_USER_TIMEOUT.
*/
bool SetTcpUserTimeout(const unsigned &milliseconds) noexcept;
bool SetV6Only(bool value) noexcept;
/**

View File

@@ -35,7 +35,7 @@ socket_bind_listen(int domain, int type, int protocol,
throw MakeSocketError("Failed to create socket");
#ifdef HAVE_UN
if (domain == AF_UNIX) {
if (domain == AF_LOCAL) {
/* Prevent access until right permissions are set */
fchmod(fd.Get(), 0);
}

View File

@@ -32,7 +32,7 @@ class SocketAddress;
/**
* Creates a socket listening on the specified address. This is a
* shortcut for socket(), bind() and listen().
* When a unix socket is created (domain == AF_UNIX), its
* When a local socket is created (domain == AF_LOCAL), its
* permissions will be stripped down to prevent unauthorized
* access. The caller is responsible to apply proper permissions
* at a later point.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -29,6 +29,7 @@
#include "config.h"
#include "StaticSocketAddress.hxx"
#include "util/StringView.hxx"
#include <algorithm>
@@ -50,6 +51,16 @@ StaticSocketAddress::operator=(SocketAddress other) noexcept
return *this;
}
#ifdef HAVE_UN
StringView
StaticSocketAddress::GetLocalRaw() const noexcept
{
return SocketAddress(*this).GetLocalRaw();
}
#endif
#ifdef HAVE_TCP
bool

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -66,14 +66,6 @@ public:
return reinterpret_cast<const struct sockaddr *>(&address);
}
struct sockaddr *GetAddress() noexcept {
return reinterpret_cast<struct sockaddr *>(&address);
}
const struct sockaddr *GetAddress() const noexcept {
return reinterpret_cast<const struct sockaddr *>(&address);
}
constexpr size_type GetCapacity() const noexcept {
return sizeof(address);
}
@@ -109,6 +101,14 @@ public:
address.ss_family = AF_UNSPEC;
}
#ifdef HAVE_UN
/**
* @see SocketAddress::GetLocalRaw()
*/
gcc_pure
StringView GetLocalRaw() const noexcept;
#endif
#ifdef HAVE_TCP
/**
* Extract the port number. Returns 0 if not applicable.

View File

@@ -106,7 +106,7 @@ ToString(SocketAddress address) noexcept
{
#ifdef HAVE_UN
if (address.GetFamily() == AF_LOCAL)
/* return path of UNIX domain sockets */
/* return path of local socket */
return LocalAddressToString(*(const sockaddr_un *)address.GetAddress(),
address.GetSize());
#endif

View File

@@ -140,9 +140,6 @@ HaikuOutput::Close() noexcept
HaikuOutput::~HaikuOutput()
{
delete_sem(new_buffer);
delete_sem(buffer_done);
finalize_application();
}

View File

@@ -581,8 +581,8 @@ PulseOutput::SetupStream(const pa_sample_spec &ss)
/* WAVE-EX is been adopted as the speaker map for most media files */
pa_channel_map chan_map;
pa_channel_map_init_auto(&chan_map, ss.channels,
PA_CHANNEL_MAP_WAVEEX);
pa_channel_map_init_extend(&chan_map, ss.channels,
PA_CHANNEL_MAP_WAVEEX);
stream = pa_stream_new(context, name, &ss, &chan_map);
if (stream == nullptr)
throw MakePulseError(context,

View File

@@ -154,7 +154,7 @@ HttpdClient::SendResponse() noexcept
FormatWarning(httpd_output_domain,
"failed to write to client: %s",
(const char *)msg);
Close();
LockClose();
return false;
}
@@ -428,6 +428,7 @@ void
HttpdClient::OnSocketError(std::exception_ptr ep) noexcept
{
LogError(ep);
LockClose();
}
void

View File

@@ -142,6 +142,8 @@ public:
/**
* Frees the client and removes it from the server's client list.
*
* Caller must lock the mutex.
*/
void Close() noexcept;

View File

@@ -208,10 +208,15 @@ public:
return HasClients();
}
/**
* Caller must lock the mutex.
*/
void AddClient(UniqueSocketDescriptor fd) noexcept;
/**
* Removes a client from the httpd_output.clients linked list.
*
* Caller must lock the mutex.
*/
void RemoveClient(HttpdClient &client) noexcept;
@@ -239,10 +244,14 @@ public:
/**
* Broadcasts data from the encoder to all clients.
*
* Mutext must not be locked.
*/
void BroadcastFromEncoder();
/**
* Mutext must not be locked.
*
* Throws #std::runtime_error on error.
*/
void EncodeAndPlay(const void *chunk, size_t size);
@@ -251,6 +260,9 @@ public:
size_t Play(const void *chunk, size_t size) override;
/**
* Mutext must not be locked.
*/
void CancelAllClients() noexcept;
void Cancel() noexcept override;

View File

@@ -76,7 +76,10 @@ if is_darwin
audiounit_dep = declare_dependency(
link_args: [
'-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices',
]
],
dependencies: [
boost_dep,
],
)
else
audiounit_dep = dependency('', required: false)

View File

@@ -229,6 +229,14 @@ SlesOutput::Open(AudioFormat &audio_format)
SL_ANDROID_KEY_STREAM_TYPE,
&stream_type,
sizeof(stream_type));
/* MPD doesn't care much about latency, so let's
configure power saving mode */
SLuint32 performance_mode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
(*android_config)->SetConfiguration(android_config,
SL_ANDROID_KEY_PERFORMANCE_MODE,
&performance_mode,
sizeof(performance_mode));
}
result = play_object.Realize(false);

View File

@@ -996,7 +996,7 @@ Player::Run() noexcept
}
}
if (dc.IsIdle() && queued && dc.pipe == pipe) {
if (dc.IsIdle() && queued) {
/* the decoder has finished the current song;
make it decode the next song */
@@ -1058,6 +1058,16 @@ Player::Run() noexcept
SongBorder();
} else if (dc.IsIdle()) {
if (queued)
/* the decoder has just stopped,
between the two IsIdle() checks,
probably while UnlockCheckOutputs()
left the mutex unlocked; to restart
the decoder instead of stopping
playback completely, let's re-enter
this loop */
continue;
/* check the size of the pipe again, because
the decoder thread may have added something
since we last checked */

View File

@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "LocateUri.hxx"
#include "PlaylistAny.hxx"
#include "PlaylistStream.hxx"
#include "PlaylistMapper.hxx"
@@ -25,17 +26,26 @@
#include "config.h"
std::unique_ptr<SongEnumerator>
playlist_open_any(const char *uri,
playlist_open_any(const LocatedUri &located_uri,
#ifdef ENABLE_DATABASE
const Storage *storage,
#endif
Mutex &mutex)
{
return uri_has_scheme(uri)
? playlist_open_remote(uri, mutex)
: playlist_mapper_open(uri,
switch (located_uri.type) {
case LocatedUri::Type::ABSOLUTE:
return playlist_open_remote(located_uri.canonical_uri, mutex);
case LocatedUri::Type::PATH:
return playlist_open_path(located_uri.path, mutex);
case LocatedUri::Type::RELATIVE:
return playlist_mapper_open(located_uri.canonical_uri,
#ifdef ENABLE_DATABASE
storage,
#endif
mutex);
}
gcc_unreachable();
}

View File

@@ -34,7 +34,7 @@ class Storage;
* music or playlist directory.
*/
std::unique_ptr<SongEnumerator>
playlist_open_any(const char *uri,
playlist_open_any(const LocatedUri &located_uri,
#ifdef ENABLE_DATABASE
const Storage *storage,
#endif

View File

@@ -18,6 +18,7 @@
*/
#include "config.h"
#include "LocateUri.hxx"
#include "PlaylistQueue.hxx"
#include "PlaylistAny.hxx"
#include "PlaylistSong.hxx"
@@ -63,7 +64,7 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
}
void
playlist_open_into_queue(const char *uri,
playlist_open_into_queue(const LocatedUri &uri,
unsigned start_index, unsigned end_index,
playlist &dest, PlayerControl &pc,
const SongLoader &loader)
@@ -78,7 +79,7 @@ playlist_open_into_queue(const char *uri,
if (playlist == nullptr)
throw PlaylistError::NoSuchList();
playlist_load_into_queue(uri, *playlist,
playlist_load_into_queue(uri.canonical_uri, *playlist,
start_index, end_index,
dest, pc, loader);
}

View File

@@ -49,7 +49,7 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
* play queue.
*/
void
playlist_open_into_queue(const char *uri,
playlist_open_into_queue(const LocatedUri &uri,
unsigned start_index, unsigned end_index,
playlist &dest, PlayerControl &pc,
const SongLoader &loader);

View File

@@ -18,6 +18,7 @@
*/
#include "config.h"
#include "LocateUri.hxx"
#include "Print.hxx"
#include "PlaylistAny.hxx"
#include "PlaylistSong.hxx"
@@ -55,7 +56,7 @@ playlist_provider_print(Response &r,
bool
playlist_file_print(Response &r, Partition &partition,
const SongLoader &loader,
const char *uri, bool detail)
const LocatedUri &uri, bool detail)
{
Mutex mutex;
@@ -71,6 +72,6 @@ playlist_file_print(Response &r, Partition &partition,
if (playlist == nullptr)
return false;
playlist_provider_print(r, loader, uri, *playlist, detail);
playlist_provider_print(r, loader, uri.canonical_uri, *playlist, detail);
return true;
}

View File

@@ -34,6 +34,6 @@ struct Partition;
bool
playlist_file_print(Response &r, Partition &partition,
const SongLoader &loader,
const char *uri, bool detail);
const LocatedUri &uri, bool detail);
#endif

View File

@@ -34,7 +34,7 @@
#include <FLAC/metadata.h>
class FlacPlaylist final : public SongEnumerator {
const char *const uri;
const std::string uri;
FLAC__StreamMetadata *const cuesheet;
const unsigned sample_rate;

View File

@@ -22,13 +22,10 @@
#include <assert.h>
inline bool
bool
StringFilter::MatchWithoutNegation(const char *s) const noexcept
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(s != nullptr);
#endif
#ifdef HAVE_PCRE
if (regex)

View File

@@ -105,7 +105,9 @@ public:
gcc_pure
bool Match(const char *s) const noexcept;
private:
/**
* Like Match(), but ignore the "negated" flag.
*/
gcc_pure
bool MatchWithoutNegation(const char *s) const noexcept;
};

View File

@@ -35,43 +35,41 @@ TagSongFilter::ToExpression() const noexcept
}
bool
TagSongFilter::MatchNN(const TagItem &item) const noexcept
TagSongFilter::Match(const Tag &tag) const noexcept
{
return (type == TAG_NUM_OF_ITEM_TYPES || item.type == type) &&
filter.Match(item.value);
}
bool
TagSongFilter::MatchNN(const Tag &tag) const noexcept
{
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
bool visited_types[TAG_NUM_OF_ITEM_TYPES]{};
for (const auto &i : tag) {
visited_types[i.type] = true;
if (MatchNN(i))
return true;
if ((type == TAG_NUM_OF_ITEM_TYPES || i.type == type) &&
filter.MatchWithoutNegation(i.value))
return !filter.IsNegated();
}
if (type < TAG_NUM_OF_ITEM_TYPES && !visited_types[type]) {
/* if the specified tag is not present, try the
fallback tags */
bool result = false;
if (ApplyTagFallback(type,
[&](TagType tag2) {
if (!visited_types[tag2])
return false;
if (ApplyTagFallback(type, [&](TagType tag2) {
if (!visited_types[tag2])
/* we already know that this tag type
isn't present, so let's bail out
without checking again */
return false;
for (const auto &item : tag) {
if (item.type == tag2 &&
filter.Match(item.value)) {
result = true;
break;
}
}
for (const auto &item : tag) {
if (item.type == tag2 &&
filter.MatchWithoutNegation(item.value)) {
result = true;
break;
}
}
return true;
}))
return result;
return true;
}))
return result != filter.IsNegated();
/* If the search critieron was not visited during the
sweep through the song's tag, it means this field
@@ -80,14 +78,14 @@ TagSongFilter::MatchNN(const Tag &tag) const noexcept
then it's a match as well and we should return
true. */
if (filter.empty())
return true;
return !filter.IsNegated();
}
return false;
return filter.IsNegated();
}
bool
TagSongFilter::Match(const LightSong &song) const noexcept
{
return MatchNN(song.tag);
return Match(song.tag);
}

View File

@@ -68,8 +68,7 @@ public:
bool Match(const LightSong &song) const noexcept override;
private:
bool MatchNN(const Tag &tag) const noexcept;
bool MatchNN(const TagItem &tag) const noexcept;
bool Match(const Tag &tag) const noexcept;
};
#endif

View File

@@ -79,6 +79,11 @@ public:
return FileDescriptor::CreatePipe(r, w);
}
static bool CreatePipeNonBlock(UniqueFileDescriptor &r,
UniqueFileDescriptor &w) noexcept {
return FileDescriptor::CreatePipeNonBlock(r, w);
}
static bool CreatePipe(FileDescriptor &r, FileDescriptor &w) noexcept;
#endif

View File

@@ -22,6 +22,11 @@
#include <utility>
/**
* Invoke the given function for all fallback tags of the given
* #TagType, until the function returns true (or until there are no
* more fallback tags).
*/
template<typename F>
bool
ApplyTagFallback(TagType type, F &&f) noexcept
@@ -43,6 +48,11 @@ ApplyTagFallback(TagType type, F &&f) noexcept
return false;
}
/**
* Invoke the given function for the given #TagType and all of its
* fallback tags, until the function returns true (or until there are
* no more fallback tags).
*/
template<typename F>
bool
ApplyTagWithFallback(TagType type, F &&f) noexcept

View File

@@ -1,4 +1,11 @@
threads_dep = dependency('threads')
if is_windows
# avoid the unused libwinpthread-1.dll dependency on Windows; MPD
# doesn't use the pthread API on Windows, but this is what Meson
# unhelpfully detects for us
threads_dep = []
else
threads_dep = dependency('threads')
endif
conf.set('HAVE_PTHREAD_SETNAME_NP', compiler.has_function('pthread_setname_np', dependencies: threads_dep))

View File

@@ -50,7 +50,7 @@ protected:
/* virtual methods from class SocketMonitor */
bool OnSocketReady(gcc_unused unsigned flags) noexcept override {
DNSServiceProcessResult(service_ref);
return false;
return true;
}
};

View File

@@ -1,5 +1,5 @@
[Socket]
ListenStream=/run/mpd/socket
ListenStream=%t/mpd/socket
ListenStream=6600
Backlog=5
KeepAlive=true

View File

@@ -3,6 +3,12 @@ if systemd_user_unit_dir == ''
systemd_user_unit_dir = join_paths(get_option('prefix'), 'lib', 'systemd', 'user')
endif
# copy the system socket unit to the "user" directory
install_data(
join_paths('..', 'system', 'mpd.socket'),
install_dir: systemd_user_unit_dir,
)
configure_file(
input: 'mpd.service.in',
output: 'mpd.service',

45
test/MakeTag.hxx Normal file
View File

@@ -0,0 +1,45 @@
/*
* Copyright 2003-2019 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 "tag/Builder.hxx"
#include "tag/Tag.hxx"
#include "util/Compiler.h"
inline void
BuildTag(gcc_unused TagBuilder &tag) noexcept
{
}
template<typename... Args>
inline void
BuildTag(TagBuilder &tag, TagType type, const char *value,
Args&&... args) noexcept
{
tag.AddItem(type, value);
BuildTag(tag, std::forward<Args>(args)...);
}
template<typename... Args>
inline Tag
MakeTag(Args&&... args) noexcept
{
TagBuilder tag;
BuildTag(tag, std::forward<Args>(args)...);
return tag.Commit();
}

View File

@@ -165,8 +165,8 @@ public:
return GetCommand();
}
void SubmitReplayGain(const ReplayGainInfo *) {}
void SubmitMixRamp(MixRampInfo &&) {}
void SubmitReplayGain(const ReplayGainInfo *) override {}
void SubmitMixRamp(MixRampInfo &&) override {}
};
void

163
test/TestTagSongFilter.cxx Normal file
View File

@@ -0,0 +1,163 @@
/*
* Copyright 2003-2019 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 "MakeTag.hxx"
#include "song/TagSongFilter.hxx"
#include "song/LightSong.hxx"
#include "tag/Type.h"
#include <gtest/gtest.h>
static bool
InvokeFilter(const TagSongFilter &f, const Tag &tag) noexcept
{
return f.Match(LightSong("dummy", tag));
}
TEST(TagSongFilter, Basic)
{
const TagSongFilter f(TAG_TITLE,
StringFilter("needle", false, false, false));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "needle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle", TAG_TITLE, "foo")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo", TAG_TITLE, "needle", TAG_ALBUM, "bar")));
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "bar")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_ARTIST, "needle")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedleBAR")));
}
/**
* Test with empty string. This matches tags where the given tag type
* does not exist.
*/
TEST(TagSongFilter, Empty)
{
const TagSongFilter f(TAG_TITLE,
StringFilter("", false, false, false));
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "bar")));
}
TEST(TagSongFilter, Substring)
{
const TagSongFilter f(TAG_TITLE,
StringFilter("needle", false, true, false));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needleBAR")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedleBAR")));
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "eedle")));
}
TEST(TagSongFilter, Negated)
{
const TagSongFilter f(TAG_TITLE,
StringFilter("needle", false, false, true));
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
}
/**
* Combine the "Empty" and "Negated" tests.
*/
TEST(TagSongFilter, EmptyNegated)
{
const TagSongFilter f(TAG_TITLE,
StringFilter("", false, false, true));
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
}
/**
* Negation with multiple tag values.
*/
TEST(TagSongFilter, MultiNegated)
{
const TagSongFilter f(TAG_TITLE,
StringFilter("needle", false, false, true));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "bar")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle", TAG_TITLE, "bar")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "needle")));
}
/**
* Check whether fallback tags work, e.g. AlbumArtist falls back to
* just Artist if there is no AlbumArtist.
*/
TEST(TagSongFilter, Fallback)
{
const TagSongFilter f(TAG_ALBUM_ARTIST,
StringFilter("needle", false, false, false));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "needle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle")));
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "foo")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo")));
/* no fallback, thus the Artist tag isn't used and this must
be a mismatch */
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle", TAG_ALBUM_ARTIST, "foo")));
}
/**
* Combine the "Empty" and "Fallback" tests.
*/
TEST(TagSongFilter, EmptyFallback)
{
const TagSongFilter f(TAG_ALBUM_ARTIST,
StringFilter("", false, false, false));
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "foo")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo")));
}
/**
* Combine the "Negated" and "Fallback" tests.
*/
TEST(TagSongFilter, NegatedFallback)
{
const TagSongFilter f(TAG_ALBUM_ARTIST,
StringFilter("needle", false, false, true));
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "foo")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "needle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo")));
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle")));
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle", TAG_ALBUM_ARTIST, "foo")));
}

View File

@@ -20,16 +20,6 @@ gtest_dep = declare_dependency(
subdir('net')
executable(
'ParseSongFilter',
'ParseSongFilter.cxx',
include_directories: inc,
dependencies: [
song_dep,
pcm_dep,
],
)
executable(
'read_conf',
'read_conf.cxx',
@@ -211,6 +201,33 @@ if zlib_dep.found()
)
endif
#
# Filter
#
executable(
'ParseSongFilter',
'ParseSongFilter.cxx',
include_directories: inc,
dependencies: [
song_dep,
pcm_dep,
],
)
test(
'TestSongFilter',
executable(
'TestSongFilter',
'TestTagSongFilter.cxx',
include_directories: inc,
dependencies: [
song_dep,
gtest_dep,
],
)
)
#
# Neighbor
#

View File

@@ -2,6 +2,7 @@
* Unit tests for playlist_check_translate_song().
*/
#include "MakeTag.hxx"
#include "playlist/PlaylistSong.hxx"
#include "song/DetachedSong.hxx"
#include "SongLoader.hxx"
@@ -38,28 +39,6 @@ uri_supported_scheme(const char *uri) noexcept
static constexpr auto music_directory = PATH_LITERAL("/music");
static Storage *storage;
static void
BuildTag(gcc_unused TagBuilder &tag)
{
}
template<typename... Args>
static void
BuildTag(TagBuilder &tag, TagType type, const char *value, Args&&... args)
{
tag.AddItem(type, value);
BuildTag(tag, std::forward<Args>(args)...);
}
template<typename... Args>
static Tag
MakeTag(Args&&... args)
{
TagBuilder tag;
BuildTag(tag, std::forward<Args>(args)...);
return tag.Commit();
}
static Tag
MakeTag1a()
{

View File

@@ -1,7 +1,7 @@
windows_conf = configuration_data()
windows_conf.set('VERSION', meson.project_version())
splitted_version = meson.project_version().split('.')
splitted_version = meson.project_version().split('~')[0].split('.')
windows_conf.set('VERSION_MAJOR', splitted_version[0])
windows_conf.set('VERSION_MINOR', splitted_version.get(1, '0'))
windows_conf.set('VERSION_REVISION', splitted_version.get(2, '0'))