diff --git a/.travis.yml b/.travis.yml index c62d7e244..38b72d547 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: cpp -matrix: +jobs: include: # Ubuntu Bionic (18.04) with GCC 7 - os: linux @@ -126,6 +126,7 @@ matrix: packages: - ccache - meson + update: true env: - MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1" diff --git a/NEWS b/NEWS index 84b9f50bb..b0fb7a816 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,18 @@ ver 0.22 (not yet released) * switch to C++17 - GCC 7 or clang 4 (or newer) recommended +ver 0.21.21 (not yet released) +* configuration + - fix bug in "metadata_to_use" setting +* playlist + - 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 + ver 0.21.20 (2020/02/16) * decoder - audiofile, ffmpeg, sndfile: handle MIME type "audio/wav" diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index a7ea8a550..83286b0b7 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="44" + android:versionName="0.21.21"> diff --git a/src/archive/plugins/Iso9660ArchivePlugin.cxx b/src/archive/plugins/Iso9660ArchivePlugin.cxx index af4b02aee..ddf81aa2d 100644 --- a/src/archive/plugins/Iso9660ArchivePlugin.cxx +++ b/src/archive/plugins/Iso9660ArchivePlugin.cxx @@ -28,6 +28,7 @@ #include "input/InputStream.hxx" #include "fs/Path.hxx" #include "util/RuntimeError.hxx" +#include "util/StringCompare.hxx" #include @@ -99,7 +100,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); diff --git a/src/db/update/InotifyUpdate.cxx b/src/db/update/InotifyUpdate.cxx index af27b7bba..449abb80f 100644 --- a/src/db/update/InotifyUpdate.cxx +++ b/src/db/update/InotifyUpdate.cxx @@ -24,6 +24,7 @@ #include "storage/StorageInterface.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileInfo.hxx" +#include "fs/Traits.hxx" #include "Log.hxx" #include @@ -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; } diff --git a/src/db/update/Walk.cxx b/src/db/update/Walk.cxx index 095b701e1..3d95ae5f5 100644 --- a/src/db/update/Walk.cxx +++ b/src/db/update/Walk.cxx @@ -219,7 +219,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 diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx index c83069f30..f7165419a 100644 --- a/src/decoder/plugins/GmeDecoderPlugin.cxx +++ b/src/decoder/plugins/GmeDecoderPlugin.cxx @@ -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/StringView.hxx" #include "util/UriExtract.hxx" @@ -223,7 +224,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).c_str()); - if (info.song != nullptr) { + if (!StringIsEmpty(info.song)) { if (track_count > 1) { /* start numbering subtunes from 1 */ const auto tag_title = @@ -235,16 +236,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); } diff --git a/src/fs/Traits.hxx b/src/fs/Traits.hxx index 755c0e6ea..d2a2934a0 100644 --- a/src/fs/Traits.hxx +++ b/src/fs/Traits.hxx @@ -108,6 +108,12 @@ struct PathTraitsFS { return IsSeparator(*p); } + gcc_pure gcc_nonnull_all + static bool IsSpecialFilename(const_pointer 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 p) noexcept { return StringLength(p); @@ -216,6 +222,12 @@ struct PathTraitsUTF8 { return IsSeparator(*p); } + gcc_pure gcc_nonnull_all + static bool IsSpecialFilename(const_pointer 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 p) noexcept { return StringLength(p); diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx index eb769f055..e50109557 100644 --- a/src/output/plugins/SolarisOutputPlugin.cxx +++ b/src/output/plugins/SolarisOutputPlugin.cxx @@ -22,22 +22,23 @@ #include "system/FileDescriptor.hxx" #include "system/Error.hxx" +#include #include #include #include #include #include -#ifdef __sun +#if defined(__sun) #include #include +#elif defined(__NetBSD__) +#include #else /* some fake declarations that allow build this plugin on systems other than Solaris, just to see if it compiles */ -#include - #ifndef I_FLUSH #define I_FLUSH 0 #endif @@ -147,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 = { diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx index 8e31b4883..b8d2da61d 100644 --- a/src/playlist/plugins/XspfPlaylistPlugin.cxx +++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx @@ -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" @@ -43,8 +44,8 @@ struct XspfParser { */ enum { ROOT, PLAYLIST, TRACKLIST, TRACK, - LOCATION, - } state; + TAG, LOCATION, + } state = ROOT; /** * The current tag within the "track" element. This is only @@ -60,8 +61,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 @@ -69,6 +82,7 @@ xspf_start_element(void *user_data, const XML_Char *element_name, gcc_unused const XML_Char **atts) { auto *parser = (XspfParser *)user_data; + parser->value.clear(); switch (parser->state) { case XspfParser::ROOT: @@ -87,7 +101,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; @@ -95,21 +108,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; } @@ -143,15 +151,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 @@ -163,19 +182,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; } } diff --git a/src/storage/plugins/LocalStorage.cxx b/src/storage/plugins/LocalStorage.cxx index 2663c63ef..a9d5a4f9b 100644 --- a/src/storage/plugins/LocalStorage.cxx +++ b/src/storage/plugins/LocalStorage.cxx @@ -144,21 +144,12 @@ LocalStorage::OpenDirectory(const char *uri_utf8) return std::make_unique(MapFSOrThrow(uri_utf8)); } -gcc_pure -static bool -SkipNameFS(PathTraitsFS::const_pointer 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 { diff --git a/src/tag/Mask.hxx b/src/tag/Mask.hxx index dae4b8765..7e43e8cf3 100644 --- a/src/tag/Mask.hxx +++ b/src/tag/Mask.hxx @@ -89,7 +89,7 @@ public: } void Unset(TagType tag) noexcept { - *this |= ~TagMask(tag); + *this &= ~TagMask(tag); } }; diff --git a/src/time/ISO8601.cxx b/src/time/ISO8601.cxx index 725215ea6..336ddcd5b 100644 --- a/src/time/ISO8601.cxx +++ b/src/time/ISO8601.cxx @@ -37,6 +37,7 @@ #include #include +#include StringBuffer<64> FormatISO8601(const struct tm &tm) noexcept