Compare commits

...

269 Commits

Author SHA1 Message Date
8842650c33 release v0.23.13 2023-05-22 19:46:38 +02:00
d5bf128cee storage/curl: throw HttpStatusError 2023-05-22 19:29:46 +02:00
5cd86e272f input/curl: disable CURLOPT_FAILONERROR
Let OnHeaders() check the status.

The status checking code was added by commit 4f021cbced in 2011,
but in 2008, commit a8e81326d0 enabled `CURLOPT_FAILONERROR`, which
means the status checking code never had any effect.

This allows `LoadExcludeListOrLog()` to hide boring "404 Not Found"
log messages via `IsFileNotFound()`.
2023-05-22 19:03:12 +02:00
740cbe9e02 event/Loop: remove failing assert()
The `assert(!quit)` can fail if the `EventThread` gets stopped before
it enters `EventLoop::Run()`. There is a similar problem with `alive`,
which gets reset by `EventThread::Stop()`.

If that happens, then `EventLoop::Run()` should return immediately
without handling any events.
2023-05-22 18:14:25 +02:00
ed890a273a doc/user.rst: document the replaygain_missing_preamp setting
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1785
2023-05-22 16:17:05 +02:00
068cd559e1 db/update/Walk: clear Song::in_playlist
Without clearing all `in_playlist` flags, the songs will never be
revealed again if they were hidden once by a CUE sheet, not even after
the CUE sheet gets deleted or modified.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1784
2023-05-22 15:41:58 +02:00
dc127f39a7 util/ScopeExit: use std::declval()
Fixes GCC 10 error:

 error: cannot call member function `Foo` without object
2023-05-22 15:01:25 +02:00
7a99a7008c util/ScopeExit: use std::exchange() 2023-05-22 14:44:45 +02:00
70b451db7b util/ScopeExit: add noexcept 2023-05-22 14:44:17 +02:00
2ab03a0914 util/ScopeExit: allow the function to throw
Fixes crash inside AtScopeExit() in the WASAPI output plugin.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1759
2023-05-22 14:43:23 +02:00
2fa8c7d2db lib/crypto/meson.build: link with ffmpeg_util_dep
This adds `MakeFfmpegError()` to the executable and fixes a linker
failure when `libavutil` is available, but `libavformat` and
`libavcodec` are not.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1786
2023-05-22 14:05:13 +02:00
7c759ba8b0 lib/ffmpeg/meson.build: move libavutil helpers into separate library 2023-05-22 14:03:47 +02:00
6d9b452fde lib/ffmpeg/LogError: remove unused library 2023-05-22 13:48:33 +02:00
f7eb1c9a83 Fix meson build warning for get_pkgconfig_variable
Otherwise, building will generate these warnings:

systemd/system/meson.build:5: WARNING: Project targeting '>= 0.56.0' but tried to use feature deprecated since '0.56.0': Dependency.get_pkgconfig_variable. use Dependency.get_variable(pkgconfig : ...) instead
systemd/user/meson.build:5: WARNING: Project targeting '>= 0.56.0' but tried to use feature deprecated since '0.56.0': Dependency.get_pkgconfig_variable. use Dependency.get_variable(pkgconfig : ...) instead
2023-05-21 21:07:14 +02:00
2d22e6dee4 subprojects: update sqlite to 3.41.2-2 2023-05-21 21:05:17 +02:00
4587bf759d subprojects: update expat to 2.5.0-2 2023-05-21 21:04:56 +02:00
e1e37cfe3c TagPrint, command/File: two more libfmt 10 workarounds
libfmt 10 doesn't know how to format a StringView, and doesn't cast to
std::string_view anymore.  The StringView class has been removed from
MPD 0.24 completely, and this is a stable-branch-only workaround.

Closes https://github.com/MusicPlayerDaemon/MPD/pull/1814
2023-05-21 21:03:20 +02:00
381934985a reorder ffmpeg to be lower priority than gme
This should prevent ffmpeg from taking priority over the gme plugin.
The ffmpeg plugin is more buggy than gme.
One of the prominent bugs of preferring ffmpeg over gme is that ffmpeg
cannot seek SAP files while gme can. This should prevent that from
happening.
2023-05-21 20:58:53 +02:00
a8042885ac TimePrint: minor fixup for libfmt 10
libfmt version 10 has difficulties formatting a `StringBuffer`, and we
need to help it by explicitly invoking the `c_str()` method.
2023-05-21 20:58:19 +02:00
a71e68db50 command/player, SongPrint: use AudioFormatFormatter()
libfmt version 10 apparently doesn't know how to format a
`StringBuffer`, failing the MPD build.  Since we have a formatter
specialization for `AudioFormat`, let's use that - it's better and
easier to use.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1807
2023-05-21 20:57:59 +02:00
1417578b3d db/update/Archive: validate directory names
Fixes assertion failure if the ZIP file contains a path that begins
with a slash.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1793
2023-05-21 20:57:38 +02:00
96befa138c db/update/Archive: ignore filenames with newline character 2023-05-21 20:56:38 +02:00
16a99804de db/update/Archive: move check to IsAcceptableFilename() 2023-05-21 20:56:32 +02:00
75a39ed279 db/update/Archive: remove useless log message 2023-05-21 20:55:59 +02:00
4d357ab77c Fix syntax error in mpdconf.example 2023-05-21 20:51:57 +02:00
d4f3dd49b4 db/SimpleDatabasePlugin: store in_playlist value of songs into database
Fixes hide_playlist_targets not working after server restart

Currently, `hide_playlists_targets` works by skipping songs with
`in_playlist` value set to true in
[`Directory::Walk`](a57bcd0238/src/db/plugins/simple/Directory.cxx (L237)). But
`in_playlist` is not stored and only updated in
[`UpdateWalk::PurgeDanglingFromPlaylists`](a57bcd0238/src/db/update/Playlist.cxx (L139)),
which will only be executed while updating DB.

This causes the problem that playlist target songs are correctly
hidden after database update, but will remain visible after mpd server
restarted. This pr solves the problem by storing `in_playlist` value
of songs into the `SimpleDatabase` file.
2023-05-21 20:51:47 +02:00
4ec6d0555a check systemd unit dir from systemd.pc in meson 2023-05-21 20:51:33 +02:00
a6a1182c4c python/build/libs.py: update OpenSSL to 3.1.0 2023-05-21 20:50:03 +02:00
a59c9c602b python/build/libs.py: update CURL to 8.0.1 2023-05-21 20:50:03 +02:00
0c4d824d64 subprojects/sqlite3.wrap: update to 3.41.0-1 2023-05-21 20:50:03 +02:00
a5281856c9 python/build/libs.py: update WildMidi to 0.4.5 2023-05-21 20:50:03 +02:00
0206a46d39 decoder/gme: require GME 0.6 or later
This allows dropping a few compile-time version checks and we can use
pkg-config to detect the library.
2023-05-21 20:50:03 +02:00
9475ef2202 fs/Charset: assign fs_charset
This got lost 8 years ago in commit 87c88fcb27

D'oh!
2023-05-21 20:50:03 +02:00
edae00e719 fs/Charset: remove useless log message 2023-05-21 20:50:03 +02:00
fb695bc55f command/{file,storage}: remove stray "#pragma GCC diagnostic pop" 2023-05-21 20:50:03 +02:00
23a5b8fd3c python/build/meson.py: remove unused import 2023-05-21 20:43:09 +02:00
273a93cfcf build/python/cmake: set CMAKE_C_FLAGS_INIT, not CMAKE_C_FLAGS
According to
https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS_INIT.html
the _INIT variables should be set in the toolchain file.
2023-05-21 20:43:05 +02:00
d105985d78 build/python/cmake: set CMAKE_OSX_SYSROOT on macOS 2023-05-21 20:43:00 +02:00
f8cfeb39e9 build/python/cmake: add "env" parameter 2023-05-21 20:42:51 +02:00
d5d3982d3c build/python/build/project.py: add "lazy" parameter to make_build_path() 2023-05-21 20:42:27 +02:00
47341107ea build/python/build/project.py: raise exception on regex mismatch 2023-05-21 20:42:18 +02:00
90eaa87a4d python/build/zlib.py: use autotools to be more portable
Right now, zlib is only built for Windows, but we may eventually
changed that, so don't hard-code `win32/Makefile.gcc`.
2023-05-21 20:42:12 +02:00
b09a54b2c2 python/build/autotools.py: use toolchain.arflags 2023-05-21 20:42:04 +02:00
10aec174d5 python/{cmake,autotools}: build in verbose mode
Make sure all the gory details are visible in CI logs.
2023-05-21 20:41:49 +02:00
d32ed194e8 python/build/autotools.py: dump config.log on configure error
For better error logs on CI.
2023-05-21 20:41:44 +02:00
70d0fbd715 python/makeproject: do not use hard-code absolute path to make 2023-05-21 20:41:38 +02:00
302432e157 python/makeproject: set appropriate build jobs count depending on the number of CPUs 2023-05-21 20:41:34 +02:00
4ab8a677dc build/python: do not use absolute path for tar 2023-05-21 20:41:29 +02:00
52e4a4c904 build/python/build/project.py: lazy tarball extraction 2023-05-21 20:41:25 +02:00
a0f6932ebe unix/SignalHandlers: shut down if parent process dies in --no-daemon mode
By default, if the parent of a process dies, the process gets SIGHUP
and is supposed to shut down.  This however doesn't work for MPD,
because MPD redefines SIGHUP with a different meaning (like most
daemons do).

To work around this, we configure the kernel to send SIGTERM instead
of SIGHUP.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1706
2023-05-21 20:40:47 +02:00
6e700dab69 CommandLine: hard-code daemon=false if ENABLE_DAEMON is not set 2023-05-21 20:40:39 +02:00
35eaed7206 python/build/libs.py: update FFmpeg to 6.0 2023-05-21 20:40:39 +02:00
e7c963f2ce python/build/libs.py: disable more unused FFmpeg features 2023-05-21 20:40:39 +02:00
949d72e368 output/PipeWire: lock thread loop in SendTag 2023-05-21 20:40:29 +02:00
8d2a184658 python/build/libs.py: update CURL to 7.88.1 2023-05-21 20:36:28 +02:00
c877a32d97 python/build/libs.py: update OpenSSL to 3.0.8 2023-05-21 20:36:23 +02:00
541468f0ca input/async: check for errors in Seek()
Fixes a busy loop in BufferingInputStream::RunThreadLocked() because
the method never learns that seeking is ignored, even though the HTTP
stream is already broken and can never be read; nobody cared to check
for errors.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1727
2023-05-21 20:34:19 +02:00
d2797effa3 command/database: add missing include for UINT_MAX 2023-05-21 20:33:34 +02:00
1170fb1e1e output/osx: change type to std::size_t to fix -Wc++11-narrowing 2023-05-21 20:33:03 +02:00
65b9b3195c lib/dbus/AppendIter: add missing include 2023-05-21 20:33:03 +02:00
258830e913 increment version number to 0.23.13 2023-05-21 20:29:23 +02:00
d91da96798 release v0.23.12 2023-01-17 18:54:47 +01:00
b3897df682 decoder/mad: add assert() 2023-01-17 18:53:18 +01:00
3cacb56bb7 fs/StandardDirectory: don't fall back to getpwuid() without $HOME
If the environment variable $HOME does not exist, don't attempt to
obtain it from /etc/passwd; without $HOME, the calling process
indicates that it does not wish MPD to access the home directory.

This also prevents MPD from attempting to load
`/root/.config/mpd/mpd.conf` if MPD got started as global systemd
service.  Reading from there makes no sense, only /etc/mpd.conf shall
be used then.

This piece of code was initially added by commit 5d85792178.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1687
2023-01-17 18:51:49 +01:00
15a1973e28 decoder/mad: fix integer underflow with very small files
When drop_start_samples and drop_end_samples overlap and are greater
than the actual number of samples, the `num_samples` calculation in
SubmitPCM() could underflow.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1712
2023-01-17 17:41:37 +01:00
ad7d47a8ba output/PipeWire: use PW_KEY_TARGET_OBJECT with PipeWire 0.3.64
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1721
2023-01-17 11:50:09 +01:00
0948c607b6 lib/curl/meson.build: require CURL 7.55.0 or later
For CURLINFO_CONTENT_LENGTH_DOWNLOAD_T (commit 4efd0a9f77).
2023-01-16 19:42:48 +01:00
60d04052c5 NEWS: mention the GCC13 fixes 2023-01-16 19:41:37 +01:00
c1780ac657 python/build/libs.py: update CURL to 7.87.0 2023-01-16 19:06:08 +01:00
e49cf0ec38 python/build/libs.py: update Boost to 1.81.0 2023-01-16 19:03:50 +01:00
e1d641f684 lib/curl/Easy: drop deprecated CURLOPT_HTTPPOST wrapper 2023-01-02 14:29:17 +01:00
4efd0a9f77 lib/curl/Easy: use CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
CURLINFO_CONTENT_LENGTH_DOWNLOAD is deprecated and is ugly because it
uses floating point.
2023-01-02 14:28:42 +01:00
f6f8751332 io/FileReader: add missing include for uint64_t 2023-01-02 14:27:47 +01:00
gd
abb28593ce TagBuilder::RemoveType: added missing tag pool lock before call to tag_pool_put_item 2022-12-29 08:43:10 +01:00
115693b046 increment version number to 0.23.12 2022-12-29 08:42:02 +01:00
e4b055eb6d v0.23.x: RemoteTagCache: add missing include
Fix build with Boost 1.81.0. `<array>` was included by one of those boost headers,
however, it's no longer included as of Boost 1.81.0.

`master` doesn't use `std::array` in this file.

While we're at it, add all necessary inclusion files.
2022-12-01 08:29:23 +07:00
9866adff95 release v0.23.11 2022-11-28 16:55:46 +01:00
a8b0c55818 input/curl: make proxy verify setting optional
These settings do not work if CURL was compiled with
CURL_DISABLE_PROXY, and cause error "An unknown option was passed in
to libcurl".

Fixes regression by commit 7ab0dfc8ce
2022-11-28 16:14:01 +01:00
cac88e8be5 python/build/libs.py: re-enable verbose error strings
This compile-time option is not about debug logging, but about
curl_easy_strerror().

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1670
2022-11-28 16:12:17 +01:00
e9f6a3482c db/Configured: add default "cache_directory" setting 2022-11-28 14:24:52 +01:00
5d2e80f188 db/Configured: use GetAppCacheDir() instead of GetUserCacheDir() 2022-11-28 14:20:15 +01:00
cfd4d5b13e StateFileConfig: use GetAppCacheDir() instead of GetUserCacheDir() 2022-11-28 14:20:14 +01:00
06514aec63 fs/StandardDirectory: add GetAppCacheDir() 2022-11-28 14:19:30 +01:00
4ded1ae67b fs/FileSystem: add CreateDirectoryNoThrow() 2022-11-28 14:19:08 +01:00
1da974e3fa fs/StandardDirectory: use PACKAGE_NAME from version.h 2022-11-28 14:05:34 +01:00
94f06f0946 fs/StandardDirectory: use mode=0777 in mkdir() call
Of course, mode=0700 is more secure, but allowing other users access
to new directories is a choice the user should make via umask().  If
the user-chosen umask allows everybody access, MPD should probably
respect that.
2022-11-28 14:04:47 +01:00
d9eec8a455 fs/StandardDirectory: do not use $RUNTIME_DIRECTORY on Android
This is systemd specific, and Android doesn't have systemd.
2022-11-28 10:44:50 +01:00
eaecbcafb2 PlaylistFile: disallow backslash in playlist names on Windows
The function spl_valid_name() should verify playlist names and prevent
path traversal, but it failed to do so on Windows, because it forgot
to check for backslashes.

This buggy piece of code was already present when stored playlists
were initially implemented in 2006 by commit 08003904d7, and
even during the many rounds of code refactoring, nobody ever bothered
to verify it.  D'oh!

(Thanks, Paul Arzelier)
2022-11-28 09:53:49 +01:00
73b5d0a9b9 system/Error: truncate the snprintf() return value
snprintf() does not return the (truncated) length actually written,
but the length that would be needed if the buffer were large enough.
This API usage mistake in FormatLastError() can lead to overflow of
the stack buffer, crashing the process (Windows only).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1676
2022-11-28 09:42:37 +01:00
c2d0f35e7a storage/meson.build: move StorageState.cxx to "mpd" executable
Fixes spurious linker errors.
2022-11-12 12:24:48 +01:00
ab99a57997 test/meson.build: reduce test_translate_song. dependencies 2022-11-12 12:17:35 +01:00
c8ebaf3521 python/build/meson.py: use "meson setup" instead of the deprecated syntax 2022-11-12 12:10:06 +01:00
52d00f7e30 subprojects: update fmt to 9.1.0 2022-11-11 19:22:39 +01:00
309491a6d8 subprojects: update expat to 2.5.0 2022-11-11 19:22:30 +01:00
gd
e7bfd32ccc doc/index.rst: added man pages links to suppress warnings: document isn't included in any toctree 2022-11-08 14:32:40 +01:00
gd
6f283b52ab doc/conf.py: set language = 'en' to suppress warning: Invalid configuration value found 2022-11-08 14:32:32 +01:00
32bddfabea archive/plugins/meson.build: do not generate empty library
If no archive library was found, return from the "plugins" directory
without creating "libarchive_plugins.a".  Empty static libraries are
unsupported on some operating systems such as macOS.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1650
2022-11-03 20:36:00 +01:00
1944c826bc doc/conf.py: fix version regular expression
Commit 44ef34db88 was broken.
2022-11-03 20:33:08 +01:00
619bb60b26 python/build/libs.py: update FLAC to 1.4.2 2022-11-03 10:28:13 +01:00
c549e16ed1 python/build/libs.py: update CURL to 7.86.0 2022-11-03 10:28:13 +01:00
01c9c4507f python/build/libs.py: update OpenSSL to 3.0.7
Punycode hooray!
2022-11-03 10:28:13 +01:00
8c9d7bf07e increment version number to 0.23.11 2022-10-20 19:09:03 +02:00
44ef34db88 doc/conf.py: read version number from meson.build 2022-10-20 19:08:27 +02:00
5781f223f6 Document curl plugin .netrc and .curlrc behavior 2022-10-18 22:39:01 +02:00
e4c8ebe056 release v0.23.10 2022-10-14 23:51:41 +02:00
76b25a1377 output/alsa: add nullptr check for snd_pcm_name() return value
It is not explicitly documented whether snd_pcm_name() is allowed to
return NULL:
https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html#ga5031edc0422df8db1f70af056a12dd77

But apparently this is legal:
0222f45d11/src/pcm/pcm.c (L2761-L2762)

That's ... surprising!

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1645
2022-10-14 23:14:30 +02:00
ccc3ee663b java/File: remove assertions to work around -Wtautological-pointer-compare 2022-10-14 23:00:35 +02:00
0626661764 android/Context: fix typo in assert() variable name
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1644
2022-10-14 22:59:39 +02:00
31db04a3ca meson.build: suppress bogus clang 14 warning on libfmt headers 2022-10-14 22:54:34 +02:00
0c7163b9db subprojects: update expat 2022-10-14 22:46:30 +02:00
7d78cad8af doc/user.rst: update Android NDK requirement to 25b 2022-10-14 22:41:33 +02:00
912530ed20 test/meson.build: remove obsolete CURL workaround
This appears to have been fixed in some recent CURL version.
2022-10-14 22:41:33 +02:00
d3f37199b9 python/build/libs.py: update libnfs to 5.0.2 2022-10-14 22:41:33 +02:00
a4748d84b0 python/build/libs.py: update CURL to 7.85.0 2022-10-14 22:41:33 +02:00
8f847ec381 python/build/libs.py: update FFmpeg to 5.1.2 2022-10-14 22:41:33 +02:00
3a70f09dd3 python/build/libs.py: update libopenmpt to 0.6.6 2022-10-14 22:41:33 +02:00
568f63100b python/build/libs.py: update zlib to 1.2.13 2022-10-14 21:54:04 +02:00
3e25916b37 time/Parser: remove unused library 2022-09-30 18:17:03 +02:00
5f9438dae6 storage/curl: include cleanup 2022-09-30 18:16:46 +02:00
99e65c58ce storage/curl: make timestamp parsing more robust
According to the latest WebDAV specification (RFC4918),
timestamp string in the getlastmodified property is formatted
as rfc1123-date, such as "Sun, 06 Nov 1994 08:49:37 GMT".
However, to process responses from servers in the older style
format specified in RFC2518, timestamps in the HTTP-date format
had better be accepted.

As described in the libcurl api documentation, curl_getdate() can handle
timestamp strings in HTTP-date formats, including rfc1123-date.

https://www.rfc-editor.org/rfc/rfc4918#section-15.7
https://www.rfc-editor.org/rfc/rfc2518.html#section-13.7
https://curl.se/libcurl/c/curl_getdate.html
2022-09-29 18:19:30 +02:00
df71b07e9d storage/curl: fix can't get timestamp of remote file 2022-09-29 18:19:03 +02:00
2694195215 storage/curl: add noexcept and [[gnu::pure]] 2022-09-29 18:18:18 +02:00
66450d1f3c subprojects: update expat, fmt, sqlite3, vorbis 2022-09-28 11:34:33 +02:00
76efea3aa7 decoder/ffmpeg: add libfmt formatter for AVSampleFormat
Fixes compiler warning because formatting unscoped enums is deprecated
since libfmt 9.
2022-09-28 11:34:33 +02:00
7ab0dfc8ce Sets the curl proxy ssl verify options to the values of the host configuration options
This fixes 
2022-09-27 20:26:50 +02:00
15ff7c4cad Merge branch 'fix-oggflac-serial' of https://github.com/anthonyde/MPD into v0.23.x 2022-09-20 14:44:13 +02:00
9ab9b97f20 encoder/flac: only set a serial number for oggflac
This fixes a bug introduced in 87fa6bca where the FLAC encoder fails to
initialize unless libFLAC is built with Ogg support. When libFLAC is
built without Ogg support, FLAC__stream_encoder_set_ogg_serial_number
unconditionally returns false.
2022-09-16 17:58:41 -07:00
88d92aceab python/build/libs.py: update libFLAC to 1.4.0 2022-09-16 18:21:47 +02:00
a2ce4352c8 python/build/libs.py: update Boost to 1.80.0 2022-09-16 17:54:07 +02:00
84f43ccde8 LogInit: default to stderr on Windows
Don't require "log_file" setting, for "--no-config" operation.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1600
2022-09-06 21:04:53 +02:00
38704c9cf3 LogInit: improve systemd/journald comment 2022-09-06 21:03:56 +02:00
910d0ec92b test/net/meson.build: add missing dependency 2022-09-06 20:44:24 +02:00
3b05c89765 archive/iso9660: fix off-by-one assertion failure
Calling data[fill] could trigger an assertion failure if
fill==data.size(), even if we call it only to take the address.

Instead of doing that, this commit changes the code to pointer
arithmetic.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1556
2022-09-06 20:28:33 +02:00
e77b3fa46f increment version number to 0.23.10 2022-09-06 20:23:50 +02:00
12147f6d58 release v0.23.9 2022-08-18 18:20:54 +02:00
40bc60d6ae Main: load Android mpd.conf from ExternalFilesDir
See also https://github.com/MusicPlayerDaemon/MPD/issues/1061

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1570
2022-08-18 18:17:43 +02:00
7778210269 Main: move code to TryReadConfigFile() 2022-08-18 18:12:21 +02:00
6229210d51 Main: move code to LoadConfigFile() 2022-08-18 18:11:49 +02:00
5d0d5b5d97 Android/Context: allow type=nullptr in GetExternalFilesDir() 2022-08-18 18:11:49 +02:00
1aa3c1e543 java/String: add static method Optional() 2022-08-18 18:10:16 +02:00
b90e32fe4e Android/Context: look up methods once during startup 2022-08-18 18:10:14 +02:00
1f4df2a64d android/Environment: pass JNIEnv to all functions 2022-08-18 18:09:54 +02:00
2efc1db6a9 android/Environment: no namespace indent 2022-08-18 18:08:45 +02:00
e2d4654e20 filter/ReplayGain: invoke the MixerListener after volume change
This ensures that Partition::OnMixerVolumeChanged() invokes
MixerMemento::InvalidateHardwareVolume(), clearing the cached volume
level.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1526
2022-08-18 14:45:45 +02:00
2b8f1170a6 mixer/Control: use Mixer::IsGlobal() 2022-08-18 14:33:35 +02:00
5c4743441e mixer/All: use Mixer::IsPlugin() 2022-08-18 14:08:31 +02:00
cb288439a4 {android,win32}/build.py: make stdout/stderr unbuffered
Avoid excessive buffering if run by CI.
2022-08-08 23:48:23 +02:00
69f741e8a6 mixer/Memento: move IDLE_MIXER out of SetVolume()
Make this idle event per-partition.
2022-08-08 23:32:57 +02:00
4b4f47002b mixer/Volume: refactor to class MixerMemento, per partition
Eliminate global variables, convert them to MixerMemento fields.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1583
2022-08-08 23:30:27 +02:00
615c301961 mixer/Volume: remove logging (mostly useless) 2022-08-08 23:13:14 +02:00
dc07180e48 input/CdioParanoia: add options "mode" and "skip"
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1529
2022-08-08 22:53:48 +02:00
d3b235bab5 input/CdioParanoia: move global variables up 2022-08-08 22:38:28 +02:00
7c920ddebe filter/ffmpeg: fix FFmpeg 5.1 deprecation warnings 2022-08-08 21:34:26 +02:00
bbc088ae4e This PR provides forward and backward compatibility at macos SDK 12.0. At SDK 12.0, API function names were changed essentially replacing
occurrences of the word Master/master with Main/main. This change was test built on two different systems.

1. macos 10.15.7 with Xcode 12.4 and clang 12.0.0 on x86_64
2. macos 12.5 with Xcode 13.4.1 and clang 13.1.6 on arm64 (Apple silicon M1)

It should be noted that on macos 10.15.7 with Xcode 11.2 and clang 11.0, MPD will not build.
The MPD documentation states that clang 11.0 is the minimum requirement,
but clang 11.0 produces compile errors. Apparently the macos version
of clang 11.0 is not fully compliant.
2022-08-08 17:39:29 +02:00
fe195257d8 python/build/libs.py: update FFmpeg to 5.1 2022-07-27 11:04:14 +02:00
57d5df8118 decoder/ffmpeg: fix FFmpeg 5.1 deprecation warnings 2022-07-27 11:04:09 +02:00
59792cb0b8 decoder/ffmpeg: wrap FFmpeg include in "extern C"
Commit ebae25d175 added that #include, but forgot to wrap it in
"extern C", so the linker tried to look up C++ symbols, causing linker
failure.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1582
2022-07-27 11:04:03 +02:00
cc557c4d60 meson: port ncpmc iconv solution
Properly deals with iconv, unlike the current solution. have_iconv fails
when libiconv CFLAGS are passed to the compiler. Tested under OpenWrt
with its CONFIG_BUILD_NLS, which adds libiconv include flags.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2022-07-20 08:03:24 +02:00
956c5faebb output/PipeWire: set app icon
Closes 
2022-07-12 13:59:05 +02:00
cd0396c1f1 test/run_decoder: remove bogus assert() 2022-07-12 11:59:14 +02:00
79f9b268bb increment version number to 0.23.9 2022-07-12 11:50:47 +02:00
b45f3c8deb Android release 0.23.8 2022-07-12 11:48:41 +02:00
f8a8de87e4 android/AndroidManifest.xml: update targetSdkVersion to 30
Required by Google Play.
2022-07-12 11:48:41 +02:00
2183f0553c android/meson.build: use apksigner instead of jarsigner
This is required for targetSdkVersion=30.

apksigner requires running zipalign first.
2022-07-12 11:48:41 +02:00
1f28790476 release v0.23.8 2022-07-09 01:05:38 +02:00
c8dae95eff output/PipeWire: after Cancel(), refill buffer before resuming playback
Deactivate the stream in Cancel().  This fixes stuttering after a
manual song change by refilling the whole ring buffer before
reactivating the stream.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1354
2022-07-09 01:03:36 +02:00
547a084c7e output/PipeWire: call pw_stream_flush() in Cancel()
Clear not only MPD's ring buffer, but also libpipewire's buffers, to
avoid playing some audio from the previous song after a manual song
change.

Fixes part 1 of https://github.com/MusicPlayerDaemon/MPD/issues/1354
2022-07-09 01:01:29 +02:00
493677ff81 output/PipeWire: skip Cancel() if already drained 2022-07-09 00:53:53 +02:00
6b430ba271 output/PipeWire: activate stream in Drain() 2022-07-09 00:53:20 +02:00
bc6924d303 output/snapcast: fix busy loop while paused
Removing the LockHasClients(); this code was copied from the "httpd"
output plugin, but unlike "httpd", the SnapCast output plugin does not
feed silence while paused, so we need to implement a delay to avoid
busy-looping the CPU.

As a side effect, this eliminates the suttering after resuming
playback, because the timer now gets reset even if there is a client.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1394
2022-07-08 22:55:41 +02:00
02b00f9146 output/PipeWire: don't force initial volume=100%
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1484
2022-07-08 18:25:41 +02:00
e807ed5870 output/PipeWire: ignore SPA_PROP_channelVolumes if n_values==0
After connecting, PipeWire sometimes sends SPA_PROP_channelVolumes
with no values, and this led to "volume=-NaN".
2022-07-08 18:13:33 +02:00
f08944253b output/PipeWire: check SPA_PROP_channelVolumes, not control name
Since PipeWire 0.3.53, there is no control name anymore, therefore the
name check doesn't work anymore, breaking volume change events.

This obsoletes the crash bug fix in commit 2ee57f9b0d
2022-07-08 18:06:36 +02:00
792d6584b9 output/PipeWire: move code to OnChannelVolumes() 2022-07-08 18:02:36 +02:00
7b45d01462 output/PipeWire: update field "volume" 2022-07-08 17:44:39 +02:00
5c17b2966a output/PipeWire: use std::accumulate 2022-07-08 17:44:08 +02:00
0c54f29446 output/PipeWire: document field "volume" 2022-07-08 17:30:57 +02:00
9c3cf39fdd output/PipeWire: catch exceptions in ParamChanged()
Fixes a potential crash bug.
2022-07-08 17:24:41 +02:00
d2fb229685 output/PipeWire: call ::SetVolume() in ParamChanged()
This is a lower-level function without some of the clutter of
PipeWireOutput::SetVolume() which is not needed in that case.
2022-07-08 17:21:17 +02:00
f55bc6682f output/PipeWire: move code to ::SetVolume() 2022-07-08 17:19:10 +02:00
6857286b42 decoder/Thread: don't scan for replay gain tags in PCM streams
This disables a long delay for playing songs from the cdio_paranoia
input plugin if ReplayGain is enabled.
2022-07-08 16:33:19 +02:00
c0d5bd2048 decoder/Thread: move code to DecoderControl::LockIsReplayGainEnabled() 2022-07-08 16:21:53 +02:00
666e5d7904 input/CdioParanoia: use integer modulo to calculate "diff" 2022-07-08 16:04:05 +02:00
3613407ac5 input/CdioParanoia: use typedef lsn_t 2022-07-08 16:03:04 +02:00
c32dceb4d4 input/CdioParanoia: remove loop from Read()
The Read() method is not required to fill the whole buffer.  By
returning as soon as at least one byte was read, we allow faster
cancellation.
2022-07-08 16:01:23 +02:00
5573e78364 input/CdioParanoia: skip seek if seeking within the buffer 2022-07-08 13:57:11 +02:00
807a19889f input/CdioParanoia: update offset only after successful seek
If seeking fails, don't leave the class with a wrong offset.
2022-07-08 13:57:11 +02:00
df7242de91 input/CdioParanoia: eliminate redundant field "lsn_relofs" 2022-07-08 13:36:59 +02:00
d62426f168 input/CdioParanoia: eliminate redundant field "lsn_to"
Use "size" instead.
2022-07-08 12:42:49 +02:00
1714cf3417 input/CdioParanoia: use IsEof() in Read() 2022-07-08 12:42:42 +02:00
1080c917be input/CdioParanoia: use std::min() 2022-07-08 12:37:21 +02:00
8eb3164878 input/CdioParanoia: fix crash if no drive was found
cdio_get_devices_with_cap() can return nullptr if no drive was found,
or it can instead return an empty list.  The latter caused MPD to
crash.
2022-07-08 12:05:20 +02:00
915c5442d1 input/CdioParanoia: use AtScopeExit() for cdio_free_device_list() 2022-07-08 12:03:57 +02:00
be0360d5e8 doc/user.rst: clarify .mpdignore documentation
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1532
2022-07-08 11:44:14 +02:00
4d6ae6ffdd output/PipeWire: add nullptr check to SetVolume()
If the PipeWire output has not yet been enabled and no thread_loop has
been created yet, a nullptr dereference in SetVolume() was possible
because nullptr was passed to pw_thread_loop_lock().
2022-07-08 11:32:59 +02:00
ecee6f415b mixer/MixerInternal: remember error details
If a mixer is not open, rethrow the original exception each time
setting the volume is requested.  This further improves error messages
sent to MPD clients.
2022-07-08 11:11:53 +02:00
47680f936b mixer/All: auto-open "global" mixers
If a mixer is "global", it is available even if the output isn't
open.  However, since the check was changed from IsEnabled() to
IsReallyEnabled(), enabled outputs have not yet been used have not
been "really" enabled yet, preventing using the mixer.

Fixes a regression by commit 35dbc1a90c
(part of https://github.com/MusicPlayerDaemon/MPD/pull/1480).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1563
2022-07-08 11:05:26 +02:00
2d7181105d output/MultipleOutputs: SetVolume() throws on error
This reveals more about the nature of an error instead of just
returning "problems setting volume".
2022-07-08 10:56:55 +02:00
9bdc75524b python/build/libs.py: update CURL to 7.84.0 2022-07-08 10:13:52 +02:00
2f6ceb4949 python/build/libs.py: update OpenSSL to 3.0.5 2022-07-08 10:10:42 +02:00
cd933aa35f subprojects: update fmt and vorbis 2022-07-08 10:08:27 +02:00
138738075b libfmt 9 support
libfmt version 9 broke the API by removing fmt::make_args_checked().

Fixes https://bugs.debian.org/1014543
2022-07-08 10:06:53 +02:00
2ee57f9b0d output/PipeWire: add nullptr check, fixing crash with PipeWire 0.3.53
Since PipeWire 0.3.53, control names can apparently be nulled, leading
to crashes in applications assertion that the string cannot be
nullptr.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1558
2022-07-04 19:20:08 +02:00
5a5655b790 lib/curl/Adapter: catch and postpone exceptions in WriteFunction()
This fixes a std::terminate() crash in the CURL storage plugin when
PropfindOperation::OnHeaders() throws an exception after receiving a
non-207 status.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1559
2022-07-01 12:43:42 +02:00
b88d1e6820 lib/curl/Headers: make the comparison type "transparent" 2022-07-01 12:17:41 +02:00
19d2864c34 lib/curl/Headers: central type definition for the header map 2022-07-01 12:17:36 +02:00
29e3a17f26 lib/curl/Request: move code from SetupEasy() to Setup.cxx 2022-07-01 12:17:26 +02:00
252e9f736f lib/curl/Request: move code to class CurlResponseHandlerAdapter 2022-07-01 12:17:20 +02:00
5d08988dda lib/curl/Handler: fix typo 2022-07-01 12:17:17 +02:00
47ca4246aa lib/curl/Request: add constructor with CurlEasy parameter 2022-07-01 12:17:13 +02:00
f8338d4f00 lib/curl/Request: use std::size_t 2022-07-01 12:16:59 +02:00
5cf6032c90 lib/curl/Request: move code to SetupEasy() 2022-07-01 12:16:55 +02:00
8d8b77412d lib/curl/Request: add API docs 2022-07-01 12:16:50 +02:00
fd9114e7e2 doc/user.rst: fix neighbor plugin config block name 2022-06-08 12:57:27 +02:00
a3fba2f8f7 python/build/libs.py: update CURL to 7.83.1 2022-05-24 10:56:29 +02:00
e2b671f1b2 python/build/libs.py: add --disable-vulkan to FFmpeg configuration
Fixes Android build failure with NDK r25 beta4 because "vulkan_beta.h"
was not found.
2022-05-24 10:55:55 +02:00
2a35fbe29e python/build/libs.py: fix the OpenSSL SHA256 2022-05-24 10:55:55 +02:00
81cde72fd0 meson.build: suppress -Wstringop-overflow due to bogus libfmt warnings
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1536
2022-05-24 10:39:30 +02:00
bf9ffba4f7 doc/user.rst: fix playlist plugin name option
`playlist_plugin` blocks use `name` to identify the plugins.
2022-05-24 10:22:45 +02:00
c975d8b943 Fix deprecation warnings caused by name changes in OSX audio inerfaces 2022-05-24 10:20:47 +02:00
2730f91872 .github/workflows/build.yml: build everything, not just unit tests (Linux) 2022-05-23 21:32:42 +02:00
97ca85e155 .github/workflows/build.yml: verbose build (Linux) 2022-05-23 21:32:02 +02:00
39bb4c5871 .github/workflows/build.yml: build everything, not just unit tests 2022-05-23 21:28:28 +02:00
bdceb90c59 .github/workflows/build.yml: verbose build 2022-05-23 21:25:28 +02:00
8bd1b5228c lib/upnp/Compat: suppress -Wunused-but-set-parameter 2022-05-19 20:10:41 +02:00
a009e95afd .github/ISSUE_TEMPLATE/bug_report.md: add "Configuration" section 2022-05-19 09:26:21 +02:00
32aafb3572 .github/ISSUE_TEMPLATE/question.md: remove, we have GitHub discussions now 2022-05-19 09:25:00 +02:00
b577783cf0 .github/FUNDING.yml: remove, no funding
This was an experiment, but I decided I don't need that.
2022-05-19 09:24:22 +02:00
aa7b872a14 .github/workflows/build.yml: run "apt-get update"
The build has been failing for a week or two because the package lists
in the image are outdated.
2022-05-19 09:23:08 +02:00
c6f7f57776 apple/Throw: add missing <cstring> header
strlen() and strcpy() are provided by the <string.h> and <cstring>
headers (as functions in global and std namespaces, respectively).

Compilers MAY provide an implementation for either of the functions
without including the extra header but the existence of a declaration
without the header is not assured.
2022-05-19 09:08:44 +02:00
106ad08cd2 increment version number to 0.23.8 2022-05-09 23:12:17 +02:00
0341ca1b6a release v0.23.7 2022-05-09 23:04:30 +02:00
7581ea55db python/build/libs.py: update CURL to 7.83.0 2022-05-09 23:03:14 +02:00
fc9cee38d8 python/build/libs.py: update OpenSSL to 3.0.3 2022-05-09 23:03:14 +02:00
b175e4128d encoder/meson.build: always generate encoder/Features.h
Fixes regression from commit 85f9863e0a
2022-05-09 22:52:59 +02:00
97b07798b0 doc/protocol.rst: clarify repeat/single/random side effects 2022-05-09 22:50:57 +02:00
112fcd206d Merge branch 'fix-hls-seeking' of https://github.com/burrocargado/MPD into v0.23.x 2022-05-09 22:44:53 +02:00
11d1f56062 Fix seeking HLS on-demand streaming not working
This issue occurs when playing HLS streaming delivered
from a server that does not support partial requests.
The issue is reproduced as follows(using Ubuntu 20.04 PC):

1. Prepare HLS example content.

$ mkdir test
$ ffmpeg -i example.flac -vn -c:a aac -b:a 128000 -f hls -hls_list_size 0 test/output.m3u8
(ffmpeg 4.2.4 is used)

2. Prepare web server without partial requests support.
(Docker version 20.10.12 and NGINX official Docker image is used)

$ docker run --name tmp-nginx-container -d nginx
$ docker cp tmp-nginx-container:/etc/nginx/conf.d/default.conf .
$ docker rm -f tmp-nginx-container

Edit default.conf and add "max_ranges 0;" to "location / {...}".
This disables partial requests support,
removes 'Accept-Ranges: bytes' header from the server response.
Then, run the server:

$ docker run --name test-nginx -v $PWD/test:/usr/share/nginx/html:ro -v $PWD/default.conf:/etc/nginx/conf.d/default.conf -d -p 8080:80 nginx

3. Setup MPD to Play the next URL.

http://address-of-the-server:8080/output.m3u8

Seeking this stream results in "exception: Not seekable".
2022-05-07 12:18:56 +09:00
bd840d4638 decoder/plugins/FFmpegDecoder: fix IsSeekable()
AVFMTCTX_UNSEEKABLE signals the stream is not seekable
according to FFmpeg source code description:
8e98dfc57f/libavformat/avformat.h (L1181)
2022-05-07 09:48:04 +09:00
c3d393f214 tag/Id3Picture: fix unaligned access 2022-04-26 21:03:48 +02:00
f88fc0ca1a util/ByteOrder: add class PackedBE32 2022-04-26 21:03:05 +02:00
fb8d8242ab tag/ApeLoader: fix unaligned access
Fixes part 4 of https://github.com/MusicPlayerDaemon/MPD/issues/1490
2022-04-26 21:00:41 +02:00
f2a3dfd700 decoder/ffmpeg: add missing nullptr checks
Fixes part 1 of https://github.com/MusicPlayerDaemon/MPD/issues/1490
2022-04-26 20:51:57 +02:00
85f9863e0a meson.build: always enable Wave encoder for Snapcast
Even if the "wave_encoder" option is disabled (and no other encoder
plugins are enabled), forcefully enable the Wave encoder (if Snapcast
is enabled).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1500
2022-04-26 20:13:43 +02:00
83572701f4 python/build/libs.py: update Boost to 1.79.0 2022-04-26 18:27:51 +02:00
fa7d7e9187 python/build/libs.py: update OpenSSL to 3.0.2 2022-04-26 18:27:51 +02:00
f818cde32c python/build/libs.py: update FFmpeg to 5.0.1 2022-04-26 18:27:51 +02:00
9da93cd887 python/build/libs.py: update zlib to 1.2.12 2022-04-26 18:27:51 +02:00
026e7ea32a update all subprojecs
Done with meson wrap update

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2022-04-26 18:01:53 +02:00
9659d19718 lib/upnp/Init: use if with initalizer 2022-04-26 17:58:33 +02:00
50d35c9677 upnp: use UpnpInit2 always
libupnp 1.14 removes the non 2 function. Fixes compilation there.

Signed-off-by: Rosen Penev <rosenp@gmail.com>

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1499
2022-04-26 17:57:48 +02:00
4260e78861 android: add gdb.sh
This script setup a dummy android native app folder and call ndk-gdb from it.

It needs a modification in ANDROID_NDK since ndk-gdb may attach to the wrong
pid, cf. comments in the script.
2022-04-26 17:47:54 +02:00
7342ae2e33 android: set application debuggable
This debuggable flag should not be set with release builds. Generally, graddle
is taking care of that.
2022-04-26 17:46:51 +02:00
35dbc1a90c mixer,output: prevent setting volume before outputs are really enabled
Previous versions of MPD would call SetVolume on enabled outputs before
they are ready, causing all of MPD to crash. Checking the really_enabled
flag prevents this, though it also prevents setting volume before the
player starts.

Before (with the PipeWire output):
  [i] ~$ mpc clear
  volume: 81%   repeat: off   random: off   single: off   consume: off
  [i] ~$ systemctl --user restart mpd.service
  [i] ~$ mpc volume 100
  MPD error: Connection closed by the server
  [i] ~ 1 $

After:
  [i] ~$ # mpd is freshly started w/o anything in the queue
  [i] ~$ mpc
  volume:100%   repeat: off   random: off   single: off   consume: off
  [i] ~$ mpc volume 80
  MPD error: problems setting volume
  [i] ~ 1 $ mpc
  volume:100%   repeat: off   random: off   single: off   consume: off
  [i] ~$
2022-04-26 17:45:29 +02:00
c7a4355153 outputs/pipewire: fix ParamChanged incorrectly setting volume
Previous versions of MPD would, on parameter change, set the PipeWire
volume before clearing the restore_volume flag, causing the call to
short circuit and do nothing. Instead, clear the flag before the call.
2022-04-26 17:44:19 +02:00
33a84a8ca2 output/shout: use shout_set_metadata_utf8() 2022-04-26 17:41:21 +02:00
1d04490ed3 output/shout: use shout_set_content_format() 2022-04-26 17:38:43 +02:00
4a30c2d79c output/shout: use shout_set_meta() 2022-04-26 17:24:49 +02:00
83072d6b9c output/shout: pass reference to Setup() 2022-04-26 16:49:18 +02:00
c779fc37eb output/shout: declare minimum version 2.4.0
This version was released 7 years ago, and it's reasonable to require
at least this version.
2022-04-26 16:46:36 +02:00
e08c13ad7e output/shout: add "noexcept" 2022-04-26 15:57:03 +02:00
2c82a6b2e0 output/shout: handle shout_metadata_add() errors
Fixes -Wunused-result
2022-04-26 15:56:55 +02:00
3929f17aef NEWS: mention the libiconv fix 2022-04-26 15:56:54 +02:00
ee39af3419 fix typo in comment 2022-04-24 04:14:17 +00:00
3882a5a263 src/lib/icu: fix iconv() detection when libiconv is installed 2022-04-20 16:10:39 +02:00
ac06088948 Make volume changes to apply to disabled software mixers.
Move audio output state check ahead of mixer check and force volume
applying even for disabled software mixed outputs.

This fixes incorrect software mixer volume that used to occur when
volume was changed while output being disabled.

This is easily reproduced with following sequence of commands on
multi-output software mixed MPD setup.

 mpc volume 38; mpc disable 3; mpc volume 88; mpc enable 3

On current MPD, following commands would result in output 3 playing at
volume 38, while all other enabled outputs would play at volume
88. Moreover, global volume would display average of outputs real
volumes. In my case, it's 75.

After applying this patch, following commands would produce expected
behavior. All outputs play at expected (88) volume. And volume is
correctly displayed as 88.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1423

Signed-off-by: Vitaly Ostrosablin tmp6154@yandex.ru


Signed-off-by: Vitaly Ostrosablin <tmp6154@yandex.ru>
2022-03-26 06:29:18 +01:00
a757eebfbb decoder/OggSyncState: allow skipping up to 64 kB after seek
This is more of what we did in commit 70bd35abe2 because it turns
out there are Ogg-Opus files with pages larger than 40 kB.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1487
2022-03-16 16:54:50 +01:00
2be4f89555 test/DumpOgg: new debug program 2022-03-16 16:51:44 +01:00
4a5c7d8261 increment version number to 0.23.7 2022-03-14 18:55:55 +01:00
167 changed files with 2371 additions and 1076 deletions
.github
NEWS
android
doc
meson.build
python/build
src
CommandLine.cxxCommandLine.hxxLog.hxxLogInit.cxxMain.cxxPartition.cxxPartition.hxxPlaylistFile.cxxRemoteTagCache.hxxSongPrint.cxxSongSave.cxxSongSave.hxxStateFile.cxxStateFileConfig.cxxTagPrint.cxxTimePrint.cxx
android
apple
archive
client
command
db
decoder
encoder
event
filter
fs
input
io
java
lib
mixer
output
storage
system
tag
time
unix
util
subprojects
systemd
test
win32

12
.github/FUNDING.yml vendored

@ -1,12 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: MaxK
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

@ -18,5 +18,9 @@ about: Create a bug report
<!-- Paste the output of "mpd --version" here -->
## Configuration
<!-- Paste your MPD configuration here -->
## Log
<!-- Paste relevant portions of the log file here (--verbose) -->

@ -1,9 +0,0 @@
---
name: Question
about: Ask a question about MPD
---
<!-- Before you ask a question on GitHub, please read MPD's
documentation. A copy is available at
https://www.musicpd.org/doc/html/ -->
## Question

@ -41,7 +41,8 @@ jobs:
key: linux
- name: Install dependencies
run: |
sudo apt install -y --no-install-recommends \
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
g++-10 libfmt-dev libboost-dev \
libgtest-dev \
libpcre2-dev \
@ -73,19 +74,30 @@ jobs:
libgcrypt20-dev
- name: Full Build
uses: BSFishy/meson-build@v1.0.3
with:
action: build
directory: output/full
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
options: --verbose
meson-version: 0.56.0
- name: Unit Tests
uses: BSFishy/meson-build@v1.0.3
with:
action: test
directory: output/full
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
options: --verbose
meson-version: 0.56.0
- name: Mini Build
uses: BSFishy/meson-build@v1.0.3
with:
action: test
action: build
directory: output/mini
setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
options: --verbose
meson-version: 0.56.0
build-macos:
@ -124,10 +136,20 @@ jobs:
wavpack \
libmpdclient
- name: Meson Build
- name: Build
uses: BSFishy/meson-build@v1.0.3
with:
action: build
directory: output
setup-options: -Ddocumentation=disabled -Dtest=true
options: --verbose
meson-version: 0.56.0
- name: Unit Tests
uses: BSFishy/meson-build@v1.0.3
with:
action: test
directory: output
setup-options: -Ddocumentation=disabled -Dtest=true
options: --verbose
meson-version: 0.56.0

101
NEWS

@ -1,3 +1,104 @@
ver 0.23.13 (2023/05/22)
* input
- curl: fix busy loop after connection failed
- curl: hide "404" log messages for non-existent ".mpdignore" files
* archive
- zzip: fix crash bug
* database
- simple: reveal hidden songs after deleting containing CUE
* decoder
- ffmpeg: reorder to a lower priority than "gme"
- gme: require GME 0.6 or later
* output
- pipewire: fix corruption bug due to missing lock
* Linux
- shut down if parent process dies in --no-daemon mode
- determine systemd unit directories via pkg-config
* support libfmt 10
ver 0.23.12 (2023/01/17)
* input
- curl: require CURL 7.55.0 or later
* decoder
- mad: fix integer underflow with very small files
* tags
- fix crash bug due to race condition
* output
- pipewire: adjust to PipeWire 0.3.64 API change
* fix build failures with GCC 13
ver 0.23.11 (2022/11/28)
* database
- simple: move default database to ~/.cache/mpd/db from ~/.cache/mpd.db
- simple: default "cache_directory" to ~/.cache/mpd/mounts
* macOS: fix build failure "no archive members specified"
* Windows
- fix crash bug (stack buffer overflow) after I/O errors
- fix path traversal bug because backslash was allowed in playlist names
* Android/Windows
- update OpenSSL to 3.0.7
- re-enable CURL's verbose error strings
ver 0.23.10 (2022/10/14)
* storage
- curl: fix file time stamps
* decoder
- ffmpeg: fix libfmt 9 compiler warning
* encoder
- flac: fix failure when libFLAC is built without Ogg support
* output
- alsa: fix crash bug
* Windows
- log to stdout by default, don't require "log_file" setting
ver 0.23.9 (2022/08/18)
* input
- cdio_paranoia: add options "mode" and "skip"
* decoder
- ffmpeg: support FFmpeg 5.1
* filter
- replay gain: fix delayed volume display with handler=mixer
* output
- pipewire: set app icon
* fix bogus volume levels with multiple partitions
* improve iconv detection
* macOS: fix macOS 10 build problem (0.23.8 regression)
* Android
- load mpd.conf from app data directory
ver 0.23.8 (2022/07/09)
* storage
- curl: fix crash if web server does not understand WebDAV
* input
- cdio_paranoia: fix crash if no drive was found
- cdio_paranoia: faster cancellation
- cdio_paranoia: don't scan for replay gain tags
- pipewire: fix playback of very short tracks
- pipewire: drop all buffers before manual song change
- pipewire: fix stuttering after manual song change
- snapcast: fix busy loop while paused
- snapcast: fix stuttering after resuming playback
* mixer
- better error messages
- alsa: fix setting volume before playback starts
- pipewire: fix crash bug
- pipewire: fix volume change events with PipeWire 0.3.53
- pipewire: don't force initial volume=100%
* support libfmt 9
ver 0.23.7 (2022/05/09)
* database
- upnp: support pupnp 1.14
* decoder
- ffmpeg: fix HLS seeking
- opus: fix missing song length on high-latency files
* output
- shout: require at least libshout 2.4.0
* mixer
- pipewire: fix volume restore
- software: update volume of disabled outputs
* support libiconv
ver 0.23.6 (2022/03/14)
* protocol
- support filename "cover.webp" for "albumart" command

@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="65"
android:versionName="0.23.5">
android:versionCode="71"
android:versionName="0.23.12">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
<uses-feature android:name="android.software.leanback"
android:required="false" />
@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true"
android:debuggable="true"
android:requestLegacyExternalStorage="true"
android:icon="@drawable/icon"
android:banner="@drawable/icon"

@ -12,18 +12,30 @@ unsigned_apk = custom_target(
],
)
aligned_apk = custom_target(
'mpd-aligned.apk',
output: 'mpd-aligned.apk',
input: unsigned_apk,
command: [
android_zipalign,
'-f', '4',
'@INPUT@', '@OUTPUT@',
],
)
if get_option('android_debug_keystore') != ''
debug_apk = custom_target(
'mpd-debug.apk',
output: 'mpd-debug.apk',
input: unsigned_apk,
input: aligned_apk,
command: [
jarsigner,
'-keystore', get_option('android_debug_keystore'),
'-storepass', 'android',
'-signedjar', '@OUTPUT@',
'@INPUT@',
'androiddebugkey',
apksigner, 'sign',
'--in', '@INPUT@',
'--out', '@OUTPUT@',
'--debuggable-apk-permitted',
'-ks', get_option('android_debug_keystore'),
'--ks-key-alias', 'androiddebugkey',
'--ks-pass', 'pass:android',
],
build_by_default: true
)
@ -31,29 +43,16 @@ endif
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
unaligned_apk = custom_target(
'mpd-unaligned.apk',
output: 'mpd-unaligned.apk',
input: unsigned_apk,
command: [
jarsigner,
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
'-keystore', get_option('android_keystore'),
'-storepass', get_option('android_keypass'),
'-signedjar', '@OUTPUT@',
'@INPUT@',
get_option('android_keyalias'),
],
)
apk = custom_target(
'mpd.apk',
output: 'mpd.apk',
input: unaligned_apk,
input: aligned_apk,
command: [
android_zipalign,
'-f', '4',
'@INPUT@', '@OUTPUT@',
apksigner, 'sign',
'--in', '@INPUT@',
'--out', '@OUTPUT@',
'-ks', get_option('android_keystore'),
'--ks-key-alias', get_option('android_keyalias'),
'--ks-pass', 'pass:' + get_option('android_keypass'),
],
build_by_default: true
)
endif

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env -S python3 -u
import os, os.path
import sys, subprocess
@ -100,6 +100,7 @@ class AndroidNdkToolchain:
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
self.ar = os.path.join(llvm_bin, 'llvm-ar')
self.arflags = 'rcs'
self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
self.nm = os.path.join(llvm_bin, 'llvm-nm')
self.strip = os.path.join(llvm_bin, 'llvm-strip')

54
android/gdb.sh Executable file

@ -0,0 +1,54 @@
#!/bin/sh
# This script need the following modification in ANDROID_NDK in order to attach
# to the good :main pid
#--- a/prebuilt/linux-x86_64/bin/ndk-gdb.py
#+++ b/prebuilt/linux-x86_64/bin/ndk-gdb.py
#@@ -669,7 +669,7 @@
# log("Sleeping for {} seconds.".format(args.delay))
# time.sleep(args.delay)
#
#- pids = gdbrunner.get_pids(device, pkg_name)
#+ pids = gdbrunner.get_pids(device, pkg_name + ":main")
# if len(pids) == 0:
# error("Failed to find running process '{}'".format(pkg_name))
# if len(pids) > 1:
SCRIPT_PATH=$(dirname $0)
BUILD_PATH="`pwd`"
TMP_PATH="$BUILD_PATH/gdb"
NDK_GDB_ARGS="--force"
ANDROID_NDK=$1
if [ ! -f $ANDROID_NDK/source.properties ];then
echo "usage: $0 ANDROID_NDK"
exit 1
fi
if [ ! -f $BUILD_PATH/libmpd.so ];then
echo "This script need to be executed from the android build directory"
exit 1
fi
rm -rf "$TMP_PATH"
mkdir -p "$TMP_PATH"
ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml"
ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1`
if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then
echo "Invalid manifest/ABI, did you try building first ?"
exit 1
fi
mkdir -p "$TMP_PATH"/jni
touch "$TMP_PATH"/jni/Android.mk
echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk
DEST=obj/local/$ABI
mkdir -p "$TMP_PATH/$DEST"
cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST"
cp "$ANDROID_MANIFEST" "$TMP_PATH"
(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS)

@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
javac = find_program('javac')
jarsigner = find_program('jarsigner')
apksigner = find_program('apksigner')
rsvg_convert = find_program('rsvg-convert')
convert = find_program('convert')
zip = find_program('zip')

@ -38,7 +38,10 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.23.6'
with open('../meson.build') as f:
import re
version = re.match(r"project\([^\)]*\bversion:\s*'([^']+)'",
f.read(4096)).group(1)
# The full version, including alpha/beta/rc tags.
#release = version + '~git'
@ -47,7 +50,7 @@ version = '0.23.6'
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
language = "en"
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:

@ -11,6 +11,12 @@ Music Player Daemon
client
protocol
.. toctree::
:maxdepth: 1
:caption: man pages:
mpd.1
mpd.conf.5
Indices and tables
==================

@ -181,7 +181,7 @@
#
#database {
# plugin "simple"
# path "~/.local/share/mpd/db
# path "~/.local/share/mpd/db"
# cache_directory "~/.local/share/mpd/cache"
#}
#

@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
- If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
* - **speed N**
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
* - **mode disable|overlap|full**
- Set the paranoia mode; ``disable`` means no fixups, ``overlap``
performs overlapped reads, and ``full`` enables all options.
* - **skip yes|no**
- If set to ``no``, then never skip failed reads.
curl
----
@ -214,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl.
Note that unless overridden by the below settings (e.g. by setting
them to a blank value), general curl configuration from environment
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
will be in effect.
variables such as ``http_proxy`` will be in effect.
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
.. list-table::
:widths: 20 80

@ -545,6 +545,13 @@ Playback options
Sets repeat state to ``STATE``,
``STATE`` should be 0 or 1.
If enabled, MPD keeps repeating the whole queue (:ref:`single mode
<command_single>` disabled) or the current song (:ref:`single mode
<command_single>` enabled).
If :ref:`random mode <command_random>` is also enabled, the
playback order will be shuffled each time the queue gets repeated.
.. _command_setvol:
:command:`setvol {VOL}`

@ -36,7 +36,9 @@ Installing on Android
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function).
If you need to tweak the configuration, you can create a file called
:file:`mpd.conf` in MPD's data directory on the external storage
(usually :file:`Android/data/org.musicpd/files/mpd.conf`).
ALSA is not available on Android; only the :ref:`OpenSL ES
<sles_output>` output plugin can be used for local playback.
@ -197,7 +199,7 @@ Compiling for Android
You need:
* Android SDK
* `Android NDK r23 <https://developer.android.com/ndk/downloads>`_
* `Android NDK r25b <https://developer.android.com/ndk/downloads>`_
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* cmake
@ -301,7 +303,7 @@ Configuring neighbor plugins
----------------------------
All neighbor plugins are disabled by default to avoid unwanted
overhead. To enable (and configure) a plugin, add a :code:`neighbor`
overhead. To enable (and configure) a plugin, add a :code:`neighbors`
block to :file:`mpd.conf`:
.. code-block:: none
@ -538,7 +540,7 @@ The following table lists the playlist_plugin options valid for all plugins:
* - Name
- Description
* - **plugin**
* - **name**
- The name of the plugin
* - **enabled yes|no**
- Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled.
@ -609,6 +611,11 @@ If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
set to a value (in dB) between ``-15`` and ``15``. This is the gain
applied to songs with ReplayGain tags.
On songs without ReplayGain tags, the setting
``replaygain_missing_preamp`` is used instead. If this setting is not
configured, then no ReplayGain is applied to such songs, and they will
appear too loud.
ReplayGain is usually implemented with a software volume filter (which
prevents `Bit-perfect playback`_). To use a hardware mixer, set
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
@ -1063,7 +1070,19 @@ The "music directory" is where you store your music files. :program:`MPD` stores
Depending on the size of your music collection and the speed of the storage, this can take a while.
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
To exclude a file from the update, create a file called
:file:`.mpdignore` in its parent directory. Each line of that file
may contain a list of shell wildcards. Matching files (or
directories) in the current directory and all subdirectories are
excluded. Example::
*.opus
99*
Subject to pattern matching is the file/directory name. It is (not
yet) possible to match nested path names, e.g. something like
``foo/*.flac`` is not possible.
Mounting other storages into the music directory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.23.6',
version: '0.23.13',
meson_version: '>= 0.56.0',
default_options: [
'c_std=c11',
@ -73,6 +73,9 @@ test_common_flags = [
# clang specific warning options:
'-Wunreachable-code-aggressive',
'-Wused-but-marked-unused',
# suppress bogus GCC12 warnings in libfmt headers
'-Wno-stringop-overflow',
]
test_global_cxxflags = test_global_common_flags + [
@ -202,7 +205,6 @@ enable_daemon = not is_windows and not is_android and get_option('daemon')
conf.set('ENABLE_DAEMON', enable_daemon)
conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
@ -248,6 +250,14 @@ endif
fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
if compiler.get_id() == 'clang' and compiler.version().version_compare('<15')
fmt_dep = declare_dependency(
dependencies: fmt_dep,
# suppress bogus clang 14 warning (the version in Android NDK r25b)
compile_args: ['-Wno-unused-local-typedef'],
)
endif
log = static_library(
'log',
'src/Log.cxx',
@ -349,7 +359,7 @@ sources = [
'src/TagStream.cxx',
'src/TagAny.cxx',
'src/TimePrint.cxx',
'src/mixer/Volume.cxx',
'src/mixer/Memento.cxx',
'src/PlaylistFile.cxx',
]
@ -379,6 +389,7 @@ endif
if enable_database
sources += [
'src/storage/StorageState.cxx',
'src/queue/PlaylistUpdate.cxx',
'src/command/StorageCommands.cxx',
'src/command/DatabaseCommands.cxx',

@ -45,14 +45,27 @@ class AutotoolsProject(MakeProject):
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
'LIBS=' + toolchain.libs + ' ' + self.libs,
'AR=' + toolchain.ar,
'ARFLAGS=' + toolchain.arflags,
'RANLIB=' + toolchain.ranlib,
'STRIP=' + toolchain.strip,
'--host=' + toolchain.arch,
'--prefix=' + toolchain.install_prefix,
'--enable-silent-rules',
'--disable-silent-rules',
] + self.configure_args
subprocess.check_call(configure, cwd=build, env=toolchain.env)
try:
print(configure)
subprocess.check_call(configure, cwd=build, env=toolchain.env)
except subprocess.CalledProcessError:
# dump config.log after a failed configure run
try:
with open(os.path.join(build, 'config.log')) as f:
sys.stdout.write(f.read())
except:
pass
# re-raise the exception
raise
return build
def _build(self, toolchain):

@ -1,4 +1,5 @@
import os
import re
import subprocess
from build.project import Project
@ -25,13 +26,33 @@ set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]})
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}")
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}")
set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
""")
__write_cmake_compiler(f, 'C', toolchain.cc)
__write_cmake_compiler(f, 'CXX', toolchain.cxx)
def configure(toolchain, src, build, args=()):
if cmake_system_name == 'Darwin':
# On macOS, cmake forcibly adds an "-isysroot" flag even if
# one is already present in the flags variable; this breaks
# cross-compiling for iOS, and can be worked around by setting
# the CMAKE_OSX_SYSROOT variable
# (https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html).
m = re.search(r'-isysroot +(\S+)', toolchain.cflags)
if m:
sysroot = m.group(1)
print(f'set(CMAKE_OSX_SYSROOT {sysroot})', file=f)
# search libraries and headers only in the sysroot, not on
# the build host
f.write(f"""
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
""")
def configure(toolchain, src, build, args=(), env=None):
cross_args = []
if toolchain.is_windows:
@ -61,15 +82,23 @@ def configure(toolchain, src, build, args=()):
'-GNinja',
] + cross_args + args
subprocess.check_call(configure, env=toolchain.env, cwd=build)
if env is None:
env = toolchain.env
else:
env = {**toolchain.env, **env}
print(configure)
subprocess.check_call(configure, env=env, cwd=build)
class CmakeProject(Project):
def __init__(self, url, md5, installed, configure_args=[],
windows_configure_args=[],
env=None,
**kwargs):
Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args
self.windows_configure_args = windows_configure_args
self.env = env
def configure(self, toolchain):
src = self.unpack(toolchain)
@ -77,10 +106,10 @@ class CmakeProject(Project):
configure_args = self.configure_args
if toolchain.is_windows:
configure_args = configure_args + self.windows_configure_args
configure(toolchain, src, build, configure_args)
configure(toolchain, src, build, configure_args, self.env)
return build
def _build(self, toolchain):
build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'],
subprocess.check_call(['ninja', '-v', 'install'],
cwd=build, env=toolchain.env)

@ -43,20 +43,22 @@ opus = AutotoolsProject(
)
flac = AutotoolsProject(
'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
'8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
'http://downloads.xiph.org/releases/flac/flac-1.4.2.tar.xz',
'e322d58a1f48d23d9dd38f432672865f6f79e73a6f9cc5a5f57fcaa83eb5a8e4',
'lib/libFLAC.a',
[
'--disable-shared', '--enable-static',
'--disable-stack-smash-protection',
'--disable-xmms-plugin', '--disable-cpplibs',
'--disable-doxygen-docs',
'--disable-programs',
],
subdirs=['include', 'src/libFLAC'],
)
zlib = ZlibProject(
'http://zlib.net/zlib-1.2.11.tar.xz',
'4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066',
'http://zlib.net/zlib-1.2.13.tar.xz',
'd14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98',
'lib/libz.a',
)
@ -112,30 +114,31 @@ libmodplug = AutotoolsProject(
)
libopenmpt = AutotoolsProject(
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
'6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
'lib/libopenmpt.a',
[
'--disable-shared', '--enable-static',
'--disable-openmpt123',
'--disable-examples',
'--disable-tests',
'--disable-doxygen-doc',
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
'--without-flac',
],
base='libopenmpt-0.5.12+release.autotools',
base='libopenmpt-0.6.6+release.autotools',
)
wildmidi = CmakeProject(
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4',
'6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430',
'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz',
'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99',
'lib/libWildMidi.a',
[
'-DBUILD_SHARED_LIBS=OFF',
'-DWANT_PLAYER=OFF',
'-DWANT_STATIC=ON',
],
base='wildmidi-wildmidi-0.4.4',
name='wildmidi',
version='0.4.4',
)
gme = CmakeProject(
@ -151,8 +154,8 @@ gme = CmakeProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz',
'51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298',
'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz',
'57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@ -166,17 +169,21 @@ ffmpeg = FfmpegProject(
'--disable-swscale',
'--disable-postproc',
'--disable-avfilter',
'--disable-lzo',
'--disable-faan',
'--disable-pixelutils',
'--disable-network',
'--disable-encoders',
'--disable-hwaccels',
'--disable-muxers',
'--disable-protocols',
'--disable-devices',
'--disable-filters',
'--disable-v4l2_m2m',
'--disable-sdl2',
'--disable-vulkan',
'--disable-xlib',
'--disable-parser=bmp',
'--disable-parser=cavsvideo',
'--disable-parser=dvbsub',
@ -189,17 +196,22 @@ ffmpeg = FfmpegProject(
'--disable-parser=h263',
'--disable-parser=h264',
'--disable-parser=hevc',
'--disable-parser=jpeg2000',
'--disable-parser=mjpeg',
'--disable-parser=mlp',
'--disable-parser=mpeg4video',
'--disable-parser=mpegvideo',
'--disable-parser=opus',
'--disable-parser=qoi',
'--disable-parser=rv30',
'--disable-parser=rv40',
'--disable-parser=vc1',
'--disable-parser=vp3',
'--disable-parser=vp8',
'--disable-parser=vp9',
'--disable-parser=png',
'--disable-parser=pnm',
'--disable-parser=webp',
'--disable-parser=xma',
'--disable-demuxer=aqtitle',
@ -215,6 +227,42 @@ ffmpeg = FfmpegProject(
'--disable-demuxer=h264',
'--disable-demuxer=ico',
'--disable-demuxer=image2',
'--disable-demuxer=image2pipe',
'--disable-demuxer=image_bmp_pipe',
'--disable-demuxer=image_cri_pipe',
'--disable-demuxer=image_dds_pipe',
'--disable-demuxer=image_dpx_pipe',
'--disable-demuxer=image_exr_pipe',
'--disable-demuxer=image_gem_pipe',
'--disable-demuxer=image_gif_pipe',
'--disable-demuxer=image_j2k_pipe',
'--disable-demuxer=image_jpeg_pipe',
'--disable-demuxer=image_jpegls_pipe',
'--disable-demuxer=image_jpegxl_pipe',
'--disable-demuxer=image_pam_pipe',
'--disable-demuxer=image_pbm_pipe',
'--disable-demuxer=image_pcx_pipe',
'--disable-demuxer=image_pfm_pipe',
'--disable-demuxer=image_pgm_pipe',
'--disable-demuxer=image_pgmyuv_pipe',
'--disable-demuxer=image_pgx_pipe',
'--disable-demuxer=image_phm_pipe',
'--disable-demuxer=image_photocd_pipe',
'--disable-demuxer=image_pictor_pipe',
'--disable-demuxer=image_png_pipe',
'--disable-demuxer=image_ppm_pipe',
'--disable-demuxer=image_psd_pipe',
'--disable-demuxer=image_qdraw_pipe',
'--disable-demuxer=image_qoi_pipe',
'--disable-demuxer=image_sgi_pipe',
'--disable-demuxer=image_sunrast_pipe',
'--disable-demuxer=image_svg_pipe',
'--disable-demuxer=image_tiff_pipe',
'--disable-demuxer=image_vbn_pipe',
'--disable-demuxer=image_webp_pipe',
'--disable-demuxer=image_xbm_pipe',
'--disable-demuxer=image_xpm_pipe',
'--disable-demuxer=image_xwd_pipe',
'--disable-demuxer=jacosub',
'--disable-demuxer=lrc',
'--disable-demuxer=microdvd',
@ -237,6 +285,7 @@ ffmpeg = FfmpegProject(
'--disable-demuxer=tedcaptions',
'--disable-demuxer=vobsub',
'--disable-demuxer=vplayer',
'--disable-demuxer=webm_dash_manifest',
'--disable-demuxer=webvtt',
'--disable-demuxer=yuv4mpegpipe',
@ -266,78 +315,179 @@ ffmpeg = FfmpegProject(
'--disable-decoder=qdmc',
# disable lots of image and video codecs
'--disable-decoder=acelp_kelvin',
'--disable-decoder=agm',
'--disable-decoder=aic',
'--disable-decoder=alias_pix',
'--disable-decoder=ansi',
'--disable-decoder=apng',
'--disable-decoder=arbc',
'--disable-decoder=argo',
'--disable-decoder=ass',
'--disable-decoder=asv1',
'--disable-decoder=asv2',
'--disable-decoder=apng',
'--disable-decoder=aura',
'--disable-decoder=aura2',
'--disable-decoder=avrn',
'--disable-decoder=avrp',
'--disable-decoder=avui',
'--disable-decoder=ayuv',
'--disable-decoder=bethsoftvid',
'--disable-decoder=bfi',
'--disable-decoder=bink',
'--disable-decoder=bintext',
'--disable-decoder=bitpacked',
'--disable-decoder=bmp',
'--disable-decoder=bmv_video',
'--disable-decoder=brender_pix',
'--disable-decoder=c93',
'--disable-decoder=cavs',
'--disable-decoder=ccaption',
'--disable-decoder=cdgraphics',
'--disable-decoder=cdtoons',
'--disable-decoder=cdxl',
'--disable-decoder=cfhd',
'--disable-decoder=cinepak',
'--disable-decoder=clearvideo',
'--disable-decoder=cljr',
'--disable-decoder=cllc',
'--disable-decoder=cpia',
'--disable-decoder=cscd',
'--disable-decoder=cyuv',
'--disable-decoder=dds',
'--disable-decoder=dirac',
'--disable-decoder=dnxhd',
'--disable-decoder=dpx',
'--disable-decoder=dsicinvideo',
'--disable-decoder=dvbsub',
'--disable-decoder=dvdsub',
'--disable-decoder=dvvideo',
'--disable-decoder=dxa',
'--disable-decoder=dxtory',
'--disable-decoder=dxv',
'--disable-decoder=eacmv',
'--disable-decoder=eamad',
'--disable-decoder=eatgq',
'--disable-decoder=eatgv',
'--disable-decoder=eatqi',
'--disable-decoder=eightbps',
'--disable-decoder=escape124',
'--disable-decoder=escape130',
'--disable-decoder=exr',
'--disable-decoder=ffv1',
'--disable-decoder=ffvhuff',
'--disable-decoder=ffwavesynth',
'--disable-decoder=fic',
'--disable-decoder=fits',
'--disable-decoder=flashsv',
'--disable-decoder=flashsv2',
'--disable-decoder=flic',
'--disable-decoder=flv',
'--disable-decoder=fmvc',
'--disable-decoder=fraps',
'--disable-decoder=fourxm',
'--disable-decoder=frwu',
'--disable-decoder=g2m',
'--disable-decoder=gdv',
'--disable-decoder=gem',
'--disable-decoder=gif',
'--disable-decoder=h261',
'--disable-decoder=h263',
'--disable-decoder=h263i',
'--disable-decoder=h263p',
'--disable-decoder=h264',
'--disable-decoder=hap',
'--disable-decoder=hevc',
'--disable-decoder=hnm4_video',
'--disable-decoder=hq_hqa',
'--disable-decoder=hqx',
'--disable-decoder=huffyuv',
'--disable-decoder=hymt',
'--disable-decoder=idcin',
'--disable-decoder=idf',
'--disable-decoder=iff_ilbm',
'--disable-decoder=imm4',
'--disable-decoder=indeo2',
'--disable-decoder=indeo3',
'--disable-decoder=indeo4',
'--disable-decoder=indeo5',
'--disable-decoder=interplay_video',
'--disable-decoder=ipu',
'--disable-decoder=jacosub',
'--disable-decoder=jpeg2000',
'--disable-decoder=jpegls',
'--disable-decoder=jv',
'--disable-decoder=kgv1',
'--disable-decoder=kmvc',
'--disable-decoder=lagarith',
'--disable-decoder=loco',
'--disable-decoder=lscr',
'--disable-decoder=m101',
'--disable-decoder=magicyuv',
'--disable-decoder=mdec',
'--disable-decoder=microdvd',
'--disable-decoder=mimic',
'--disable-decoder=mjpeg',
'--disable-decoder=mmvideo',
'--disable-decoder=mpl2',
'--disable-decoder=mobiclip',
'--disable-decoder=motionpixels',
'--disable-decoder=movtext',
'--disable-decoder=mpeg1video',
'--disable-decoder=mpeg2video',
'--disable-decoder=mpeg4',
'--disable-decoder=mpegvideo',
'--disable-decoder=msa1',
'--disable-decoder=mscc',
'--disable-decoder=msmpeg4_crystalhd',
'--disable-decoder=msmpeg4v1',
'--disable-decoder=msmpeg4v2',
'--disable-decoder=msmpeg4v3',
'--disable-decoder=msp2',
'--disable-decoder=msrle',
'--disable-decoder=mss1',
'--disable-decoder=msvideo1',
'--disable-decoder=mszh',
'--disable-decoder=mts2',
'--disable-decoder=mv30',
'--disable-decoder=mvc1',
'--disable-decoder=mvc2',
'--disable-decoder=mvdv',
'--disable-decoder=mvha',
'--disable-decoder=mwsc',
'--disable-decoder=notchlc',
'--disable-decoder=nuv',
'--disable-decoder=on2avc',
'--disable-decoder=paf_video',
'--disable-decoder=pam',
'--disable-decoder=pbm',
'--disable-decoder=pcx',
'--disable-decoder=pgm',
'--disable-decoder=pgmyuv',
'--disable-decoder=pgssub',
'--disable-decoder=pgx',
'--disable-decoder=phm',
'--disable-decoder=photocd',
'--disable-decoder=png',
'--disable-decoder=pictor',
'--disable-decoder=pixlet',
'--disable-decoder=pjs',
'--disable-decoder=ppm',
'--disable-decoder=prores',
'--disable-decoder=prosumer',
'--disable-decoder=psd',
'--disable-decoder=ptx',
'--disable-decoder=qdraw',
'--disable-decoder=qoi',
'--disable-decoder=qpeg',
'--disable-decoder=qtrle',
'--disable-decoder=rawvideo',
'--disable-decoder=r10k',
'--disable-decoder=r210',
'--disable-decoder=rasc',
'--disable-decoder=realtext',
'--disable-decoder=rl2',
'--disable-decoder=rpza',
'--disable-decoder=roq',
'--disable-decoder=roq_dpcm',
'--disable-decoder=rscc',
@ -346,53 +496,120 @@ ffmpeg = FfmpegProject(
'--disable-decoder=rv30',
'--disable-decoder=rv40',
'--disable-decoder=sami',
'--disable-decoder=sanm',
'--disable-decoder=scpr',
'--disable-decoder=screenpresso',
'--disable-decoder=sga',
'--disable-decoder=sgi',
'--disable-decoder=sgirle',
'--disable-decoder=sheervideo',
'--disable-decoder=simbiosis_imx',
'--disable-decoder=smc',
'--disable-decoder=snow',
'--disable-decoder=speedhq',
'--disable-decoder=srgc',
'--disable-decoder=srt',
'--disable-decoder=ssa',
'--disable-decoder=stl',
'--disable-decoder=subrip',
'--disable-decoder=subviewer',
'--disable-decoder=subviewer1',
'--disable-decoder=sunrast',
'--disable-decoder=svq1',
'--disable-decoder=svq3',
'--disable-decoder=targa',
'--disable-decoder=targa_y216',
'--disable-decoder=text',
'--disable-decoder=tiff',
'--disable-decoder=tiertexseqvideo',
'--disable-decoder=tmv',
'--disable-decoder=truemotion1',
'--disable-decoder=truemotion2',
'--disable-decoder=truemotion2rt',
'--disable-decoder=tscc',
'--disable-decoder=tscc2',
'--disable-decoder=twinvq',
'--disable-decoder=txd',
'--disable-decoder=ulti',
'--disable-decoder=utvideo',
'--disable-decoder=v210',
'--disable-decoder=v210x',
'--disable-decoder=v308',
'--disable-decoder=v408',
'--disable-decoder=v410',
'--disable-decoder=vb',
'--disable-decoder=vble',
'--disable-decoder=vbn',
'--disable-decoder=vc1',
'--disable-decoder=vcr1',
'--disable-decoder=vmdvideo',
'--disable-decoder=vmnc',
'--disable-decoder=vp3',
'--disable-decoder=vp5',
'--disable-decoder=vp6',
'--disable-decoder=vp7',
'--disable-decoder=vp8',
'--disable-decoder=vp9',
'--disable-decoder=vplayer',
'--disable-decoder=vqa',
'--disable-decoder=webvtt',
'--disable-decoder=wcmv',
'--disable-decoder=wmv1',
'--disable-decoder=wmv2',
'--disable-decoder=wmv3',
'--disable-decoder=wnv1',
'--disable-decoder=wrapped_avframe',
'--disable-decoder=xan_wc3',
'--disable-decoder=xan_wc4',
'--disable-decoder=xbin',
'--disable-decoder=xbm',
'--disable-decoder=xface',
'--disable-decoder=xl',
'--disable-decoder=xpm',
'--disable-decoder=xsub',
'--disable-decoder=xwd',
'--disable-decoder=y41p',
'--disable-decoder=ylc',
'--disable-decoder=yop',
'--disable-decoder=yuv4',
'--disable-decoder=zero12v',
'--disable-decoder=zerocodec',
'--disable-decoder=zlib',
'--disable-decoder=zmbv',
'--disable-bsf=av1_frame_merge',
'--disable-bsf=av1_frame_split',
'--disable-bsf=av1_metadata',
'--disable-bsf=dts2pts',
'--disable-bsf=h264_metadata',
'--disable-bsf=h264_mp4toannexb',
'--disable-bsf=h264_redundant_pps',
'--disable-bsf=hevc_metadata',
'--disable-bsf=hevc_mp4toannexb',
'--disable-bsf=mjpeg2jpeg',
'--disable-bsf=opus_metadata',
'--disable-bsf=pgs_frame_merge',
'--disable-bsf=text2movsub',
'--disable-bsf=vp9_metadata',
'--disable-bsf=vp9_raw_reorder',
'--disable-bsf=vp9_superframe',
'--disable-bsf=vp9_superframe_split',
],
)
openssl = OpenSSLProject(
'https://www.openssl.org/source/openssl-3.0.1.tar.gz',
'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1',
'https://www.openssl.org/source/openssl-3.1.0.tar.gz',
'aaa925ad9828745c4cad9d9efeb273deca820f2cdcf2c3ac7d7c1212b7c497b4',
'include/openssl/ossl_typ.h',
)
curl = CmakeProject(
'https://curl.se/download/curl-7.82.0.tar.xz',
'0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c',
'https://curl.se/download/curl-8.0.1.tar.xz',
'0a381cd82f4d00a9a334438b8ca239afea5bfefcfa9a1025f2bf118e79e0b5f0',
'lib/libcurl.a',
[
'-DBUILD_CURL_EXE=OFF',
'-DBUILD_SHARED_LIBS=OFF',
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
'-DCURL_DISABLE_LDAP=ON',
'-DCURL_DISABLE_TELNET=ON',
'-DCURL_DISABLE_DICT=ON',
@ -421,8 +638,8 @@ curl = CmakeProject(
)
libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
'7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
'637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
'lib/libnfs.a',
[
'--disable-shared', '--enable-static',
@ -433,7 +650,7 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples',
],
base='libnfs-libnfs-5.0.1',
base='libnfs-libnfs-5.0.2',
autoreconf=True,
)
@ -444,7 +661,7 @@ jack = JackProject(
)
boost = BoostProject(
'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2',
'8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc',
'https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2',
'71feeed900fbccca04a3b4f2f84a7c217186f28a940ed8b7ed4725986baf99fa',
'include/boost/version.hpp',
)

@ -1,4 +1,4 @@
import subprocess
import subprocess, multiprocessing
from build.project import Project
@ -10,7 +10,12 @@ class MakeProject(Project):
self.install_target = install_target
def get_simultaneous_jobs(self):
return 12
try:
# use twice as many simultaneous jobs as we have CPU cores
return multiprocessing.cpu_count() * 2
except NotImplementedError:
# default to 12, if multiprocessing.cpu_count() is not implemented
return 12
def get_make_args(self, toolchain):
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
@ -19,7 +24,7 @@ class MakeProject(Project):
return ['--quiet', self.install_target]
def make(self, toolchain, wd, args):
subprocess.check_call(['/usr/bin/make'] + args,
subprocess.check_call(['make'] + args,
cwd=wd, env=toolchain.env)
def build_make(self, toolchain, wd, install=True):

@ -1,4 +1,5 @@
import os.path, subprocess, sys
import os
import subprocess
import platform
from build.project import Project
@ -82,8 +83,8 @@ endian = '{endian}'
def configure(toolchain, src, build, args=()):
cross_file = make_cross_file(toolchain)
configure = [
'meson',
src, build,
'meson', 'setup',
build, src,
'--prefix', toolchain.install_prefix,
@ -115,5 +116,5 @@ class MesonProject(Project):
def _build(self, toolchain):
build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'],
subprocess.check_call(['ninja', '-v', 'install'],
cwd=build, env=toolchain.env)

@ -14,13 +14,14 @@ class Project:
if base is None:
basename = os.path.basename(url)
m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
if not m: raise
if not m: raise RuntimeError('Could not identify tarball name: ' + basename)
self.base = m.group(1)
else:
self.base = base
if name is None or version is None:
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
if not m: raise RuntimeError('Could not identify tarball name: ' + self.base)
if name is None: name = m.group(1)
if version is None: version = m.group(2)
@ -55,8 +56,8 @@ class Project:
parent_path = toolchain.src_path
else:
parent_path = toolchain.build_path
path = untar(self.download(toolchain), parent_path, self.base)
path = untar(self.download(toolchain), parent_path, self.base,
lazy=out_of_tree and self.patches is None)
if self.patches is not None:
push_all(toolchain, path, self.patches)
@ -71,8 +72,10 @@ class Project:
return path
def make_build_path(self, toolchain):
def make_build_path(self, toolchain, lazy=False):
path = os.path.join(toolchain.build_path, self.base)
if lazy and os.path.isdir(path):
return path
try:
shutil.rmtree(path)
except FileNotFoundError:

@ -1,14 +1,16 @@
import os, shutil, subprocess
def untar(tarball_path, parent_path, base):
def untar(tarball_path, parent_path, base, lazy=False):
path = os.path.join(parent_path, base)
if lazy and os.path.isdir(path):
return path
try:
shutil.rmtree(path)
except FileNotFoundError:
pass
os.makedirs(parent_path, exist_ok=True)
try:
subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path])
subprocess.check_call(['tar', 'xfC', tarball_path, parent_path])
except FileNotFoundError:
import tarfile
tar = tarfile.open(tarball_path)

@ -1,22 +1,32 @@
import os.path, subprocess
import subprocess
from build.project import Project
from build.makeproject import MakeProject
class ZlibProject(Project):
class ZlibProject(MakeProject):
def __init__(self, url, md5, installed,
**kwargs):
Project.__init__(self, url, md5, installed, **kwargs)
MakeProject.__init__(self, url, md5, installed, **kwargs)
def get_make_args(self, toolchain):
return MakeProject.get_make_args(self, toolchain) + [
'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags,
'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags,
'AR=' + toolchain.ar,
'ARFLAGS=' + toolchain.arflags,
'RANLIB=' + toolchain.ranlib,
'LDSHARED=' + toolchain.cc + ' -shared',
'libz.a'
]
def get_make_install_args(self, toolchain):
return [
'RANLIB=' + toolchain.ranlib,
self.install_target
]
def _build(self, toolchain):
src = self.unpack(toolchain, out_of_tree=False)
subprocess.check_call(['/usr/bin/make', '--quiet',
'-f', 'win32/Makefile.gcc',
'PREFIX=' + toolchain.arch + '-',
'-j12',
'install',
'INCLUDE_PATH='+ os.path.join(toolchain.install_prefix, 'include'),
'LIBRARY_PATH=' + os.path.join(toolchain.install_prefix, 'lib'),
'BINARY_PATH=' + os.path.join(toolchain.install_prefix, 'bin'),
],
cwd=src, env=toolchain.env)
subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],
cwd=src, env=toolchain.env)
self.build_make(toolchain, src)

@ -352,12 +352,16 @@ ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
break;
case OPTION_NO_DAEMON:
#ifdef ENABLE_DAEMON
options.daemon = false;
#endif
break;
#ifdef __linux__
case OPTION_SYSTEMD:
#ifdef ENABLE_DAEMON
options.daemon = false;
#endif
options.systemd = true;
break;
#endif

@ -20,11 +20,18 @@
#ifndef MPD_COMMAND_LINE_HXX
#define MPD_COMMAND_LINE_HXX
#include "config.h" // for ENABLE_DAEMON
struct ConfigData;
struct CommandLineOptions {
bool kill = false;
#ifdef ENABLE_DAEMON
bool daemon = true;
#else
static constexpr bool daemon = false;
#endif
#ifdef __linux__
bool systemd = false;

@ -45,7 +45,10 @@ void
LogFmt(LogLevel level, const Domain &domain,
const S &format_str, Args&&... args) noexcept
{
#if FMT_VERSION >= 70000
#if FMT_VERSION >= 90000
return LogVFmt(level, domain, format_str,
fmt::make_format_args(args...));
#elif FMT_VERSION >= 70000
return LogVFmt(level, domain, fmt::to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str,
args...));

@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
getenv("NOTIFY_SOCKET") != nullptr) {
/* if MPD was started as a systemd
service, default to journal (which
is connected to fd=2) */
is connected to stdout&stderr) */
out_fd = STDOUT_FILENO;
return;
}
#endif
#ifndef HAVE_SYSLOG
#ifdef _WIN32
/* default to stdout on Windows */
out_fd = STDOUT_FILENO;
#elif !defined(HAVE_SYSLOG)
throw std::runtime_error("config parameter 'log_file' not found");
#endif
#ifdef HAVE_SYSLOG

@ -482,7 +482,10 @@ MainConfigured(const CommandLineOptions &options,
#ifndef ANDROID
setup_log_output();
const ScopeSignalHandlersInit signal_handlers_init(instance);
const ScopeSignalHandlersInit signal_handlers_init{
instance,
options.daemon,
};
#endif
instance.io_thread.Start();
@ -590,19 +593,46 @@ MainConfigured(const CommandLineOptions &options,
#ifdef ANDROID
/**
* Wrapper for ReadConfigFile() which returns false if the file was
* not found.
*/
static bool
TryReadConfigFile(ConfigData &config, Path path)
{
if (!FileExists(path))
return false;
ReadConfigFile(config, path);
return true;
}
static void
AndroidMain()
LoadConfigFile(JNIEnv *env, ConfigData &config)
{
/* try loading mpd.conf from
"Android/data/org.musicpd/files/mpd.conf" (the app specific
data directory) first */
if (const auto dir = context->GetExternalFilesDir(env);
!dir.IsNull() &&
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")))
return;
/* if that fails, attempt to load "mpd.conf" from the root of
the SD card (pre-0.23.9, ceases to work since Android
12) */
if (const auto dir = Environment::getExternalStorageDirectory(env);
!dir.IsNull())
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"));
}
static void
AndroidMain(JNIEnv *env)
{
CommandLineOptions options;
ConfigData raw_config;
const auto sdcard = Environment::getExternalStorageDirectory();
if (!sdcard.IsNull()) {
const auto config_path =
sdcard / Path::FromFS("mpd.conf");
if (FileExists(config_path))
ReadConfigFile(raw_config, config_path);
}
LoadConfigFile(env, raw_config);
MainConfigured(options, raw_config);
}
@ -614,9 +644,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
Java::Init(env);
Java::Object::Initialise(env);
Java::File::Initialise(env);
Environment::Initialise(env);
AtScopeExit(env) { Environment::Deinitialise(env); };
Context::Initialise(env);
context = new Context(env, _context);
AtScopeExit() { delete context; };
@ -625,7 +658,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
AtScopeExit() { delete logListener; };
try {
AndroidMain();
AndroidMain(env);
} catch (...) {
LogError(std::current_exception());
}

@ -23,7 +23,6 @@
#include "Log.hxx"
#include "lib/fmt/ExceptionFormatter.hxx"
#include "song/DetachedSong.hxx"
#include "mixer/Volume.hxx"
#include "IdleFlags.hxx"
#include "client/Listener.hxx"
#include "client/Client.hxx"
@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
void
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
{
InvalidateHardwareVolume();
mixer_memento.InvalidateHardwareVolume();
/* notify clients */
EmitIdle(IDLE_MIXER);

@ -25,6 +25,7 @@
#include "queue/Listener.hxx"
#include "output/MultipleOutputs.hxx"
#include "mixer/Listener.hxx"
#include "mixer/Memento.hxx"
#include "player/Control.hxx"
#include "player/Listener.hxx"
#include "protocol/RangeArg.hxx"
@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
MultipleOutputs outputs;
MixerMemento mixer_memento;
PlayerControl pc;
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;

@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
*/
return std::strchr(name_utf8, '/') == nullptr &&
#ifdef _WIN32
std::strchr(name_utf8, '\\') == nullptr &&
#endif
std::strchr(name_utf8, '\n') == nullptr &&
std::strchr(name_utf8, '\r') == nullptr;
}

@ -28,7 +28,11 @@
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>
#include <array>
#include <functional>
#include <memory>
#include <string>
#include <utility>
class RemoteTagCacheHandler;

@ -24,6 +24,7 @@
#include "TagPrint.hxx"
#include "client/Response.hxx"
#include "fs/Traits.hxx"
#include "lib/fmt/AudioFormatFormatter.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringBuffer.hxx"
#include "util/UriUtil.hxx"
@ -93,7 +94,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
time_print(r, "Last-Modified", song.mtime);
if (song.audio_format.IsDefined())
r.Fmt(FMT_STRING("Format: {}\n"), ToString(song.audio_format));
r.Fmt(FMT_STRING("Format: {}\n"), song.audio_format);
tag_print_values(r, song.tag);
@ -116,7 +117,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
time_print(r, "Last-Modified", song.GetLastModified());
if (const auto &f = song.GetAudioFormat(); f.IsDefined())
r.Fmt(FMT_STRING("Format: {}\n"), ToString(f));
r.Fmt(FMT_STRING("Format: {}\n"), f);
tag_print_values(r, song.GetTag());

@ -63,6 +63,9 @@ song_save(BufferedOutputStream &os, const Song &song)
if (song.audio_format.IsDefined())
os.Format("Format: %s\n", ToString(song.audio_format).c_str());
if (song.in_playlist)
os.Write("InPlaylist: yes\n");
if (!IsNegative(song.mtime))
os.Format(SONG_MTIME ": %li\n",
(long)std::chrono::system_clock::to_time_t(song.mtime));
@ -86,7 +89,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
DetachedSong
song_load(LineReader &file, const char *uri,
std::string *target_r)
std::string *target_r, bool *in_playlist_r)
{
DetachedSong song(uri);
@ -132,6 +135,9 @@ song_load(LineReader &file, const char *uri,
song.SetStartTime(SongTime::FromMS(start_ms));
song.SetEndTime(SongTime::FromMS(end_ms));
} else if (StringIsEqual(line, "InPlaylist")) {
if (in_playlist_r != nullptr)
*in_playlist_r = StringIsEqual(value, "yes");
} else {
throw FormatRuntimeError("unknown line in db: %s", line);
}

@ -44,6 +44,6 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
*/
DetachedSong
song_load(LineReader &file, const char *uri,
std::string *target_r=nullptr);
std::string *target_r=nullptr, bool *in_playlist_r=nullptr);
#endif

@ -27,7 +27,6 @@
#include "storage/StorageState.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "mixer/Volume.hxx"
#include "SongLoader.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
void
StateFile::RememberVersions() noexcept
{
prev_volume_version = sw_volume_state_get_hash();
prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash();
prev_output_version = audio_output_state_get_version();
prev_playlist_version = playlist_state_get_hash(partition.playlist,
partition.pc);
@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
bool
StateFile::IsModified() const noexcept
{
return prev_volume_version != sw_volume_state_get_hash() ||
return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() ||
prev_output_version != audio_output_state_get_version() ||
prev_playlist_version != playlist_state_get_hash(partition.playlist,
partition.pc)
@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
inline void
StateFile::Write(BufferedOutputStream &os)
{
save_sw_volume_state(os);
partition.mixer_memento.SaveSoftwareVolumeState(os);
audio_output_state_save(os, partition.outputs);
#ifdef ENABLE_DATABASE
@ -125,7 +124,7 @@ try {
const char *line;
while ((line = file.ReadLine()) != nullptr) {
success = read_sw_volume_state(line, partition.outputs) ||
success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) ||
audio_output_state_read(line, partition.outputs) ||
playlist_state_restore(config, line, file, song_loader,
partition.playlist,

@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
{
#ifdef ANDROID
if (path.IsNull()) {
const auto cache_dir = GetUserCacheDir();
const auto cache_dir = GetAppCacheDir();
if (cache_dir.IsNull())
return;

@ -35,8 +35,9 @@ tag_print_types(Response &r) noexcept
}
void
tag_print(Response &r, TagType type, StringView value) noexcept
tag_print(Response &r, TagType type, StringView _value) noexcept
{
const std::string_view value{_value};
r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
}

@ -36,5 +36,5 @@ time_print(Response &r, const char *name,
return;
}
r.Fmt(FMT_STRING("{}: {}\n"), name, s);
r.Fmt(FMT_STRING("{}: {}\n"), name, s.c_str());
}

@ -26,19 +26,30 @@
#include "AudioManager.hxx"
AllocatedPath
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
static jmethodID getExternalFilesDir_method,
getCacheDir_method,
getSystemService_method;
void
Context::Initialise(JNIEnv *env) noexcept
{
assert(_type != nullptr);
Java::Class cls{env, "android/content/Context"};
Java::Class cls{env, env->GetObjectClass(Get())};
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
"(Ljava/lang/String;)Ljava/io/File;");
assert(method);
getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
"(Ljava/lang/String;)Ljava/io/File;");
getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
"()Ljava/io/File;");
getSystemService_method = env->GetMethodID(cls, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
}
Java::String type{env, _type};
AllocatedPath
Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept
{
assert(type != nullptr);
jobject file = env->CallObjectMethod(Get(), method, type.Get());
jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method,
Java::String::Optional(env, type).Get());
if (Java::DiscardException(env) || file == nullptr)
return nullptr;
@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
{
assert(env != nullptr);
Java::Class cls(env, env->GetObjectClass(Get()));
jmethodID method = env->GetMethodID(cls, "getCacheDir",
"()Ljava/io/File;");
assert(method);
jobject file = env->CallObjectMethod(Get(), method);
jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
if (Java::DiscardException(env) || file == nullptr)
return nullptr;
@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
{
assert(env != nullptr);
Java::Class cls(env, env->GetObjectClass(Get()));
jmethodID method = env->GetMethodID(cls, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
assert(method);
Java::String name(env, "audio");
jobject am = env->CallObjectMethod(Get(), method, name.Get());
jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
if (Java::DiscardException(env) || am == nullptr)
return nullptr;

@ -27,12 +27,21 @@ class AudioManager;
class Context : public Java::GlobalObject {
public:
/**
* Global initialisation. Looks up the methods of the
* Context Java class.
*/
static void Initialise(JNIEnv *env) noexcept;
Context(JNIEnv *env, jobject obj) noexcept
:Java::GlobalObject(env, obj) {}
/**
* @param type the subdirectory name; may be nullptr
*/
[[gnu::pure]]
AllocatedPath GetExternalFilesDir(JNIEnv *env,
const char *type) noexcept;
const char *type=nullptr) noexcept;
[[gnu::pure]]
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;

@ -25,13 +25,13 @@
#include "fs/AllocatedPath.hxx"
namespace Environment {
static Java::TrivialClass cls;
static jmethodID getExternalStorageDirectory_method;
static jmethodID getExternalStoragePublicDirectory_method;
}
static Java::TrivialClass cls;
static jmethodID getExternalStorageDirectory_method;
static jmethodID getExternalStoragePublicDirectory_method;
void
Environment::Initialise(JNIEnv *env) noexcept
Initialise(JNIEnv *env) noexcept
{
cls.Find(env, "android/os/Environment");
@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
}
void
Environment::Deinitialise(JNIEnv *env) noexcept
Deinitialise(JNIEnv *env) noexcept
{
cls.Clear(env);
}
AllocatedPath
Environment::getExternalStorageDirectory() noexcept
getExternalStorageDirectory(JNIEnv *env) noexcept
{
JNIEnv *env = Java::GetEnv();
jobject file =
env->CallStaticObjectMethod(cls,
getExternalStorageDirectory_method);
@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
}
AllocatedPath
Environment::getExternalStoragePublicDirectory(const char *type) noexcept
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
{
if (getExternalStoragePublicDirectory_method == nullptr)
/* needs API level 8 */
return nullptr;
JNIEnv *env = Java::GetEnv();
Java::String type2(env, type);
jobject file = env->CallStaticObjectMethod(Environment::cls,
Environment::getExternalStoragePublicDirectory_method,
jobject file = env->CallStaticObjectMethod(cls,
getExternalStoragePublicDirectory_method,
type2.Get());
if (file == nullptr)
return nullptr;
return Java::File::ToAbsolutePath(env, file);
}
} // namespace Environment

@ -17,27 +17,29 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_ANDROID_ENVIRONMENT_HXX
#define MPD_ANDROID_ENVIRONMENT_HXX
#include "util/Compiler.h"
#pragma once
#include <jni.h>
class AllocatedPath;
namespace Environment {
void Initialise(JNIEnv *env) noexcept;
void Deinitialise(JNIEnv *env) noexcept;
/**
* Determine the mount point of the external SD card.
*/
[[gnu::pure]]
AllocatedPath getExternalStorageDirectory() noexcept;
void
Initialise(JNIEnv *env) noexcept;
[[gnu::pure]]
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
}
void
Deinitialise(JNIEnv *env) noexcept;
#endif
/**
* Determine the mount point of the external SD card.
*/
[[gnu::pure]]
AllocatedPath
getExternalStorageDirectory(JNIEnv *env) noexcept;
[[gnu::pure]]
AllocatedPath
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
} // namespace Environment

@ -31,6 +31,7 @@
#include "ErrorRef.hxx"
#include "StringRef.hxx"
#include <cstring>
#include <stdexcept>
namespace Apple {
@ -57,8 +58,8 @@ ThrowOSStatus(OSStatus status, const char *_msg)
const Apple::StringRef cfstr(cferr.CopyDescription());
char msg[1024];
strcpy(msg, _msg);
size_t length = strlen(msg);
std::strcpy(msg, _msg);
size_t length = std::strlen(msg);
cfstr.GetCString(msg + length, sizeof(msg) - length);
throw std::runtime_error(msg);

@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream {
assert(fill <= data.size());
assert(position <= fill);
return {&data[position], &data[fill]};
return {data.data() + position, data.data() + fill};
}
void Consume(size_t nbytes) noexcept {

@ -22,6 +22,10 @@ if libzzip_dep.found()
found_archive_plugin = true
endif
if not found_archive_plugin
subdir_done()
endif
archive_plugins = static_library(
'archive_plugins',
archive_plugins_sources,

@ -82,7 +82,10 @@ public:
template<typename S, typename... Args>
bool Fmt(const S &format_str, Args&&... args) noexcept {
#if FMT_VERSION >= 70000
#if FMT_VERSION >= 90000
return VFmt(format_str,
fmt::make_format_args(args...));
#elif FMT_VERSION >= 70000
return VFmt(fmt::to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str,
args...));
@ -109,7 +112,10 @@ public:
template<typename S, typename... Args>
void FmtError(enum ack code,
const S &format_str, Args&&... args) noexcept {
#if FMT_VERSION >= 70000
#if FMT_VERSION >= 90000
return VFmtError(code, format_str,
fmt::make_format_args(args...));
#elif FMT_VERSION >= 70000
return VFmtError(code, fmt::to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str,
args...));

@ -41,6 +41,8 @@
#include <memory>
#include <vector>
#include <limits.h> // for UINT_MAX
CommandResult
handle_listfiles_db(Client &client, Response &r, const char *uri)
{

@ -100,10 +100,6 @@ handle_listfiles_local(Response &r, Path path_fs)
return CommandResult::OK;
}
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
#pragma GCC diagnostic pop
#endif
gcc_pure
static bool
IsValidName(const StringView s) noexcept
@ -130,7 +126,8 @@ public:
explicit PrintCommentHandler(Response &_response) noexcept
:NullTagHandler(WANT_PAIR), response(_response) {}
void OnPair(StringView key, StringView value) noexcept override {
void OnPair(StringView _key, StringView _value) noexcept override {
const std::string_view key{_key}, value{_value};
if (IsValidName(key) && IsValidValue(value))
response.Fmt(FMT_STRING("{}: {}\n"), key, value);
}

@ -33,7 +33,6 @@
#include "TimePrint.hxx"
#include "decoder/DecoderPrint.hxx"
#include "ls.hxx"
#include "mixer/Volume.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/StringAPI.hxx"
@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
{
auto &partition = client.GetPartition();
const auto volume = volume_level_get(partition.outputs);
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume);
@ -333,15 +332,13 @@ handle_getvol(Client &client, Request, Response &r)
}
CommandResult
handle_setvol(Client &client, Request args, Response &r)
handle_setvol(Client &client, Request args, Response &)
{
unsigned level = args.ParseUnsigned(0, 100);
if (!volume_level_change(client.GetPartition().outputs, level)) {
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
return CommandResult::ERROR;
}
auto &partition = client.GetPartition();
partition.mixer_memento.SetVolume(partition.outputs, level);
partition.EmitIdle(IDLE_MIXER);
return CommandResult::OK;
}
@ -350,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
{
int relative = args.ParseInt(0, -100, 100);
auto &outputs = client.GetPartition().outputs;
auto &partition = client.GetPartition();
auto &outputs = partition.outputs;
auto &mixer_memento = partition.mixer_memento;
const int old_volume = volume_level_get(outputs);
const int old_volume = mixer_memento.GetVolume(outputs);
if (old_volume < 0) {
r.Error(ACK_ERROR_SYSTEM, "No mixer");
return CommandResult::ERROR;
@ -364,10 +363,9 @@ handle_volume(Client &client, Request args, Response &r)
else if (new_volume > 100)
new_volume = 100;
if (new_volume != old_volume &&
!volume_level_change(outputs, new_volume)) {
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
return CommandResult::ERROR;
if (new_volume != old_volume) {
mixer_memento.SetVolume(outputs, new_volume);
partition.EmitIdle(IDLE_MIXER);
}
return CommandResult::OK;

@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
assert(args.size == 1);
unsigned device = args.ParseUnsigned(0);
if (!audio_output_enable_index(client.GetPartition().outputs, device)) {
auto &partition = client.GetPartition();
if (!audio_output_enable_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR;
}
@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
assert(args.size == 1);
unsigned device = args.ParseUnsigned(0);
if (!audio_output_disable_index(client.GetPartition().outputs, device)) {
auto &partition = client.GetPartition();
if (!audio_output_disable_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR;
}
@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
assert(args.size == 1);
unsigned device = args.ParseUnsigned(0);
if (!audio_output_toggle_index(client.GetPartition().outputs, device)) {
auto &partition = client.GetPartition();
if (!audio_output_toggle_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR;
}

@ -25,10 +25,10 @@
#include "SingleMode.hxx"
#include "client/Client.hxx"
#include "client/Response.hxx"
#include "mixer/Volume.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "IdleFlags.hxx"
#include "lib/fmt/AudioFormatFormatter.hxx"
#include "util/StringBuffer.hxx"
#include "util/ScopeExit.hxx"
#include "util/Exception.hxx"
@ -131,7 +131,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
const auto &playlist = partition.playlist;
const auto volume = volume_level_get(partition.outputs);
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume);
@ -186,7 +186,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
if (player_status.audio_format.IsDefined())
r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"),
ToString(player_status.audio_format));
player_status.audio_format);
}
#ifdef ENABLE_DATABASE

@ -83,10 +83,6 @@ handle_listfiles_storage(Response &r, StorageDirectoryReader &reader)
}
}
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
#pragma GCC diagnostic pop
#endif
CommandResult
handle_listfiles_storage(Response &r, Storage &storage, const char *uri)
{

@ -24,6 +24,7 @@
#include "config/Param.hxx"
#include "config/Block.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "fs/StandardDirectory.hxx"
#include "util/RuntimeError.hxx"
@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
} else {
/* if there is no override, use the cache directory */
const AllocatedPath cache_dir = GetUserCacheDir();
const AllocatedPath cache_dir = GetAppCacheDir();
if (cache_dir.IsNull())
return nullptr;
const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("mpd.db"));
const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("db"));
auto db_file_utf8 = db_file.ToUTF8();
if (db_file_utf8.empty())
return nullptr;
ConfigBlock block;
block.AddBlockParam("path", std::move(db_file_utf8), -1);
{
const auto mounts_dir = cache_dir
/ Path::FromFS(PATH_LITERAL("mounts"));
CreateDirectoryNoThrow(mounts_dir);
if (auto mounts_dir_utf8 = mounts_dir.ToUTF8();
!mounts_dir_utf8.empty())
block.AddBlockParam("cache_directory",
std::move(mounts_dir_utf8),
-1);
}
return DatabaseGlobalInit(main_event_loop, io_event_loop,
listener, block);
}

@ -126,6 +126,18 @@ Directory::LookupTargetSong(std::string_view _target) noexcept
return lr.directory->FindSong(lr.rest);
}
void
Directory::ClearInPlaylist() noexcept
{
assert(holding_db_lock());
for (auto &child : children)
child.ClearInPlaylist();
for (auto &song : songs)
song.in_playlist = false;
}
void
Directory::PruneEmpty() noexcept
{

@ -287,6 +287,14 @@ public:
*/
SongPtr RemoveSong(Song *song) noexcept;
/**
* Recursively walk through the whole tree and set all
* `Song::in_playlist` fields to `false`.
*
* Caller must lock the #db_mutex.
*/
void ClearInPlaylist() noexcept;
/**
* Caller must lock the #db_mutex.
*/

@ -168,12 +168,14 @@ directory_load(LineReader &file, Directory &directory)
throw FormatRuntimeError("Duplicate song '%s'", name);
std::string target;
bool in_playlist = false;
auto detached_song = song_load(file, name,
&target);
&target, &in_playlist);
auto song = std::make_unique<Song>(std::move(detached_song),
directory);
song->target = std::move(target);
song->in_playlist = in_playlist;
directory.AddSong(std::move(song));
} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {

@ -51,6 +51,15 @@ LockFindSong(Directory &directory, std::string_view name) noexcept
return directory.FindSong(name);
}
[[gnu::pure]]
static bool
IsAcceptableFilename(std::string_view name) noexcept
{
return !name.empty() &&
/* newlines cannot be represented in MPD's protocol */
name.find('\n') == name.npos;
}
void
UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
const char *name) noexcept
@ -58,6 +67,9 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
const char *tmp = std::strchr(name, '/');
if (tmp) {
const std::string_view child_name(name, tmp - name);
if (!IsAcceptableFilename(child_name))
return;
//add dir is not there already
Directory *subdir = LockMakeChild(directory, child_name);
subdir->device = DEVICE_INARCHIVE;
@ -65,11 +77,8 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
//create directories first
UpdateArchiveTree(archive, *subdir, tmp + 1);
} else {
if (StringIsEmpty(name)) {
LogWarning(update_domain,
"archive returned directory only");
if (!IsAcceptableFilename(name))
return;
}
//add file
Song *song = LockFindSong(directory, name);

@ -531,6 +531,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
{
const ScopeDatabaseLock protect;
root.ClearInPlaylist();
PurgeDanglingFromPlaylists(root);
}

@ -257,6 +257,12 @@ public:
return HasFailed();
}
[[gnu::pure]]
bool LockIsReplayGainEnabled() const noexcept {
const std::scoped_lock<Mutex> protect(mutex);
return replay_gain_mode != ReplayGainMode::OFF;
}
/**
* Transition this obejct from DecoderState::START to
* DecoderState::DECODE.

@ -114,11 +114,11 @@ constexpr const struct DecoderPlugin *decoder_plugins[] = {
#ifdef ENABLE_ADPLUG
&adplug_decoder_plugin,
#endif
#ifdef ENABLE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
#ifdef ENABLE_GME
&gme_decoder_plugin,
#endif
#ifdef ENABLE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
&pcm_decoder_plugin,
nullptr

@ -36,6 +36,7 @@
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringCompare.hxx"
#include "thread/Name.hxx"
#include "tag/ApeReplayGain.hxx"
#include "Log.hxx"
@ -261,12 +262,16 @@ LoadReplayGain(DecoderClient &client, InputStream &is)
static void
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
{
{
const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
/* ReplayGain is disabled */
return;
}
if (!bridge.dc.LockIsReplayGainEnabled())
/* ReplayGain is disabled */
return;
if (is.HasMimeType() &&
StringStartsWith(is.GetMimeType(), "audio/x-mpd-"))
/* skip for (virtual) files (e.g. from the
cdio_paranoia input plugin) which cannot possibly
contain tags */
return;
LoadReplayGain(bridge, is);
}

@ -31,6 +31,7 @@
#include "lib/ffmpeg/Format.hxx"
#include "lib/ffmpeg/Codec.hxx"
#include "lib/ffmpeg/SampleFormat.hxx"
#include "lib/ffmpeg/LibFmt.hxx"
#include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx"
#include "FfmpegIo.hxx"
@ -384,7 +385,8 @@ static void
FfmpegParseMetaData(const AVStream &stream,
ReplayGainInfo &rg, MixRampInfo &mr)
{
FfmpegParseMetaData(*stream.metadata, rg, mr);
if (stream.metadata != nullptr)
FfmpegParseMetaData(*stream.metadata, rg, mr);
}
static void
@ -393,7 +395,9 @@ FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream,
{
assert(audio_stream >= 0);
FfmpegParseMetaData(*format_context.metadata, rg, mr);
if (format_context.metadata != nullptr)
FfmpegParseMetaData(*format_context.metadata, rg, mr);
FfmpegParseMetaData(*format_context.streams[audio_stream],
rg, mr);
}
@ -468,7 +472,7 @@ static bool
IsSeekable(const AVFormatContext &format_context) noexcept
{
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100)
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0;
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) == 0;
#else
(void)format_context;
return false;
@ -520,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
return;
}
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = codec_context->ch_layout.nb_channels;
#else
const unsigned channels = codec_context->channels;
#endif
const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
sample_format,
codec_context->channels);
channels);
const SignedSongTime total_time =
av_stream.duration != (int64_t)AV_NOPTS_VALUE
@ -530,9 +540,8 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
client.Ready(audio_format,
input
? input->IsSeekable()
: IsSeekable(format_context),
(input ? input->IsSeekable() : false)
|| IsSeekable(format_context),
total_time);
FfmpegParseMetaData(client, format_context, audio_stream);
@ -633,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
AV_TIME_BASE_Q));
const auto &codec_params = *stream.codecpar;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = codec_params.ch_layout.nb_channels;
#else
const unsigned channels = codec_params.channels;
#endif
try {
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
codec_params.channels));
channels));
} catch (...) {
}

@ -21,10 +21,13 @@
#define __STDC_CONSTANT_MACROS
#include "FfmpegIo.hxx"
#include "libavutil/mem.h"
#include "../DecoderAPI.hxx"
#include "input/InputStream.hxx"
extern "C" {
#include <libavutil/mem.h>
}
AvioStream::~AvioStream()
{
if (io != nullptr) {

@ -56,20 +56,17 @@ struct GmeContainerPath {
unsigned track;
};
#if GME_VERSION >= 0x000600
static int gme_accuracy;
#endif
static unsigned gme_default_fade;
static bool
gme_plugin_init([[maybe_unused]] const ConfigBlock &block)
{
#if GME_VERSION >= 0x000600
auto accuracy = block.GetBlockParam("accuracy");
gme_accuracy = accuracy != nullptr
? (int)accuracy->GetBoolValue()
: -1;
#endif
auto fade = block.GetBlockParam("default_fade");
gme_default_fade = fade != nullptr
? fade->GetUnsignedValue() * 1000
@ -163,10 +160,8 @@ gme_file_decode(DecoderClient &client, Path path_fs)
FmtDebug(gme_domain, "emulator type '{}'",
gme_type_system(gme_type(emu)));
#if GME_VERSION >= 0x000600
if (gme_accuracy >= 0)
gme_enable_accuracy(emu, gme_accuracy);
#endif
gme_info_t *ti;
const char *gme_err = gme_track_info(emu, &ti, container.track);

@ -798,6 +798,8 @@ MadDecoder::UpdateTimerNextFrame() noexcept
DecoderCommand
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
{
assert(i <= pcm_length);
size_t num_samples = pcm_length - i;
mad_fixed_to_24_buffer(output_buffer, synth.pcm,
@ -843,7 +845,7 @@ MadDecoder::SynthAndSubmit() noexcept
size_t pcm_length = synth.pcm.length;
if (drop_end_samples &&
current_frame == max_frames - drop_end_frames - 1) {
if (drop_end_samples >= pcm_length)
if (i + drop_end_samples >= pcm_length)
return DecoderCommand::STOP;
pcm_length -= drop_end_samples;

@ -81,7 +81,7 @@ if libfaad_dep.found()
decoder_plugins_sources += 'FaadDecoderPlugin.cxx'
endif
libgme_dep = c_compiler.find_library('gme', required: get_option('gme'))
libgme_dep = dependency('libgme', version: '>= 0.6', required: get_option('gme'))
decoder_features.set('ENABLE_GME', libgme_dep.found())
if libgme_dep.found()
decoder_plugins_sources += 'GmeDecoderPlugin.cxx'

@ -3,6 +3,23 @@ encoder_features = configuration_data()
encoder_features.set('ENABLE_ENCODER', need_encoder)
if not need_encoder
if need_wave_encoder
# Special case for the Snapcast output plugin which only needs the
# PCM wave encoder encoder plugin
encoder_glue = static_library(
'encoder_glue',
'plugins/WaveEncoderPlugin.cxx',
include_directories: inc,
)
encoder_glue_dep = declare_dependency(
link_with: encoder_glue,
)
configure_file(output: 'Features.h', configuration: encoder_features)
subdir_done()
endif
encoder_glue_dep = dependency('', required: false)
configure_file(output: 'Features.h', configuration: encoder_features)
subdir_done()

@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
FLAC__StreamEncoder *const fse;
const unsigned compression;
const bool oggflac;
PcmBuffer expand_buffer;
@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
}
static void
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
const AudioFormat &audio_format)
{
unsigned bits_per_sample;
@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
throw FormatRuntimeError("error setting flac sample rate to %d",
audio_format.sample_rate);
if (!FLAC__stream_encoder_set_ogg_serial_number(fse,
if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
GenerateSerial()))
throw FormatRuntimeError("error setting ogg serial number");
}
@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
:Encoder(_oggchaining),
audio_format(_audio_format), fse(_fse),
compression(_compression),
oggflac(_oggflac),
output_buffer(8192)
{
/* this immediately outputs data through callback */
auto init_status = _oggflac ?
auto init_status = oggflac ?
FLAC__stream_encoder_init_ogg_stream(fse,
nullptr, WriteCallback,
nullptr, nullptr, nullptr,
@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
throw std::runtime_error("FLAC__stream_encoder_new() failed");
try {
flac_encoder_setup(fse, compression, audio_format);
flac_encoder_setup(fse, compression, oggflac, audio_format);
} catch (...) {
FLAC__stream_encoder_delete(fse);
throw;
@ -222,7 +224,7 @@ void
FlacEncoder::SendTag(const Tag &tag)
{
/* re-initialize encoder since flac_encoder_finish resets everything */
flac_encoder_setup(fse, compression, audio_format);
flac_encoder_setup(fse, compression, oggflac, audio_format);
FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
FLAC__StreamMetadata_VorbisComment_Entry entry;

@ -35,7 +35,7 @@ if libshine_dep.found()
endif
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
if get_option('wave_encoder')
if get_option('wave_encoder') or need_wave_encoder
encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
endif

@ -272,9 +272,8 @@ EventLoop::Run() noexcept
#endif
assert(IsInside());
assert(!quit);
#ifdef HAVE_THREADED_EVENT_LOOP
assert(alive);
assert(alive || quit);
assert(busy);
wake_event.Schedule(SocketEvent::READ);
@ -299,7 +298,7 @@ EventLoop::Run() noexcept
steady_clock_cache.flush();
do {
while (!quit) {
again = false;
/* invoke timers */
@ -361,7 +360,7 @@ EventLoop::Run() noexcept
socket_event.Dispatch();
}
} while (!quit);
}
#ifdef HAVE_THREADED_EVENT_LOOP
#ifndef NDEBUG

@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
buffer_sink(_buffer_sink),
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
in_sample_rate(in_audio_format.sample_rate),
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
in_channels(in_audio_format.channels),
#endif
in_audio_frame_size(in_audio_format.GetFrameSize()),
out_audio_frame_size(_out_audio_format.GetFrameSize())
{
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
#endif
}
ConstBuffer<void>
@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
frame.Unref();
frame->format = in_format;
frame->sample_rate = in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
frame->ch_layout = in_ch_layout;
#else
frame->channels = in_channels;
#endif
frame->nb_samples = src.size / in_audio_frame_size;
frame.GetBuffer();

@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
FfmpegBuffer interleave_buffer;
const int in_format, in_sample_rate, in_channels;
const int in_format, in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
AVChannelLayout in_ch_layout;
#else
const int in_channels;
#endif
const size_t in_audio_frame_size;
const size_t out_audio_frame_size;

@ -23,6 +23,8 @@
#include "ReplayGainInfo.hxx"
#include "ReplayGainConfig.hxx"
#include "mixer/MixerControl.hxx"
#include "mixer/MixerInternal.hxx"
#include "mixer/Listener.hxx"
#include "pcm/AudioFormat.hxx"
#include "pcm/Volume.hxx"
#include "util/ConstBuffer.hxx"
@ -171,9 +173,11 @@ ReplayGainFilter::Update()
try {
mixer_set_volume(mixer, _volume);
/* TODO: emit this idle event only for the
current partition */
idle_add(IDLE_MIXER);
/* invoke the mixer's listener manually, just
in case the mixer implementation didn't do
that already (this depends on the
implementation) */
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
} catch (...) {
LogError(std::current_exception(),
"Failed to update hardware mixer");

@ -20,7 +20,6 @@
#include "Charset.hxx"
#include "Features.hxx"
#include "Domain.hxx"
#include "Log.hxx"
#include "lib/icu/Converter.hxx"
#include "util/AllocatedString.hxx"
#include "config.h"
@ -45,11 +44,9 @@ SetFSCharset(const char *charset)
assert(charset != nullptr);
assert(fs_converter == nullptr);
fs_charset = charset;
fs_converter = IcuConverter::Create(charset);
assert(fs_converter != nullptr);
FmtDebug(path_domain,
"SetFSCharset: fs charset is {}", fs_charset);
}
#endif

@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
#endif
static inline bool
CreateDirectoryNoThrow(Path path) noexcept
{
#ifdef _WIN32
return CreateDirectory(path.c_str(), nullptr);
#else
return mkdir(path.c_str(), 0777);
#endif
}
/**
* Truncate a file that exists already. Throws std::system_error on
* error.

@ -34,7 +34,6 @@
#include <shlobj.h>
#else
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#endif
@ -53,6 +52,12 @@
#include "Main.hxx"
#endif
#ifdef USE_XDG
#include "Version.h" // for PACKAGE_NAME
#define APP_FILENAME PATH_LITERAL(PACKAGE_NAME)
static constexpr Path app_filename = Path::FromFS(APP_FILENAME);
#endif
#if !defined(_WIN32) && !defined(ANDROID)
class PasswdEntry
{
@ -74,15 +79,6 @@ public:
return result != nullptr;
}
bool ReadByUid(uid_t uid) {
#ifdef HAVE_GETPWUID_R
getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
#else
result = getpwuid(uid);
#endif
return result != nullptr;
}
const passwd *operator->() {
assert(result != nullptr);
return result;
@ -254,7 +250,8 @@ GetUserMusicDir() noexcept
#elif defined(USE_XDG)
return GetUserDir("XDG_MUSIC_DIR");
#elif defined(ANDROID)
return Environment::getExternalStoragePublicDirectory("Music");
return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
"Music");
#else
return nullptr;
#endif
@ -283,6 +280,24 @@ GetUserCacheDir() noexcept
#endif
}
AllocatedPath
GetAppCacheDir() noexcept
{
#ifdef USE_XDG
if (const auto user_dir = GetUserCacheDir(); !user_dir.IsNull()) {
auto dir = user_dir / app_filename;
CreateDirectoryNoThrow(dir);
return dir;
}
return nullptr;
#elif defined(ANDROID)
return context->GetCacheDir(Java::GetEnv());
#else
return nullptr;
#endif
}
AllocatedPath
GetUserRuntimeDir() noexcept
{
@ -296,7 +311,7 @@ GetUserRuntimeDir() noexcept
AllocatedPath
GetAppRuntimeDir() noexcept
{
#ifdef __linux__
#if defined(__linux__) && !defined(ANDROID)
/* systemd specific; see systemd.exec(5) */
if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
if (auto dir = StringView{runtime_directory}.Split(':').first;
@ -306,8 +321,8 @@ GetAppRuntimeDir() noexcept
#ifdef USE_XDG
if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
auto dir = user_dir / Path::FromFS("mpd");
mkdir(dir.c_str(), 0700);
auto dir = user_dir / app_filename;
CreateDirectoryNoThrow(dir);
return dir;
}
#endif
@ -350,10 +365,8 @@ GetHomeDir() noexcept
if (const auto home = getenv("HOME");
IsValidPathString(home) && IsValidDir(home))
return AllocatedPath::FromFS(home);
if (PasswdEntry pw; pw.ReadByUid(getuid()))
return SafePathFromFS(pw->pw_dir);
#endif
return nullptr;
}

@ -43,6 +43,13 @@ GetUserMusicDir() noexcept;
AllocatedPath
GetUserCacheDir() noexcept;
/**
* Obtains cache directory for this application.
*/
[[gnu::const]]
AllocatedPath
GetAppCacheDir() noexcept;
/**
* Obtains the runtime directory for the current user.
*/

@ -101,9 +101,17 @@ AsyncInputStream::Seek(std::unique_lock<Mutex> &lock,
assert(IsReady());
assert(seek_state == SeekState::NONE);
if (new_offset == offset)
/* no-op */
if (new_offset == offset) {
/* no-op, but if the stream is not open anymore (maybe
because it has failed), nothing can be read, so we
should check for errors here instead of pretending
everything's fine */
if (!open)
Check();
return;
}
if (!IsSeekable())
throw std::runtime_error("Not seekable");

@ -30,10 +30,12 @@
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "util/ByteOrder.hxx"
#include "util/ScopeExit.hxx"
#include "fs/AllocatedPath.hxx"
#include "Log.hxx"
#include "config/Block.hxx"
#include <algorithm>
#include <cassert>
#include <cstdint>
@ -43,31 +45,35 @@
#include <cdio/cd_types.h>
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static unsigned speed = 0;
/* Default to full paranoia, but allow skipping sectors. */
static int mode_flags = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;
class CdioParanoiaInputStream final : public InputStream {
cdrom_drive_t *const drv;
CdIo_t *const cdio;
CdromParanoia para;
const lsn_t lsn_from, lsn_to;
int lsn_relofs;
const lsn_t lsn_from;
char buffer[CDIO_CD_FRAMESIZE_RAW];
int buffer_lsn;
lsn_t buffer_lsn;
public:
CdioParanoiaInputStream(const char *_uri, Mutex &_mutex,
cdrom_drive_t *_drv, CdIo_t *_cdio,
bool reverse_endian,
lsn_t _lsn_from, lsn_t _lsn_to)
lsn_t _lsn_from, lsn_t lsn_to)
:InputStream(_uri, _mutex),
drv(_drv), cdio(_cdio), para(drv),
lsn_from(_lsn_from), lsn_to(_lsn_to),
lsn_relofs(0),
lsn_from(_lsn_from),
buffer_lsn(-1)
{
/* Set reading mode for full paranoia, but allow
skipping sectors. */
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
para.SetMode(mode_flags);
/* seek to beginning of the track */
para.Seek(lsn_from);
@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
};
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static unsigned speed = 0;
static void
input_cdio_init(EventLoop &, const ConfigBlock &block)
{
@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
value);
}
speed = block.GetBlockValue("speed",0U);
if (const auto *param = block.GetBlockParam("mode")) {
param->With([](const char *s){
if (StringIsEqual(s, "disable"))
mode_flags = PARANOIA_MODE_DISABLE;
else if (StringIsEqual(s, "overlap"))
mode_flags = PARANOIA_MODE_OVERLAP;
else if (StringIsEqual(s, "full"))
mode_flags = PARANOIA_MODE_FULL;
else
throw std::invalid_argument{"Invalid paranoia mode"};
});
}
if (const auto *param = block.GetBlockParam("skip")) {
if (param->GetBoolValue())
mode_flags &= ~PARANOIA_MODE_NEVERSKIP;
else
mode_flags |= PARANOIA_MODE_NEVERSKIP;
}
}
struct CdioUri {
@ -173,9 +194,12 @@ cdio_detect_device()
if (devices == nullptr)
return nullptr;
AllocatedPath path = AllocatedPath::FromFS(devices[0]);
cdio_free_device_list(devices);
return path;
AtScopeExit(devices) { cdio_free_device_list(devices); };
if (devices[0] == nullptr)
return nullptr;
return AllocatedPath::FromFS(devices[0]);
}
static InputStreamPtr
@ -271,81 +295,70 @@ CdioParanoiaInputStream::Seek(std::unique_lock<Mutex> &,
return;
/* calculate current LSN */
lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
offset = new_offset;
const lsn_t lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
{
if (lsn_relofs != buffer_lsn) {
const ScopeUnlock unlock(mutex);
para.Seek(lsn_from + lsn_relofs);
}
offset = new_offset;
}
size_t
CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
void *ptr, size_t length)
{
size_t nbytes = 0;
char *wptr = (char *) ptr;
/* end of track ? */
if (IsEOF())
return 0;
while (length > 0) {
/* end of track ? */
if (lsn_from + lsn_relofs > lsn_to)
break;
//current sector was changed ?
const int16_t *rbuf;
//current sector was changed ?
const int16_t *rbuf;
if (lsn_relofs != buffer_lsn) {
const ScopeUnlock unlock(mutex);
const lsn_t lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
const std::size_t diff = offset % CDIO_CD_FRAMESIZE_RAW;
try {
rbuf = para.Read().data;
} catch (...) {
char *s_err = cdio_cddap_errors(drv);
if (s_err) {
FmtError(cdio_domain,
"paranoia_read: {}", s_err);
cdio_cddap_free_messages(s_err);
}
if (lsn_relofs != buffer_lsn) {
const ScopeUnlock unlock(mutex);
throw;
try {
rbuf = para.Read().data;
} catch (...) {
char *s_err = cdio_cddap_errors(drv);
if (s_err) {
FmtError(cdio_domain,
"paranoia_read: {}", s_err);
cdio_cddap_free_messages(s_err);
}
//store current buffer
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
buffer_lsn = lsn_relofs;
} else {
//use cached sector
rbuf = (const int16_t *)buffer;
throw;
}
//correct offset
const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
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, ((const char *)rbuf) + diff, len);
//update pointer
wptr += len;
nbytes += len;
//update offset
offset += len;
lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
//update length
length -= len;
//store current buffer
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
buffer_lsn = lsn_relofs;
} else {
//use cached sector
rbuf = (const int16_t *)buffer;
}
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
const std::size_t nbytes = std::min(length, maxwrite);
//skip diff bytes from this lsn
memcpy(ptr, ((const char *)rbuf) + diff, nbytes);
//update offset
offset += nbytes;
return nbytes;
}
bool
CdioParanoiaInputStream::IsEOF() const noexcept
{
return lsn_from + lsn_relofs > lsn_to;
return offset >= size;
}
static constexpr const char *cdio_paranoia_prefixes[] = {

@ -82,7 +82,7 @@ class CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
public:
template<typename I>
CurlInputStream(EventLoop &event_loop, const char *_url,
const std::multimap<std::string, std::string> &headers,
const Curl::Headers &headers,
I &&_icy,
Mutex &_mutex);
@ -92,7 +92,7 @@ public:
CurlInputStream &operator=(const CurlInputStream &) = delete;
static InputStreamPtr Open(const char *url,
const std::multimap<std::string, std::string> &headers,
const Curl::Headers &headers,
Mutex &mutex);
private:
@ -131,8 +131,7 @@ private:
void SeekInternal(offset_type new_offset);
/* virtual methods from CurlResponseHandler */
void OnHeaders(unsigned status,
std::multimap<std::string, std::string> &&headers) override;
void OnHeaders(unsigned status, Curl::Headers &&headers) override;
void OnData(ConstBuffer<void> data) override;
void OnEnd() override;
void OnError(std::exception_ptr e) noexcept override;
@ -227,7 +226,7 @@ WithConvertedTagValue(const char *uri, const char *value, F &&f) noexcept
void
CurlInputStream::OnHeaders(unsigned status,
std::multimap<std::string, std::string> &&headers)
Curl::Headers &&headers)
{
assert(GetEventLoop().IsInside());
assert(!postponed_exception);
@ -391,7 +390,7 @@ input_curl_finish() noexcept
template<typename I>
inline
CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
const std::multimap<std::string, std::string> &headers,
const Curl::Headers &headers,
I &&_icy,
Mutex &_mutex)
:AsyncInputStream(event_loop, _url, _mutex,
@ -418,7 +417,6 @@ CurlInputStream::InitEasy()
request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
request->SetOption(CURLOPT_MAXREDIRS, 5L);
request->SetOption(CURLOPT_FAILONERROR, 1L);
/* this option eliminates the probe request when
username/password are specified */
@ -440,6 +438,14 @@ CurlInputStream::InitEasy()
request->SetVerifyPeer(verify_peer);
request->SetVerifyHost(verify_host);
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
try {
request->SetProxyVerifyPeer(verify_peer);
request->SetProxyVerifyHost(verify_host);
} catch (...) {
/* these methods fail if libCURL was compiled with
CURL_DISABLE_PROXY; ignore silently */
}
}
void
@ -491,7 +497,7 @@ CurlInputStream::DoSeek(offset_type new_offset)
inline InputStreamPtr
CurlInputStream::Open(const char *url,
const std::multimap<std::string, std::string> &headers,
const Curl::Headers &headers,
Mutex &mutex)
{
auto icy = std::make_shared<IcyMetaDataParser>();
@ -510,8 +516,7 @@ CurlInputStream::Open(const char *url,
}
InputStreamPtr
OpenCurlInputStream(const char *uri,
const std::multimap<std::string, std::string> &headers,
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
Mutex &mutex)
{
return CurlInputStream::Open(uri, headers, mutex);

@ -20,12 +20,10 @@
#ifndef MPD_INPUT_CURL_HXX
#define MPD_INPUT_CURL_HXX
#include "lib/curl/Headers.hxx"
#include "input/Ptr.hxx"
#include "thread/Mutex.hxx"
#include <string>
#include <map>
extern const struct InputPlugin input_plugin_curl;
/**
@ -36,8 +34,7 @@ extern const struct InputPlugin input_plugin_curl;
* Throws on error.
*/
InputStreamPtr
OpenCurlInputStream(const char *uri,
const std::multimap<std::string, std::string> &headers,
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
Mutex &mutex);
#endif

@ -164,7 +164,7 @@ QobuzClient::InvokeHandlers() noexcept
std::string
QobuzClient::MakeUrl(const char *object, const char *method,
const std::multimap<std::string, std::string> &query) const noexcept
const Curl::Headers &query) const noexcept
{
assert(!query.empty());
@ -183,7 +183,7 @@ QobuzClient::MakeUrl(const char *object, const char *method,
std::string
QobuzClient::MakeSignedUrl(const char *object, const char *method,
const std::multimap<std::string, std::string> &query) const noexcept
const Curl::Headers &query) const noexcept
{
assert(!query.empty());

@ -23,12 +23,12 @@
#include "QobuzSession.hxx"
#include "QobuzLoginRequest.hxx"
#include "lib/curl/Init.hxx"
#include "lib/curl/Headers.hxx"
#include "thread/Mutex.hxx"
#include "event/DeferEvent.hxx"
#include "util/IntrusiveList.hxx"
#include <memory>
#include <map>
#include <string>
class QobuzSessionHandler
@ -94,10 +94,10 @@ public:
QobuzSession GetSession() const;
std::string MakeUrl(const char *object, const char *method,
const std::multimap<std::string, std::string> &query) const noexcept;
const Curl::Headers &query) const noexcept;
std::string MakeSignedUrl(const char *object, const char *method,
const std::multimap<std::string, std::string> &query) const noexcept;
const Curl::Headers &query) const noexcept;
private:
void StartLogin();

@ -38,7 +38,7 @@ static constexpr yajl_callbacks qobuz_error_parser_callbacks = {
};
QobuzErrorParser::QobuzErrorParser(unsigned _status,
const std::multimap<std::string, std::string> &headers)
const Curl::Headers &headers)
:YajlResponseParser(&qobuz_error_parser_callbacks, nullptr, this),
status(_status)
{

@ -20,11 +20,9 @@
#ifndef QOBUZ_ERROR_PARSER_HXX
#define QOBUZ_ERROR_PARSER_HXX
#include "lib/curl/Headers.hxx"
#include "lib/yajl/ResponseParser.hxx"
#include <string>
#include <map>
template<typename T> struct ConstBuffer;
struct StringView;
@ -46,8 +44,7 @@ public:
* May throw if there is a formal error in the response
* headers.
*/
QobuzErrorParser(unsigned status,
const std::multimap<std::string, std::string> &headers);
QobuzErrorParser(unsigned status, const Curl::Headers &headers);
protected:
/* virtual methods from CurlResponseParser */

@ -77,7 +77,7 @@ QobuzLoginRequest::ResponseParser::GetSession()
return std::move(session);
}
static std::multimap<std::string, std::string>
static Curl::Headers
MakeLoginForm(const char *app_id,
const char *username, const char *email,
const char *password,
@ -85,7 +85,7 @@ MakeLoginForm(const char *app_id,
{
assert(username != nullptr || email != nullptr);
std::multimap<std::string, std::string> form{
Curl::Headers form{
{"app_id", app_id},
{"password", password},
{"device_manufacturer_id", device_manufacturer_id},
@ -134,8 +134,7 @@ QobuzLoginRequest::~QobuzLoginRequest() noexcept
}
std::unique_ptr<CurlResponseParser>
QobuzLoginRequest::MakeParser(unsigned status,
std::multimap<std::string, std::string> &&headers)
QobuzLoginRequest::MakeParser(unsigned status, Curl::Headers &&headers)
{
if (status != 200)
return std::make_unique<QobuzErrorParser>(status, headers);

@ -56,7 +56,7 @@ public:
private:
/* virtual methods from DelegateCurlResponseHandler */
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
std::multimap<std::string, std::string> &&headers) override;
Curl::Headers &&headers) override;
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
/* virtual methods from CurlResponseHandler */

@ -99,8 +99,7 @@ QobuzTagScanner::~QobuzTagScanner() noexcept
}
std::unique_ptr<CurlResponseParser>
QobuzTagScanner::MakeParser(unsigned status,
std::multimap<std::string, std::string> &&headers)
QobuzTagScanner::MakeParser(unsigned status, Curl::Headers &&headers)
{
if (status != 200)
return std::make_unique<QobuzErrorParser>(status, headers);

@ -49,7 +49,7 @@ public:
private:
/* virtual methods from DelegateCurlResponseHandler */
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
std::multimap<std::string, std::string> &&headers) override;
Curl::Headers &&headers) override;
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
/* virtual methods from CurlResponseHandler */

@ -93,7 +93,7 @@ QobuzTrackRequest::~QobuzTrackRequest() noexcept
std::unique_ptr<CurlResponseParser>
QobuzTrackRequest::MakeParser(unsigned status,
std::multimap<std::string, std::string> &&headers)
Curl::Headers &&headers)
{
if (status != 200)
return std::make_unique<QobuzErrorParser>(status, headers);

@ -56,7 +56,7 @@ public:
private:
/* virtual methods from DelegateCurlResponseHandler */
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
std::multimap<std::string, std::string> &&headers) override;
Curl::Headers &&headers) override;
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
/* virtual methods from CurlResponseHandler */

@ -42,6 +42,8 @@
#include "io/UniqueFileDescriptor.hxx"
#endif
#include <cstdint>
class Path;
class FileInfo;

@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept
AllocatedPath
Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
{
assert(env != nullptr);
assert(_file != nullptr);
LocalObject file(env, _file);
const jstring path = GetAbsolutePath(env, file);

@ -89,6 +89,16 @@ public:
String(JNIEnv *_env, const char *_value) noexcept
:LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {}
/**
* This constructor allows passing a nullptr value, which maps
* to a "null" in Java.
*/
static String Optional(JNIEnv *_env, const char *_value) noexcept {
return _value != nullptr
? String{_env, _value}
: String{};
}
static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept {
return {env, s, env->GetStringUTFChars(s, nullptr)};
}

@ -18,13 +18,13 @@ endif
conf.set('HAVE_MD5', crypto_md5_dep.found())
if libavutil_dep.found()
if ffmpeg_util_dep.found()
crypto_base64 = static_library(
'crypto_base64',
'Base64.cxx',
include_directories: inc,
dependencies: [
libavutil_dep,
ffmpeg_util_dep,
],
)

204
src/lib/curl/Adapter.cxx Normal file

@ -0,0 +1,204 @@
/*
* Copyright 2008-2021 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.
*/
#include "Adapter.hxx"
#include "Easy.hxx"
#include "Handler.hxx"
#include "util/CharUtil.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringStrip.hxx"
#include "util/StringView.hxx"
#include <algorithm>
#include <cassert>
void
CurlResponseHandlerAdapter::Install(CurlEasy &easy)
{
assert(state == State::UNINITIALISED);
error_buffer[0] = 0;
easy.SetErrorBuffer(error_buffer);
easy.SetHeaderFunction(_HeaderFunction, this);
easy.SetWriteFunction(WriteFunction, this);
curl = easy.Get();
state = State::HEADERS;
}
void
CurlResponseHandlerAdapter::FinishHeaders()
{
assert(state >= State::HEADERS);
if (state != State::HEADERS)
return;
state = State::BODY;
long status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
handler.OnHeaders(status, std::move(headers));
}
void
CurlResponseHandlerAdapter::FinishBody()
{
FinishHeaders();
if (state != State::BODY)
return;
state = State::CLOSED;
handler.OnEnd();
}
void
CurlResponseHandlerAdapter::Done(CURLcode result) noexcept
{
if (postponed_error) {
state = State::CLOSED;
handler.OnError(std::move(postponed_error));
return;
}
try {
if (result != CURLE_OK) {
StripRight(error_buffer);
const char *msg = error_buffer;
if (*msg == 0)
msg = curl_easy_strerror(result);
throw FormatRuntimeError("CURL failed: %s", msg);
}
FinishBody();
} catch (...) {
state = State::CLOSED;
handler.OnError(std::current_exception());
}
}
[[gnu::pure]]
static bool
IsResponseBoundaryHeader(StringView s) noexcept
{
return s.size > 5 && (s.StartsWith("HTTP/") ||
/* the proprietary "ICY 200 OK" is
emitted by Shoutcast */
s.StartsWith("ICY 2"));
}
inline void
CurlResponseHandlerAdapter::HeaderFunction(StringView s) noexcept
{
if (state > State::HEADERS)
return;
if (IsResponseBoundaryHeader(s)) {
/* this is the boundary to a new response, for example
after a redirect */
headers.clear();
return;
}
const char *header = s.data;
const char *end = StripRight(header, header + s.size);
const char *value = s.Find(':');
if (value == nullptr)
return;
std::string name(header, value);
std::transform(name.begin(), name.end(), name.begin(),
static_cast<char(*)(char)>(ToLowerASCII));
/* skip the colon */
++value;
/* strip the value */
value = StripLeft(value, end);
end = StripRight(value, end);
headers.emplace(std::move(name), std::string(value, end));
}
std::size_t
CurlResponseHandlerAdapter::_HeaderFunction(char *ptr, std::size_t size,
std::size_t nmemb,
void *stream) noexcept
{
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
size *= nmemb;
c.HeaderFunction({ptr, size});
return size;
}
inline std::size_t
CurlResponseHandlerAdapter::DataReceived(const void *ptr,
std::size_t received_size) noexcept
{
assert(received_size > 0);
try {
FinishHeaders();
handler.OnData({ptr, received_size});
return received_size;
} catch (CurlResponseHandler::Pause) {
return CURL_WRITEFUNC_PAUSE;
} catch (...) {
/* from inside this libCURL callback function, we
can't do much, so we remember the exception to be
handled later by Done(), and return 0, causing the
response to be aborted with CURLE_WRITE_ERROR */
postponed_error = std::current_exception();
return 0;
}
}
std::size_t
CurlResponseHandlerAdapter::WriteFunction(char *ptr, std::size_t size,
std::size_t nmemb,
void *stream) noexcept
{
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
size *= nmemb;
if (size == 0)
return 0;
return c.DataReceived(ptr, size);
}

91
src/lib/curl/Adapter.hxx Normal file

@ -0,0 +1,91 @@
/*
* Copyright 2008-2022 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.
*/
#pragma once
#include "Headers.hxx"
#include <curl/curl.h>
#include <cstddef>
#include <exception>
struct StringView;
class CurlEasy;
class CurlResponseHandler;
class CurlResponseHandlerAdapter {
CURL *curl;
CurlResponseHandler &handler;
Curl::Headers headers;
/**
* An exception caught from within the WriteFunction() which
* will later be handled by Done().
*/
std::exception_ptr postponed_error;
/** error message provided by libcurl */
char error_buffer[CURL_ERROR_SIZE];
enum class State {
UNINITIALISED,
HEADERS,
BODY,
CLOSED,
} state = State::UNINITIALISED;
public:
explicit CurlResponseHandlerAdapter(CurlResponseHandler &_handler) noexcept
:handler(_handler) {}
void Install(CurlEasy &easy);
void Done(CURLcode result) noexcept;
private:
void FinishHeaders();
void FinishBody();
void HeaderFunction(StringView s) noexcept;
/** called by curl when a new header is available */
static std::size_t _HeaderFunction(char *ptr,
std::size_t size, std::size_t nmemb,
void *stream) noexcept;
std::size_t DataReceived(const void *ptr, std::size_t size) noexcept;
/** called by curl when new data is available */
static std::size_t WriteFunction(char *ptr,
std::size_t size, std::size_t nmemb,
void *stream) noexcept;
};

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