diff --git a/NEWS b/NEWS index 2203fb9ce..9c8ec529f 100644 --- a/NEWS +++ b/NEWS @@ -3,8 +3,6 @@ ver 0.22 (not yet released) - "findadd"/"searchadd"/"searchaddpl" support the "sort" and "window" parameters - add command "readpicture" to download embedded pictures - - relax the ISO 8601 parser: allow omitting the time of day and the "Z" - suffix * tags - new tags "Grouping" (for ID3 "TIT1"), "Work" and "Conductor" * input @@ -32,9 +30,15 @@ ver 0.22 (not yet released) * switch to C++17 - GCC 7 or clang 4 (or newer) recommended -ver 0.21.17 (not yet released) +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) diff --git a/doc/plugins.rst b/doc/plugins.rst index 538292e88..28efb9bb9 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -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 ---- @@ -1120,7 +1120,7 @@ This plugin requires building with ``libavfilter`` (FFmpeg). normalize --------- -Normalize the volume during playback (at the expensve of quality). +Normalize the volume during playback (at the expense of quality). null diff --git a/src/LocateUri.cxx b/src/LocateUri.cxx index 2b8a14e9c..c924b89dc 100644 --- a/src/LocateUri.cxx +++ b/src/LocateUri.cxx @@ -29,6 +29,8 @@ #include "storage/StorageInterface.hxx" #endif +#include + static LocatedUri LocateFileUri(const char *uri, const Client *client #ifdef ENABLE_DATABASE diff --git a/src/archive/plugins/ZzipArchivePlugin.cxx b/src/archive/plugins/ZzipArchivePlugin.cxx index 2d5118160..e5b682635 100644 --- a/src/archive/plugins/ZzipArchivePlugin.cxx +++ b/src/archive/plugins/ZzipArchivePlugin.cxx @@ -27,6 +27,7 @@ #include "../ArchiveVisitor.hxx" #include "input/InputStream.hxx" #include "fs/Path.hxx" +#include "system/Error.hxx" #include "util/RuntimeError.hxx" #include @@ -121,9 +122,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(dir, pathname, mutex, diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx index e600ed356..5223dd883 100644 --- a/src/decoder/plugins/MadDecoderPlugin.cxx +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -686,6 +686,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) { const auto action = DecodeNextFrame(false, tag); switch (action) { diff --git a/src/input/meson.build b/src/input/meson.build index 5f7533751..e4124e5ec 100644 --- a/src/input/meson.build +++ b/src/input/meson.build @@ -40,6 +40,9 @@ input_glue = static_library( 'cache/Item.cxx', 'cache/Stream.cxx', include_directories: inc, + dependencies: [ + boost_dep, + ], ) input_glue_dep = declare_dependency( diff --git a/src/lib/curl/Slist.hxx b/src/lib/curl/Slist.hxx index e92262228..b53580a21 100644 --- a/src/lib/curl/Slist.hxx +++ b/src/lib/curl/Slist.hxx @@ -33,6 +33,7 @@ #include #include +#include /** * OO wrapper for "struct curl_slist *". diff --git a/src/lib/gcrypt/MD5.hxx b/src/lib/gcrypt/MD5.hxx index 21a4f8d43..2944acb70 100644 --- a/src/lib/gcrypt/MD5.hxx +++ b/src/lib/gcrypt/MD5.hxx @@ -35,6 +35,8 @@ #include +#include + template struct ConstBuffer; namespace Gcrypt { diff --git a/src/lib/nfs/FileReader.cxx b/src/lib/nfs/FileReader.cxx index 3806de803..ee55d127c 100644 --- a/src/lib/nfs/FileReader.cxx +++ b/src/lib/nfs/FileReader.cxx @@ -25,6 +25,7 @@ #include "util/ASCII.hxx" #include +#include #include #include diff --git a/src/lib/xiph/meson.build b/src/lib/xiph/meson.build index 07ef922fb..ae4267a24 100644 --- a/src/lib/xiph/meson.build +++ b/src/lib/xiph/meson.build @@ -52,6 +52,9 @@ xiph = static_library( 'VorbisPicture.cxx', 'XiphTags.cxx', include_directories: inc, + dependencies: [ + libvorbis_dep, + ], ) xiph_dep = declare_dependency( diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx index a7efb88e3..0e3cca0bc 100644 --- a/src/output/plugins/ShoutOutputPlugin.cxx +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -381,6 +381,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"); diff --git a/src/pcm/Convert.cxx b/src/pcm/Convert.cxx index 78aab247a..1cfd186d8 100644 --- a/src/pcm/Convert.cxx +++ b/src/pcm/Convert.cxx @@ -21,6 +21,8 @@ #include "ConfiguredResampler.hxx" #include "util/ConstBuffer.hxx" +#include + #include void diff --git a/src/song/Filter.cxx b/src/song/Filter.cxx index 17913cbdb..9107102a6 100644 --- a/src/song/Filter.cxx +++ b/src/song/Filter.cxx @@ -106,14 +106,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).first; + /* rethrow the ParseISO8601() error */ + throw; + } } static constexpr bool diff --git a/src/system/Error.hxx b/src/system/Error.hxx index a22c3ba29..5027e6864 100644 --- a/src/system/Error.hxx +++ b/src/system/Error.hxx @@ -147,6 +147,18 @@ FormatErrno(const char *fmt, Args&&... args) noexcept return FormatErrno(errno, fmt, std::forward(args)...); } +template +static inline std::system_error +FormatFileNotFound(const char *fmt, Args&&... args) noexcept +{ +#ifdef _WIN32 + return FormatLastError(ERROR_FILE_NOT_FOUND, fmt, + std::forward(args)...); +#else + return FormatErrno(ENOENT, fmt, std::forward(args)...); +#endif +} + gcc_pure inline bool IsErrno(const std::system_error &e, int code) noexcept diff --git a/src/time/ISO8601.cxx b/src/time/ISO8601.cxx index b92a0e743..5cb4c486c 100644 --- a/src/time/ISO8601.cxx +++ b/src/time/ISO8601.cxx @@ -123,8 +123,12 @@ ParseISO8601(const char *s) /* parse the date */ const char *end = strptime(s, "%F", &tm); - if (end == nullptr) - throw std::runtime_error("Failed to parse date"); + if (end == nullptr) { + /* try without field separators */ + end = strptime(s, "%Y%m%d", &tm); + if (end == nullptr) + throw std::runtime_error("Failed to parse date"); + } s = end; @@ -136,6 +140,12 @@ ParseISO8601(const char *s) if ((end = strptime(s, "%T", &tm)) != nullptr) precision = std::chrono::seconds(1); + else if ((end = strptime(s, "%H%M%S", &tm)) != nullptr) + /* no field separators */ + precision = std::chrono::seconds(1); + else if ((end = strptime(s, "%H%M", &tm)) != nullptr) + /* no field separators */ + precision = std::chrono::minutes(1); else if ((end = strptime(s, "%H:%M", &tm)) != nullptr) precision = std::chrono::minutes(1); else if ((end = strptime(s, "%H", &tm)) != nullptr) diff --git a/src/util/StringBuffer.hxx b/src/util/StringBuffer.hxx index a4dc4189a..07ce6dd31 100644 --- a/src/util/StringBuffer.hxx +++ b/src/util/StringBuffer.hxx @@ -35,14 +35,14 @@ /** * A statically allocated string buffer. */ -template +template class BasicStringBuffer { public: typedef T value_type; typedef T &reference; typedef T *pointer; typedef const T *const_pointer; - typedef size_t size_type; + typedef std::size_t size_type; static constexpr value_type SENTINEL = '\0'; @@ -104,7 +104,7 @@ public: } }; -template +template class StringBuffer : public BasicStringBuffer {}; #endif diff --git a/src/util/StringFormat.hxx b/src/util/StringFormat.hxx index 4fee2fc37..723591c69 100644 --- a/src/util/StringFormat.hxx +++ b/src/util/StringFormat.hxx @@ -36,13 +36,13 @@ template static inline void -StringFormat(char *buffer, size_t size, +StringFormat(char *buffer, std::size_t size, const char *fmt, Args&&... args) noexcept { snprintf(buffer, size, fmt, args...); } -template +template static inline void StringFormat(StringBuffer &buffer, const char *fmt, Args&&... args) noexcept @@ -50,7 +50,7 @@ StringFormat(StringBuffer &buffer, StringFormat(buffer.data(), buffer.capacity(), fmt, args...); } -template +template static inline StringBuffer StringFormat(const char *fmt, Args&&... args) noexcept { diff --git a/test/TestISO8601.cxx b/test/TestISO8601.cxx index 3305b47de..cd0897c1a 100644 --- a/test/TestISO8601.cxx +++ b/test/TestISO8601.cxx @@ -71,6 +71,15 @@ static constexpr struct { { "2019-02-04T16:46:41+0200", 1549291601, std::chrono::seconds(1) }, { "2019-02-04T16:46:41+02:00", 1549291601, std::chrono::seconds(1) }, { "2019-02-04T16:46:41-0200", 1549306001, std::chrono::seconds(1) }, + + /* without field separators */ + { "19700101T000000Z", 0, std::chrono::seconds(1) }, + { "19700101T000001Z", 1, std::chrono::seconds(1) }, + { "20190204T164641Z", 1549298801, std::chrono::seconds(1) }, + { "19700101", 0, std::chrono::hours(24) }, + { "20190204", 1549238400, std::chrono::hours(24) }, + { "20190204T1646", 1549298760, std::chrono::minutes(1) }, + { "20190204T16", 1549296000, std::chrono::hours(1) }, }; TEST(ISO8601, Parse)