Compare commits

...

145 Commits

Author SHA1 Message Date
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
Max Kellermann
1930d5774d release v0.21.5 2019-02-22 15:23:33 +01:00
Max Kellermann
7220a76be0 doc/plugins.rst: document udisks2/policykit rule 2019-02-22 15:22:20 +01:00
Max Kellermann
83f7610dd1 storage/udisks2: move empty string check out of the fallback block in MapUTF8()
Even if the LocalStorage is available, return the "udisks://" URI when
the MapUTF8() parameter is an empty string.  This fixes the mount URI
in the state file.
2019-02-22 15:07:40 +01:00
Max Kellermann
30e0644722 db/simple: call ReturnSong() on mounted database
Fixes a memory leak, or an assertion failure in the debug build.
2019-02-22 14:52:13 +01:00
Max Kellermann
3ada464020 db/simple: use C++11 initializer 2019-02-22 14:52:01 +01:00
Max Kellermann
d5983dd362 storage/udisks2: use the relative path
Closes 
2019-02-22 14:41:56 +01:00
Max Kellermann
98258acc37 storage/udisks2: pass Path to SetMountPoint() 2019-02-22 14:41:56 +01:00
Max Kellermann
8002bc752f NEWS: mention the udisks2 AlreadyMounted fix 2019-02-22 14:41:56 +01:00
Max Kellermann
834ad7a58f TagPrint: omit tags which were disabled by the client
Closes 
2019-02-22 13:05:38 +01:00
Max Kellermann
e8f2f98048 tag/Mask: fix another typo, this time in operator^=
Similar to commit ff1ff1e54a
2019-02-22 12:44:36 +01:00
Max Kellermann
c672b60d07 build/pkg-config.sh: add comment 2019-02-22 12:39:59 +01:00
Max Kellermann
ea269c9c92 python/build/libs.py: upgrade CURL to 7.64.0 2019-02-22 12:10:06 +01:00
Max Kellermann
1fe3a77640 python/build/libs.py: upgrade FFmpeg to 4.1.1 2019-02-22 12:09:35 +01:00
Max Kellermann
bbaeea1ab7 storage/udisks2: use existing mount point if already mounted
Fixes the "org.freedesktop.UDisks2.Error.AlreadyMounted" error.

Closes 
2019-02-21 13:32:03 +01:00
Max Kellermann
0a3aee9d82 storage/udisks2: move code to SetMountPoint() 2019-02-21 13:31:59 +01:00
Max Kellermann
2434020971 storage/udisks2: adjust lambda indent 2019-02-21 13:31:57 +01:00
Max Kellermann
41e0eb7378 lib/dbus/udisks2: parse the MountPoints property 2019-02-21 13:28:26 +01:00
Max Kellermann
6adf964c81 lib/dbus/ReadIter: add dbus_message_iter_get_fixed_array() wrapper 2019-02-21 12:56:05 +01:00
Max Kellermann
b59f37bc0a db/simple/Directory: close the Database in destructor
Fixes assertion failure.
2019-02-20 22:50:15 +01:00
Max Kellermann
cf2d171ccc db/simple: reorder checks in assert() to fix assertion failure
`light_song.Get()` could cause an assertion failure because the
`Manual<>` object must not be used if uninitialized.

Regression by commit ebc006ab52
2019-02-20 21:24:01 +01:00
Max Kellermann
cc28a7b67f Main: create Database on stack, move to Instance after Open() succeeded
This fixes use-after-free bug in SimpleDatabase::Close(), accessing
the `root` object which was already freed by the `catch` block in
Open().

By having the Database on the stack first, we can avoid calling
Close() on the failed-to-open Database from Instance's destructor.

Closes 
2019-02-20 20:50:28 +01:00
Max Kellermann
8b5c33cecd Instance: use std::unique_ptr<> to manage the Database pointer 2019-02-20 20:48:20 +01:00
Max Kellermann
6c28adbcd2 db/Plugin: use std::unique_ptr<> to manage Database pointers 2019-02-20 20:43:31 +01:00
Max Kellermann
2125e3ed57 db/simple/Directory: add noexcept 2019-02-20 20:39:49 +01:00
Max Kellermann
3da7ecfadf mixer/pulse: add missing ParseFloat() check 2019-02-20 19:27:13 +01:00
Max Kellermann
5bb02bbd39 mixer/pulse: move volume_scale_factor up to improve struct packing 2019-02-20 19:25:55 +01:00
Max Kellermann
f11aa09f7c mixer/pulse: add const to volume_scale_factor 2019-02-20 19:25:53 +01:00
Max Kellermann
02eb4752d3 mixer/pulse: use C++11 initializer 2019-02-20 19:25:45 +01:00
Max Kellermann
d9c3215584 mixer/pulse: rename scale to scale_volume
Make it less generic, to avoid clashes.
2019-02-20 19:23:11 +01:00
Clément Pit-Claudel
110e6d026b mixer/pulse: Add a new 'scale' parameter to allow volumes above 100
Closes GH-479.
2019-02-17 16:14:52 -05:00
Max Kellermann
c0f57b8a8b net/IPv[46]Address: update copyright 2019-02-19 13:00:45 +01:00
Max Kellermann
57633fbcb3 net/AllocatedSocketAddress: add methods IsV6Any(), IsV4Mapped() 2019-02-19 12:51:24 +01:00
Max Kellermann
864c87e6c0 net/SocketAddress: add method GetLocalPath() 2019-02-19 12:50:40 +01:00
Max Kellermann
1a516cf3c0 net/AllocatedSocketAddress: add method GetLocalRaw() 2019-02-19 12:43:16 +01:00
Max Kellermann
5c25499c5e lib/cdio/Paranoia: add method GetDiscSectorRange() 2019-02-19 12:40:36 +01:00
Max Kellermann
da4bb4c298 fs/io/OutputStream: update include guard 2019-02-19 12:39:29 +01:00
Max Kellermann
5b8ff61799 fs/io/BufferedOutputStream: add WithBufferedOutputStream() 2019-02-19 12:37:53 +01:00
Max Kellermann
56bded07b1 system/UniqueFileDescriptor: import std::swap 2019-02-19 12:36:54 +01:00
Max Kellermann
db144a43ad system/Open: add OpenWriteOnly(), OpenDirectory() 2019-02-19 12:16:41 +01:00
Max Kellermann
5965f62b56 system/EpollFD: include cleanup 2019-02-19 11:51:52 +01:00
Max Kellermann
05aa9f72a9 util/StringView: add SkipPrefix(), RemoveSuffix() 2019-02-19 11:51:32 +01:00
Max Kellermann
281461f0f0 nfs: work around assertion failure on exception during program init
Closes 
2019-02-15 18:33:58 +01:00
Max Kellermann
f70eb63879 Instance: eliminate FinishShutdownUpdate(), move code to destructor 2019-02-15 18:20:11 +01:00
Max Kellermann
99c23cf139 Instance: eliminate ShutdownDatabase(), move code to destructor
Destruct automatically, even if leaving the scope due to exception
being thrown.
2019-02-15 18:04:23 +01:00
Max Kellermann
9aa75e738c Merge branch 'protocol-doc-typo' of git://github.com/mxjeff/MPD 2019-02-15 18:03:49 +01:00
Max Kellermann
e9c45a9140 playlist/Registry: add RAII class 2019-02-05 23:03:29 +01:00
Max Kellermann
a065c6e6b9 Main: use AtScopeExit() to call DeinitFS() 2019-02-05 23:02:50 +01:00
Max Kellermann
feb5ff9bd2 Mapper: remove empty function mapper_finish() 2019-02-05 23:01:09 +01:00
Max Kellermann
92ec3f0881 valgrind.suppressions: add GObject/libgcrypt/libsmbclient suppressions 2019-02-05 22:53:02 +01:00
Max Kellermann
98c47d9d36 Instance: remove FinishShutdownPartitions()
The list of partitions is cleared automatically.
2019-02-05 22:53:02 +01:00
Max Kellermann
6c67408944 event/Loop: add flag alive
This replaces the old `dead` flag which was unreliable; it was `false`
if the EventThread was not yet started, which could cause deadlocks in
BlockingCall().
2019-02-05 22:38:45 +01:00
Max Kellermann
261a816b21 command/AllCommands: remove empty function command_finish() 2019-02-05 22:15:41 +01:00
Max Kellermann
7a23c123c8 decoder/List: add RAII class 2019-02-05 22:12:22 +01:00
Max Kellermann
e85b24bee0 decoder/List: add noexcept 2019-02-05 22:11:51 +01:00
Max Kellermann
9e73ea77b4 input/Init: add RAII class 2019-02-05 22:07:49 +01:00
Max Kellermann
b0739eca87 test/ConfigGlue: merge duplicate code from various debug programs 2019-02-05 21:56:20 +01:00
Max Kellermann
848f6aa5ab Main: stop io_thread and rtio_thread automatically
They will be stopped by ~EventThread() when the `Instance` is deleted.
2019-02-05 21:49:59 +01:00
Max Kellermann
c9ba4f3f9c archive/List: add RAII class 2019-02-05 21:40:07 +01:00
Max Kellermann
c0e9246a66 archive/List: add noexcept 2019-02-05 21:38:46 +01:00
Max Kellermann
096c23f27d unix/SignalHandlers: add RAII class 2019-02-05 21:36:51 +01:00
Max Kellermann
40bde1eac9 unix/SignalHandlers: add noexcept 2019-02-05 21:36:35 +01:00
Max Kellermann
4b55ed17a9 LogInit: add noexcept 2019-02-05 21:36:35 +01:00
kaliko
4f757a5add Fixed protocol documentation
* "lsinfo" argument is optional
 * "tagtypes disable" arguments are mandatory (typo)
2019-02-03 10:38:34 +01:00
Max Kellermann
674c137e5f NEWS: mention the TagMask typo fix 2019-02-02 15:17:25 +01:00
kaliko
ff1ff1e54a Fixed typo in TagMask 2019-02-02 15:14:31 +01:00
Yue Wang
42b22187c8 [OSXOutput] Throw an error when device not found
Currently it falls back to system default device (either internal speaker or headphone) when device not found. 
I believe it is a better to fail in this case, to make it better aligned with platforms (such as alsa).
2019-01-25 19:50:27 -08:00
Max Kellermann
cfe22502ab fs/io/StdioOutputStream: add noexcept 2019-01-22 09:03:49 +01:00
Max Kellermann
d77b0c7dcd net/SocketAddress: add constexpr 2019-01-22 08:42:35 +01:00
Max Kellermann
5cf889b676 util/WStringView: add missing include 2019-01-22 08:38:03 +01:00
Max Kellermann
ffc36d5255 input/buffered: implement seeking to end of file
Previously, a seek to the end of the file would cause an assertion
failure in SparseMap::Check() because the given offset was invalid.

Closes 
2019-01-22 07:42:00 +01:00
Max Kellermann
0126276e2f FileCommands: log irregular errors while looking for cover art 2019-01-21 22:21:11 +01:00
Max Kellermann
58d6ddab9e FileCommands: catch all exceptions 2019-01-21 22:19:32 +01:00
Max Kellermann
05db6934eb FileCommands: fix deadlock in "albumart" command
Must lock the mutex before calling any of the unprotected InputStream methods.

Closes 
2019-01-21 22:16:46 +01:00
Max Kellermann
02c68c5cdb net/HostParser: add noexcept 2019-01-21 21:20:43 +01:00
Max Kellermann
b02fee7309 util/PrintException: support "const char *" 2019-01-21 21:19:35 +01:00
Max Kellermann
424f75c9e1 util/OffsetPointer: remove redundant inline keywords from constexpr functions 2019-01-21 21:19:09 +01:00
Max Kellermann
f6e1176f97 util/CharUtil: remove redundant inline keywords from constexpr functions 2019-01-21 21:18:23 +01:00
Max Kellermann
e4700c0a27 util/Cast: remove redundant inline keywords from constexpr functions 2019-01-21 21:17:58 +01:00
Max Kellermann
cf23fd8774 fs/io/FileOutputStream: add constructor with directory fd 2019-01-21 21:10:02 +01:00
Max Kellermann
dee8872395 fs/io/FileOutputStream: move code to Open() 2019-01-21 21:09:34 +01:00
Max Kellermann
4ba9357a9c input/CdioParanoia: C++ wrappers for libcdio types 2019-01-21 20:20:20 +01:00
Max Kellermann
48ec09ab1e test/net/TestIPv4Address: make literal unsigned to work around -Wsign-compare 2019-01-21 14:39:24 +01:00
Max Kellermann
754f4048a8 output/shout: evaluate tls option only if TLS is enabled in libshout
Fixes build failure after commit
0cea67ee70
2019-01-21 14:36:43 +01:00
Max Kellermann
037bb07d08 db/VHelper: include DetachedSong.hxx to fix GCC 9 build failure
GCC 9's libstdc++ is unable to use forward-declared types as
std::vector item because the compiler wants to resolve `noexcept()` on
the item destructor.
2019-01-21 14:34:12 +01:00
Max Kellermann
87635c5268 input/CdioParanoia: use the new function names 2019-01-21 14:18:55 +01:00
Max Kellermann
528b4338f4 input/CdioParanoia: use cdio_cddap_free_messages() on recent library versions 2019-01-21 14:16:51 +01:00
Max Kellermann
c780b8bba9 input/CdioParanoia: remove useless cdda_messages() call 2019-01-21 12:36:59 +01:00
Max Kellermann
ca34f3250b input/CdioParanoia: detect libcdio version at compile time
libcdio_paranoia was split from libcdio in version 90, and at the same
time, the header was moved from cdio/paranoia.h to
cdio/paranoia/paranoia.h.  We can easily detect this version at
compile time which is faster than configure time.
2019-01-21 12:14:13 +01:00
Max Kellermann
6a68e1c3f3 test/net/TestIPv6Address: work around failure on macOS 2019-01-21 12:13:52 +01:00
Max Kellermann
85f77ec81d test/net/TestLocalSocketAddress: can't use strcmp() if the string isn't null-terminated. 2019-01-21 12:12:36 +01:00
Max Kellermann
37debed0b8 python/build/libs.py: upgrade Boost to 1.69.0 2019-01-21 10:19:46 +01:00
Max Kellermann
008383f24a python/build/libs.py: upgrade CURL to 7.63.0 2019-01-21 10:11:50 +01:00
Jörg Krause
4f7d52dbf2 meson: add fixed-point Vorbis (Tremor) decoder support
Re-add build support for the fixed-point Vorbis (Tremor) decoder, which
was dropped when switching from Autotools to Meson.

Note, that it is not possible to build both, the Vorbis and the Tremor
decoder.

Closes: 
2019-01-21 08:35:17 +01:00
Max Kellermann
c7848da8f2 input/CdioParanoia: add const to pointer 2019-01-20 22:03:49 +01:00
Max Kellermann
10a6c5c57d input/CdioParanoia: make variables more local 2019-01-20 21:59:57 +01:00
Max Kellermann
2cc2bab309 test/net: new unit tests 2019-01-20 21:05:21 +01:00
Max Kellermann
701fd1d939 net/IPv4Address: fix comment typo 2019-01-20 21:05:12 +01:00
Max Kellermann
d1bdea8edb Merge branch 'shout_tls' of git://github.com/JakobOvrum/MPD 2019-01-20 21:03:42 +01:00
Jakob Ovrum
0cea67ee70 shout output plugin: add support for TLS 2019-01-19 17:36:14 +01:00
Thomas Klausner
3a0480a482 Add missing include of stdlib.h.
Closes https://github.com/MusicPlayerDaemon/MPD/issues/456
2019-01-15 16:52:40 +01:00
Max Kellermann
1fa99da3c2 net/IPv[46]Address: make the initializers even more portable
Similar to 5a5229b499: use more C++14
constexpr.
2019-01-14 19:21:07 +01:00
James D. Smith
22d669da18 Add APE mapping for album artist.
"De-facto" field mappings are available at http://wiki.hydrogenaud.io/index.php?title=Tag_Mapping.
2019-01-14 19:15:42 +01:00
Thomas Zander
772681f23d Fix link_args for mDNSResponder on non-darwin platforms 2019-01-13 14:09:14 +01:00
Max Kellermann
1862a98a44 increment version number to 0.21.5 2019-01-04 19:31:07 +01:00
148 changed files with 1959 additions and 681 deletions
NEWS
android
build
doc
meson.buildmeson_options.txt
python/build
src
Instance.cxxInstance.hxxLogInit.cxxLogInit.hxxMain.cxxMapper.cxxMapper.hxxPlaylistFile.cxxStateFile.cxxStats.cxxTagPrint.cxx
archive
command
config
db
decoder
event
fs
input
lib
mixer
net
output
playlist
song
storage
system
tag
thread
unix
util
zeroconf
test
valgrind.suppressions

43
NEWS

@@ -1,3 +1,46 @@
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
- fix "tagtypes disable" command
* database
- simple: fix assertion failure
- fix assertion failures with mount points
* storage
- udisks: fix "AlreadyMounted" error
- udisks: use relative path from mount URI
- fix memory leak
* input
- buffer: fix crash bug when playing remote WAV file
* tags
- ape: map "Album Artist"
* output
- shout: add support for TLS
* mixer
- pulse: add "scale_volume" setting
ver 0.21.4 (2019/01/04)
* database
- inotify: fix crash bug "terminate called after throwing ..."

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

@@ -1,5 +1,9 @@
#!/bin/sh -e
# This is a wrapper for pkg-config which helps with cross-compiling;
# it sets up environment variables to pkg-config searches for
# libraries in the sysroot where a copy of this script is located.
BIN=`dirname $0`
ROOT=`dirname "$BIN"`

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

@@ -87,6 +87,22 @@ Mount file systems (e.g. USB sticks or other removable media) using
the udisks2 daemon via D-Bus. To obtain a valid udisks2 URI, consult
:ref:`the according neighbor plugin <neighbor_plugin>`.
It might be necessary to grant :program:`MPD` privileges to control
:program:`udisks2` through :program:`policykit`. To do this, create a
file called :file:`/usr/share/polkit-1/rules.d/mpd-udisks.rules` with
the following text::
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.udisks2.filesystem-mount" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-other-seat") &&
subject.user == "mpd") {
return polkit.Result.YES;
}
});
If you run MPD as a different user, change ``mpd`` to the name of your
MPD user.
.. _neighbor_plugin:
Neighbor plugins
@@ -935,6 +951,8 @@ The pulse plugin connects to a `PulseAudio <http://www.freedesktop.org/wiki/Soft
- Sets the host name of the PulseAudio server. By default, :program:`MPD` connects to the local PulseAudio server.
* - **sink NAME**
- Specifies the name of the PulseAudio sink :program:`MPD` should play on.
* - **scale_volume FACTOR**
- Specifies a linear scaling coefficient (ranging from 0.5 to 5.0) to apply when adjusting volume through :program:`MPD`. For example, chosing a factor equal to ``"0.7"`` means that setting the volume to 100 in :program:`MPD` will set the PulseAudio volume to 70%, and a factor equal to ``"3.5"`` means that volume 100 in :program:`MPD` corresponds to a 350% PulseAudio volume.
recorder
~~~~~~~~
@@ -974,6 +992,8 @@ You must set a format.
- Set the timeout for the shout connection in seconds. Defaults to 2 seconds.
* - **protocol icecast2|icecast1|shoutcast**
- Specifies the protocol that wil be used to connect to the server. The default is "icecast2".
* - **tls disabled|auto|auto_no_plain|rfc2818|rfc2817**
- Specifies what kind of TLS to use. The default is "disabled" (no TLS).
* - **mount URI**
- Mounts the :program:`MPD` stream in the specified URI.
* - **user USERNAME**

@@ -144,15 +144,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.
@@ -178,7 +183,7 @@ of:
- ``(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 +212,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
@@ -714,7 +719,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 UNIX domain socket), or
remote playlists (absolute URI with a supported scheme).
:command:`listplaylist {NAME}`
@@ -909,7 +916,7 @@ The music database
.. _command_lsinfo:
:command:`lsinfo {URI}`
:command:`lsinfo [URI]`
Lists the contents of the directory
``URI``. The response contains records
starting with ``file``,
@@ -1131,7 +1138,7 @@ Connection settings
``tagtypes`` sub commands configure this
list.
:command:`tagtypes disable {NAME...]`
:command:`tagtypes disable {NAME...}`
Remove one or more tags from the list of tag types the
client is interested in. These will be omitted from
responses to this client.

@@ -531,6 +531,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::

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.4',
version: '0.21.6',
meson_version: '>= 0.47.2',
default_options: [
'c_std=c99',
@@ -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 = [

@@ -128,6 +128,7 @@ option('mpg123', type: 'feature', description: 'MP3 decoder using libmpg123')
option('opus', type: 'feature', description: 'Opus decoder plugin')
option('sidplay', type: 'feature', description: 'C64 SID support via libsidplayfp or libsidplay2')
option('sndfile', type: 'feature', description: 'libsndfile decoder plugin')
option('tremor', type: 'feature', description: 'Fixed-point vorbis decoder plugin')
option('vorbis', type: 'feature', description: 'Vorbis decoder plugin')
option('wavpack', type: 'feature', description: 'WavPack decoder plugin')
option('wildmidi', type: 'feature', description: 'WildMidi decoder plugin')

@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.1.tar.xz',
'a38ec4d026efb58506a99ad5cd23d5a9793b4bf415f2c4c2e9c1bb444acd1994',
'http://ffmpeg.org/releases/ffmpeg-4.1.1.tar.xz',
'373749824dfd334d84e55dff406729edfd1606575ee44dd485d97d45ea4d2d86',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.62.0.tar.xz',
'dab5643a5fe775ae92570b9f3df6b0ef4bc2a827a959361fb130c73b721275c1',
'http://curl.haxx.se/download/curl-7.64.0.tar.xz',
'2f2f13fa34d44aa29cb444077ad7dc4dc6d189584ad552e0aaeb06e608af6001',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -392,7 +392,7 @@ libnfs = AutotoolsProject(
)
boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.68.0/boost_1_68_0.tar.bz2',
'7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7',
'http://downloads.sourceforge.net/project/boost/boost/1.69.0/boost_1_69_0.tar.bz2',
'8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406',
'include/boost/version.hpp',
)

@@ -30,6 +30,9 @@
#ifdef ENABLE_DATABASE
#include "db/DatabaseError.hxx"
#include "db/Interface.hxx"
#include "db/update/Service.hxx"
#include "storage/StorageInterface.hxx"
#ifdef ENABLE_SQLITE
#include "sticker/StickerDatabase.hxx"
@@ -48,7 +51,19 @@ Instance::Instance()
{
}
Instance::~Instance() noexcept = default;
Instance::~Instance() noexcept
{
#ifdef ENABLE_DATABASE
delete update;
if (database != nullptr) {
database->Close();
database.reset();
}
delete storage;
#endif
}
Partition *
Instance::FindPartition(const char *name) noexcept

@@ -41,7 +41,7 @@ class NeighborGlue;
#ifdef ENABLE_DATABASE
#include "db/DatabaseListener.hxx"
class Database;
#include "db/Ptr.hxx"
class Storage;
class UpdateService;
#endif
@@ -104,7 +104,7 @@ struct Instance final
#endif
#ifdef ENABLE_DATABASE
Database *database;
DatabasePtr database;
/**
* This is really a #CompositeStorage. To avoid heavy include
@@ -147,7 +147,6 @@ struct Instance final
Partition *FindPartition(const char *name) noexcept;
void BeginShutdownPartitions() noexcept;
void FinishShutdownPartitions() noexcept;
#ifdef ENABLE_DATABASE
/**
@@ -156,7 +155,7 @@ struct Instance final
* music_directory was configured).
*/
Database *GetDatabase() {
return database;
return database.get();
}
/**
@@ -168,8 +167,6 @@ struct Instance final
#endif
void BeginShutdownUpdate() noexcept;
void FinishShutdownUpdate() noexcept;
void ShutdownDatabase() noexcept;
#ifdef ENABLE_CURL
void LookupRemoteTag(const char *uri) noexcept;

@@ -109,7 +109,7 @@ parse_log_level(const char *value, int line)
#endif
void
log_early_init(bool verbose)
log_early_init(bool verbose) noexcept
{
#ifdef ANDROID
(void)verbose;
@@ -171,7 +171,7 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
#ifndef ANDROID
static void
close_log_files(void)
close_log_files() noexcept
{
#ifdef HAVE_SYSLOG
LogFinishSysLog();
@@ -181,7 +181,7 @@ close_log_files(void)
#endif
void
log_deinit(void)
log_deinit() noexcept
{
#ifndef ANDROID
close_log_files();
@@ -213,7 +213,8 @@ void setup_log_output()
#endif
}
int cycle_log_files(void)
int
cycle_log_files() noexcept
{
#ifdef ANDROID
return 0;

@@ -31,7 +31,7 @@ struct ConfigData;
* @param verbose true when the program is started with --verbose
*/
void
log_early_init(bool verbose);
log_early_init(bool verbose) noexcept;
/**
* Throws #std::runtime_error on error.
@@ -40,12 +40,12 @@ void
log_init(const ConfigData &config, bool verbose, bool use_stdout);
void
log_deinit();
log_deinit() noexcept;
void
setup_log_output();
int
cycle_log_files();
cycle_log_files() noexcept;
#endif /* LOG_H */

@@ -185,19 +185,16 @@ InitStorage(const ConfigData &config, EventLoop &event_loop)
static bool
glue_db_init_and_load(const ConfigData &config)
{
instance->database =
CreateConfiguredDatabase(config, instance->event_loop,
instance->io_thread.GetEventLoop(),
*instance);
if (instance->database == nullptr)
auto db = CreateConfiguredDatabase(config, instance->event_loop,
instance->io_thread.GetEventLoop(),
*instance);
if (!db)
return true;
if (instance->database->GetPlugin().RequireStorage()) {
if (db->GetPlugin().RequireStorage()) {
InitStorage(config, instance->io_thread.GetEventLoop());
if (instance->storage == nullptr) {
delete instance->database;
instance->database = nullptr;
LogDefault(config_domain,
"Found database setting without "
"music_directory - disabling database");
@@ -211,22 +208,24 @@ glue_db_init_and_load(const ConfigData &config)
}
try {
instance->database->Open();
db->Open();
} catch (...) {
std::throw_with_nested(std::runtime_error("Failed to open database plugin"));
}
auto *db = dynamic_cast<SimpleDatabase *>(instance->database);
if (db == nullptr)
instance->database = std::move(db);
auto *sdb = dynamic_cast<SimpleDatabase *>(instance->database.get());
if (sdb == nullptr)
return true;
instance->update = new UpdateService(config,
instance->event_loop, *db,
instance->event_loop, *sdb,
static_cast<CompositeStorage &>(*instance->storage),
*instance);
/* run database update after daemonization? */
return db->FileExists();
return sdb->FileExists();
}
static bool
@@ -351,27 +350,6 @@ Instance::BeginShutdownUpdate() noexcept
#endif
}
inline void
Instance::FinishShutdownUpdate() noexcept
{
#ifdef ENABLE_DATABASE
delete update;
#endif
}
inline void
Instance::ShutdownDatabase() noexcept
{
#ifdef ENABLE_DATABASE
if (instance->database != nullptr) {
instance->database->Close();
delete instance->database;
}
delete instance->storage;
#endif
}
inline void
Instance::BeginShutdownPartitions() noexcept
{
@@ -381,12 +359,6 @@ Instance::BeginShutdownPartitions() noexcept
}
}
inline void
Instance::FinishShutdownPartitions() noexcept
{
partitions.clear();
}
void
Instance::OnIdle(unsigned flags)
{
@@ -523,18 +495,19 @@ static int
mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
{
ConfigureFS(raw_config);
AtScopeExit() { DeinitFS(); };
glue_mapper_init(raw_config);
initPermissions(raw_config);
spl_global_init(raw_config);
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
const ScopeArchivePluginsInit archive_plugins_init;
#endif
pcm_convert_global_init(raw_config);
decoder_plugin_init_all(raw_config);
const ScopeDecoderPluginsInit decoder_plugins_init(raw_config);
#ifdef ENABLE_DATABASE
const bool create_db = InitDatabaseAndStorage(raw_config);
@@ -553,9 +526,9 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
}
client_manager_init(raw_config);
input_stream_global_init(raw_config,
instance->io_thread.GetEventLoop());
playlist_list_global_init(raw_config);
const ScopeInputPluginsInit input_plugins_init(raw_config,
instance->io_thread.GetEventLoop());
const ScopePlaylistPluginsInit playlist_plugins_init(raw_config);
#ifdef ENABLE_DAEMON
daemonize_commit();
@@ -564,7 +537,7 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
#ifndef ANDROID
setup_log_output();
SignalHandlersInit(instance->event_loop);
const ScopeSignalHandlersInit signal_handlers_init(instance->event_loop);
#endif
instance->io_thread.Start();
@@ -652,34 +625,10 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
}
#endif
instance->FinishShutdownUpdate();
instance->ShutdownDatabase();
#ifdef ENABLE_SQLITE
sticker_global_finish();
#endif
playlist_list_global_finish();
input_stream_global_finish();
#ifdef ENABLE_DATABASE
mapper_finish();
#endif
DeinitFS();
instance->FinishShutdownPartitions();
command_finish();
decoder_plugin_deinit_all();
#ifdef ENABLE_ARCHIVE
archive_plugin_deinit_all();
#endif
instance->rtio_thread.Stop();
instance->io_thread.Stop();
#ifndef ANDROID
SignalHandlersFinish();
#endif
return EXIT_SUCCESS;
}

@@ -58,11 +58,6 @@ mapper_init(AllocatedPath &&_playlist_dir)
mapper_set_playlist_dir(std::move(_playlist_dir));
}
void
mapper_finish() noexcept
{
}
#ifdef ENABLE_DATABASE
AllocatedPath

@@ -37,9 +37,6 @@ class AllocatedPath;
void
mapper_init(AllocatedPath &&playlist_dir);
void
mapper_finish() noexcept;
#ifdef ENABLE_DATABASE
/**

@@ -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;

@@ -119,7 +119,7 @@ try {
TextFile file(config.path);
#ifdef ENABLE_DATABASE
const SongLoader song_loader(partition.instance.database,
const SongLoader song_loader(partition.instance.GetDatabase(),
partition.instance.storage);
#else
const SongLoader song_loader(nullptr, nullptr);

@@ -124,7 +124,7 @@ stats_print(Response &r, const Partition &partition)
std::lround(partition.pc.GetTotalPlayTime().count()));
#ifdef ENABLE_DATABASE
const Database *db = partition.instance.database;
const Database *db = partition.instance.GetDatabase();
if (db != nullptr)
db_stats_print(r, *db);
#endif

@@ -25,8 +25,9 @@
void
tag_print_types(Response &r) noexcept
{
const auto tag_mask = global_tag_mask & r.GetTagMask();
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
if (IsTagEnabled(i))
if (tag_mask.Test(TagType(i)))
r.Format("tagtype: %s\n", tag_item_names[i]);
}

@@ -49,7 +49,7 @@ static bool archive_plugins_enabled[ARRAY_SIZE(archive_plugins) - 1];
if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins])
const ArchivePlugin *
archive_plugin_from_suffix(const char *suffix)
archive_plugin_from_suffix(const char *suffix) noexcept
{
if (suffix == nullptr)
return nullptr;
@@ -63,7 +63,7 @@ archive_plugin_from_suffix(const char *suffix)
}
const ArchivePlugin *
archive_plugin_from_name(const char *name)
archive_plugin_from_name(const char *name) noexcept
{
archive_plugins_for_each_enabled(plugin)
if (strcmp(plugin->name, name) == 0)
@@ -81,7 +81,8 @@ void archive_plugin_init_all(void)
}
}
void archive_plugin_deinit_all(void)
void
archive_plugin_deinit_all() noexcept
{
archive_plugins_for_each_enabled(plugin)
if (plugin->finish != nullptr)

@@ -33,10 +33,10 @@ extern const ArchivePlugin *const archive_plugins[];
/* interface for using plugins */
const ArchivePlugin *
archive_plugin_from_suffix(const char *suffix);
archive_plugin_from_suffix(const char *suffix) noexcept;
const ArchivePlugin *
archive_plugin_from_name(const char *name);
archive_plugin_from_name(const char *name) noexcept;
/* this is where we "load" all the "plugins" ;-) */
void
@@ -44,6 +44,17 @@ archive_plugin_init_all();
/* this is where we "unload" all the "plugins" */
void
archive_plugin_deinit_all();
archive_plugin_deinit_all() noexcept;
class ScopeArchivePluginsInit {
public:
ScopeArchivePluginsInit() {
archive_plugin_init_all();
}
~ScopeArchivePluginsInit() noexcept {
archive_plugin_deinit_all();
}
};
#endif

@@ -285,11 +285,6 @@ command_init()
#endif
}
void
command_finish()
{
}
static const struct command *
command_lookup(const char *name)
{

@@ -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,

@@ -37,9 +37,11 @@
#include "fs/FileInfo.hxx"
#include "fs/DirectoryReader.hxx"
#include "input/InputStream.hxx"
#include "input/Error.hxx"
#include "LocateUri.hxx"
#include "TimePrint.hxx"
#include "thread/Mutex.hxx"
#include "Log.hxx"
#include <assert.h>
#include <inttypes.h> /* for PRIu64 */
@@ -256,7 +258,11 @@ find_stream_art(const char *directory, Mutex &mutex)
try {
return InputStream::OpenReady(art_file.c_str(), mutex);
} catch (const std::exception &e) {}
} catch (...) {
auto e = std::current_exception();
if (!IsFileNotFound(e))
LogError(e);
}
}
return nullptr;
}
@@ -285,8 +291,11 @@ read_stream_art(Response &r, const char *uri, size_t offset)
uint8_t buffer[CHUNK_SIZE];
size_t read_size;
is->Seek(offset);
read_size = is->Read(&buffer, CHUNK_SIZE);
{
const std::lock_guard<Mutex> protect(mutex);
is->Seek(offset);
read_size = is->Read(&buffer, CHUNK_SIZE);
}
r.Format("size: %" PRIoffset "\n"
"binary: %u\n",

@@ -294,7 +294,7 @@ handle_update(Client &client, Request args, Response &r, bool discard)
if (update != nullptr)
return handle_update(r, *update, path, discard);
Database *db = client.GetInstance().database;
Database *db = client.GetInstance().GetDatabase();
if (db != nullptr)
return handle_update(r, *db, path, discard);
#else

@@ -38,6 +38,7 @@
#include "util/UriUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/ChronoUtil.hxx"
#include "LocateUri.hxx"
bool
playlist_commands_available() noexcept
@@ -66,12 +67,17 @@ 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(args.front(), &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
RangeArg range = args.ParseOptional(1, RangeArg::All());
const ScopeBulkEdit bulk_edit(client.GetPartition());
const SongLoader loader(client);
playlist_open_into_queue(args.front(),
playlist_open_into_queue(uri,
range.start, range.end,
client.GetPlaylist(),
client.GetPlayerControl(), loader);
@@ -81,7 +87,11 @@ handle_load(Client &client, Request args, gcc_unused Response &r)
CommandResult
handle_listplaylist(Client &client, Request args, Response &r)
{
const char *const name = args.front();
const auto name = LocateUri(args.front(), &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
name, false))
@@ -93,7 +103,11 @@ 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(args.front(), &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
name, true))

@@ -209,7 +209,7 @@ handle_mount(Client &client, Request args, Response &r)
instance.EmitIdle(IDLE_MOUNT);
#ifdef ENABLE_DATABASE
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.database)) {
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
try {
db->Mount(local_uri, remote_uri);
} catch (...) {
@@ -253,7 +253,7 @@ handle_unmount(Client &client, Request args, Response &r)
destroy here */
instance.update->CancelMount(local_uri);
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.database)) {
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
if (db->Unmount(local_uri))
// TODO: call Instance::OnDatabaseModified()?
instance.EmitIdle(IDLE_DATABASE);

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

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "Configured.hxx"
#include "DatabaseGlue.hxx"
#include "Interface.hxx"
#include "config/Data.hxx"
#include "config/Param.hxx"
#include "config/Block.hxx"
@@ -26,7 +27,7 @@
#include "fs/StandardDirectory.hxx"
#include "util/RuntimeError.hxx"
Database *
DatabasePtr
CreateConfiguredDatabase(const ConfigData &config,
EventLoop &main_event_loop, EventLoop &io_event_loop,
DatabaseListener &listener)

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,10 +20,11 @@
#ifndef MPD_DB_CONFIG_HXX
#define MPD_DB_CONFIG_HXX
#include "Ptr.hxx"
struct ConfigData;
class EventLoop;
class DatabaseListener;
class Database;
/**
* Read database configuration settings and create a #Database
@@ -32,7 +33,7 @@ class Database;
*
* Throws #std::runtime_error on error.
*/
Database *
DatabasePtr
CreateConfiguredDatabase(const ConfigData &config,
EventLoop &main_event_loop, EventLoop &io_event_loop,
DatabaseListener &listener);

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,13 +18,14 @@
*/
#include "DatabaseGlue.hxx"
#include "Interface.hxx"
#include "Registry.hxx"
#include "DatabaseError.hxx"
#include "util/RuntimeError.hxx"
#include "config/Block.hxx"
#include "DatabasePlugin.hxx"
Database *
DatabasePtr
DatabaseGlobalInit(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,12 +20,11 @@
#ifndef MPD_DATABASE_GLUE_HXX
#define MPD_DATABASE_GLUE_HXX
#include "util/Compiler.h"
#include "Ptr.hxx"
struct ConfigBlock;
class EventLoop;
class DatabaseListener;
class Database;
/**
* Initialize the database library.
@@ -34,7 +33,7 @@ class Database;
*
* @param block the database configuration block
*/
Database *
DatabasePtr
DatabaseGlobalInit(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,10 +26,11 @@
#ifndef MPD_DATABASE_PLUGIN_HXX
#define MPD_DATABASE_PLUGIN_HXX
#include "Ptr.hxx"
struct ConfigBlock;
class EventLoop;
class DatabaseListener;
class Database;
struct DatabasePlugin {
/**
@@ -52,10 +53,10 @@ struct DatabasePlugin {
* @param io_event_loop the #EventLoop running on the
* #EventThread, i.e. the one used for background I/O
*/
Database *(*create)(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
DatabasePtr (*create)(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
constexpr bool RequireStorage() const {
return flags & FLAG_REQUIRE_STORAGE;

29
src/db/Ptr.hxx Normal file

@@ -0,0 +1,29 @@
/*
* 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.
*/
#ifndef MPD_DATABASE_PTR_HXX
#define MPD_DATABASE_PTR_HXX
#include <memory>
class Database;
typedef std::unique_ptr<Database> DatabasePtr;
#endif

@@ -18,11 +18,11 @@
*/
#include "VHelper.hxx"
#include "song/DetachedSong.hxx"
#include "song/LightSong.hxx"
#include "song/Filter.hxx"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
DatabaseVisitorHelper::DatabaseVisitorHelper(const DatabaseSelection &_selection,

@@ -22,6 +22,7 @@
#include "Visitor.hxx"
#include "Selection.hxx"
#include "song/DetachedSong.hxx"
#include <vector>

@@ -112,10 +112,10 @@ public:
ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
const ConfigBlock &block);
static Database *Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
static DatabasePtr Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
void Open() override;
void Close() noexcept override;
@@ -440,12 +440,12 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
{
}
Database *
DatabasePtr
ProxyDatabase::Create(EventLoop &loop, EventLoop &,
DatabaseListener &listener,
const ConfigBlock &block)
{
return new ProxyDatabase(loop, listener, block);
return std::make_unique<ProxyDatabase>(loop, listener, block);
}
void

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -37,22 +37,25 @@
#include <string.h>
#include <stdlib.h>
Directory::Directory(std::string &&_path_utf8, Directory *_parent)
Directory::Directory(std::string &&_path_utf8, Directory *_parent) noexcept
:parent(_parent),
path(std::move(_path_utf8))
{
}
Directory::~Directory()
Directory::~Directory() noexcept
{
delete mounted_database;
if (mounted_database != nullptr) {
mounted_database->Close();
mounted_database.reset();
}
songs.clear_and_dispose(Song::Disposer());
children.clear_and_dispose(DeleteDisposer());
}
void
Directory::Delete()
Directory::Delete() noexcept
{
assert(holding_db_lock());
assert(parent != nullptr);
@@ -70,7 +73,7 @@ Directory::GetName() const noexcept
}
Directory *
Directory::CreateChild(const char *name_utf8)
Directory::CreateChild(const char *name_utf8) noexcept
{
assert(holding_db_lock());
assert(name_utf8 != nullptr);
@@ -160,7 +163,7 @@ Directory::LookupDirectory(const char *uri) noexcept
}
void
Directory::AddSong(Song *song)
Directory::AddSong(Song *song) noexcept
{
assert(holding_db_lock());
assert(song != nullptr);

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
#include "util/Compiler.h"
#include "db/Visitor.hxx"
#include "db/PlaylistVector.hxx"
#include "db/Ptr.hxx"
#include "Song.hxx"
#include <boost/intrusive/list.hpp>
@@ -43,7 +44,6 @@ static constexpr unsigned DEVICE_INARCHIVE = -1;
static constexpr unsigned DEVICE_CONTAINER = -2;
class SongFilter;
class Database;
struct Directory {
static constexpr auto link_mode = boost::intrusive::normal_link;
@@ -96,21 +96,21 @@ struct Directory {
* If this is not nullptr, then this directory does not really
* exist, but is a mount point for another #Database.
*/
Database *mounted_database = nullptr;
DatabasePtr mounted_database;
public:
Directory(std::string &&_path_utf8, Directory *_parent);
~Directory();
Directory(std::string &&_path_utf8, Directory *_parent) noexcept;
~Directory() noexcept;
/**
* Create a new root #Directory object.
*/
gcc_malloc gcc_returns_nonnull
static Directory *NewRoot() {
static Directory *NewRoot() noexcept {
return new Directory(std::string(), nullptr);
}
bool IsMount() const {
bool IsMount() const noexcept {
return mounted_database != nullptr;
}
@@ -120,7 +120,7 @@ public:
*
* Caller must lock the #db_mutex.
*/
void Delete();
void Delete() noexcept;
/**
* Create a new #Directory object as a child of the given one.
@@ -129,7 +129,7 @@ public:
*
* @param name_utf8 the UTF-8 encoded name of the new sub directory
*/
Directory *CreateChild(const char *name_utf8);
Directory *CreateChild(const char *name_utf8) noexcept;
/**
* Caller must lock the #db_mutex.
@@ -149,7 +149,7 @@ public:
*
* Caller must lock the #db_mutex.
*/
Directory *MakeChild(const char *name_utf8) {
Directory *MakeChild(const char *name_utf8) noexcept {
Directory *child = FindChild(name_utf8);
if (child == nullptr)
child = CreateChild(name_utf8);
@@ -242,7 +242,7 @@ public:
* Add a song object to this directory. Its "parent" attribute must
* be set already.
*/
void AddSong(Song *song);
void AddSong(Song *song) noexcept;
/**
* Remove a song object from this directory (which effectively

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -60,8 +60,7 @@ inline SimpleDatabase::SimpleDatabase(const ConfigBlock &block)
#ifdef ENABLE_ZLIB
compress(block.GetBlockValue("compress", true)),
#endif
cache_path(block.GetPath("cache_directory")),
prefixed_light_song(nullptr)
cache_path(block.GetPath("cache_directory"))
{
if (path.IsNull())
throw std::runtime_error("No \"path\" parameter specified");
@@ -84,12 +83,12 @@ inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path,
prefixed_light_song(nullptr) {
}
Database *
DatabasePtr
SimpleDatabase::Create(EventLoop &, EventLoop &,
gcc_unused DatabaseListener &listener,
const ConfigBlock &block)
{
return new SimpleDatabase(block);
return std::make_unique<SimpleDatabase>(block);
}
void
@@ -219,6 +218,7 @@ SimpleDatabase::GetSong(const char *uri) const
prefixed_light_song =
new PrefixedLightSong(*song, r.directory->GetPath());
r.directory->mounted_database->ReturnSong(song);
return prefixed_light_song;
}
@@ -251,7 +251,7 @@ void
SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const noexcept
{
assert(song != nullptr);
assert(song == &light_song.Get() || song == prefixed_light_song);
assert(song == prefixed_light_song || song == &light_song.Get());
if (prefixed_light_song != nullptr) {
delete prefixed_light_song;
@@ -390,13 +390,13 @@ SimpleDatabase::Save()
}
void
SimpleDatabase::Mount(const char *uri, Database *db)
SimpleDatabase::Mount(const char *uri, DatabasePtr db)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(uri != nullptr);
assert(db != nullptr);
#endif
assert(db != nullptr);
assert(*uri != 0);
ScopeDatabaseLock protect;
@@ -411,7 +411,7 @@ SimpleDatabase::Mount(const char *uri, Database *db)
"Parent not found");
Directory *mnt = r.directory->CreateChild(r.uri);
mnt->mounted_database = db;
mnt->mounted_database = std::move(db);
}
static constexpr bool
@@ -441,27 +441,21 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
#ifndef ENABLE_ZLIB
constexpr bool compress = false;
#endif
auto db = new SimpleDatabase(cache_path / name_fs,
compress);
try {
db->Open();
} catch (...) {
delete db;
throw;
}
auto db = std::make_unique<SimpleDatabase>(cache_path / name_fs,
compress);
db->Open();
// TODO: update the new database instance?
try {
Mount(local_uri, db);
Mount(local_uri, std::move(db));
} catch (...) {
db->Close();
delete db;
throw;
}
}
inline Database *
inline DatabasePtr
SimpleDatabase::LockUmountSteal(const char *uri) noexcept
{
ScopeDatabaseLock protect;
@@ -470,8 +464,7 @@ SimpleDatabase::LockUmountSteal(const char *uri) noexcept
if (r.uri != nullptr || !r.directory->IsMount())
return nullptr;
Database *db = r.directory->mounted_database;
r.directory->mounted_database = nullptr;
auto db = std::move(r.directory->mounted_database);
r.directory->Delete();
return db;
@@ -480,12 +473,11 @@ SimpleDatabase::LockUmountSteal(const char *uri) noexcept
bool
SimpleDatabase::Unmount(const char *uri) noexcept
{
Database *db = LockUmountSteal(uri);
auto db = LockUmountSteal(uri);
if (db == nullptr)
return false;
db->Close();
delete db;
return true;
}

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
#include "db/Interface.hxx"
#include "db/Ptr.hxx"
#include "fs/AllocatedPath.hxx"
#include "song/LightSong.hxx"
#include "util/Manual.hxx"
@@ -57,7 +58,7 @@ class SimpleDatabase : public Database {
* A buffer for GetSong() when prefixing the #LightSong
* instance from a mounted #Database.
*/
mutable PrefixedLightSong *prefixed_light_song;
mutable PrefixedLightSong *prefixed_light_song = nullptr;
/**
* A buffer for GetSong().
@@ -68,15 +69,14 @@ class SimpleDatabase : public Database {
mutable unsigned borrowed_song_count;
#endif
public:
SimpleDatabase(const ConfigBlock &block);
SimpleDatabase(AllocatedPath &&_path, bool _compress) noexcept;
public:
static Database *Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
static DatabasePtr Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
gcc_pure
Directory &GetRoot() noexcept {
@@ -99,7 +99,7 @@ public:
* success, this object gains ownership of the given #Database
*/
gcc_nonnull_all
void Mount(const char *uri, Database *db);
void Mount(const char *uri, DatabasePtr db);
/**
* Throws #std::runtime_error on error.
@@ -142,7 +142,7 @@ private:
*/
void Load();
Database *LockUmountSteal(const char *uri) noexcept;
DatabasePtr LockUmountSteal(const char *uri) noexcept;
};
extern const DatabasePlugin simple_db_plugin;

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -82,10 +82,10 @@ public:
:Database(upnp_db_plugin),
event_loop(_event_loop) {}
static Database *Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block) noexcept;
static DatabasePtr Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block) noexcept;
void Open() override;
void Close() noexcept override;
@@ -146,12 +146,12 @@ private:
const UPnPDirObject& dirent) const;
};
Database *
DatabasePtr
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
gcc_unused DatabaseListener &listener,
const ConfigBlock &) noexcept
{
return new UpnpDatabase(io_event_loop);
return std::make_unique<UpnpDatabase>(io_event_loop);
}
void

@@ -94,7 +94,7 @@ UpdateService::CancelMount(const char *uri)
cancel_current = next.IsDefined() && next.storage == storage2;
}
if (auto *db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database)) {
if (auto *db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get())) {
queue.Erase(*db2);
cancel_current |= next.IsDefined() && next.db == db2;
}
@@ -188,7 +188,7 @@ UpdateService::Enqueue(const char *path, bool discard)
/* follow the mountpoint, update the mounted
database */
db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database);
db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get());
if (db2 == nullptr)
throw std::runtime_error("Cannot update this type of database");

@@ -152,7 +152,8 @@ decoder_plugin_init_all(const ConfigData &config)
}
}
void decoder_plugin_deinit_all(void)
void
decoder_plugin_deinit_all() noexcept
{
decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
plugin.Finish();

@@ -40,11 +40,22 @@ decoder_plugin_init_all(const ConfigData &config);
/* this is where we "unload" all the "plugins" */
void
decoder_plugin_deinit_all();
decoder_plugin_deinit_all() noexcept;
class ScopeDecoderPluginsInit {
public:
explicit ScopeDecoderPluginsInit(const ConfigData &config) {
decoder_plugin_init_all(config);
}
~ScopeDecoderPluginsInit() noexcept {
decoder_plugin_deinit_all();
}
};
template<typename F>
static inline const DecoderPlugin *
decoder_plugins_find(F f)
decoder_plugins_find(F f) noexcept
{
for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))

@@ -33,6 +33,7 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
static constexpr Domain audiofile_domain("audiofile");

@@ -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)

@@ -36,8 +36,9 @@ if flac_dep.found()
]
endif
conf.set('ENABLE_VORBIS_DECODER', libvorbis_dep.found())
if libvorbis_dep.found()
conf.set('ENABLE_VORBIS_DECODER', libvorbis_dep.found() or libvorbisidec_dep.found())
conf.set('HAVE_TREMOR', libvorbisidec_dep.found())
if libvorbis_dep.found() or libvorbisidec_dep.found()
decoder_plugins_sources += [
'VorbisDecoderPlugin.cxx',
'VorbisDomain.cxx',
@@ -181,6 +182,7 @@ decoder_plugins = static_library(
libsidplay_dep,
libsndfile_dep,
libvorbis_dep,
libvorbisidec_dep,
ogg_dep,
wavpack_dep,
wildmidi_dep,

@@ -80,7 +80,7 @@ private:
void
BlockingCall(EventLoop &loop, std::function<void()> &&f)
{
if (loop.IsDead() || loop.IsInside()) {
if (!loop.IsAlive() || loop.IsInside()) {
/* we're already inside the loop - we can simply call
the function */
f();

@@ -25,7 +25,13 @@
EventLoop::EventLoop(ThreadId _thread)
:SocketMonitor(*this),
quit(false), dead(false),
/* if this instance is hosted by an EventThread (no ThreadId
known yet) then we're not yet alive until the thread is
started; for the main EventLoop instance, we assume it's
already alive, because nobody but EventThread will call
SetAlive() */
alive(!_thread.IsNull()),
quit(false),
thread(_thread)
{
SocketMonitor::Open(SocketDescriptor(wake_fd.Get()));
@@ -143,12 +149,11 @@ EventLoop::Run() noexcept
assert(IsInside());
assert(!quit);
assert(!dead);
assert(alive);
assert(busy);
SocketMonitor::Schedule(SocketMonitor::READ);
AtScopeExit(this) {
dead = true;
SocketMonitor::Cancel();
};
@@ -215,7 +220,6 @@ EventLoop::Run() noexcept
} while (!quit);
#ifndef NDEBUG
assert(!dead);
assert(busy);
assert(thread.IsInside());
#endif

@@ -85,12 +85,15 @@ class EventLoop final : SocketMonitor
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
std::atomic_bool quit;
/**
* If this is true, then Run() has returned.
* Is this #EventLoop alive, i.e. can events be scheduled?
* This is used by BlockingCall() to determine whether
* schedule in the #EventThread or to call directly (if
* there's no #EventThread yet/anymore).
*/
std::atomic_bool dead;
bool alive;
std::atomic_bool quit;
/**
* True when the object has been modified and another check is
@@ -207,9 +210,12 @@ private:
bool OnSocketReady(unsigned flags) noexcept override;
public:
gcc_pure
bool IsDead() const noexcept {
return dead;
void SetAlive(bool _alive) noexcept {
alive = _alive;
}
bool IsAlive() const noexcept {
return alive;
}
/**

@@ -396,3 +396,19 @@ ServerSocket::AddPath(AllocatedPath &&path)
#endif /* !HAVE_UN */
}
#ifdef __linux__
void
ServerSocket::AddAbstract(const char *name)
{
assert(name != nullptr);
assert(*name == '@');
AllocatedSocketAddress address;
address.SetLocal(name);
AddAddress(std::move(address));
}
#endif

@@ -99,6 +99,18 @@ public:
*/
void AddPath(AllocatedPath &&path);
#ifdef __linux__
/**
* 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);
#endif
/**
* Add a socket descriptor that is accepting connections. After this
* has been called, don't call server_socket_open(), because the

@@ -26,8 +26,11 @@
void
EventThread::Start()
{
assert(!event_loop.IsAlive());
assert(!thread.IsDefined());
event_loop.SetAlive(true);
thread.Start();
}
@@ -35,6 +38,9 @@ void
EventThread::Stop() noexcept
{
if (thread.IsDefined()) {
assert(event_loop.IsAlive());
event_loop.SetAlive(false);
event_loop.Break();
thread.Join();
}

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2018 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2014-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
@@ -88,4 +88,13 @@ private:
#endif
};
template<typename F>
void
WithBufferedOutputStream(OutputStream &os, F &&f)
{
BufferedOutputStream bos(os);
f(bos);
bos.Flush();
}
#endif

@@ -31,8 +31,33 @@
#include "system/Error.hxx"
#include "util/StringFormat.hxx"
#ifdef __linux__
#include <fcntl.h>
#endif
#ifdef __linux__
FileOutputStream::FileOutputStream(FileDescriptor _directory_fd,
Path _path, Mode _mode)
:path(_path),
directory_fd(_directory_fd),
mode(_mode)
{
Open();
}
#endif
FileOutputStream::FileOutputStream(Path _path, Mode _mode)
:path(_path), mode(_mode)
:path(_path),
#ifdef __linux__
directory_fd(AT_FDCWD),
#endif
mode(_mode)
{
Open();
}
inline void
FileOutputStream::Open()
{
switch (mode) {
case Mode::CREATE:
@@ -149,8 +174,12 @@ FileOutputStream::Cancel() noexcept
* Open a file using Linux's O_TMPFILE for writing the given file.
*/
static bool
OpenTempFile(FileDescriptor &fd, Path path)
OpenTempFile(FileDescriptor directory_fd,
FileDescriptor &fd, Path path)
{
if (directory_fd != FileDescriptor(AT_FDCWD))
return fd.Open(directory_fd, ".", O_TMPFILE|O_WRONLY, 0666);
const auto directory = path.GetDirectoryName();
if (directory.IsNull())
return false;
@@ -165,11 +194,15 @@ FileOutputStream::OpenCreate(bool visible)
{
#ifdef HAVE_O_TMPFILE
/* try Linux's O_TMPFILE first */
is_tmpfile = !visible && OpenTempFile(fd, GetPath());
is_tmpfile = !visible && OpenTempFile(directory_fd, fd, GetPath());
if (!is_tmpfile) {
#endif
/* fall back to plain POSIX */
if (!fd.Open(GetPath().c_str(),
if (!fd.Open(
#ifdef __linux__
directory_fd,
#endif
GetPath().c_str(),
O_WRONLY|O_CREAT|O_TRUNC,
0666))
throw FormatErrno("Failed to create %s",
@@ -188,7 +221,11 @@ FileOutputStream::OpenAppend(bool create)
if (create)
flags |= O_CREAT;
if (!fd.Open(path.c_str(), flags))
if (!fd.Open(
#ifdef __linux__
directory_fd,
#endif
path.c_str(), flags))
throw FormatErrno("Failed to append to %s",
path.c_str());
}
@@ -219,12 +256,12 @@ FileOutputStream::Commit()
#ifdef HAVE_O_TMPFILE
if (is_tmpfile) {
unlink(GetPath().c_str());
unlinkat(directory_fd.Get(), GetPath().c_str(), 0);
/* hard-link the temporary file to the final path */
if (linkat(AT_FDCWD,
StringFormat<64>("/proc/self/fd/%d", fd.Get()),
AT_FDCWD, path.c_str(),
directory_fd.Get(), path.c_str(),
AT_SYMLINK_FOLLOW) < 0)
throw FormatErrno("Failed to commit %s",
path.c_str());
@@ -253,7 +290,11 @@ FileOutputStream::Cancel() noexcept
#ifdef HAVE_O_TMPFILE
if (!is_tmpfile)
#endif
unlink(GetPath().c_str());
#ifdef __linux__
unlinkat(directory_fd.Get(), GetPath().c_str(), 0);
#else
unlink(GetPath().c_str());
#endif
break;
case Mode::CREATE_VISIBLE:

@@ -59,6 +59,10 @@ class Path;
class FileOutputStream final : public OutputStream {
const AllocatedPath path;
#ifdef __linux__
const FileDescriptor directory_fd;
#endif
#ifdef _WIN32
HANDLE handle = INVALID_HANDLE_VALUE;
#else
@@ -108,6 +112,11 @@ private:
public:
explicit FileOutputStream(Path _path, Mode _mode=Mode::CREATE);
#ifdef __linux__
FileOutputStream(FileDescriptor _directory_fd, Path _path,
Mode _mode=Mode::CREATE);
#endif
~FileOutputStream() noexcept {
if (IsDefined())
Cancel();
@@ -133,6 +142,7 @@ public:
private:
void OpenCreate(bool visible);
void OpenAppend(bool create);
void Open();
bool Close() noexcept {
assert(IsDefined());

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2018 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2014-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
@@ -27,8 +27,8 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef MPD_OUTPUT_STREAM_HXX
#define MPD_OUTPUT_STREAM_HXX
#ifndef OUTPUT_STREAM_HXX
#define OUTPUT_STREAM_HXX
#include <stddef.h>

@@ -39,7 +39,7 @@ class StdioOutputStream final : public OutputStream {
FILE *const file;
public:
explicit StdioOutputStream(FILE *_file):file(_file) {}
explicit StdioOutputStream(FILE *_file) noexcept:file(_file) {}
/* virtual methods from class OutputStream */
void Write(const void *data, size_t size) override {

@@ -66,6 +66,11 @@ BufferedInputStream::Check()
void
BufferedInputStream::Seek(offset_type new_offset)
{
if (new_offset >= size) {
offset = size;
return;
}
auto r = buffer.Read(new_offset);
if (r.HasData()) {
/* nice, we already have some data at the desired

@@ -35,4 +35,16 @@ input_stream_global_init(const ConfigData &config, EventLoop &event_loop);
void
input_stream_global_finish() noexcept;
class ScopeInputPluginsInit {
public:
ScopeInputPluginsInit(const ConfigData &config,
EventLoop &event_loop) {
input_stream_global_init(config, event_loop);
}
~ScopeInputPluginsInit() noexcept {
input_stream_global_finish();
}
};
#endif

@@ -23,6 +23,7 @@
#include "config.h"
#include "CdioParanoiaInputPlugin.hxx"
#include "lib/cdio/Paranoia.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/TruncateString.hxx"
@@ -41,18 +42,12 @@
#include <stdlib.h>
#include <assert.h>
#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
#include <cdio/paranoia/paranoia.h>
#else
#include <cdio/paranoia.h>
#endif
#include <cdio/cd_types.h>
class CdioParanoiaInputStream final : public InputStream {
cdrom_drive_t *const drv;
CdIo_t *const cdio;
cdrom_paranoia_t *const para;
CdromParanoia para;
const lsn_t lsn_from, lsn_to;
int lsn_relofs;
@@ -66,18 +61,17 @@ class CdioParanoiaInputStream final : public InputStream {
bool reverse_endian,
lsn_t _lsn_from, lsn_t _lsn_to)
:InputStream(_uri, _mutex),
drv(_drv), cdio(_cdio), para(cdio_paranoia_init(drv)),
drv(_drv), cdio(_cdio), para(drv),
lsn_from(_lsn_from), lsn_to(_lsn_to),
lsn_relofs(0),
buffer_lsn(-1)
{
/* Set reading mode for full paranoia, but allow
skipping sectors. */
paranoia_modeset(para,
PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
/* seek to beginning of the track */
cdio_paranoia_seek(para, lsn_from, SEEK_SET);
para.Seek(lsn_from);
seekable = true;
size = (lsn_to - lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
@@ -90,7 +84,7 @@ class CdioParanoiaInputStream final : public InputStream {
}
~CdioParanoiaInputStream() {
cdio_paranoia_free(para);
para = {};
cdio_cddap_close_no_free_cdio(drv);
cdio_destroy(cdio);
}
@@ -208,10 +202,10 @@ input_cdio_open(const char *uri,
throw std::runtime_error("Unable to identify audio CD disc.");
}
cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
cdio_cddap_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
if (speed > 0) {
FormatDebug(cdio_domain,"Attempting to set CD speed to %dx",speed);
cdda_speed_set(drv,speed);
cdio_cddap_speed_set(drv,speed);
}
if (0 != cdio_cddap_open(drv)) {
@@ -277,7 +271,7 @@ CdioParanoiaInputStream::Seek(offset_type new_offset)
{
const ScopeUnlock unlock(mutex);
cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET);
para.Seek(lsn_from + lsn_relofs);
}
}
@@ -285,56 +279,53 @@ size_t
CdioParanoiaInputStream::Read(void *ptr, size_t length)
{
size_t nbytes = 0;
int diff;
size_t len, maxwrite;
int16_t *rbuf;
char *s_err, *s_mess;
char *wptr = (char *) ptr;
while (length > 0) {
/* end of track ? */
if (lsn_from + lsn_relofs > lsn_to)
break;
//current sector was changed ?
const int16_t *rbuf;
if (lsn_relofs != buffer_lsn) {
const ScopeUnlock unlock(mutex);
rbuf = cdio_paranoia_read(para, nullptr);
try {
rbuf = para.Read().data;
} catch (...) {
char *s_err = cdio_cddap_errors(drv);
if (s_err) {
FormatError(cdio_domain,
"paranoia_read: %s", s_err);
#if LIBCDIO_VERSION_NUM >= 90
cdio_cddap_free_messages(s_err);
#else
free(s_err);
#endif
}
s_err = cdda_errors(drv);
if (s_err) {
FormatError(cdio_domain,
"paranoia_read: %s", s_err);
free(s_err);
throw;
}
s_mess = cdda_messages(drv);
if (s_mess) {
free(s_mess);
}
if (!rbuf)
throw std::runtime_error("paranoia read error");
//store current buffer
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
buffer_lsn = lsn_relofs;
} else {
//use cached sector
rbuf = (int16_t *)buffer;
rbuf = (const int16_t *)buffer;
}
//correct offset
diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
len = (length < maxwrite? length : maxwrite);
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
const size_t len = (length < maxwrite? length : maxwrite);
//skip diff bytes from this lsn
memcpy(wptr, ((char*)rbuf) + diff, len);
memcpy(wptr, ((const char *)rbuf) + diff, len);
//update pointer
wptr += len;
nbytes += len;

@@ -10,10 +10,6 @@ libcdio_paranoia_dep = dependency('libcdio_paranoia', version: '>= 0.4', require
conf.set('ENABLE_CDIO_PARANOIA', libcdio_paranoia_dep.found())
if libcdio_paranoia_dep.found()
input_plugins_sources += 'CdioParanoiaInputPlugin.cxx'
conf.set('HAVE_CDIO_PARANOIA_PARANOIA_H',
compiler.has_header('cdio/paranoia/paranoia.h',
dependencies: libcdio_paranoia_dep))
endif
if curl_dep.found()

166
src/lib/cdio/Paranoia.hxx Normal file

@@ -0,0 +1,166 @@
/*
* Copyright 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CDIO_PARANOIA_HXX
#define CDIO_PARANOIA_HXX
#include "util/ConstBuffer.hxx"
#include "util/Compiler.h"
#include <cdio/version.h>
#if LIBCDIO_VERSION_NUM >= 90
#include <cdio/paranoia/paranoia.h>
#else
#include <cdio/paranoia.h>
#endif
#include <stdexcept>
#include <utility>
#include <cstdio>
class CdromDrive {
cdrom_drive_t *drv = nullptr;
public:
CdromDrive() = default;
explicit CdromDrive(CdIo_t *cdio)
:drv(cdio_cddap_identify_cdio(cdio, 1, nullptr))
{
if (drv == nullptr)
throw std::runtime_error("Failed to identify audio CD");
cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT,
CDDA_MESSAGE_FORGETIT);
}
~CdromDrive() noexcept {
if (drv != nullptr)
cdio_cddap_close_no_free_cdio(drv);
}
CdromDrive(CdromDrive &&src) noexcept
:drv(std::exchange(src.drv, nullptr)) {}
CdromDrive &operator=(CdromDrive &&src) noexcept {
using std::swap;
swap(drv, src.drv);
return *this;
}
auto get() const noexcept {
return drv;
}
void Open() {
if (cdio_cddap_open(drv) != 0)
throw std::runtime_error("Failed to open disc");
}
auto GetDiscSectorRange() const {
auto first = cdio_cddap_disc_firstsector(drv);
auto last = cdio_cddap_disc_lastsector(drv);
if (first < 0 || last < 0)
throw std::runtime_error("Failed to get disc audio sectors");
return std::make_pair(first, last);
}
gcc_pure
bool IsAudioTrack(track_t i) const noexcept {
return cdio_cddap_track_audiop(drv, i);
}
auto GetTrackSectorRange(track_t i) const {
auto first = cdio_cddap_track_firstsector(drv, i);
auto last = cdio_cddap_track_lastsector(drv, i);
if (first < 0 || last < 0)
throw std::runtime_error("Invalid track number");
return std::make_pair(first, last);
}
gcc_pure
unsigned GetTrackCount() const noexcept {
return cdio_cddap_tracks(drv);
}
unsigned GetTrackChannels(track_t i) const {
auto value = cdio_cddap_track_channels(drv, i);
if (value < 0)
throw std::runtime_error("cdio_cddap_track_channels() failed");
return unsigned(value);
}
};
class CdromParanoia {
cdrom_paranoia_t *paranoia = nullptr;
public:
CdromParanoia() = default;
explicit CdromParanoia(cdrom_drive_t *drv) noexcept
:paranoia(cdio_paranoia_init(drv)) {}
~CdromParanoia() noexcept {
if (paranoia != nullptr)
cdio_paranoia_free(paranoia);
}
CdromParanoia(CdromParanoia &&src) noexcept
:paranoia(std::exchange(src.paranoia, nullptr)) {}
CdromParanoia &operator=(CdromParanoia &&src) noexcept {
using std::swap;
swap(paranoia, src.paranoia);
return *this;
}
auto get() const noexcept {
return paranoia;
}
void SetMode(int mode_flags) noexcept {
paranoia_modeset(paranoia, mode_flags);
}
void Seek(int32_t seek, int whence=SEEK_SET) {
if (cdio_paranoia_seek(paranoia, seek, whence) < 0)
throw std::runtime_error("Failed to seek disc");
}
ConstBuffer<int16_t> Read() {
const int16_t *data = cdio_paranoia_read(paranoia, nullptr);
if (data == nullptr)
throw std::runtime_error("Read from audio CD failed");
return {data, CDIO_CD_FRAMESIZE_RAW / sizeof(int16_t)};
}
};
#endif

@@ -1,20 +1,19 @@
Index: curl-7.58.0/lib/url.c
===================================================================
--- curl-7.58.0.orig/lib/url.c
+++ curl-7.58.0/lib/url.c
@@ -3503,6 +3503,7 @@ static CURLcode override_login(struct Cu
diff -ur curl-7.63.0.orig/lib/url.c curl-7.63.0/lib/url.c
--- curl-7.63.0.orig/lib/url.c 2019-01-21 10:15:51.368019445 +0100
+++ curl-7.63.0/lib/url.c 2019-01-21 10:19:16.307523984 +0100
@@ -3057,6 +3057,7 @@
}
conn->bits.netrc = FALSE;
+#ifndef __BIONIC__
if(data->set.use_netrc != CURL_NETRC_IGNORED) {
int ret = Curl_parsenetrc(conn->host.name,
userp, passwdp,
@@ -3524,6 +3525,7 @@ static CURLcode override_login(struct Cu
conn->bits.user_passwd = TRUE; /* enable user+password */
if(data->set.use_netrc != CURL_NETRC_IGNORED &&
(!*userp || !**userp || !*passwdp || !**passwdp)) {
bool netrc_user_changed = FALSE;
@@ -3090,6 +3091,7 @@
}
}
}
+#endif
return CURLE_OK;
}
/* for updated strings, we update them in the URL */
if(user_changed) {

@@ -35,6 +35,7 @@
#include "Iter.hxx"
#include "util/Compiler.h"
#include "util/ConstBuffer.hxx"
#if GCC_OLDER_THAN(8,0)
/* switch off completely bogus shadow warnings in older GCC
@@ -81,6 +82,14 @@ public:
return value;
}
template<typename T>
ConstBuffer<T> GetFixedArray() noexcept {
void *value;
int n_elements;
dbus_message_iter_get_fixed_array(&iter, &value, &n_elements);
return {(const T *)value, size_t(n_elements)};
}
/**
* Create a new iterator which recurses into a container
* value.

@@ -22,6 +22,7 @@
#include "ReadIter.hxx"
#include "ObjectManager.hxx"
#include "util/StringAPI.hxx"
#include "util/StringView.hxx"
#include "util/Compiler.h"
#include <functional>
@@ -39,6 +40,40 @@ CheckString(I &&i) noexcept
return i.GetString();
}
template<typename I>
gcc_pure
static StringView
CheckRecursedByteArrayToString(I &&i) noexcept
{
if (i.GetArgType() != DBUS_TYPE_BYTE)
return nullptr;
auto value = i.template GetFixedArray<char>();
return { value.data, value.size };
}
template<typename I>
gcc_pure
static StringView
CheckByteArrayToString(I &&i) noexcept
{
if (i.GetArgType() != DBUS_TYPE_ARRAY)
return nullptr;
return CheckRecursedByteArrayToString(i.Recurse());
}
template<typename I>
gcc_pure
static StringView
CheckByteArrayArrayFrontToString(I &&i) noexcept
{
if (i.GetArgType() != DBUS_TYPE_ARRAY)
return nullptr;
return CheckByteArrayToString(i.Recurse());
}
static void
ParseDriveDictEntry(Object &o, const char *name,
ODBus::ReadMessageIter &&value_i) noexcept
@@ -61,6 +96,25 @@ ParseBlockDictEntry(Object &o, const char *name,
}
}
static void
ParseFileesystemDictEntry(Object &o, const char *name,
ODBus::ReadMessageIter &&value_i) noexcept
{
if (StringIsEqual(name, "MountPoints")) {
if (!o.mount_point.empty())
/* we already know one mount point, and we're
not interested in more */
return;
/* get the first string in the array */
auto value = CheckByteArrayArrayFrontToString(value_i);
if (value != nullptr)
o.mount_point = {value.data, value.size};
// TODO: check whether the string is a valid filesystem path
}
}
static void
ParseInterface(Object &o, const char *interface,
ODBus::ReadMessageIter &&i) noexcept
@@ -74,6 +128,10 @@ ParseInterface(Object &o, const char *interface,
std::ref(o), _1, _2));
} else if (StringIsEqual(interface, "org.freedesktop.UDisks2.Filesystem")) {
o.is_filesystem = true;
i.ForEachProperty(std::bind(ParseFileesystemDictEntry,
std::ref(o), _1, _2));
}
}

@@ -39,6 +39,13 @@ struct Object {
std::string drive_id, block_id;
/**
* The first element of the "MountPoints" array of the
* "Filesystem" interface. Empty if no "MountPoints" property
* exists.
*/
std::string mount_point;
bool is_filesystem = false;
explicit Object(const char *_path) noexcept

@@ -73,7 +73,7 @@ NfsManager::Compare::operator()(const ManagedConnection &a,
NfsManager::~NfsManager() noexcept
{
assert(GetEventLoop().IsInside());
assert(!GetEventLoop().IsAlive() || GetEventLoop().IsInside());
CollectGarbage();
@@ -103,7 +103,7 @@ NfsManager::GetConnection(const char *server, const char *export_name) noexcept
void
NfsManager::CollectGarbage() noexcept
{
assert(GetEventLoop().IsInside());
assert(!GetEventLoop().IsAlive() || GetEventLoop().IsInside());
garbage.clear_and_dispose(DeleteDisposer());
}

@@ -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;
}

@@ -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) {}

@@ -1,6 +1,24 @@
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'))
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')
endif
libvorbisenc_dep = dependency('', required: false)
if need_encoder and not get_option('vorbisenc').disabled()
@@ -13,7 +31,7 @@ if need_encoder and not get_option('vorbisenc').disabled()
endif
endif
if libopus_dep.found() or libvorbis_dep.found() or libvorbisenc_dep.found()
if libopus_dep.found() or libvorbis_dep.found() or libvorbisenc_dep.found() or libvorbisidec_dep.found()
libogg_dep = dependency('ogg')
else
libogg_dep = dependency('', required: false)

@@ -24,6 +24,9 @@
#include "mixer/MixerInternal.hxx"
#include "mixer/Listener.hxx"
#include "output/plugins/PulseOutputPlugin.hxx"
#include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx"
#include "config/Block.hxx"
#include <pulse/context.h>
#include <pulse/introspect.h>
@@ -37,13 +40,18 @@
class PulseMixer final : public Mixer {
PulseOutput &output;
bool online;
const float volume_scale_factor;
bool online = false;
struct pa_cvolume volume;
public:
PulseMixer(PulseOutput &_output, MixerListener &_listener)
PulseMixer(PulseOutput &_output, MixerListener &_listener,
double _volume_scale_factor)
:Mixer(pulse_mixer_plugin, _listener),
output(_output), online(false)
output(_output),
volume_scale_factor(_volume_scale_factor)
{
}
@@ -159,13 +167,30 @@ pulse_mixer_on_change(PulseMixer &pm,
pm.Update(context, stream);
}
static float
parse_volume_scale_factor(const char *value) {
if (value == nullptr)
return 1.0;
char *endptr;
float factor = ParseFloat(value, &endptr);
if (endptr == value || *endptr != '\0' || factor < 0.5 || factor > 5.0)
throw FormatRuntimeError("\"%s\" is not a number in the "
"range 0.5 to 5.0",
value);
return factor;
}
static Mixer *
pulse_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
MixerListener &listener,
gcc_unused const ConfigBlock &block)
const ConfigBlock &block)
{
PulseOutput &po = (PulseOutput &)ao;
PulseMixer *pm = new PulseMixer(po, listener);
float scale = parse_volume_scale_factor(block.GetBlockValue("scale_volume"));
PulseMixer *pm = new PulseMixer(po, listener, scale);
pulse_output_set_mixer(po, *pm);
@@ -191,8 +216,9 @@ PulseMixer::GetVolume()
int
PulseMixer::GetVolumeInternal()
{
pa_volume_t max_pa_volume = volume_scale_factor * PA_VOLUME_NORM;
return online ?
(int)((100 * (pa_cvolume_avg(&volume) + 1)) / PA_VOLUME_NORM)
(int)((100 * (pa_cvolume_avg(&volume) + 1)) / max_pa_volume)
: -1;
}
@@ -204,9 +230,11 @@ PulseMixer::SetVolume(unsigned new_volume)
if (!online)
throw std::runtime_error("disconnected");
pa_volume_t max_pa_volume = volume_scale_factor * PA_VOLUME_NORM;
struct pa_cvolume cvolume;
pa_cvolume_set(&cvolume, volume.channels,
(new_volume * PA_VOLUME_NORM + 50) / 100);
(new_volume * max_pa_volume + 50) / 100);
pulse_output_set_volume(output, &cvolume);
volume = cvolume;
}

@@ -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 "AllocatedSocketAddress.hxx"
#include "util/StringView.hxx"
#include <string.h>
@@ -70,6 +71,12 @@ AllocatedSocketAddress::SetSize(size_type new_size) noexcept
#ifdef HAVE_UN
StringView
AllocatedSocketAddress::GetLocalRaw() const noexcept
{
return SocketAddress(*this).GetLocalRaw();
}
void
AllocatedSocketAddress::SetLocal(const char *path) noexcept
{

@@ -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
@@ -27,8 +27,8 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ALLOCATED_SOCKET_ADDRESS_HPP
#define ALLOCATED_SOCKET_ADDRESS_HPP
#ifndef ALLOCATED_SOCKET_ADDRESS_HXX
#define ALLOCATED_SOCKET_ADDRESS_HXX
#include "SocketAddress.hxx"
#include "Features.hxx"
@@ -140,6 +140,20 @@ public:
}
#ifdef HAVE_UN
/**
* @see SocketAddress::GetLocalRaw()
*/
gcc_pure
StringView GetLocalRaw() const noexcept;
/**
* @see SocketAddress::GetLocalPath()
*/
gcc_pure
const char *GetLocalPath() const noexcept {
return ((SocketAddress)*this).GetLocalPath();
}
/**
* Make this a "local" address (UNIX domain socket). If the path
* begins with a '@', then the rest specifies an "abstract" local
@@ -149,6 +163,14 @@ public:
#endif
#ifdef HAVE_TCP
bool IsV6Any() const noexcept {
return ((SocketAddress)*this).IsV6Any();
}
bool IsV4Mapped() const noexcept {
return ((SocketAddress)*this).IsV4Mapped();
}
/**
* Extract the port number. Returns 0 if not applicable.
*/

@@ -36,7 +36,7 @@
#include <string.h>
static inline bool
IsValidHostnameChar(char ch)
IsValidHostnameChar(char ch) noexcept
{
return IsAlphaNumericASCII(ch) ||
ch == '-' || ch == '.' ||
@@ -44,14 +44,14 @@ IsValidHostnameChar(char ch)
}
static inline bool
IsValidScopeChar(char ch)
IsValidScopeChar(char ch) noexcept
{
return IsAlphaNumericASCII(ch) ||
ch == '-' || ch == '_';
}
static const char *
FindScopeEnd(const char *p)
FindScopeEnd(const char *p) noexcept
{
if (*p == '%' && IsValidScopeChar(p[1])) {
p += 2;
@@ -63,7 +63,7 @@ FindScopeEnd(const char *p)
}
static inline bool
IsValidIPv6Char(char ch)
IsValidIPv6Char(char ch) noexcept
{
return IsDigitASCII(ch) ||
(ch >= 'a' && ch <= 'f') ||
@@ -72,7 +72,7 @@ IsValidIPv6Char(char ch)
}
static const char *
FindIPv6End(const char *p)
FindIPv6End(const char *p) noexcept
{
while (IsValidIPv6Char(*p))
++p;
@@ -84,7 +84,7 @@ FindIPv6End(const char *p)
}
ExtractHostResult
ExtractHost(const char *src)
ExtractHost(const char *src) noexcept
{
ExtractHostResult result{nullptr, src};
const char *hostname;

@@ -57,7 +57,7 @@ struct ExtractHostResult {
*/
const char *end;
constexpr bool HasFailed() const {
constexpr bool HasFailed() const noexcept {
return host == nullptr;
}
};
@@ -71,6 +71,6 @@ struct ExtractHostResult {
*/
gcc_pure
ExtractHostResult
ExtractHost(const char *src);
ExtractHost(const char *src) noexcept;
#endif

@@ -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

@@ -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
@@ -51,7 +51,12 @@ class IPv4Address {
#ifdef _WIN32
static constexpr struct in_addr ConstructInAddr(uint8_t a, uint8_t b,
uint8_t c, uint8_t d) noexcept {
return {{{ a, b, c, d }}};
struct in_addr result{};
result.s_net = a;
result.s_host = b;
result.s_lh = c;
result.s_impno = d;
return result;
}
#else
@@ -66,7 +71,7 @@ class IPv4Address {
static constexpr struct in_addr ConstructInAddr(uint8_t a, uint8_t b,
uint8_t c, uint8_t d) noexcept {
return { ConstructInAddrT(a, b, c, d) };
return ConstructInAddrBE(ConstructInAddrT(a, b, c, d));
}
#endif
@@ -158,7 +163,7 @@ public:
*/
static constexpr const IPv4Address &Cast(const SocketAddress &src) noexcept {
/* this reinterpret_cast works because this class is
just a wrapper for struct sockaddr_in6 */
just a wrapper for struct sockaddr_in */
return *(const IPv4Address *)(const void *)src.GetAddress();
}

@@ -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

@@ -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
@@ -53,22 +53,24 @@ class IPv6Address {
uint16_t c, uint16_t d,
uint16_t e, uint16_t f,
uint16_t g, uint16_t h) noexcept {
return {{
#ifndef __HAIKU__
{
#endif
uint8_t(a >> 8), uint8_t(a),
uint8_t(b >> 8), uint8_t(b),
uint8_t(c >> 8), uint8_t(c),
uint8_t(d >> 8), uint8_t(d),
uint8_t(e >> 8), uint8_t(e),
uint8_t(f >> 8), uint8_t(f),
uint8_t(g >> 8), uint8_t(g),
uint8_t(h >> 8), uint8_t(h),
#ifndef __HAIKU__
}
#endif
}};
struct in6_addr result{};
result.s6_addr[0] = a >> 8;
result.s6_addr[1] = a;
result.s6_addr[2] = b >> 8;
result.s6_addr[3] = b;
result.s6_addr[4] = c >> 8;
result.s6_addr[5] = c;
result.s6_addr[6] = d >> 8;
result.s6_addr[7] = d;
result.s6_addr[8] = e >> 8;
result.s6_addr[9] = e;
result.s6_addr[10] = f >> 8;
result.s6_addr[11] = f;
result.s6_addr[12] = g >> 8;
result.s6_addr[13] = g;
result.s6_addr[14] = h >> 8;
result.s6_addr[15] = h;
return result;
}
static constexpr struct sockaddr_in6 Construct(struct in6_addr address,

@@ -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
@@ -73,6 +73,21 @@ SocketAddress::GetLocalRaw() const noexcept
return {path, size - header_size};
}
const char *
SocketAddress::GetLocalPath() const noexcept
{
const auto raw = GetLocalRaw();
return !raw.empty() &&
/* must be an absolute path */
raw.front() == '/' &&
/* must be null-terminated */
raw.back() == 0 &&
/* there must not be any other null byte */
memchr(raw.data, 0, raw.size - 1) == nullptr
? raw.data
: nullptr;
}
#endif
#ifdef HAVE_TCP

@@ -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
@@ -93,7 +93,7 @@ public:
* Does the object have a well-defined address? Check !IsNull()
* before calling this method.
*/
bool IsDefined() const noexcept {
constexpr bool IsDefined() const noexcept {
return GetFamily() != AF_UNSPEC;
}
@@ -106,6 +106,13 @@ public:
*/
gcc_pure
StringView GetLocalRaw() const noexcept;
/**
* Returns the local socket path or nullptr if not applicable
* (or if the path is corrupt).
*/
gcc_pure
const char *GetLocalPath() const noexcept;
#endif
#ifdef HAVE_TCP

@@ -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
{

@@ -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;
/**

@@ -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

@@ -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.

@@ -637,11 +637,8 @@ osx_output_set_device(OSXOutput *oo)
}
}
if (i == numdevices) {
FormatWarning(osx_output_domain,
"Found no audio device with name '%s' "
"(will use default audio device)",
throw FormatRuntimeError("Found no audio device with name '%s' ",
oo->device_name);
return;
}
status = AudioUnitSetProperty(oo->au,

@@ -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,

@@ -141,6 +141,27 @@ ShoutOutput::ShoutOutput(const ConfigBlock &block)
protocol = SHOUT_PROTOCOL_HTTP;
}
#ifdef SHOUT_TLS
unsigned tls;
value = block.GetBlockValue("tls");
if (value != nullptr) {
if (0 == strcmp(value, "disabled"))
tls = SHOUT_TLS_DISABLED;
else if(0 == strcmp(value, "auto"))
tls = SHOUT_TLS_AUTO;
else if(0 == strcmp(value, "auto_no_plain"))
tls = SHOUT_TLS_AUTO_NO_PLAIN;
else if(0 == strcmp(value, "rfc2818"))
tls = SHOUT_TLS_RFC2818;
else if(0 == strcmp(value, "rfc2817"))
tls = SHOUT_TLS_RFC2817;
else
throw FormatRuntimeError("invalid shout TLS option \"%s\"", value);
} else {
tls = SHOUT_TLS_DISABLED;
}
#endif
if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS ||
shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS ||
shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS ||
@@ -151,6 +172,9 @@ ShoutOutput::ShoutOutput(const ConfigBlock &block)
shout_set_format(shout_conn, shout_format)
!= SHOUTERR_SUCCESS ||
shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS ||
#ifdef SHOUT_TLS
shout_set_tls(shout_conn, tls) != SHOUTERR_SUCCESS ||
#endif
shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS)
throw std::runtime_error(shout_get_error(shout_conn));

@@ -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)

@@ -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);

@@ -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();
}

@@ -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

@@ -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);
}

@@ -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);

@@ -47,6 +47,17 @@ playlist_list_global_init(const ConfigData &config);
void
playlist_list_global_finish() noexcept;
class ScopePlaylistPluginsInit {
public:
explicit ScopePlaylistPluginsInit(const ConfigData &config) {
playlist_list_global_init(config);
}
~ScopePlaylistPluginsInit() noexcept {
playlist_list_global_finish();
}
};
/**
* Opens a playlist by its URI.
*/

@@ -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;
}

Some files were not shown because too many files have changed in this diff Show More