Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c560ec8ea6 | ||
![]() |
56c234b410 | ||
![]() |
82743dfd02 | ||
![]() |
33694642bd | ||
![]() |
c71242d743 | ||
![]() |
c45f113856 | ||
![]() |
e0a8fd398c | ||
![]() |
3e97058151 | ||
![]() |
51b1dd8672 | ||
![]() |
98a7d8da6c | ||
![]() |
acb29f792f | ||
![]() |
cd364023ae | ||
![]() |
8d34a1cfc6 | ||
![]() |
73a1f078a6 | ||
![]() |
b7ce452308 | ||
![]() |
5faf76051d | ||
![]() |
5fe70a3417 | ||
![]() |
7a68b1e71f | ||
![]() |
d5468dfe89 | ||
![]() |
976372ff63 | ||
![]() |
9abb686eeb |
.travis.ymlNEWS
android
doc
meson.buildsrc
@@ -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"
|
||||
|
||||
|
13
NEWS
13
NEWS
@@ -1,3 +1,16 @@
|
||||
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"
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="43"
|
||||
android:versionName="0.21.20">
|
||||
android:versionCode="44"
|
||||
android:versionName="0.21.21">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||
|
||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.20'
|
||||
version = '0.21.21'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
@@ -695,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.20',
|
||||
version: '0.21.21',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -22,22 +22,23 @@
|
||||
#include "system/FileDescriptor.hxx"
|
||||
#include "system/Error.hxx"
|
||||
|
||||
#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 */
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#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 = {
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -89,7 +89,7 @@ public:
|
||||
}
|
||||
|
||||
void Unset(TagType tag) {
|
||||
*this |= ~TagMask(tag);
|
||||
*this &= ~TagMask(tag);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -37,6 +37,7 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
StringBuffer<64>
|
||||
FormatISO8601(const struct tm &tm) noexcept
|
||||
|
Reference in New Issue
Block a user