Compare commits

...

184 Commits

Author SHA1 Message Date
Max Kellermann
c560ec8ea6 release v0.21.21 2020-03-19 15:22:28 +01:00
Max Kellermann
56c234b410 raise default "max_connections" value to 100
Documentation says the limit is 5, but it was really 10 (at least
since 2004).  But since MPD wants to promote using many small clients
idling around, and these clients consume only very few resources, it
seems reasonable to raise this limit's default value.
2020-03-19 13:30:46 +01:00
Max Kellermann
82743dfd02 playlist/asx: concatenate multiple CharacterData fragments
Similar to c45f113856
2020-03-12 21:07:37 +01:00
Max Kellermann
33694642bd playlist/asx: add State::TAG 2020-03-12 20:42:16 +01:00
Max Kellermann
c71242d743 playlist/asx: use tag_table to convert element name to TagType 2020-03-12 20:40:18 +01:00
Max Kellermann
c45f113856 playlist/xspf: concatenate multiple CharacterData fragments
Closes https://github.com/MusicPlayerDaemon/MPD/issues/781
2020-03-12 08:02:58 +01:00
Max Kellermann
e0a8fd398c playlist/xspf: add State::TAG 2020-03-12 08:00:54 +01:00
Max Kellermann
3e97058151 playlist/xspf: move location.empty() check to _start_element() 2020-03-11 20:54:53 +01:00
Max Kellermann
51b1dd8672 playlist/xspf: use tag_table to convert element name to TagType 2020-03-11 20:51:47 +01:00
Max Kellermann
98a7d8da6c playlist/xspf: use C++11 initializer 2020-03-11 20:51:10 +01:00
Max Kellermann
acb29f792f tag/Mask: fix yet another typo, this time in Unset()
Similar to commits e8f2f98048 and
ff1ff1e54a

Closes https://github.com/MusicPlayerDaemon/MPD/issues/783
2020-03-11 20:34:02 +01:00
Max Kellermann
cd364023ae .travis.yml: rename "matrix" to "jobs"
Travis has changed the canonical name for this a while ago.

(Now really.  The last commit for this was empty.)
2020-03-07 09:31:46 +01:00
Max Kellermann
8d34a1cfc6 archive/iso9660: skip empty filenames
Aparently, libcdio sometimes returns empty filenames, causing MPD
crashes.  This shouldn't really happen, and I consider this a libcdio
bug - but if it happens, people blame MPD, so let's add a check.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/776
2020-03-07 09:30:56 +01:00
Max Kellermann
73a1f078a6 archive/iso9660: use IsSpecialFilename() 2020-03-07 09:30:56 +01:00
Max Kellermann
b7ce452308 fs/Traits: add IsSpecialFilename()
Merge some duplicate code in a central library.
2020-03-07 09:30:56 +01:00
Max Kellermann
5faf76051d .travis.yml: force updating homebrew on OSX
Workaround for Travis failures as described in
 https://travis-ci.community/t/macos-build-fails-because-of-homebrew-bundle-unknown-command/7296/18
2020-03-07 09:30:56 +01:00
Max Kellermann
5fe70a3417 .travis.yml: rename "matrix" to "jobs"
Travis has changed the canonical name for this a while ago.
2020-03-07 09:30:56 +01:00
Thomas Klausner
7a68b1e71f Adapt SolarisOutputPlugin.cxx to be usable on NetBSD. 2020-02-29 10:05:29 +01:00
Thomas Klausner
d5468dfe89 Add missing header.
Fixes
../src/time/ISO8601.cxx:67:24: error: use of undeclared identifier 'strtoul'
        unsigned long value = strtoul(s, &endptr, 10);
                              ^
../src/time/ISO8601.cxx:77:14: error: use of undeclared identifier 'strtoul'
                        minutes = strtoul(s, &endptr, 10);
                                  ^

on NetBSD with clang 9.0.0.
2020-02-29 10:04:54 +01:00
John Regan
976372ff63 gme: check for empty metadata strings instead of nullptr
Using libgme 0.6.2 on macOS, it appears that gme_info_t strings can be
empty, which creates weird track titles: (001/050)

This adds an additional check for an empty string.
2020-02-25 20:12:08 +01:00
Max Kellermann
9abb686eeb increment version number to 0.21.21 2020-02-16 20:48:46 +01:00
Max Kellermann
f24bcc7f42 release v0.21.20 2020-02-16 20:43:35 +01:00
Max Kellermann
89800324cb .travis.yml: use GTest 1.8.0 instead of HEAD
Fixes strange C++11 compatibility problems during GTest installation:

 /tmp/gtest-20200216-20679-mu1044/googletest/include/gtest/internal/gtest-internal.h:1249:1: error: unknown type name 'constexpr'
 constexpr bool TypedTestCaseIsDeprecated() { return true; }

 /tmp/gtest-20200216-20679-mu1044/googletest/include/gtest/internal/gtest-internal.h:1249:11: error: expected unqualified-id
 constexpr bool TypedTestCaseIsDeprecated() { return true; }

 /tmp/gtest-20200216-20679-mu1044/googletest/include/gtest/internal/gtest-internal.h:1254:1: error: unknown type name 'constexpr'
 constexpr bool RegisterTypedTestCase_P_IsDeprecated() { return true; }
2020-02-16 20:33:15 +01:00
Rosen Penev
050e30418c Fix travis
pip is bugged on arm64 and s390x. Add --no-cache-dir to fix. For
consistency, it doesn't hurt to add everywhere.
2020-02-16 19:29:30 +01:00
Max Kellermann
5397d18ed9 protocol/ArgParser: cast seek offset to SignedSongTime
"The issue is that ParseCommandArgSignedSongTime parses with
SongTime::FromS, not SignedSongTime::FromS, before casting back to a
SignedSongTime for the return. With x86 overflow rules this doesn't
matter, but on ARM the first cast turns negative values to zero."

Closes https://github.com/MusicPlayerDaemon/MPD/issues/757
2020-02-10 09:18:37 +01:00
Max Kellermann
42eb69f46f test/meson.build: restore -Wno-missing-declarations for GTest
This is still needed for GTest 1.8.0 (Travis / Ubuntu Bionic).
2020-02-04 22:06:51 +01:00
Max Kellermann
f1ad21d2bf test/meson.build: add -Wno-unused-command-line-argument for clang 9+ 2020-02-04 16:35:44 +01:00
Max Kellermann
535a099a27 test/meson.build: drop obsolete gtest warning suppressions 2020-02-04 16:32:13 +01:00
Max Kellermann
50003f6ad2 decoder/ffmpeg: add two more missing commas 2020-02-04 16:30:05 +01:00
Rosen Penev
0914644d2b add missing comma
Found with bugprone-suspicious-missing-comma

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2020-02-04 16:29:23 +01:00
Max Kellermann
5ad6e7fec5 decoder/{audio,snd}file: handle MIME type "audio/wav" 2020-02-01 11:26:57 +01:00
Rosen Penev
0bb943ba3e FfmpegDecoderPlugin: add WAV support 2020-02-01 11:24:51 +01:00
Rosen Penev
80a0cf694f MadDecoderPlugin: fix bad printf format
max_frames is size_t, not unsigned long. Fixes GCC warning.
2020-01-31 21:21:39 +01:00
Max Kellermann
0c9e25b3c4 NEWS: add missing line 2020-01-25 20:09:16 +01:00
Max Kellermann
943a67c805 decoder/ogg: need to sync small files while looking for EOS
When calling OggSeekFindEOS() from inside a OggVisitor callback, then
the #InputStream may be in the middle of an Ogg packet, and the newly
initialized #ogg_sync_state will not be able to load it without the
help of ogg_sync_pageseek().  By passing "synced=false" to
OggSeekFindEOS(), we force the use of ogg_sync_pageseek() even when
not actually seeking.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/719
2020-01-25 20:07:11 +01:00
Max Kellermann
881d91f86b lib/xiph/OggFind: add parameter "synced" 2020-01-25 20:07:10 +01:00
Max Kellermann
54d57fdcc2 test/DumpDecoderClient: dump the seekable flag 2020-01-25 20:07:09 +01:00
Max Kellermann
f6f30d6d64 increment version number to 0.21.20 2020-01-25 20:06:58 +01:00
Max Kellermann
4013fa15b9 release v0.21.19 2020-01-17 15:49:02 +01:00
Max Kellermann
ac1b844c15 android/AndroidManifest.xml: increase targetSdkVersion to 28
Google Play refuses uploads with a lower targetSdkVersion.
2020-01-17 15:48:43 +01:00
Max Kellermann
b8614048d4 python/build/libs.py: update Boost to 1.72.0 2020-01-17 15:33:13 +01:00
Max Kellermann
aed0d13591 python/build/libs.py: update expat to 2.2.9 2020-01-17 15:33:13 +01:00
Max Kellermann
9d02103ebe python/build/libs.py: disable various CURL features we don't use 2020-01-17 15:33:13 +01:00
Max Kellermann
61784c2144 python/build/libs.py: update CURL to 7.68.0 2020-01-17 15:19:46 +01:00
Max Kellermann
7059215795 python/build/libs.py: update FFmpeg to 4.2.2 2020-01-17 15:19:00 +01:00
Max Kellermann
2190cc7927 python/build/libs.py: update FLAC to 1.3.3 2020-01-17 15:17:21 +01:00
Max Kellermann
75dc9506c2 python/build/libs.py: update libmpdclient to 2.17 2020-01-17 15:16:20 +01:00
Max Kellermann
4f11fa0d41 config/File: allow overriding top-level settings in includes
Remove the error message and instead erase the old setting if the
"repeatable" flag is not set.

https://github.com/MusicPlayerDaemon/MPD/issues/684
2020-01-17 14:58:40 +01:00
Max Kellermann
ce7ec2b3f5 meson.build: add -f{function,data}-sections to C++ as well
By accident, this was only enabled for C.
2020-01-14 17:48:48 +01:00
Max Kellermann
fada4aa529 NEWS: mention the Android build fix 2020-01-12 13:08:37 +01:00
Max Kellermann
aa0e121ade android/build.py: support x86_64 builds 2020-01-12 13:03:18 +01:00
Max Kellermann
b4700039fd android/build.py: drop -lmstackrealign
This flag doesn't appear to work, and since we never had a problem
without the flag, implementing Google's recommendation is useless.
2020-01-12 13:03:18 +01:00
Max Kellermann
ab41c16eb5 android/build.py: add -Wl,--exclude-libs=ALL
Don't export the symbols of all those static libraries.  Most
importantly, don't export the whole libc++ ABI.
2020-01-12 12:47:40 +01:00
Max Kellermann
04101f37b8 android/build.py: add -fno-faddrsig and -lmstackrealign 2020-01-12 12:41:37 +01:00
Max Kellermann
8c31370534 android/build.py: drop --sysroot, -isystem, -L, ...
These appear to be no longer necessary (tested with NDK r20b).

Closes https://github.com/android/ndk/issues/951
2020-01-12 12:11:09 +01:00
Max Kellermann
2306b0d78c android/build.py: append API level to LLVM triple
This implicitly defines __ANDROID_API__, which means we can drop the
"-D__ANDROID_API__=" parameter.

This is recommended on
https://android.googlesource.com/platform/ndk/+/ndk-release-r20/docs/BuildSystemMaintainers.md
2020-01-12 12:02:12 +01:00
Max Kellermann
cb1a9045e6 android/build.py: drop "-none" from llvm_triple
It's not mentioned on
https://developer.android.com/ndk/guides/other_build_systems
2020-01-12 11:51:20 +01:00
Max Kellermann
e92af06664 android/build.py: remove obsolete dict key android_api_level 2020-01-12 11:49:10 +01:00
Max Kellermann
af20a1c994 pulse: obey Pulse's maximum sample rate (fixes DSD128 playback)
Closes https://github.com/MusicPlayerDaemon/MPD/issues/708
2020-01-12 07:58:17 +01:00
Max Kellermann
756560eac3 .travis.yml: add ARM64 build (for NEON optimizations) 2020-01-01 18:36:59 +01:00
Jacob Vosmaer
dca0519336 Clang: only use [[fallthrough]] in C++ files
It appears that [[fallthrough]] is valid in C++ but not in C. And
in some Clang versions (e.g. Clang 11 on macOS), Clang is pedantic
about this and considers it an error to use [[fallthrough]] in a
.c file such as src/util/format.c.

This changes makes gcc_fallthrough a no-op under Clang in C files.
2019-12-31 13:54:09 +01:00
Max Kellermann
b9a7f30443 .travis.yml: add s390x build (big-endian) 2019-12-24 16:47:07 +01:00
Max Kellermann
32a17a997a increment version number to 0.21.19 2019-12-24 16:41:13 +01:00
Max Kellermann
bf41d1ad2b release v0.21.18 2019-12-24 16:13:16 +01:00
Max Kellermann
d27e534a85 time/ISO8601: fix Windows build failure
Caused by 2bc127bb43
2019-12-24 16:12:52 +01:00
Max Kellermann
6d54928d7c Revert "lib/curl/Global: remove lower bound on timeouts"
This reverts commit 4475b8ca04.  Further
testing revealed that the threaded resolver still uses a timeout of
0ms.  This revert however lowers the bound to a minimum of 1ms instead
of 10ms.
2019-12-24 16:09:26 +01:00
Max Kellermann
0dffe05bf7 input/curl: remove unnecessary InvalidateSockets() call
Like fe598e7d30
2019-12-24 11:41:52 +01:00
Max Kellermann
9ef1f10319 .travis.yml: install brew packages using addons/homebrew 2019-12-24 11:20:54 +01:00
Max Kellermann
23fcfdbd2a .travis.yml: remove "brew update"
The packages from the Travis image are good enough, and this speeds up
the Travis build.
2019-12-24 11:20:29 +01:00
Max Kellermann
3401d26d4c .travis.yml: switch to xcode9.4, the current default 2019-12-24 10:20:04 +01:00
Max Kellermann
256753ea46 .travis.yml: disable Homebrew analytics
Don't bother sending analytics data for a CI runner.  This adds
traffic but doesn't bring anybody any benefit.
2019-12-24 10:19:25 +01:00
Max Kellermann
76cd5f8595 .travis.yml: cache Homebrew on osx 2019-12-24 10:17:37 +01:00
Max Kellermann
5684025847 .travis.yml: change "cache" yaml syntax 2019-12-24 10:17:35 +01:00
Max Kellermann
744bd1eadc time/ISO8601: refactor ParseTimeOfDay() to parse one by one
This prepares the migration away from strptime() for Windows
portability.

But the real reason I'm doing this is that strptime() on Apple is
buggy: strptime("14", "%H%M%S") (without separating colons) succeeds
even though only the hour has been parsed.  This fixes recent Travis
failures in the ParseISO8601() unit test.
2019-12-24 10:15:03 +01:00
Max Kellermann
2bc127bb43 time/ISO8601: move code to ParseTimeOfDay() 2019-12-24 10:15:01 +01:00
Max Kellermann
7770298a65 util/Compiler.h: use [[fallthrough]] on clang
Older clang versions don't support the GCC __attribute__ syntax.  For
those, don't use anything at all, and new clang versions shall use the
standard syntax.
2019-12-24 08:04:48 +01:00
Max Kellermann
fa50cdb39e .travis.yml: escape dollar signs in MATRIX_EVAL
Expand $PATH at evaluation and not at assignment, which fixes the
problem that /usr/lib/ccache was added to $PATH between the
MATRIX_EVAL assignment and its evaluation.
2019-12-24 07:51:55 +01:00
Max Kellermann
816ef12088 .travis.yml: add Ubuntu Bionic build 2019-12-23 18:12:04 +01:00
Max Kellermann
5ff786e59c .travis.yml: enable ccache on Linux 2019-12-23 18:10:58 +01:00
Max Kellermann
80fe88e8f6 .travis.yml: enable ccache on osx 2019-12-23 18:10:52 +01:00
Max Kellermann
a1afe9afc6 util/Compiler.h: add gcc_fallthrough
Works around build failures with ccache which may feed processed code
to GCC, which doesn't have the "fall through" code comments.
2019-12-23 17:53:57 +01:00
Max Kellermann
fe598e7d30 lib/curl/Global: remove InvalidateSockets() call from Remove()
curl_multi_remove_handle() calls our socket function, and there's no
need to call curl_multi_socket_action().
2019-12-23 14:52:46 +01:00
Max Kellermann
4475b8ca04 lib/curl/Global: remove lower bound on timeouts
This was a problem 9 years ago, and apparently, it has been fixed long
ago.
2019-12-23 14:50:51 +01:00
Max Kellermann
a714bdb0ce lib/curl: drop support for CURL versions older than 7.32.0
For simplicity, this commit removes a workaround for an old CURL bug.
2019-12-23 14:41:06 +01:00
Max Kellermann
087874620f test/RunCurl: new debug program 2019-12-23 14:26:56 +01:00
Max Kellermann
f1116c9258 event/Loop: remove bogus assertion
Can fail if somebody calls Break().
2019-12-23 14:20:09 +01:00
Max Kellermann
d01fb6730a storage/curl: move start call out of the constructor
This can cause request completion in the I/O thread before this
constructor returns, leaving the object in an abstract state, causing
a crash due to pure virtual method call.  We should not start the
request until this object is fully constructed.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/665
2019-12-23 13:37:58 +01:00
Max Kellermann
7bfe6a3304 test/run_storage: add command "stat" 2019-12-22 19:54:31 +01:00
Max Kellermann
9a577f8060 event/MultiSocketMonitor: add workaround for /dev/null
The ALSA "null" driver opens /dev/null and returns the file handle
from snd_pcm_poll_descriptors(), but /dev/null cannot be used with
epoll, the epoll_ctl() system call returns -EPERM.  This means that
the ALSA output hangs, eventually freezing the whole MPD process.

This commit adds a workaround to the MultiSocketMonitor class which is
used by the ALSA output plugin.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/695
2019-12-22 12:08:44 +01:00
Max Kellermann
d75a0d714e event/MultiSocketMonitor: remove unnecessary friend declaration 2019-12-22 12:08:44 +01:00
Max Kellermann
9be3a1554e event/MultiSocketMonitor: remove duplicate IdleMonitor::Schedule() call
SetReady() does this already.
2019-12-22 12:08:10 +01:00
Max Kellermann
7764719513 event/MultiSocketMonitor: un-inline AddSocket() 2019-12-22 12:00:12 +01:00
Max Kellermann
dcbb9fe07c event/Loop: round timeout up to avoid unnecessary wakeups 2019-12-22 11:58:31 +01:00
Max Kellermann
e3b347820a event/MultiSocketMonitor: use std::exchange() 2019-12-20 13:42:35 +01:00
Max Kellermann
a84bf5a92e event/MultiSocketMonitor: AddSocket() returns bool 2019-12-18 17:50:21 +01:00
Max Kellermann
732bdc800d event/SocketMonitor: Schedule() returns bool 2019-12-18 17:46:33 +01:00
Max Kellermann
a8661b5931 increment version number to 0.21.18 2019-12-18 16:49:04 +01:00
Max Kellermann
5680a3a4b7 release v0.21.17 2019-12-16 23:32:44 +01:00
Max Kellermann
15ce8eb487 time/ISO8601: support omitting field separators
Closes https://github.com/MusicPlayerDaemon/MPD/issues/685
2019-12-16 23:31:29 +01:00
Max Kellermann
b7744be208 song/Filter: try ParseISO8601() first
Prepare for allowing ISO8601 time stamps without delimiters, such as
20191216, and prevent them from being interpreted as numeric UNIX time
stamps.
2019-12-16 23:31:29 +01:00
Max Kellermann
63c5d66016 time/ISO8601: support omitting minutes 2019-12-16 23:24:43 +01:00
Max Kellermann
d09bd9178f time/ISO8601: support omitting seconds 2019-12-16 23:24:43 +01:00
Max Kellermann
7d8b1860c3 time/ISO8601: support time zone offset 2019-12-16 23:24:43 +01:00
Max Kellermann
b06825829b time/ISO8601: allow omitting the "Z" suffix
And allow "Z" suffix after date.
2019-12-16 23:24:43 +01:00
Max Kellermann
ba4cd47fd8 time/ISO8601: allow omitting the time of day 2019-12-16 23:24:43 +01:00
Max Kellermann
bbe403f141 test/TestISO8601: unit test for time/ISO8601 2019-12-16 23:24:43 +01:00
Max Kellermann
5df2707d98 time/ISO8601: ParseISO8601() returns precision 2019-12-16 23:24:43 +01:00
Max Kellermann
4859ea468f time/ISO8601: implement with strptime(), without ParseTimePoint()
Prepare for adding more flexible parsing.
2019-12-16 23:24:43 +01:00
Max Kellermann
2a8830db70 time/Convert: fallback TimeGm() implementation
Move code from Parser.cxx.
2019-12-16 23:24:38 +01:00
Max Kellermann
fed9b6fd74 time/Parser: use TimeGm() 2019-12-16 23:03:31 +01:00
Max Kellermann
b02890eb8a time/Parser: explicitly initialize struct tm before strptime()
This is recommended by the strptime() manpage, because strptime() does
not initialize/set attributes which were not specified in the format
string.
2019-12-16 23:03:25 +01:00
Max Kellermann
da882a6eb6 time/Convert: include sys/time.h for struct timeval
Closes https://github.com/MusicPlayerDaemon/MPD/issues/562
2019-12-16 23:03:21 +01:00
Max Kellermann
aeb89aa9d6 time/ISO8601: forward-declare StringBuffer 2019-12-16 23:02:59 +01:00
Max Kellermann
f885807ecc time/Convert: update copyright 2019-12-16 23:02:58 +01:00
Max Kellermann
b826fd71f0 time/Convert: mention exceptions 2019-12-16 23:02:57 +01:00
Max Kellermann
ae35df1126 zeroconf/AvahiPoll: move TimevalToChrono() to time/Convert.cxx 2019-12-16 23:02:45 +01:00
Max Kellermann
80e55f6bfc time/Convert: add noexcept 2019-12-16 23:02:40 +01:00
Max Kellermann
e7411c0c4b time/Convert: add pure attributes 2019-12-16 23:02:38 +01:00
Max Kellermann
e9af692973 util/Time*: move to time/ 2019-12-16 23:02:14 +01:00
Max Kellermann
0cf90ee8b6 decoder/mad: work around bogus -Wuninitialized in GCC 10 2019-12-16 23:01:31 +01:00
Max Kellermann
dc3c0c8866 pcm/Convert, ...: add missing include for std::runtime_error 2019-12-16 22:52:50 +01:00
Max Kellermann
1c46bb1ba6 lib/gcrypt/MD5: add missing include for uint8_t 2019-12-16 22:52:22 +01:00
Max Kellermann
2e8f42c6ad util/StringBuffer: use std::size_t instead of size_t 2019-12-16 22:51:23 +01:00
Jacob Vosmaer
2b301ffd2c lib/xiph: add missing meson dependency 2019-12-16 17:11:14 +01:00
Jacob Vosmaer
ef0765ca10 input: add missing boost meson dependency 2019-12-16 17:11:14 +01:00
Naglis Jonaitis
9766ac6db3 Fix typo in documentation 2019-12-16 17:09:45 +01:00
Max Kellermann
32799ff682 archive/zzip: improve error reporting
Most importantly, this commit translates ZZIP_ENOENT to
std::system_error(ENOENT) so IsFileNotFound() returns true and
find_stream_art() can suppress the log line.
2019-12-04 12:33:42 +01:00
Max Kellermann
ce093be12c system/Error: add FormatFileNotFound() 2019-12-04 12:33:38 +01:00
Max Kellermann
2c276770f0 util/PrintException, ...: update copyright 2019-12-04 12:33:36 +01:00
Max Kellermann
75a592f629 system/Error: move code to IsErrno() 2019-12-04 12:33:33 +01:00
Max Kellermann
13ce07d181 output/shout: declare metadata as UTF-8
Apparently, Icecast defaults to ISO-8859-1 for MP3:

 http://icecast.org/docs/icecast-2.4.0/config-file.html#mountsettings

This change forces Icecast to UTF-8 without having to configure it in
Icecast's configuration file.
2019-11-06 16:05:22 +01:00
Max Kellermann
d659c7df19 python/build/libs: update CURL to 7.66.0 2019-11-04 14:27:00 +01:00
Max Kellermann
f8403a1d29 python/build/libs: update FFmpeg to 4.2.1 2019-11-04 14:26:05 +01:00
Max Kellermann
ebb952c4ad neighbor/meson.build: disable if -Ddatabase=false
Fixes yet another build failure (which however only affects the 0.22
branch).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/666
2019-11-04 14:18:48 +01:00
Max Kellermann
bea3b954a5 meson.build, SongUpdate: disable db-specific archive code if -Ddatabase=false
Fixes another build failure.
2019-11-04 14:15:48 +01:00
Max Kellermann
129d8e89b9 lib/sqlite: disable if -Ddatabase=false
Fixes build failure in StickerCommands.cxx.
2019-11-04 14:10:03 +01:00
Niklas Haas
65778a3774 output/Jack: mark ports as terminal
This is the correct thing to do for ports from which a signal ultimately
"originates", such as is the case with mpd.
2019-10-29 10:07:44 +01:00
kaliko
d9841668ff doc/user.rst: update build dependencies on Debian Buster 2019-10-28 15:20:05 +01:00
Max Kellermann
85d27cbcb9 increment version number to 0.21.17 2019-10-16 14:20:28 +02:00
Max Kellermann
9b95e65bd9 release v0.21.16 2019-10-16 11:58:36 +02:00
Max Kellermann
12a86c4975 queue/PlaylistEdit: fix relative destination offset when moving a range
Commit 13208bf5a7 added range support to
the `move` command, but applied the wrong offset to the `to` variable.
When the source range is before the current song, and the song thus
gets decremented by the range size, then the final destination offset
must also be decremented by the range size.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/663
2019-10-15 17:00:16 +02:00
Max Kellermann
0b9435858b storage/curl: unescape file names from PROPFIND
This is the last missing piece for https://github.com/MusicPlayerDaemon/MPD/issues/662
2019-10-15 16:49:17 +02:00
Max Kellermann
f0386459ee storage/curl: follow redirects for collections without trailing slash 2019-10-15 16:42:39 +02:00
Max Kellermann
e98d4670b8 storage/curl: work around different case in hex digits 2019-10-15 16:26:53 +02:00
Max Kellermann
56cc42b752 storage/curl: use MapUTF8() to reuse existing escaping code
Commit 29f78b18b1 continued.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/662
2019-10-15 16:26:53 +02:00
Max Kellermann
ead208987d storage/curl: unescape URI in MapToRelativeUTF8() 2019-10-15 16:26:49 +02:00
Max Kellermann
364acc8949 lib/curl/Escape: add CurlUnescape() 2019-10-15 13:39:02 +02:00
Max Kellermann
a8f4d2b6fc storage/curl: move code to EscapeUriPath() 2019-10-15 13:24:06 +02:00
Max Kellermann
0eb113e7c6 lib/curl/String: OO wrapper for allocated strings returned from CURL 2019-10-15 13:13:39 +02:00
Andre Heider
96a9670c69 lib/icu: fix build with iconv() 2019-10-07 13:34:04 +02:00
Max Kellermann
dcc5ce6792 storage/curl: request the "resourcetype" property to fix update
Without requesting the property, "good" WebDAV servers would not send
it, and so MPD could never recognize a directory, failing the database
update.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/660
2019-10-07 12:44:48 +02:00
Max Kellermann
23d08820a2 db/update/Walk: fix crash when music_directory is not a directory
Add a runtime sanity check to avoid the assertion failure.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/660
2019-10-07 12:24:25 +02:00
Max Kellermann
b9b906ab20 increment version number to 0.21.16 2019-10-07 12:24:25 +02:00
Max Kellermann
964804a4c2 release v0.21.15 2019-09-25 21:24:15 +02:00
Max Kellermann
92495d2b0b decoder/mpcdec: fix bogus ReplayGain values
Apparently, libmpcdec sets gain/peak variables to zero if they are not
present.  This clashes with our formula and results in bogus values
which cause noise during playback.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/640
2019-09-13 19:52:11 +02:00
Max Kellermann
9270829b5b ReplayGainInfo: move more code to a function 2019-09-13 19:50:49 +02:00
Max Kellermann
b6243a9945 decoder/mpcdec: merge duplicate code 2019-09-13 19:50:43 +02:00
Max Kellermann
496f88653d ReplayGainInfo: add static method Undefined() 2019-09-13 19:46:39 +02:00
Max Kellermann
5ef645df97 NEWS: add missing line for 818b7e0641 2019-09-08 12:54:16 +02:00
Max Kellermann
bf49c9e4e2 decoder/{dsf,dsdiff}: precalculate bit rate 2019-09-08 12:52:02 +02:00
Max Kellermann
0da9c91af2 decoder/{dsf,dsdiff}: fix displayed bit rate
The formula did not consider the channel count.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/639
2019-09-08 12:45:05 +02:00
Max Kellermann
193e637dd9 python/build/libs: update Boost to 1.71.0 2019-09-01 13:03:50 +02:00
Max Kellermann
928bee933d python/build/libs: update expat to 2.2.7 2019-09-01 13:02:56 +02:00
Max Kellermann
4d1720c886 python/build/libs: update CURL to 7.65.3 2019-09-01 13:02:04 +02:00
Max Kellermann
8f8ed87327 python/build/libs: update FFmpeg to 4.2 2019-09-01 13:00:26 +02:00
Max Kellermann
28a441c977 python/build/libs: update Opus to 1.3.1 2019-09-01 12:59:17 +02:00
Max Kellermann
8cf50b08f2 python/build/libs: update libogg to 1.3.4 2019-09-01 12:58:26 +02:00
Max Kellermann
818b7e0641 output/solaris: include sys/stropts.h only on Solaris
This header had been available for a long time on Linux, but was
removed in glibc 2.30.  This commit moves the `#include` line inside
the `#ifdef __sun` block and adds a fake declaration of `I_FLUSH` for
the Linux build.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/630
2019-08-22 11:41:12 +02:00
Max Kellermann
e70f40fac1 increment version number to 0.21.15 2019-08-22 11:40:17 +02:00
Max Kellermann
bc89ca92b4 release v0.21.14 2019-08-21 10:47:53 +02:00
Max Kellermann
b968e1b6de output/Thread: add missing return in exception handler 2019-08-21 10:20:17 +02:00
Max Kellermann
6c9f9c136b command/all: don't create new Response instance in exception handler
The new Response instance in the `catch` block didn't have the
`command` attribute set, so the error response didn't indicate which
command had failed, which however is required in the MPD protocol.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/628
2019-08-20 20:31:36 +02:00
Max Kellermann
9bff5f9e36 client/Process, command/all: add noexcept
Clarify that those can't throw, preparing for the next commit.
2019-08-20 20:28:15 +02:00
Max Kellermann
2bf26a2ff8 command/all: remove obsolete prototype 2019-08-20 20:28:10 +02:00
Max Kellermann
e33b50d9c5 command/all: simplify return from command_process() 2019-08-20 20:26:07 +02:00
Max Kellermann
21fa44c0d5 command/all: catch all exceptions 2019-08-20 20:23:54 +02:00
Max Kellermann
44444e1b89 decoder/Thread: on late SEEK, start decoder at seek position
Previously, a bogus value (whatever happened to be still in
`start_time`) was used.
2019-08-20 20:15:08 +02:00
Max Kellermann
ca450663d0 decoder/Control: work around crash after SEEK was too late
See code comment.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/629
2019-08-20 20:01:53 +02:00
Max Kellermann
f3d16f6d1b output/Thread: fix typo in comment 2019-08-13 13:08:40 +02:00
Max Kellermann
4464cdcc67 doc/protocol.rst: add missing newline to "albumart" example
This was missing in commit 0f488dcecf
2019-08-12 20:20:17 +02:00
Fredrik Noring
2d61e526de decoder/sidplay: Fix date field to have year but not company or author
Field 2 is called <released>, formerly used as <copyright>[1][2]. It is
formatted <year><space><company or author or group>, where <year> may be
<YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for example "1987", "199?", "19??"
or "1985-87". The <company or author or group> may be for example Rob
Hubbard. A full field may be for example "1987 Rob Hubbard".

This change splits the <released> field at the first <space>, to retain
the <year> part.

The 51823 SID files in High Voltage SID Collection (HVSC) version 71
have the following distribution of dates:

    333 19??         11 1990-92       6 1995-99       2 2006-08
    827 198?         88 1990-93    2140 1996        530 2007
     32 1982         69 1990-94       9 1996-97      15 2007-08
      1 1982-83      49 1990-95       2 1996-98       2 2007-09
    255 1983       3467 1991          5 1996-99       1 2007-10
    677 1984         75 1991-92    1840 1997        430 2008
    775 1985         65 1991-93       4 1997-98      23 2008-09
      3 1985-86      10 1991-94    1276 1998          1 2008-12
     10 1985-87      35 1991-97       4 1998-99     631 2009
    943 1986       3320 1992        865 1999          1 2009-10
     12 1986-87      26 1992-93      24 200?        645 2010
      5 1986-89      59 1992-94     590 2000          1 2010-12
   2083 1987          1 1992-96       4 2000-01     538 2011
     31 1987-88    2996 1993        727 2001          1 2011-12
     44 1987-89      42 1993-94     875 2002        651 2012
   2510 1988         12 1993-95       2 2002-04     811 2013
    129 1988-89       2 1993-97     844 2003        790 2014
     91 1988-90    2737 1994          3 2003-05     740 2015
     58 1988-91      16 1994-95     842 2004        792 2016
   3466 1989         20 1994-96       2 2004-05     775 2017
     95 1989-90      17 1994-97     707 2005        638 2018
    150 1989-91    2271 1995          1 2005-06     284 2019
   1077 199?          2 1995-96       2 2005-07
   2834 1990          4 1995-97     785 2006
    119 1990-91       2 1995-98       6 2006-07

References:

[1] https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt
[2] https://hvsc.c64.org/info
2019-08-10 10:50:51 +02:00
Fredrik Noring
7723c481db decoder/sidplay: Fix windows-1252 to utf-8 string conversion
High Voltage SID Collection (HVSC) metadata fields are encoded in
windows-1252, as described in DOCUMENTS/SID_file_format.txt:

https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt

If utf-8 transcoding fails, or the ICU library is unavailable, fall
back to plain ASCII and replace other characters with '?'.
2019-08-10 10:45:02 +02:00
Fredrik Noring
0ed10542cc decoder/sidplay: Fix song length initialisation during container scan
The song length was previously undetermined.
2019-08-09 15:39:36 +02:00
Max Kellermann
ab830f9afd increment version number to 0.21.14 2019-08-09 15:38:01 +02:00
123 changed files with 1580 additions and 470 deletions
.travis.ymlNEWS
android
doc
meson.build
python/build
src
LocateUri.cxxMain.cxxPlaylistDatabase.cxxReplayGainInfo.hxxSongPrint.cxxSongSave.cxxSongUpdate.cxxStats.cxxTimePrint.cxx
archive
client
command
config
db
decoder
event
fs
input
lib
neighbor
net
output
pcm
player
playlist
protocol
queue
song
storage
system
tag
time
util
zeroconf
test

@@ -1,7 +1,73 @@
language: cpp
matrix:
jobs:
include:
# Ubuntu Bionic (18.04) with GCC 7
- os: linux
dist: bionic
addons:
apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- libgtest-dev
- libboost-dev
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Bionic (18.04) with GCC 7 on big-endian
- os: linux
arch: s390x
dist: bionic
addons:
apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- libgtest-dev
- libboost-dev
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Bionic (18.04) with GCC 7 on ARM64
- os: linux
arch: arm64
dist: bionic
addons:
apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- libgtest-dev
- libboost-dev
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Trusty (16.04) with GCC 6
- os: linux
dist: trusty
addons:
@@ -20,13 +86,14 @@ matrix:
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
- MATRIX_EVAL="export CC=gcc-6 CXX=g++-6 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Trusty (16.04) with GCC 8
- os: linux
dist: trusty
addons:
@@ -45,31 +112,44 @@ matrix:
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
- MATRIX_EVAL="export CC=gcc-8 CXX=g++-8 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
- os: osx
osx_image: xcode9.3beta
osx_image: xcode9.4
addons:
homebrew:
packages:
- ccache
- meson
update: true
env:
- MATRIX_EVAL=""
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
cache:
- apt
- ccache
apt: true
ccache: true
directories:
- $HOME/Library/Caches/Homebrew
before_cache:
- test "$TRAVIS_OS_NAME" != "osx" || brew cleanup
before_install:
- eval "${MATRIX_EVAL}"
# C++14
- test "$TRAVIS_OS_NAME" != "osx" || brew update
install:
# C++14
- test "$TRAVIS_OS_NAME" != "osx" || brew install ccache meson
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
# Work around "Target /usr/local/lib/libgtest.a is a symlink
# belonging to nss. You can unlink it" during gtest install
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
- test "$TRAVIS_OS_NAME" != "osx" || brew install https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
before_script:
- ccache -s

78
NEWS

@@ -1,3 +1,81 @@
ver 0.21.21 (2020/03/19)
* configuration
- fix bug in "metadata_to_use" setting
* playlist
- asx, xspf: fix corrupt tags in the presence of XML entities
* archive
- iso9660: skip empty file names to work around libcdio bug
* decoder
- gme: ignore empty tags
* output
- solaris: port to NetBSD
* raise default "max_connections" value to 100
ver 0.21.20 (2020/02/16)
* decoder
- audiofile, ffmpeg, sndfile: handle MIME type "audio/wav"
- ffmpeg: fix playback of AIFF and TTA
- vorbis, opus: fix seeking in small files
* fix backwards seeking on ARM (and other non-x86 CPUs)
ver 0.21.19 (2020/01/17)
* configuration
- allow overriding top-level settings in includes
* output
- pulse: obey Pulse's maximum sample rate (fixes DSD128 playback)
* fix build failure with clang 10
* fix build failure with Android NDK r20
ver 0.21.18 (2019/12/24)
* protocol
- work around Mac OS X bug in the ISO 8601 parser
* output
- alsa: fix hang bug with ALSA "null" outputs
* storage
- curl: fix crash bug
* drop support for CURL versions older than 7.32.0
* reduce unnecessary CPU wakeups
ver 0.21.17 (2019/12/16)
* protocol
- relax the ISO 8601 parser: allow omitting field separators, the
time of day and the "Z" suffix
* archive
- zzip: improve error reporting
* outputs
- jack: mark ports as terminal
- shout: declare metadata as UTF-8
* fix build failure with -Ddatabase=false
ver 0.21.16 (2019/10/16)
* queue
- fix relative destination offset when moving a range
* storage
- curl: request the "resourcetype" property to fix database update
- curl: URL-encode more paths
- curl: follow redirects for collections without trailing slash
* update
- fix crash when music_directory is not a directory
* fix build with iconv() instead of ICU
ver 0.21.15 (2019/09/25)
* decoder
- dsdiff, dsf: fix displayed bit rate
- mpcdec: fix bogus ReplayGain values
* output
- solaris: fix build with glibc 2.30
ver 0.21.14 (2019/08/21)
* decoder
- sidplay: show track durations in database
- sidplay: convert tag values from Windows-1252 charset
- sidplay: strip text from "Date" tag
* player
- fix crash after song change
- fix seek position after restarting the decoder
* protocol
- include command name in error responses
ver 0.21.13 (2019/08/06)
* input
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1

@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="36"
android:versionName="0.21.13">
android:versionCode="44"
android:versionName="0.21.21">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

@@ -25,16 +25,15 @@ android_abis = {
'arch': 'arm-linux-androideabi',
'ndk_arch': 'arm',
'toolchain_arch': 'arm-linux-androideabi',
'llvm_triple': 'armv7-none-linux-androideabi',
'llvm_triple': 'armv7-linux-androideabi',
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
},
'arm64-v8a': {
'android_api_level': '21',
'arch': 'aarch64-linux-android',
'ndk_arch': 'arm64',
'toolchain_arch': 'aarch64-linux-android',
'llvm_triple': 'aarch64-none-linux-android',
'llvm_triple': 'aarch64-linux-android',
'cflags': '',
},
@@ -42,9 +41,17 @@ android_abis = {
'arch': 'i686-linux-android',
'ndk_arch': 'x86',
'toolchain_arch': 'x86',
'llvm_triple': 'i686-none-linux-android',
'llvm_triple': 'i686-linux-android',
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
'x86_64': {
'arch': 'x86_64-linux-android',
'ndk_arch': 'x86_64',
'toolchain_arch': 'x86_64',
'llvm_triple': 'x86_64-linux-android',
'cflags': '-m64',
},
}
# select the NDK target
@@ -76,24 +83,18 @@ class AndroidNdkToolchain:
ndk_arch = abi_info['ndk_arch']
android_api_level = '21'
ndk_platform = 'android-' + android_api_level
# select the NDK compiler
gcc_version = '4.9'
ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
sysroot = os.path.join(ndk_path, 'sysroot')
target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
install_prefix = os.path.join(arch_path, 'root')
self.arch = arch
self.install_prefix = install_prefix
self.sysroot = sysroot
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = abi_info['llvm_triple']
llvm_triple = abi_info['llvm_triple'] + android_api_level
common_flags = '-Os -g'
common_flags += ' -fPIC'
@@ -107,6 +108,9 @@ class AndroidNdkToolchain:
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
# required flags from https://android.googlesource.com/platform/ndk/+/ndk-release-r20/docs/BuildSystemMaintainers.md#additional-required-arguments
common_flags += ' -fno-addrsig'
self.ar = os.path.join(toolchain_bin, arch + '-ar')
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
self.nm = os.path.join(toolchain_bin, arch + '-nm')
@@ -114,15 +118,11 @@ class AndroidNdkToolchain:
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = '--sysroot=' + sysroot + \
' -isystem ' + os.path.join(install_prefix, 'include') + \
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
' -D__ANDROID_API__=' + android_api_level
self.ldflags = '--sysroot=' + sysroot + \
' -L' + os.path.join(install_prefix, 'lib') + \
' -L' + os.path.join(target_root, 'usr', 'lib') + \
' -B' + os.path.join(target_root, 'usr', 'lib') + \
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
' -Wl,--exclude-libs=ALL' + \
' ' + common_flags
self.ldflags = common_flags
self.libs = ''
self.is_arm = ndk_arch == 'arm'
@@ -130,13 +130,10 @@ class AndroidNdkToolchain:
self.is_aarch64 = ndk_arch == 'arm64'
self.is_windows = False
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
libstdcxx_flags = ''
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
libstdcxx_libs = '-lc++_static -lc++abi'
libstdcxx_cxxflags = ''
libstdcxx_ldflags = ''
libstdcxx_libs = '-static-libstdc++'
if self.is_armv7:
# On 32 bit ARM, clang generates no ".eh_frame" section;

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

@@ -42,7 +42,7 @@ Provides access to the database of another :program:`MPD` instance using libmpdc
* - **password**
- The password used to log in to the "master" :program:`MPD` instance.
* - **keepalive yes|no**
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expensive of a very small amount of additional network traffic. Disabled by default.
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expense of a very small amount of additional network traffic. Disabled by default.
upnp
----
@@ -1069,7 +1069,7 @@ Filter plugins
normalize
---------
Normalize the volume during playback (at the expensve of quality).
Normalize the volume during playback (at the expense of quality).
null

@@ -824,7 +824,8 @@ The music database
albumart
size: 1024768
binary: 8192
<8192 bytes>OK
<8192 bytes>
OK
:command:`count {FILTER} [group {GROUPTYPE}]`
Count the number of songs and their total playtime in

@@ -62,16 +62,16 @@ In any case, you need:
Each plugin usually needs a codec library, which you also need to
install. Check the :doc:`plugins` for details about required libraries
For example, the following installs a fairly complete list of build dependencies on Debian Jessie:
For example, the following installs a fairly complete list of build dependencies on Debian Buster:
.. code-block:: none
apt install g++ \
apt install meson g++ \
libpcre3-dev \
libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev \
libflac-dev libvorbis-dev libopus-dev libogg-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
libfluidsynth-dev libgme-dev libmikmod2-dev libmodplug-dev \
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
libmpcdec-dev libwavpack-dev libwildmidi-dev \
libsidplay2-dev libsidutils-dev libresid-builder-dev \
libavcodec-dev libavformat-dev \
@@ -91,7 +91,9 @@ For example, the following installs a fairly complete list of build dependencies
libsystemd-dev \
libgtest-dev \
libboost-dev \
libicu-dev
libicu-dev \
libchromaprint-dev \
libgcrypt20-dev
Now configure the source tree:
@@ -693,7 +695,7 @@ These settings are various limitations to prevent :program:`MPD` from using too
* - **connection_timeout SECONDS**
- If a client does not send any new data in this time period, the connection is closed. Clients waiting in "idle" mode are excluded from this. Default is 60.
* - **max_connections NUMBER**
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 5.
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 100.
* - **max_playlist_length NUMBER**
- The maximum number of songs that can be in the playlist. Default is 16384.
* - **max_command_list_size KBYTES**

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.13',
version: '0.21.21',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',
@@ -88,6 +88,10 @@ test_ldflags = [
]
if get_option('buildtype') != 'debug'
test_cxxflags += [
'-ffunction-sections',
'-fdata-sections',
]
test_cflags += [
'-ffunction-sections',
'-fdata-sections',
@@ -304,6 +308,7 @@ if enable_database
endif
subdir('src/util')
subdir('src/time')
subdir('src/system')
subdir('src/thread')
subdir('src/event')
@@ -385,8 +390,11 @@ endif
if archive_glue_dep.found()
sources += [
'src/TagArchive.cxx',
'src/db/update/Archive.cxx',
]
if enable_database
sources += ['src/db/update/Archive.cxx']
endif
endif
if is_windows

@@ -9,14 +9,14 @@ from build.ffmpeg import FfmpegProject
from build.boost import BoostProject
libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.16.tar.xz',
'fa6bdab67c0e0490302b38f00c27b4959735c3ec8aef7a88327adb1407654464',
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.17.tar.xz',
'ee9b8f1c7e95b65c8f18a354daf7b16bfcd455fc52a0f3b5abe402316bce3559',
'lib/libmpdclient.a',
)
libogg = AutotoolsProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
'lib/libogg.a',
[
'--disable-shared', '--enable-static',
@@ -38,8 +38,8 @@ libvorbis = AutotoolsProject(
)
opus = AutotoolsProject(
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
'lib/libopus.a',
[
'--disable-shared', '--enable-static',
@@ -52,8 +52,8 @@ opus = AutotoolsProject(
)
flac = AutotoolsProject(
'http://downloads.xiph.org/releases/flac/flac-1.3.2.tar.xz',
'91cfc3ed61dc40f47f050a109b08610667d73477af6ef36dcad31c31a4a8d53f',
'http://downloads.xiph.org/releases/flac/flac-1.3.3.tar.xz',
'213e82bd716c9de6db2f98bcadbc4c24c7e2efe8c75939a1a84e28539c4e1748',
'lib/libFLAC.a',
[
'--disable-shared', '--enable-static',
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
'http://ffmpeg.org/releases/ffmpeg-4.2.2.tar.xz',
'cb754255ab0ee2ea5f66f8850e1bd6ad5cac1cd855d0a2f4990fb8c668b0d29c',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
'http://curl.haxx.se/download/curl-7.68.0.tar.xz',
'b724240722276a27f6e770b952121a3afd097129d8c9fe18e6272dc34192035a',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -358,6 +358,11 @@ curl = AutotoolsProject(
'--disable-manual',
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
'--disable-doh',
'--disable-mime',
'--disable-netrc',
'--disable-progress-meter',
'--disable-alt-svc',
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
],
@@ -365,8 +370,8 @@ curl = AutotoolsProject(
)
libexpat = AutotoolsProject(
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
'https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.bz2',
'f1063084dc4302a427dabcca499c8312b3a32a29b7d2506653ecc8f950a9a237',
'lib/libexpat.a',
[
'--disable-shared', '--enable-static',
@@ -392,7 +397,7 @@ libnfs = AutotoolsProject(
)
boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
'https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2',
'59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722',
'include/boost/version.hpp',
)

@@ -29,6 +29,8 @@
#include "storage/StorageInterface.hxx"
#endif
#include <stdexcept>
static LocatedUri
LocateFileUri(const char *uri, const Client *client
#ifdef ENABLE_DATABASE

@@ -460,7 +460,7 @@ MainOrThrow(int argc, char *argv[])
#endif
const unsigned max_clients =
raw_config.GetPositive(ConfigOption::MAX_CONN, 10);
raw_config.GetPositive(ConfigOption::MAX_CONN, 100);
instance->client_list = new ClientList(max_clients);
initialize_decoder_and_player(raw_config, config.replay_gain);

@@ -21,8 +21,8 @@
#include "db/PlaylistVector.hxx"
#include "fs/io/TextFile.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringStrip.hxx"
#include "util/ChronoUtil.hxx"
#include "util/RuntimeError.hxx"
#include <string.h>

@@ -38,6 +38,10 @@ struct ReplayGainTuple {
return gain > -100;
}
static constexpr ReplayGainTuple Undefined() noexcept {
return {-200.0f, 0.0f};
}
gcc_pure
float CalculateScale(const ReplayGainConfig &config) const noexcept;
};
@@ -49,6 +53,13 @@ struct ReplayGainInfo {
return track.IsDefined() || album.IsDefined();
}
static constexpr ReplayGainInfo Undefined() noexcept {
return {
ReplayGainTuple::Undefined(),
ReplayGainTuple::Undefined(),
};
}
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
return mode == ReplayGainMode::ALBUM
? (album.IsDefined() ? album : track)

@@ -27,7 +27,7 @@
#include "TagPrint.hxx"
#include "client/Response.hxx"
#include "fs/Traits.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#define SONG_FILE "file: "

@@ -27,7 +27,7 @@
#include "tag/ParseName.hxx"
#include "tag/Tag.hxx"
#include "tag/Builder.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringAPI.hxx"
#include "util/StringBuffer.hxx"
#include "util/StringStrip.hxx"

@@ -98,8 +98,6 @@ Song::UpdateFile(Storage &storage) noexcept
return true;
}
#endif
#ifdef ENABLE_ARCHIVE
Song *
@@ -145,6 +143,8 @@ Song::UpdateFileInArchive(ArchiveFile &archive) noexcept
#endif
#endif /* ENABLE_DATABASE */
bool
DetachedSong::LoadFile(Path path) noexcept
{

@@ -28,7 +28,7 @@
#include "db/Stats.hxx"
#include "system/Clock.hxx"
#include "Log.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include <chrono>
#include <cmath>

@@ -19,7 +19,7 @@
#include "TimePrint.hxx"
#include "client/Response.hxx"
#include "util/TimeISO8601.hxx"
#include "time/ISO8601.hxx"
void
time_print(Response &r, const char *name,

@@ -28,6 +28,7 @@
#include "input/InputStream.hxx"
#include "fs/Path.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
#include <cdio/iso9660.h>
@@ -93,7 +94,10 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
auto *statbuf = (iso9660_stat_t *)
_cdio_list_node_data(entnode);
const char *filename = statbuf->filename;
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
if (StringIsEmpty(filename) ||
PathTraitsUTF8::IsSpecialFilename(filename))
/* skip empty names (libcdio bug?) */
/* skip special names like "." and ".." */
continue;
size_t filename_length = strlen(filename);

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,7 @@
#include "../ArchiveVisitor.hxx"
#include "input/InputStream.hxx"
#include "fs/Path.hxx"
#include "system/Error.hxx"
#include "util/RuntimeError.hxx"
#include <zzip/zzip.h>
@@ -120,9 +121,19 @@ ZzipArchiveFile::OpenStream(const char *pathname,
Mutex &mutex)
{
ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0);
if (_file == nullptr)
throw FormatRuntimeError("not found in the ZIP file: %s",
pathname);
if (_file == nullptr) {
const auto error = (zzip_error_t)zzip_error(dir->dir);
switch (error) {
case ZZIP_ENOENT:
throw FormatFileNotFound("Failed to open '%s' in ZIP file",
pathname);
default:
throw FormatRuntimeError("Failed to open '%s' in ZIP file: %s",
pathname,
zzip_strerror(error));
}
}
return std::make_unique<ZzipInputStream>(dir, pathname,
mutex,

@@ -35,6 +35,6 @@ extern size_t client_max_command_list_size;
extern size_t client_max_output_buffer_size;
CommandResult
client_process_line(Client &client, char *line);
client_process_line(Client &client, char *line) noexcept;
#endif

@@ -30,7 +30,7 @@
static CommandResult
client_process_command_list(Client &client, bool list_ok,
std::list<std::string> &&list)
std::list<std::string> &&list) noexcept
{
CommandResult ret = CommandResult::OK;
unsigned num = 0;
@@ -51,7 +51,7 @@ client_process_command_list(Client &client, bool list_ok,
}
CommandResult
client_process_line(Client &client, char *line)
client_process_line(Client &client, char *line) noexcept
{
CommandResult ret;

@@ -206,9 +206,10 @@ static constexpr struct command commands[] = {
static constexpr unsigned num_commands = ARRAY_SIZE(commands);
gcc_pure
static bool
command_available(gcc_unused const Partition &partition,
gcc_unused const struct command *cmd)
gcc_unused const struct command *cmd) noexcept
{
#ifdef ENABLE_SQLITE
if (StringIsEqual(cmd->cmd, "sticker"))
@@ -235,7 +236,7 @@ command_available(gcc_unused const Partition &partition,
static CommandResult
PrintAvailableCommands(Response &r, const Partition &partition,
unsigned permission)
unsigned permission) noexcept
{
for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i];
@@ -249,7 +250,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
}
static CommandResult
PrintUnavailableCommands(Response &r, unsigned permission)
PrintUnavailableCommands(Response &r, unsigned permission) noexcept
{
for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i];
@@ -276,7 +277,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
}
void
command_init()
command_init() noexcept
{
#ifndef NDEBUG
/* ensure that the command list is sorted */
@@ -285,8 +286,9 @@ command_init()
#endif
}
gcc_pure
static const struct command *
command_lookup(const char *name)
command_lookup(const char *name) noexcept
{
unsigned a = 0, b = num_commands, i;
@@ -308,7 +310,7 @@ command_lookup(const char *name)
static bool
command_check_request(const struct command *cmd, Response &r,
unsigned permission, Request args)
unsigned permission, Request args) noexcept
{
if (cmd->permission != (permission & cmd->permission)) {
r.FormatError(ACK_ERROR_PERMISSION,
@@ -342,7 +344,7 @@ command_check_request(const struct command *cmd, Response &r,
static const struct command *
command_checked_lookup(Response &r, unsigned permission,
const char *cmd_name, Request args)
const char *cmd_name, Request args) noexcept
{
const struct command *cmd = command_lookup(cmd_name);
if (cmd == nullptr) {
@@ -360,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
}
CommandResult
command_process(Client &client, unsigned num, char *line)
try {
command_process(Client &client, unsigned num, char *line) noexcept
{
Response r(client, num);
/* get the command name (first word on the line) */
@@ -389,34 +391,33 @@ try {
char *argv[COMMAND_ARGV_MAX];
Request args(argv, 0);
/* now parse the arguments (quoted or unquoted) */
try {
/* now parse the arguments (quoted or unquoted) */
while (true) {
if (args.size == COMMAND_ARGV_MAX) {
r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
while (true) {
if (args.size == COMMAND_ARGV_MAX) {
r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
}
char *a = tokenizer.NextParam();
if (a == nullptr)
break;
argv[args.size++] = a;
}
char *a = tokenizer.NextParam();
if (a == nullptr)
break;
/* look up and invoke the command handler */
argv[args.size++] = a;
const struct command *cmd =
command_checked_lookup(r, client.GetPermission(),
cmd_name, args);
if (cmd == nullptr)
return CommandResult::ERROR;
return cmd->handler(client, args, r);
} catch (...) {
PrintError(r, std::current_exception());
return CommandResult::ERROR;
}
/* look up and invoke the command handler */
const struct command *cmd =
command_checked_lookup(r, client.GetPermission(),
cmd_name, args);
CommandResult ret = cmd
? cmd->handler(client, args, r)
: CommandResult::ERROR;
return ret;
} catch (const std::exception &e) {
Response r(client, num);
PrintError(r, std::current_exception());
return CommandResult::ERROR;
}

@@ -25,12 +25,9 @@
class Client;
void
command_init();
void
command_finish();
command_init() noexcept;
CommandResult
command_process(Client &client, unsigned num, char *line);
command_process(Client &client, unsigned num, char *line) noexcept;
#endif

@@ -35,7 +35,7 @@
#include "decoder/DecoderPrint.hxx"
#include "ls.hxx"
#include "mixer/Volume.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/StringAPI.hxx"
#include "fs/AllocatedPath.hxx"

@@ -37,9 +37,9 @@
#include "client/Response.hxx"
#include "Mapper.hxx"
#include "fs/AllocatedPath.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/ChronoUtil.hxx"
#include "LocateUri.hxx"
bool

@@ -23,8 +23,8 @@
#include "StorageCommands.hxx"
#include "Request.hxx"
#include "CommandError.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/ChronoUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "fs/Traits.hxx"
#include "client/Client.hxx"

@@ -153,11 +153,9 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
name, reader.GetLineNumber());
if (!option.repeatable)
if (const auto *param = config_data.GetParam(o))
throw FormatRuntimeError("config parameter \"%s\" is first defined "
"on line %d and redefined on line %u\n",
name, param->line,
reader.GetLineNumber());
/* if the option is not repeatable, override the old
value by removing it first */
config_data.GetParamList(o).clear();
/* now parse the block or the value */

@@ -34,7 +34,7 @@
#include "PlaylistInfo.hxx"
#include "Interface.hxx"
#include "fs/Traits.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/RecursiveMap.hxx"
#include <functional>

@@ -25,7 +25,7 @@
#include "PlaylistDatabase.hxx"
#include "fs/io/TextFile.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringCompare.hxx"
#include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx"

@@ -24,6 +24,7 @@
#include "storage/StorageInterface.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileInfo.hxx"
#include "fs/Traits.hxx"
#include "Log.hxx"
#include <string>
@@ -146,8 +147,7 @@ WatchDirectory::GetUriFS() const noexcept
/* we don't look at "." / ".." nor files with newlines in their name */
static bool skip_path(const char *path)
{
return (path[0] == '.' && path[1] == 0) ||
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
return PathTraitsFS::IsSpecialFilename(path) ||
strchr(path, '\n') != nullptr;
}

@@ -237,7 +237,7 @@ try {
LogError(std::current_exception());
}
/* we don't look at "." / ".." nor files with newlines in their name */
/* we don't look at files with newlines in their name */
gcc_pure
static bool
skip_path(const char *name_utf8) noexcept
@@ -493,6 +493,12 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
if (!GetInfo(storage, "", info))
return false;
if (!info.IsDirectory()) {
FormatError(update_domain, "Not a directory: %s",
storage.MapUTF8("").c_str());
return false;
}
ExcludeList exclude_list;
UpdateDirectory(root, exclude_list, info);

@@ -147,6 +147,18 @@ DecoderControl::Seek(SongTime t)
seek_error = false;
SynchronousCommandLocked(DecoderCommand::SEEK);
while (state == DecoderState::START)
/* If the decoder falls back to DecoderState::START,
this means that our SEEK command arrived too late,
and the decoder had meanwhile finished decoding and
went idle. Our SEEK command is finished, but that
means only that the decoder thread has launched the
decoder. To work around illegal states, we wait
until the decoder plugin has become ready. This is
a kludge, built on top of the "late seek" kludge.
Not exactly elegant, sorry. */
WaitForDecoder();
if (seek_error)
throw std::runtime_error("Decoder failed to seek");
}

@@ -455,6 +455,11 @@ static void
decoder_run_song(DecoderControl &dc,
const DetachedSong &song, const char *uri, Path path_fs)
{
if (dc.command == DecoderCommand::SEEK)
/* if the SEEK command arrived too late, start the
decoder at the seek position */
dc.start_time = dc.seek_time;
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
/* pass the song tag only if it's
authoritative, i.e. if it's a local

@@ -269,6 +269,8 @@ static const char *const audiofile_suffixes[] = {
};
static const char *const audiofile_mime_types[] = {
"audio/wav",
"audio/aiff",
"audio/x-wav",
"audio/x-aiff",
nullptr

@@ -362,6 +362,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
unsigned channels, unsigned sample_rate,
const offset_type total_bytes)
{
const unsigned kbit_rate = channels * sample_rate / 1000;
const offset_type start_offset = is.GetOffset();
uint8_t buffer[8192];
@@ -408,7 +409,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
bit_reverse_buffer(buffer, buffer + nbytes);
cmd = client.SubmitData(is, buffer, nbytes,
sample_rate / 1000);
kbit_rate);
}
return true;

@@ -256,6 +256,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
offset_type n_blocks,
bool bitreverse)
{
const unsigned kbit_rate = channels * sample_rate / 1000;
const size_t block_size = channels * DSF_BLOCK_SIZE;
const offset_type start_offset = is.GetOffset();
@@ -291,7 +292,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
cmd = client.SubmitData(is,
interleaved_buffer, block_size,
sample_rate / 1000);
kbit_rate);
++i;
}

@@ -762,7 +762,7 @@ static const char *const ffmpeg_mime_types[] = {
"audio/aac",
"audio/aacp",
"audio/ac3",
"audio/aiff"
"audio/aiff",
"audio/amr",
"audio/basic",
"audio/flac",
@@ -775,12 +775,13 @@ static const char *const ffmpeg_mime_types[] = {
"audio/qcelp",
"audio/vorbis",
"audio/vorbis+ogg",
"audio/wav",
"audio/x-8svx",
"audio/x-16sv",
"audio/x-aac",
"audio/x-ac3",
"audio/x-adx",
"audio/x-aiff"
"audio/x-aiff",
"audio/x-alaw",
"audio/x-au",
"audio/x-dca",
@@ -800,7 +801,7 @@ static const char *const ffmpeg_mime_types[] = {
"audio/x-pn-realaudio",
"audio/x-pn-multirate-realaudio",
"audio/x-speex",
"audio/x-tta"
"audio/x-tta",
"audio/x-voc",
"audio/x-wav",
"audio/x-wma",

@@ -28,6 +28,7 @@
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringCompare.hxx"
#include "util/StringFormat.hxx"
#include "util/UriUtil.hxx"
#include "util/Domain.hxx"
@@ -222,7 +223,7 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
if (track_count > 1)
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1));
if (info.song != nullptr) {
if (!StringIsEmpty(info.song)) {
if (track_count > 1) {
/* start numbering subtunes from 1 */
const auto tag_title =
@@ -234,16 +235,16 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
handler.OnTag(TAG_TITLE, info.song);
}
if (info.author != nullptr)
if (!StringIsEmpty(info.author))
handler.OnTag(TAG_ARTIST, info.author);
if (info.game != nullptr)
if (!StringIsEmpty(info.game))
handler.OnTag(TAG_ALBUM, info.game);
if (info.comment != nullptr)
if (!StringIsEmpty(info.comment))
handler.OnTag(TAG_COMMENT, info.comment);
if (info.copyright != nullptr)
if (!StringIsEmpty(info.copyright))
handler.OnTag(TAG_DATE, info.copyright);
}

@@ -719,6 +719,11 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
{
struct xing xing;
#if GCC_CHECK_VERSION(10,0)
/* work around bogus -Wuninitialized in GCC 10 */
xing.frames = 0;
#endif
while (true) {
MadDecoderAction ret;
do {
@@ -788,7 +793,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
if (max_frames > 8 * 1024 * 1024) {
FormatWarning(mad_domain,
"mp3 file header indicates too many frames: %lu",
"mp3 file header indicates too many frames: %zu",
max_frames);
return false;
}

@@ -137,6 +137,28 @@ mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
*dest++ = mpc_to_mpd_sample(*src++);
}
static constexpr ReplayGainTuple
ImportMpcdecReplayGain(mpc_uint16_t gain, mpc_uint16_t peak) noexcept
{
auto t = ReplayGainTuple::Undefined();
if (gain != 0 && peak != 0) {
t.gain = MPC_OLD_GAIN_REF - (gain / 256.);
t.peak = pow(10, peak / 256. / 20) / 32767;
}
return t;
}
static constexpr ReplayGainInfo
ImportMpcdecReplayGain(const mpc_streaminfo &info) noexcept
{
auto rgi = ReplayGainInfo::Undefined();
rgi.album = ImportMpcdecReplayGain(info.gain_album, info.peak_album);
rgi.track = ImportMpcdecReplayGain(info.gain_title, info.peak_title);
return rgi;
}
static void
mpcdec_decode(DecoderClient &client, InputStream &is)
{
@@ -167,14 +189,11 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
mpcdec_sample_format,
info.channels);
ReplayGainInfo rgi;
rgi.Clear();
rgi.album.gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
rgi.album.peak = pow(10, info.peak_album / 256. / 20) / 32767;
rgi.track.gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
rgi.track.peak = pow(10, info.peak_title / 256. / 20) / 32767;
client.SubmitReplayGain(&rgi);
{
const auto rgi = ImportMpcdecReplayGain(info);
if (rgi.IsDefined())
client.SubmitReplayGain(&rgi);
}
client.Ready(audio_format, is.IsSeekable(),
SongTime::FromS(mpc_streaminfo_get_length(&info)));

@@ -47,8 +47,12 @@ OggDecoder::LoadEndPacket(ogg_packet &packet) const
DecoderReader reader(client, input_stream);
OggSyncState sync2(reader);
OggStreamState stream2(GetSerialNo());
/* passing synced=false because we're inside an
OggVisitor callback, and our InputStream may be in
the middle of an Ogg packet */
result = OggSeekFindEOS(sync2, stream2, packet,
input_stream);
input_stream, false);
}
/* restore the previous file position */

@@ -25,6 +25,7 @@
#include "song/DetachedSong.hxx"
#include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx"
#include "lib/icu/Converter.hxx"
#ifdef HAVE_SIDPLAYFP
#include "fs/io/FileReader.hxx"
#include "util/RuntimeError.hxx"
@@ -32,6 +33,8 @@
#include "util/Macros.hxx"
#include "util/StringFormat.hxx"
#include "util/Domain.hxx"
#include "util/AllocatedString.hxx"
#include "util/CharUtil.hxx"
#include "system/ByteOrder.hxx"
#include "Log.hxx"
@@ -432,19 +435,70 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
} while (cmd != DecoderCommand::STOP);
}
static AllocatedString<char>
Windows1252ToUTF8(const char *s) noexcept
{
#ifdef HAVE_ICU_CONVERTER
try {
std::unique_ptr<IcuConverter>
converter(IcuConverter::Create("windows-1252"));
return converter->ToUTF8(s);
} catch (...) { }
#endif
/*
* Fallback to not transcoding windows-1252 to utf-8, that may result
* in invalid utf-8 unless nonprintable characters are replaced.
*/
auto t = AllocatedString<char>::Duplicate(s);
for (size_t i = 0; t[i] != AllocatedString<char>::SENTINEL; i++)
if (!IsPrintableASCII(t[i]))
t[i] = '?';
return t;
}
gcc_pure
static const char *
static AllocatedString<char>
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
{
#ifdef HAVE_SIDPLAYFP
return info.numberOfInfoStrings() > i
const char *s = info.numberOfInfoStrings() > i
? info.infoString(i)
: nullptr;
: "";
#else
return info.numberOfInfoStrings > i
const char *s = info.numberOfInfoStrings > i
? info.infoString[i]
: nullptr;
: "";
#endif
return Windows1252ToUTF8(s);
}
gcc_pure
static AllocatedString<char>
GetDateString(const SidTuneInfo &info) noexcept
{
/*
* Field 2 is called <released>, previously used as <copyright>.
* It is formatted <year><space><company or author or group>,
* where <year> may be <YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for
* example "1987", "199?", "19??" or "1985-87". The <company or
* author or group> may be for example Rob Hubbard. A full field
* may be for example "1987 Rob Hubbard".
*/
AllocatedString<char> release = GetInfoString(info, 2);
/* Keep the <year> part only for the date. */
for (size_t i = 0; release[i] != AllocatedString<char>::SENTINEL; i++)
if (std::isspace(release[i])) {
release[i] = AllocatedString<char>::SENTINEL;
break;
}
return release;
}
static void
@@ -452,27 +506,25 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
TagHandler &handler) noexcept
{
/* title */
const char *title = GetInfoString(info, 0);
if (title == nullptr)
title = "";
const auto title = GetInfoString(info, 0);
if (n_tracks > 1) {
const auto tag_title =
StringFormat<1024>("%s (%u/%u)",
title, track, n_tracks);
handler.OnTag(TAG_TITLE, tag_title);
title.c_str(), track, n_tracks);
handler.OnTag(TAG_TITLE, tag_title.c_str());
} else
handler.OnTag(TAG_TITLE, title);
handler.OnTag(TAG_TITLE, title.c_str());
/* artist */
const char *artist = GetInfoString(info, 1);
if (artist != nullptr)
handler.OnTag(TAG_ARTIST, artist);
const auto artist = GetInfoString(info, 1);
if (!artist.empty())
handler.OnTag(TAG_ARTIST, artist.c_str());
/* date */
const char *date = GetInfoString(info, 2);
if (date != nullptr)
handler.OnTag(TAG_DATE, date);
const auto date = GetDateString(info);
if (!date.empty())
handler.OnTag(TAG_DATE, date.c_str());
/* track */
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track));
@@ -547,6 +599,10 @@ sidplay_container_scan(Path path_fs)
AddTagHandler h(tag_builder);
ScanSidTuneInfo(info, i, n_tracks, h);
const SignedSongTime duration = get_song_length(tune);
if (!duration.IsNegative())
h.OnDuration(SongTime(duration));
char track_name[32];
/* Construct container/tune path names, eg.
Delta.sid/tune_001.sid */

@@ -322,6 +322,8 @@ static const char *const sndfile_suffixes[] = {
};
static const char *const sndfile_mime_types[] = {
"audio/wav",
"audio/aiff",
"audio/x-wav",
"audio/x-aiff",

@@ -137,7 +137,8 @@ static constexpr int
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
{
return timeout >= timeout.zero()
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count())
/* round up (+1) to avoid unnecessary wakeups */
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()) + 1
: -1;
}
@@ -220,7 +221,6 @@ EventLoop::Run() noexcept
} while (!quit);
#ifndef NDEBUG
assert(busy);
assert(thread.IsInside());
#endif
}

@@ -22,6 +22,10 @@
#include <algorithm>
#ifdef USE_EPOLL
#include <errno.h>
#endif
#ifndef _WIN32
#include <poll.h>
#endif
@@ -37,17 +41,42 @@ MultiSocketMonitor::Reset() noexcept
assert(GetEventLoop().IsInside());
fds.clear();
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
IdleMonitor::Cancel();
timeout_event.Cancel();
ready = refresh = false;
}
bool
MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept
{
fds.emplace_front(*this, fd);
bool success = fds.front().Schedule(events);
if (!success) {
fds.pop_front();
#ifdef USE_EPOLL
if (errno == EPERM)
/* not supported by epoll (e.g. "/dev/null"):
add it to the "always ready" list */
always_ready_fds.push_front({fd, events});
#endif
}
return success;
}
void
MultiSocketMonitor::ClearSocketList() noexcept
{
assert(GetEventLoop().IsInside());
fds.clear();
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
}
#ifndef _WIN32
@@ -55,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept
void
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
{
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
pollfd *const end = pfds + n;
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
@@ -64,9 +97,7 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
if (i == end)
return 0;
auto events = i->events;
i->events = 0;
return events;
return std::exchange(i->events, 0);
});
for (auto i = pfds; i != end; ++i)
@@ -79,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
void
MultiSocketMonitor::Prepare() noexcept
{
const auto timeout = PrepareSockets();
auto timeout = PrepareSockets();
#ifdef USE_EPOLL
if (!always_ready_fds.empty()) {
/* if there was at least one file descriptor not
supported by epoll, install a very short timeout
because we assume it's always ready */
constexpr std::chrono::steady_clock::duration ready_timeout =
std::chrono::milliseconds(1);
if (timeout < timeout.zero() || timeout > ready_timeout)
timeout = ready_timeout;
}
#endif
if (timeout >= timeout.zero())
timeout_event.Schedule(timeout);
else

@@ -50,12 +50,10 @@ class MultiSocketMonitor : IdleMonitor
unsigned revents;
public:
SingleFD(MultiSocketMonitor &_multi, SocketDescriptor _fd,
unsigned events) noexcept
SingleFD(MultiSocketMonitor &_multi,
SocketDescriptor _fd) noexcept
:SocketMonitor(_fd, _multi.GetEventLoop()),
multi(_multi), revents(0) {
Schedule(events);
}
multi(_multi), revents(0) {}
SocketDescriptor GetSocket() const noexcept {
return SocketMonitor::GetSocket();
@@ -86,8 +84,6 @@ class MultiSocketMonitor : IdleMonitor
}
};
friend class SingleFD;
TimerEvent timeout_event;
/**
@@ -106,6 +102,21 @@ class MultiSocketMonitor : IdleMonitor
std::forward_list<SingleFD> fds;
#ifdef USE_EPOLL
struct AlwaysReady {
const SocketDescriptor fd;
const unsigned revents;
};
/**
* A list of file descriptors which are always ready. This is
* a kludge needed because the ALSA output plugin gives us a
* file descriptor to /dev/null, which is incompatible with
* epoll (epoll_ctl() returns -EPERM).
*/
std::forward_list<AlwaysReady> always_ready_fds;
#endif
public:
static constexpr unsigned READ = SocketMonitor::READ;
static constexpr unsigned WRITE = SocketMonitor::WRITE;
@@ -147,9 +158,7 @@ public:
*
* May only be called from PrepareSockets().
*/
void AddSocket(SocketDescriptor fd, unsigned events) noexcept {
fds.emplace_front(*this, fd, events);
}
bool AddSocket(SocketDescriptor fd, unsigned events) noexcept;
/**
* Remove all sockets.
@@ -204,6 +213,11 @@ public:
i.ClearReturnedEvents();
}
}
#ifdef USE_EPOLL
for (const auto &i : always_ready_fds)
f(i.fd, i.revents);
#endif
}
protected:
@@ -232,7 +246,6 @@ private:
void OnTimeout() noexcept {
SetReady();
IdleMonitor::Schedule();
}
virtual void OnIdle() noexcept final;

@@ -68,20 +68,24 @@ SocketMonitor::Close() noexcept
Steal().Close();
}
void
bool
SocketMonitor::Schedule(unsigned flags) noexcept
{
assert(IsDefined());
if (flags == GetScheduledFlags())
return;
return true;
bool success;
if (scheduled_flags == 0)
loop.AddFD(fd.Get(), flags, *this);
success = loop.AddFD(fd.Get(), flags, *this);
else if (flags == 0)
loop.RemoveFD(fd.Get(), *this);
success = loop.RemoveFD(fd.Get(), *this);
else
loop.ModifyFD(fd.Get(), flags, *this);
success = loop.ModifyFD(fd.Get(), flags, *this);
scheduled_flags = flags;
if (success)
scheduled_flags = flags;
return success;
}

@@ -98,18 +98,22 @@ public:
return scheduled_flags;
}
void Schedule(unsigned flags) noexcept;
/**
* @return true on success, false on error (with errno set if
* USE_EPOLL is defined)
*/
bool Schedule(unsigned flags) noexcept;
void Cancel() noexcept {
Schedule(0);
}
void ScheduleRead() noexcept {
Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
bool ScheduleRead() noexcept {
return Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
}
void ScheduleWrite() noexcept {
Schedule(GetScheduledFlags() | WRITE);
bool ScheduleWrite() noexcept {
return Schedule(GetScheduledFlags() | WRITE);
}
void CancelRead() noexcept {

@@ -108,6 +108,12 @@ struct PathTraitsFS {
return IsSeparator(*p);
}
gcc_pure gcc_nonnull_all
static bool IsSpecialFilename(const_pointer_type name) noexcept {
return (name[0] == '.' && name[1] == 0) ||
(name[0] == '.' && name[1] == '.' && name[2] == 0);
}
gcc_pure gcc_nonnull_all
static size_t GetLength(const_pointer_type p) noexcept {
return StringLength(p);
@@ -216,6 +222,12 @@ struct PathTraitsUTF8 {
return IsSeparator(*p);
}
gcc_pure gcc_nonnull_all
static bool IsSpecialFilename(const_pointer_type name) noexcept {
return (name[0] == '.' && name[1] == 0) ||
(name[0] == '.' && name[1] == '.' && name[2] == 0);
}
gcc_pure gcc_nonnull_all
static size_t GetLength(const_pointer_type p) noexcept {
return StringLength(p);

@@ -35,6 +35,9 @@ input_glue = static_library(
'BufferedInputStream.cxx',
'MaybeBufferedInputStream.cxx',
include_directories: inc,
dependencies: [
boost_dep,
],
)
input_glue_dep = declare_dependency(

@@ -180,7 +180,6 @@ CurlInputStream::FreeEasyIndirect() noexcept
{
BlockingCall(GetEventLoop(), [this](){
FreeEasy();
(*curl_init)->InvalidateSockets();
});
}

@@ -30,6 +30,8 @@
#ifndef CURL_EASY_HXX
#define CURL_EASY_HXX
#include "String.hxx"
#include <curl/curl.h>
#include <utility>
@@ -88,8 +90,8 @@ public:
throw std::runtime_error(curl_easy_strerror(code));
}
char *Escape(const char *string, int length=0) const noexcept {
return curl_easy_escape(handle, string, length);
CurlString Escape(const char *string, int length=0) const noexcept {
return CurlString(curl_easy_escape(handle, string, length));
}
};

71
src/lib/curl/Escape.cxx Normal file

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 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 "Escape.hxx"
#include "Easy.hxx"
#include "String.hxx"
#include "util/IterableSplitString.hxx"
std::string
CurlEscapeUriPath(CURL *curl, StringView src) noexcept
{
std::string dest;
for (const auto i : IterableSplitString(src, '/')) {
CurlString escaped(curl_easy_escape(curl, i.data, i.size));
if (!dest.empty())
dest.push_back('/');
dest += escaped.c_str();
}
return dest;
}
std::string
CurlEscapeUriPath(StringView src) noexcept
{
CurlEasy easy;
return CurlEscapeUriPath(easy.Get(), src);
}
std::string
CurlUnescape(CURL *curl, StringView src) noexcept
{
int outlength;
CurlString tmp(curl_easy_unescape(curl, src.data, src.size,
&outlength));
return std::string(tmp.c_str(), outlength);
}
std::string
CurlUnescape(StringView src) noexcept
{
CurlEasy easy;
return CurlUnescape(easy.Get(), src);
}

@@ -1,8 +1,5 @@
/*
* Copyright 2007-2017 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
* Copyright (C) 2018 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
@@ -30,32 +27,25 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "TimeISO8601.hxx"
#include "TimeConvert.hxx"
#include "TimeParser.hxx"
#ifndef CURL_ESCAPE_HXX
#define CURL_ESCAPE_HXX
#include <curl/curl.h>
#include <string>
struct StringView;
std::string
CurlEscapeUriPath(CURL *curl, StringView src) noexcept;
std::string
CurlEscapeUriPath(StringView src) noexcept;
std::string
CurlUnescape(CURL *curl, StringView src) noexcept;
std::string
CurlUnescape(StringView src) noexcept;
StringBuffer<64>
FormatISO8601(const struct tm &tm) noexcept
{
StringBuffer<64> buffer;
strftime(buffer.data(), buffer.capacity(),
#ifdef _WIN32
"%Y-%m-%dT%H:%M:%SZ",
#else
"%FT%TZ",
#endif
&tm);
return buffer;
}
StringBuffer<64>
FormatISO8601(std::chrono::system_clock::time_point tp)
{
return FormatISO8601(GmTime(tp));
}
std::chrono::system_clock::time_point
ParseISO8601(const char *s)
{
return ParseTimePoint(s, "%FT%TZ");
}

@@ -28,6 +28,7 @@
*/
#include "Form.hxx"
#include "String.hxx"
std::string
EncodeForm(CURL *curl,
@@ -43,12 +44,10 @@ EncodeForm(CURL *curl,
result.push_back('=');
if (!i.second.empty()) {
char *value = curl_easy_escape(curl, i.second.data(),
i.second.length());
if (value != nullptr) {
CurlString value(curl_easy_escape(curl, i.second.data(),
i.second.length()));
if (value)
result.append(value);
curl_free(value);
}
}
}

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2016 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
@@ -181,8 +181,6 @@ CurlGlobal::Remove(CURL *easy) noexcept
assert(easy != nullptr);
curl_multi_remove_handle(multi.Get(), easy);
InvalidateSockets();
}
static CurlRequest *
@@ -227,12 +225,12 @@ CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
return;
}
if (timeout_ms < 10)
/* CURL 7.21.1 likes to report "timeout=0", which
if (timeout_ms < 1)
/* CURL's threaded resolver sets a timeout of 0ms, which
means we're running in a busy loop. Quite a bad
idea to waste so much CPU. Let's use a lower limit
of 10ms. */
timeout_ms = 10;
of 1ms. */
timeout_ms = 1;
timeout_event.Schedule(std::chrono::milliseconds(timeout_ms));
}

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2016 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
@@ -74,16 +74,6 @@ public:
SocketAction(CURL_SOCKET_TIMEOUT, 0);
}
/**
* This is a kludge to allow pausing/resuming a stream with
* libcurl < 7.32.0. Read the curl_easy_pause manpage for
* more information.
*/
void ResumeSockets() {
int running_handles;
curl_multi_socket_all(multi.Get(), &running_handles);
}
private:
void UpdateTimeout(long timeout_ms) noexcept;
static int TimerFunction(CURLM *global, long timeout_ms,

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2016-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 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
@@ -30,7 +30,6 @@
#include "config.h"
#include "Request.hxx"
#include "Global.hxx"
#include "Version.hxx"
#include "Handler.hxx"
#include "event/Call.hxx"
#include "util/RuntimeError.hxx"
@@ -124,12 +123,6 @@ CurlRequest::Resume() noexcept
curl_easy_pause(easy.Get(), CURLPAUSE_CONT);
if (IsCurlOlderThan(0x072000))
/* libcurl older than 7.32.0 does not update
its sockets after curl_easy_pause(); force
libcurl to do it now */
global.ResumeSockets();
global.InvalidateSockets();
}

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 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
@@ -33,6 +33,7 @@
#include <curl/curl.h>
#include <algorithm>
#include <stdexcept>
/**
* OO wrapper for "struct curl_slist *".

77
src/lib/curl/String.hxx Normal file

@@ -0,0 +1,77 @@
/*
* Copyright 2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CURL_STRING_HXX
#define CURL_STRING_HXX
#include <curl/curl.h>
#include <utility>
/**
* An OO wrapper for an allocated string to be freed with curl_free().
*/
class CurlString {
char *p = nullptr;
public:
CurlString() noexcept = default;
CurlString(std::nullptr_t) noexcept {}
explicit CurlString(char *_p) noexcept
:p(_p) {}
CurlString(CurlString &&src) noexcept
:p(std::exchange(src.p, nullptr)) {}
~CurlString() noexcept {
if (p != nullptr)
curl_free(p);
}
CurlString &operator=(CurlString &&src) noexcept {
using std::swap;
swap(p, src.p);
return *this;
}
operator bool() const noexcept {
return p != nullptr;
}
operator const char *() const noexcept {
return p;
}
const char *c_str() const noexcept {
return p;
}
};
#endif

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2017-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2017-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,4 +1,4 @@
curl_dep = dependency('libcurl', version: '>= 7.18', required: get_option('curl'))
curl_dep = dependency('libcurl', version: '>= 7.32', required: get_option('curl'))
conf.set('ENABLE_CURL', curl_dep.found())
if not curl_dep.found()
subdir_done()
@@ -11,6 +11,7 @@ curl = static_library(
'Init.cxx',
'Global.cxx',
'Request.cxx',
'Escape.cxx',
'Form.cxx',
include_directories: inc,
dependencies: [

@@ -35,6 +35,8 @@
#include <array>
#include <stdint.h>
template<typename T> struct ConstBuffer;
gcc_pure

@@ -20,7 +20,7 @@ if icu_dep.found()
elif not get_option('iconv').disabled()
have_iconv = compiler.has_function('iconv')
conf.set('HAVE_ICONV', have_iconv)
if get_option('iconv').enabled()
if not have_iconv and get_option('iconv').enabled()
error('iconv() not available')
endif
endif

@@ -25,6 +25,7 @@
#include "util/ASCII.hxx"
#include <utility>
#include <stdexcept>
#include <assert.h>
#include <string.h>

@@ -1,4 +1,9 @@
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
if enable_database
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
else
sqlite_dep = dependency('', required: false)
endif
conf.set('ENABLE_SQLITE', sqlite_dep.found())
if not sqlite_dep.found()
subdir_done()

@@ -57,13 +57,14 @@ OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
bool
OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
InputStream &is)
InputStream &is, bool synced)
{
if (!is.KnownSize())
return false;
if (is.GetRest() < 65536)
return OggFindEOS(oy, os, packet);
return (synced || oy.ExpectPageSeekIn(os)) &&
OggFindEOS(oy, os, packet);
if (!is.CheapSeeking())
return false;

@@ -47,10 +47,13 @@ OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
* Try to find the end-of-stream (EOS) packet. Seek to the end of the
* file if necessary.
*
* @param synced is the #OggSyncState currently synced? If not, then
* we need to use ogg_sync_pageseek() instead of ogg_sync_pageout(),
* which is more expensive
* @return true if the EOS packet was found
*/
bool
OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
InputStream &is);
InputStream &is, bool synced=true);
#endif

@@ -49,6 +49,9 @@ xiph = static_library(
'VorbisComments.cxx',
'XiphTags.cxx',
include_directories: inc,
dependencies: [
libvorbis_dep,
],
)
xiph_dep = declare_dependency(

@@ -1,4 +1,4 @@
if not get_option('neighbor')
if not get_option('neighbor') or not enable_database
conf.set('ENABLE_NEIGHBOR_PLUGINS', false)
neighbor_glue_dep = dependency('', required: false)
subdir_done()

@@ -31,6 +31,7 @@
#define STATIC_SOCKET_ADDRESS_HXX
#include "SocketAddress.hxx"
#include "Features.hxx"
#include "util/Compiler.h"
#include <assert.h>

@@ -159,6 +159,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format,
} catch (...) {
LogError(std::current_exception());
Failure(std::current_exception());
return;
}
if (f != in_audio_format || f != output->out_audio_format)
@@ -458,7 +459,7 @@ AudioOutputControl::Task() noexcept
case Command::RELEASE:
if (!open) {
/* the output has failed after
the PAUSE command was submitted; bail
the RELEASE command was submitted; bail
out */
CommandFinished();
break;

@@ -403,10 +403,11 @@ JackOutput::Connect()
jack_on_shutdown(client, mpd_jack_shutdown, this);
for (unsigned i = 0; i < num_source_ports; ++i) {
unsigned long portflags = JackPortIsOutput | JackPortIsTerminal;
ports[i] = jack_port_register(client,
source_ports[i].c_str(),
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
portflags, 0);
if (ports[i] == nullptr) {
Disconnect();
throw FormatRuntimeError("Cannot register output port \"%s\"",

@@ -650,7 +650,7 @@ PulseOutput::Open(AudioFormat &audio_format)
break;
}
ss.rate = audio_format.sample_rate;
ss.rate = std::min(audio_format.sample_rate, PA_RATE_MAX);
ss.channels = audio_format.channels;
/* create a stream .. */

@@ -383,6 +383,7 @@ ShoutOutput::SendTag(const Tag &tag)
shout_tag_to_metadata(tag, song, sizeof(song));
shout_metadata_add(meta, "song", song);
shout_metadata_add(meta, "charset", "UTF-8");
if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) {
LogWarning(shout_output_domain,
"error setting shout metadata");

@@ -22,20 +22,27 @@
#include "system/FileDescriptor.hxx"
#include "system/Error.hxx"
#include <sys/stropts.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#ifdef __sun
#if defined(__sun)
#include <sys/audio.h>
#include <sys/stropts.h>
#elif defined(__NetBSD__)
#include <sys/audioio.h>
#else
/* some fake declarations that allow build this plugin on systems
other than Solaris, just to see if it compiles */
#ifndef I_FLUSH
#define I_FLUSH 0
#endif
#define AUDIO_GETINFO 0
#define AUDIO_SETINFO 0
#define AUDIO_ENCODING_LINEAR 0
@@ -141,7 +148,11 @@ SolarisOutput::Play(const void *chunk, size_t size)
void
SolarisOutput::Cancel() noexcept
{
#if defined(AUDIO_FLUSH)
ioctl(fd.Get(), AUDIO_FLUSH);
#elif defined(I_FLUSH)
ioctl(fd.Get(), I_FLUSH);
#endif
}
const struct AudioOutputPlugin solaris_output_plugin = {

@@ -21,6 +21,8 @@
#include "ConfiguredResampler.hxx"
#include "util/ConstBuffer.hxx"
#include <stdexcept>
#include <assert.h>
void

@@ -46,6 +46,7 @@
#include "CrossFade.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
#include "util/Compiler.h"
#include "util/Domain.hxx"
#include "thread/Name.hxx"
#include "Log.hxx"
@@ -1171,6 +1172,7 @@ try {
}
/* fall through */
gcc_fallthrough;
case PlayerCommand::PAUSE:
next_song.reset();

@@ -21,6 +21,7 @@
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
#include "tag/Builder.hxx"
#include "tag/Table.hxx"
#include "util/ASCII.hxx"
#include "util/StringView.hxx"
#include "lib/expat/ExpatParser.hxx"
@@ -41,6 +42,7 @@ struct AsxParser {
*/
enum {
ROOT, ENTRY,
TAG,
} state;
/**
@@ -57,23 +59,33 @@ struct AsxParser {
TagBuilder tag_builder;
std::string value;
AsxParser()
:state(ROOT) {}
};
static constexpr struct tag_table asx_tag_elements[] = {
/* is that correct? or should it be COMPOSER or PERFORMER? */
{ "author", TAG_ARTIST },
{ "title", TAG_TITLE },
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
};
static void XMLCALL
asx_start_element(void *user_data, const XML_Char *element_name,
const XML_Char **atts)
{
AsxParser *parser = (AsxParser *)user_data;
parser->value.clear();
switch (parser->state) {
case AsxParser::ROOT:
if (StringEqualsCaseASCII(element_name, "entry")) {
parser->state = AsxParser::ENTRY;
parser->location.clear();
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
@@ -84,14 +96,17 @@ asx_start_element(void *user_data, const XML_Char *element_name,
ExpatParser::GetAttributeCase(atts, "href");
if (href != nullptr)
parser->location = href;
} else if (StringEqualsCaseASCII(element_name, "author"))
/* is that correct? or should it be COMPOSER
or PERFORMER? */
parser->tag_type = TAG_ARTIST;
else if (StringEqualsCaseASCII(element_name, "title"))
parser->tag_type = TAG_TITLE;
} else {
parser->tag_type = tag_table_lookup_i(asx_tag_elements,
element_name);
if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
parser->state = AsxParser::TAG;
}
break;
case AsxParser::TAG:
break;
}
}
@@ -111,11 +126,20 @@ asx_end_element(void *user_data, const XML_Char *element_name)
parser->tag_builder.Commit());
parser->state = AsxParser::ROOT;
} else
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
case AsxParser::TAG:
if (!parser->value.empty())
parser->tag_builder.AddItem(parser->tag_type,
StringView(parser->value.data(),
parser->value.length()));
parser->state = AsxParser::ENTRY;
break;
}
parser->value.clear();
}
static void XMLCALL
@@ -125,13 +149,11 @@ asx_char_data(void *user_data, const XML_Char *s, int len)
switch (parser->state) {
case AsxParser::ROOT:
case AsxParser::ENTRY:
break;
case AsxParser::ENTRY:
if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
parser->tag_builder.AddItem(parser->tag_type,
StringView(s, len));
case AsxParser::TAG:
parser->value.append(s, len);
break;
}
}

@@ -23,6 +23,7 @@
#include "song/DetachedSong.hxx"
#include "input/InputStream.hxx"
#include "tag/Builder.hxx"
#include "tag/Table.hxx"
#include "util/StringView.hxx"
#include "lib/expat/ExpatParser.hxx"
#include "Log.hxx"
@@ -44,8 +45,8 @@ struct XspfParser {
*/
enum {
ROOT, PLAYLIST, TRACKLIST, TRACK,
LOCATION,
} state;
TAG, LOCATION,
} state = ROOT;
/**
* The current tag within the "track" element. This is only
@@ -61,8 +62,20 @@ struct XspfParser {
TagBuilder tag_builder;
XspfParser()
:state(ROOT) {}
std::string value;
};
static constexpr struct tag_table xspf_tag_elements[] = {
{ "title", TAG_TITLE },
/* TAG_COMPOSER would be more correct according to the XSPF
spec */
{ "creator", TAG_ARTIST },
{ "annotation", TAG_COMMENT },
{ "album", TAG_ALBUM },
{ "trackNum", TAG_TRACK },
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
};
static void XMLCALL
@@ -70,6 +83,7 @@ xspf_start_element(void *user_data, const XML_Char *element_name,
gcc_unused const XML_Char **atts)
{
XspfParser *parser = (XspfParser *)user_data;
parser->value.clear();
switch (parser->state) {
case XspfParser::ROOT:
@@ -88,7 +102,6 @@ xspf_start_element(void *user_data, const XML_Char *element_name,
if (strcmp(element_name, "track") == 0) {
parser->state = XspfParser::TRACK;
parser->location.clear();
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
@@ -96,21 +109,16 @@ xspf_start_element(void *user_data, const XML_Char *element_name,
case XspfParser::TRACK:
if (strcmp(element_name, "location") == 0)
parser->state = XspfParser::LOCATION;
else if (strcmp(element_name, "title") == 0)
parser->tag_type = TAG_TITLE;
else if (strcmp(element_name, "creator") == 0)
/* TAG_COMPOSER would be more correct
according to the XSPF spec */
parser->tag_type = TAG_ARTIST;
else if (strcmp(element_name, "annotation") == 0)
parser->tag_type = TAG_COMMENT;
else if (strcmp(element_name, "album") == 0)
parser->tag_type = TAG_ALBUM;
else if (strcmp(element_name, "trackNum") == 0)
parser->tag_type = TAG_TRACK;
else if (!parser->location.empty()) {
parser->tag_type = tag_table_lookup(xspf_tag_elements,
element_name);
if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
parser->state = XspfParser::TAG;
}
break;
case XspfParser::TAG:
case XspfParser::LOCATION:
break;
}
@@ -144,15 +152,26 @@ xspf_end_element(void *user_data, const XML_Char *element_name)
parser->tag_builder.Commit());
parser->state = XspfParser::TRACKLIST;
} else
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
case XspfParser::TAG:
if (!parser->value.empty())
parser->tag_builder.AddItem(parser->tag_type,
StringView(parser->value.data(),
parser->value.length()));
parser->state = XspfParser::TRACK;
break;
case XspfParser::LOCATION:
parser->location = std::move(parser->value);
parser->state = XspfParser::TRACK;
break;
}
parser->value.clear();
}
static void XMLCALL
@@ -164,19 +183,12 @@ xspf_char_data(void *user_data, const XML_Char *s, int len)
case XspfParser::ROOT:
case XspfParser::PLAYLIST:
case XspfParser::TRACKLIST:
break;
case XspfParser::TRACK:
if (!parser->location.empty() &&
parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
parser->tag_builder.AddItem(parser->tag_type,
StringView(s, len));
break;
case XspfParser::TAG:
case XspfParser::LOCATION:
parser->location.assign(s, len);
parser->value.append(s, len);
break;
}
}

@@ -175,5 +175,5 @@ SignedSongTime
ParseCommandArgSignedSongTime(const char *s)
{
auto value = ParseCommandArgFloat(s);
return SongTime::FromS(value);
return SignedSongTime::FromS(value);
}

@@ -353,7 +353,7 @@ playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
return;
to = (currentSong + abs(to)) % GetLength();
if (start < (unsigned)to)
to--;
to -= end - start;
}
queue.MoveRange(start, end, to);

@@ -29,8 +29,9 @@
#include "AudioParser.hxx"
#include "tag/ParseName.hxx"
#include "tag/Tag.hxx"
#include "time/ChronoUtil.hxx"
#include "time/ISO8601.hxx"
#include "util/CharUtil.hxx"
#include "util/ChronoUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
@@ -38,7 +39,6 @@
#include "util/StringStrip.hxx"
#include "util/StringView.hxx"
#include "util/ASCII.hxx"
#include "util/TimeISO8601.hxx"
#include "util/UriUtil.hxx"
#include "lib/icu/CaseFold.hxx"
@@ -113,14 +113,19 @@ ParseTimeStamp(const char *s)
{
assert(s != nullptr);
char *endptr;
unsigned long long value = strtoull(s, &endptr, 10);
if (*endptr == 0 && endptr > s)
/* it's an integral UNIX time stamp */
return std::chrono::system_clock::from_time_t((time_t)value);
try {
/* try ISO 8601 */
return ParseISO8601(s).first;
} catch (...) {
char *endptr;
unsigned long long value = strtoull(s, &endptr, 10);
if (*endptr == 0 && endptr > s)
/* it's an integral UNIX time stamp */
return std::chrono::system_clock::from_time_t((time_t)value);
/* try ISO 8601 */
return ParseISO8601(s);
/* rethrow the ParseISO8601() error */
throw;
}
}
static constexpr bool

@@ -19,7 +19,8 @@
#include "ModifiedSinceSongFilter.hxx"
#include "LightSong.hxx"
#include "util/TimeISO8601.hxx"
#include "time/ISO8601.hxx"
#include "util/StringBuffer.hxx"
std::string
ModifiedSinceSongFilter::ToExpression() const noexcept

@@ -24,6 +24,7 @@ song_dep = declare_dependency(
icu_dep,
pcre_dep,
tag_dep,
time_dep,
util_dep,
],
)

@@ -25,21 +25,23 @@
#include "lib/curl/Init.hxx"
#include "lib/curl/Global.hxx"
#include "lib/curl/Slist.hxx"
#include "lib/curl/String.hxx"
#include "lib/curl/Request.hxx"
#include "lib/curl/Handler.hxx"
#include "lib/curl/Escape.hxx"
#include "lib/expat/ExpatParser.hxx"
#include "fs/Traits.hxx"
#include "event/Call.hxx"
#include "event/DeferEvent.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "time/ChronoUtil.hxx"
#include "time/Parser.hxx"
#include "util/ASCII.hxx"
#include "util/ChronoUtil.hxx"
#include "util/IterableSplitString.hxx"
#include "time/ChronoUtil.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
#include "util/StringFormat.hxx"
#include "util/TimeParser.hxx"
#include "util/UriUtil.hxx"
#include <algorithm>
@@ -77,26 +79,15 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept
if (StringIsEmpty(uri_utf8))
return base;
CurlEasy easy;
std::string path_esc;
for (auto elt: IterableSplitString(uri_utf8, '/')) {
char *elt_esc = easy.Escape(elt.data, elt.size);
if (!path_esc.empty())
path_esc.push_back('/');
path_esc += elt_esc;
curl_free(elt_esc);
}
std::string path_esc = CurlEscapeUriPath(uri_utf8);
return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
}
const char *
CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept
{
// TODO: escape/unescape?
return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
return PathTraitsUTF8::Relative(base.c_str(),
CurlUnescape(uri_utf8).c_str());
}
class BlockingHttpRequest : protected CurlResponseHandler {
@@ -118,7 +109,9 @@ public:
BIND_THIS_METHOD(OnDeferredStart)),
request(curl, uri, *this) {
// TODO: use CurlInputStream's configuration
}
void DeferStart() noexcept {
/* start the transfer inside the IOThread */
defer_start.Schedule();
}
@@ -132,6 +125,10 @@ public:
std::rethrow_exception(postponed_error);
}
CURL *GetEasy() noexcept {
return request.Get();
}
protected:
void SetDone() {
assert(!done);
@@ -269,6 +266,8 @@ public:
CommonExpatParser(ExpatNamespaceSeparator{'|'})
{
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
request.SetOption(CURLOPT_FOLLOWLOCATION, 1l);
request.SetOption(CURLOPT_MAXREDIRS, 1l);
request_headers.Append(StringFormat<40>("depth: %u", depth));
@@ -277,6 +276,7 @@ public:
request.SetOption(CURLOPT_POSTFIELDS,
"<?xml version=\"1.0\"?>\n"
"<a:propfind xmlns:a=\"DAV:\">"
"<a:prop><a:resourcetype/></a:prop>"
"<a:prop><a:getcontenttype/></a:prop>"
"<a:prop><a:getcontentlength/></a:prop>"
"</a:propfind>");
@@ -284,6 +284,8 @@ public:
// TODO: send request body
}
using BlockingHttpRequest::GetEasy;
using BlockingHttpRequest::DeferStart;
using BlockingHttpRequest::Wait;
protected:
@@ -431,6 +433,7 @@ public:
}
const StorageFileInfo &Perform() {
DeferStart();
Wait();
return info;
}
@@ -454,9 +457,7 @@ CurlStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
{
// TODO: escape the given URI
std::string uri = base;
uri += uri_utf8;
const auto uri = MapUTF8(uri_utf8);
return HttpGetInfoOperation(*curl, uri.c_str()).Perform();
}
@@ -484,6 +485,7 @@ public:
base_path(UriPathOrSlash(uri)) {}
std::unique_ptr<StorageDirectoryReader> Perform() {
DeferStart();
Wait();
return ToReader();
}
@@ -503,7 +505,11 @@ private:
if (path == nullptr)
return nullptr;
path = StringAfterPrefix(path, base_path.c_str());
/* kludge: ignoring case in this comparison to avoid
false negatives if the web server uses a different
case in hex digits in escaped characters; TODO:
implement properly */
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
if (path == nullptr || *path == 0)
return nullptr;
@@ -529,10 +535,7 @@ protected:
if (escaped_name.IsNull())
return;
// TODO: unescape
const auto name = escaped_name;
entries.emplace_front(std::string(name.data, name.size));
entries.emplace_front(CurlUnescape(GetEasy(), escaped_name));
auto &info = entries.front().info;
info = StorageFileInfo(r.collection
@@ -546,10 +549,7 @@ protected:
std::unique_ptr<StorageDirectoryReader>
CurlStorage::OpenDirectory(const char *uri_utf8)
{
// TODO: escape the given URI
std::string uri = base;
uri += uri_utf8;
std::string uri = MapUTF8(uri_utf8);
/* collection URIs must end with a slash */
if (uri.back() != '/')

@@ -144,21 +144,12 @@ LocalStorage::OpenDirectory(const char *uri_utf8)
return std::make_unique<LocalDirectoryReader>(MapFSOrThrow(uri_utf8));
}
gcc_pure
static bool
SkipNameFS(PathTraitsFS::const_pointer_type name_fs) noexcept
{
return name_fs[0] == '.' &&
(name_fs[1] == 0 ||
(name_fs[1] == '.' && name_fs[2] == 0));
}
const char *
LocalDirectoryReader::Read() noexcept
{
while (reader.ReadEntry()) {
const Path name_fs = reader.GetEntry();
if (SkipNameFS(name_fs.c_str()))
if (PathTraitsFS::IsSpecialFilename(name_fs.c_str()))
continue;
try {

@@ -57,5 +57,6 @@ storage_plugins_dep = declare_dependency(
dependencies: [
storage_api_dep,
fs_dep,
time_dep,
],
)

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2013-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2015 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2013-2015 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
@@ -147,6 +147,26 @@ FormatErrno(const char *fmt, Args&&... args) noexcept
return FormatErrno(errno, fmt, std::forward<Args>(args)...);
}
template<typename... Args>
static inline std::system_error
FormatFileNotFound(const char *fmt, Args&&... args) noexcept
{
#ifdef _WIN32
return FormatLastError(ERROR_FILE_NOT_FOUND, fmt,
std::forward<Args>(args)...);
#else
return FormatErrno(ENOENT, fmt, std::forward<Args>(args)...);
#endif
}
gcc_pure
inline bool
IsErrno(const std::system_error &e, int code) noexcept
{
return e.code().category() == ErrnoCategory() &&
e.code().value() == code;
}
gcc_pure
static inline bool
IsFileNotFound(const std::system_error &e) noexcept
@@ -155,8 +175,7 @@ IsFileNotFound(const std::system_error &e) noexcept
return e.code().category() == std::system_category() &&
e.code().value() == ERROR_FILE_NOT_FOUND;
#else
return e.code().category() == ErrnoCategory() &&
e.code().value() == ENOENT;
return IsErrno(e, ENOENT);
#endif
}
@@ -168,8 +187,7 @@ IsPathNotFound(const std::system_error &e) noexcept
return e.code().category() == std::system_category() &&
e.code().value() == ERROR_PATH_NOT_FOUND;
#else
return e.code().category() == ErrnoCategory() &&
e.code().value() == ENOTDIR;
return IsErrno(e, ENOTDIR);
#endif
}
@@ -181,8 +199,7 @@ IsAccessDenied(const std::system_error &e) noexcept
return e.code().category() == std::system_category() &&
e.code().value() == ERROR_ACCESS_DENIED;
#else
return e.code().category() == ErrnoCategory() &&
e.code().value() == EACCES;
return IsErrno(e, EACCES);
#endif
}

@@ -20,9 +20,9 @@
#include "Format.hxx"
#include "Tag.hxx"
#include "ParseName.hxx"
#include "time/Convert.hxx"
#include "util/format.h"
#include "util/TruncateString.hxx"
#include "util/TimeConvert.hxx"
#include <algorithm>

@@ -89,7 +89,7 @@ public:
}
void Unset(TagType tag) {
*this |= ~TagMask(tag);
*this &= ~TagMask(tag);
}
};

@@ -48,6 +48,7 @@ tag = static_library(
tag_dep = declare_dependency(
link_with: tag,
dependencies: [
time_dep,
util_dep,
],
)

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