Merge branch 'v0.21.x'
This commit is contained in:
commit
01632d37ef
|
@ -1,6 +1,6 @@
|
||||||
language: cpp
|
language: cpp
|
||||||
|
|
||||||
matrix:
|
jobs:
|
||||||
include:
|
include:
|
||||||
# Ubuntu Bionic (18.04) with GCC 7
|
# Ubuntu Bionic (18.04) with GCC 7
|
||||||
- os: linux
|
- os: linux
|
||||||
|
@ -126,6 +126,7 @@ matrix:
|
||||||
packages:
|
packages:
|
||||||
- ccache
|
- ccache
|
||||||
- meson
|
- meson
|
||||||
|
update: true
|
||||||
env:
|
env:
|
||||||
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
||||||
|
|
||||||
|
|
12
NEWS
12
NEWS
|
@ -35,6 +35,18 @@ ver 0.22 (not yet released)
|
||||||
* switch to C++17
|
* switch to C++17
|
||||||
- GCC 7 or clang 4 (or newer) recommended
|
- 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)
|
ver 0.21.20 (2020/02/16)
|
||||||
* decoder
|
* decoder
|
||||||
- audiofile, ffmpeg, sndfile: handle MIME type "audio/wav"
|
- audiofile, ffmpeg, sndfile: handle MIME type "audio/wav"
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="43"
|
android:versionCode="44"
|
||||||
android:versionName="0.21.20">
|
android:versionName="0.21.21">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/StringCompare.hxx"
|
||||||
|
|
||||||
#include <cdio/iso9660.h>
|
#include <cdio/iso9660.h>
|
||||||
|
|
||||||
|
@ -99,7 +100,10 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
||||||
auto *statbuf = (iso9660_stat_t *)
|
auto *statbuf = (iso9660_stat_t *)
|
||||||
_cdio_list_node_data(entnode);
|
_cdio_list_node_data(entnode);
|
||||||
const char *filename = statbuf->filename;
|
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;
|
continue;
|
||||||
|
|
||||||
size_t filename_length = strlen(filename);
|
size_t filename_length = strlen(filename);
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/FileInfo.hxx"
|
#include "fs/FileInfo.hxx"
|
||||||
|
#include "fs/Traits.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -146,8 +147,7 @@ WatchDirectory::GetUriFS() const noexcept
|
||||||
/* we don't look at "." / ".." nor files with newlines in their name */
|
/* we don't look at "." / ".." nor files with newlines in their name */
|
||||||
static bool skip_path(const char *path)
|
static bool skip_path(const char *path)
|
||||||
{
|
{
|
||||||
return (path[0] == '.' && path[1] == 0) ||
|
return PathTraitsFS::IsSpecialFilename(path) ||
|
||||||
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
|
|
||||||
strchr(path, '\n') != nullptr;
|
strchr(path, '\n') != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ try {
|
||||||
LogError(std::current_exception());
|
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
|
gcc_pure
|
||||||
static bool
|
static bool
|
||||||
skip_path(const char *name_utf8) noexcept
|
skip_path(const char *name_utf8) noexcept
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/StringFormat.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
#include "util/StringView.hxx"
|
#include "util/StringView.hxx"
|
||||||
#include "util/UriExtract.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)
|
if (track_count > 1)
|
||||||
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1).c_str());
|
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1).c_str());
|
||||||
|
|
||||||
if (info.song != nullptr) {
|
if (!StringIsEmpty(info.song)) {
|
||||||
if (track_count > 1) {
|
if (track_count > 1) {
|
||||||
/* start numbering subtunes from 1 */
|
/* start numbering subtunes from 1 */
|
||||||
const auto tag_title =
|
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);
|
handler.OnTag(TAG_TITLE, info.song);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.author != nullptr)
|
if (!StringIsEmpty(info.author))
|
||||||
handler.OnTag(TAG_ARTIST, info.author);
|
handler.OnTag(TAG_ARTIST, info.author);
|
||||||
|
|
||||||
if (info.game != nullptr)
|
if (!StringIsEmpty(info.game))
|
||||||
handler.OnTag(TAG_ALBUM, info.game);
|
handler.OnTag(TAG_ALBUM, info.game);
|
||||||
|
|
||||||
if (info.comment != nullptr)
|
if (!StringIsEmpty(info.comment))
|
||||||
handler.OnTag(TAG_COMMENT, info.comment);
|
handler.OnTag(TAG_COMMENT, info.comment);
|
||||||
|
|
||||||
if (info.copyright != nullptr)
|
if (!StringIsEmpty(info.copyright))
|
||||||
handler.OnTag(TAG_DATE, info.copyright);
|
handler.OnTag(TAG_DATE, info.copyright);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,12 @@ struct PathTraitsFS {
|
||||||
return IsSeparator(*p);
|
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
|
gcc_pure gcc_nonnull_all
|
||||||
static size_t GetLength(const_pointer p) noexcept {
|
static size_t GetLength(const_pointer p) noexcept {
|
||||||
return StringLength(p);
|
return StringLength(p);
|
||||||
|
@ -216,6 +222,12 @@ struct PathTraitsUTF8 {
|
||||||
return IsSeparator(*p);
|
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
|
gcc_pure gcc_nonnull_all
|
||||||
static size_t GetLength(const_pointer p) noexcept {
|
static size_t GetLength(const_pointer p) noexcept {
|
||||||
return StringLength(p);
|
return StringLength(p);
|
||||||
|
|
|
@ -22,22 +22,23 @@
|
||||||
#include "system/FileDescriptor.hxx"
|
#include "system/FileDescriptor.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#ifdef __sun
|
#if defined(__sun)
|
||||||
#include <sys/audio.h>
|
#include <sys/audio.h>
|
||||||
#include <sys/stropts.h>
|
#include <sys/stropts.h>
|
||||||
|
#elif defined(__NetBSD__)
|
||||||
|
#include <sys/audioio.h>
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/* some fake declarations that allow build this plugin on systems
|
/* some fake declarations that allow build this plugin on systems
|
||||||
other than Solaris, just to see if it compiles */
|
other than Solaris, just to see if it compiles */
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
|
|
||||||
#ifndef I_FLUSH
|
#ifndef I_FLUSH
|
||||||
#define I_FLUSH 0
|
#define I_FLUSH 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -147,7 +148,11 @@ SolarisOutput::Play(const void *chunk, size_t size)
|
||||||
void
|
void
|
||||||
SolarisOutput::Cancel() noexcept
|
SolarisOutput::Cancel() noexcept
|
||||||
{
|
{
|
||||||
|
#if defined(AUDIO_FLUSH)
|
||||||
|
ioctl(fd.Get(), AUDIO_FLUSH);
|
||||||
|
#elif defined(I_FLUSH)
|
||||||
ioctl(fd.Get(), I_FLUSH);
|
ioctl(fd.Get(), I_FLUSH);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct AudioOutputPlugin solaris_output_plugin = {
|
const struct AudioOutputPlugin solaris_output_plugin = {
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "tag/Builder.hxx"
|
#include "tag/Builder.hxx"
|
||||||
|
#include "tag/Table.hxx"
|
||||||
#include "util/StringView.hxx"
|
#include "util/StringView.hxx"
|
||||||
#include "lib/expat/ExpatParser.hxx"
|
#include "lib/expat/ExpatParser.hxx"
|
||||||
|
|
||||||
|
@ -43,8 +44,8 @@ struct XspfParser {
|
||||||
*/
|
*/
|
||||||
enum {
|
enum {
|
||||||
ROOT, PLAYLIST, TRACKLIST, TRACK,
|
ROOT, PLAYLIST, TRACKLIST, TRACK,
|
||||||
LOCATION,
|
TAG, LOCATION,
|
||||||
} state;
|
} state = ROOT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current tag within the "track" element. This is only
|
* The current tag within the "track" element. This is only
|
||||||
|
@ -60,8 +61,20 @@ struct XspfParser {
|
||||||
|
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
|
|
||||||
XspfParser()
|
std::string value;
|
||||||
:state(ROOT) {}
|
};
|
||||||
|
|
||||||
|
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
|
static void XMLCALL
|
||||||
|
@ -69,6 +82,7 @@ xspf_start_element(void *user_data, const XML_Char *element_name,
|
||||||
gcc_unused const XML_Char **atts)
|
gcc_unused const XML_Char **atts)
|
||||||
{
|
{
|
||||||
auto *parser = (XspfParser *)user_data;
|
auto *parser = (XspfParser *)user_data;
|
||||||
|
parser->value.clear();
|
||||||
|
|
||||||
switch (parser->state) {
|
switch (parser->state) {
|
||||||
case XspfParser::ROOT:
|
case XspfParser::ROOT:
|
||||||
|
@ -87,7 +101,6 @@ xspf_start_element(void *user_data, const XML_Char *element_name,
|
||||||
if (strcmp(element_name, "track") == 0) {
|
if (strcmp(element_name, "track") == 0) {
|
||||||
parser->state = XspfParser::TRACK;
|
parser->state = XspfParser::TRACK;
|
||||||
parser->location.clear();
|
parser->location.clear();
|
||||||
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -95,21 +108,16 @@ xspf_start_element(void *user_data, const XML_Char *element_name,
|
||||||
case XspfParser::TRACK:
|
case XspfParser::TRACK:
|
||||||
if (strcmp(element_name, "location") == 0)
|
if (strcmp(element_name, "location") == 0)
|
||||||
parser->state = XspfParser::LOCATION;
|
parser->state = XspfParser::LOCATION;
|
||||||
else if (strcmp(element_name, "title") == 0)
|
else if (!parser->location.empty()) {
|
||||||
parser->tag_type = TAG_TITLE;
|
parser->tag_type = tag_table_lookup(xspf_tag_elements,
|
||||||
else if (strcmp(element_name, "creator") == 0)
|
element_name);
|
||||||
/* TAG_COMPOSER would be more correct
|
if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
|
||||||
according to the XSPF spec */
|
parser->state = XspfParser::TAG;
|
||||||
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;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case XspfParser::TAG:
|
||||||
case XspfParser::LOCATION:
|
case XspfParser::LOCATION:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -143,15 +151,26 @@ xspf_end_element(void *user_data, const XML_Char *element_name)
|
||||||
parser->tag_builder.Commit());
|
parser->tag_builder.Commit());
|
||||||
|
|
||||||
parser->state = XspfParser::TRACKLIST;
|
parser->state = XspfParser::TRACKLIST;
|
||||||
} else
|
}
|
||||||
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
|
|
||||||
|
|
||||||
break;
|
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:
|
case XspfParser::LOCATION:
|
||||||
|
parser->location = std::move(parser->value);
|
||||||
parser->state = XspfParser::TRACK;
|
parser->state = XspfParser::TRACK;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parser->value.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void XMLCALL
|
static void XMLCALL
|
||||||
|
@ -163,19 +182,12 @@ xspf_char_data(void *user_data, const XML_Char *s, int len)
|
||||||
case XspfParser::ROOT:
|
case XspfParser::ROOT:
|
||||||
case XspfParser::PLAYLIST:
|
case XspfParser::PLAYLIST:
|
||||||
case XspfParser::TRACKLIST:
|
case XspfParser::TRACKLIST:
|
||||||
break;
|
|
||||||
|
|
||||||
case XspfParser::TRACK:
|
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;
|
break;
|
||||||
|
|
||||||
|
case XspfParser::TAG:
|
||||||
case XspfParser::LOCATION:
|
case XspfParser::LOCATION:
|
||||||
parser->location.assign(s, len);
|
parser->value.append(s, len);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,21 +144,12 @@ LocalStorage::OpenDirectory(const char *uri_utf8)
|
||||||
return std::make_unique<LocalDirectoryReader>(MapFSOrThrow(uri_utf8));
|
return std::make_unique<LocalDirectoryReader>(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 *
|
const char *
|
||||||
LocalDirectoryReader::Read() noexcept
|
LocalDirectoryReader::Read() noexcept
|
||||||
{
|
{
|
||||||
while (reader.ReadEntry()) {
|
while (reader.ReadEntry()) {
|
||||||
const Path name_fs = reader.GetEntry();
|
const Path name_fs = reader.GetEntry();
|
||||||
if (SkipNameFS(name_fs.c_str()))
|
if (PathTraitsFS::IsSpecialFilename(name_fs.c_str()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -89,7 +89,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unset(TagType tag) noexcept {
|
void Unset(TagType tag) noexcept {
|
||||||
*this |= ~TagMask(tag);
|
*this &= ~TagMask(tag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
StringBuffer<64>
|
StringBuffer<64>
|
||||||
FormatISO8601(const struct tm &tm) noexcept
|
FormatISO8601(const struct tm &tm) noexcept
|
||||||
|
|
Loading…
Reference in New Issue