release v0.21.14

-----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEA5IzWngIOJSkMBxDI26KWMbbRRIFAl1dBToQHG1heEBtdXNp
 Y3BkLm9yZwAKCRAjbopYxttFEhlID/4gcrbaegKpTkftGPLBGlSUc4W0qwSHbdiJ
 SuzK7sraSGUlLHHiBfqvFfE8tyEaLfsJarqiOdGgfiaW5QyapdwuU5s76nAN6jjY
 onEo2QK6vSbv4J/B1+Jv+NGCgVM3ZkPy91GhfTz8MEtJMaBmztFhnpxK60r9jYPG
 5EBsnVa6HhI7gniQAtOwE8SXYnn92Q4j72S8OuAbJ7Vwh0oqdIyXECqzcAE97Fk2
 TNX/YSLST3I7Chv2OBDb3vOegh9nFUyr0qeSYdi8vk2BBYcgX2xYOOBQCC3ta/nr
 NNeuSJOLgcF1XrFqVRRhDKZ8Y2inD6qVAXOH5WtChT1n3uXqYW7vdq0fW+/w3W/E
 vouzgt5KvU5Me4Mk2M2dMjEWW+7Y8EUjvrwDnbDIkyP+Yi+BLTmTnyBgAW3cvRO9
 UoCcWTBOEgyX2wAFl3r+NMPEneuMLbMCZUMju4/zveiRJdFExA0LC8wk0/iWqQbW
 +WD8y6RTo7Z1jsP1vnBimgAkzkLAOuMlKLYNI15ETrxwKWgOjN6a7Q61GVdsaiqG
 fKQeO0kZWWFcJ7HECgp3tpwWEi1+7/uqt0TwQgOKDdZHYL0Wb6Ur09KJS4b+eKIl
 UzYwCrPgUx1pcYR+rTbMxCNpWn2kA+vp2UaPBN60c/J98d+6C/2nKtIvXfr5MrUA
 CEb1epw/ew==
 =mEFF
 -----END PGP SIGNATURE-----

Merge tag 'v0.21.14'

release v0.21.14
This commit is contained in:
Max Kellermann 2019-08-21 10:52:49 +02:00
commit e6600b8562
7 changed files with 64 additions and 42 deletions

7
NEWS
View File

@ -29,11 +29,16 @@ ver 0.22 (not yet released)
* switch to C++17
- GCC 7 or clang 4 (or newer) recommended
ver 0.21.14 (not yet released)
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

View File

@ -839,7 +839,8 @@ The music database
albumart foo/bar.ogg 0
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

View File

@ -212,9 +212,10 @@ static constexpr struct command commands[] = {
static constexpr unsigned num_commands = std::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"))
@ -241,7 +242,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];
@ -255,7 +256,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];
@ -282,7 +283,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 */
@ -291,8 +292,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;
@ -314,7 +316,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,
@ -348,7 +350,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) {
@ -366,8 +368,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) */
@ -395,34 +397,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;
}

View File

@ -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

View File

@ -138,6 +138,18 @@ DecoderControl::Seek(std::unique_lock<Mutex> &lock, SongTime t)
seek_error = false;
SynchronousCommandLocked(lock, 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(lock);
if (seek_error)
throw std::runtime_error("Decoder failed to seek");
}

View File

@ -418,6 +418,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

View File

@ -158,6 +158,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)
@ -457,7 +458,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;