Commit e6600b85 authored by Max Kellermann's avatar Max Kellermann

Merge tag 'v0.21.14'

release v0.21.14
parents 04e2d084 bc89ca92
...@@ -29,11 +29,16 @@ ver 0.22 (not yet released) ...@@ -29,11 +29,16 @@ 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.14 (not yet released) ver 0.21.14 (2019/08/21)
* decoder * decoder
- sidplay: show track durations in database - sidplay: show track durations in database
- sidplay: convert tag values from Windows-1252 charset - sidplay: convert tag values from Windows-1252 charset
- sidplay: strip text from "Date" tag - 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) ver 0.21.13 (2019/08/06)
* input * input
......
...@@ -839,7 +839,8 @@ The music database ...@@ -839,7 +839,8 @@ The music database
albumart foo/bar.ogg 0 albumart foo/bar.ogg 0
size: 1024768 size: 1024768
binary: 8192 binary: 8192
<8192 bytes>OK <8192 bytes>
OK
:command:`count {FILTER} [group {GROUPTYPE}]` :command:`count {FILTER} [group {GROUPTYPE}]`
Count the number of songs and their total playtime in Count the number of songs and their total playtime in
......
...@@ -212,9 +212,10 @@ static constexpr struct command commands[] = { ...@@ -212,9 +212,10 @@ static constexpr struct command commands[] = {
static constexpr unsigned num_commands = std::size(commands); static constexpr unsigned num_commands = std::size(commands);
gcc_pure
static bool static bool
command_available(gcc_unused const Partition &partition, command_available(gcc_unused const Partition &partition,
gcc_unused const struct command *cmd) gcc_unused const struct command *cmd) noexcept
{ {
#ifdef ENABLE_SQLITE #ifdef ENABLE_SQLITE
if (StringIsEqual(cmd->cmd, "sticker")) if (StringIsEqual(cmd->cmd, "sticker"))
...@@ -241,7 +242,7 @@ command_available(gcc_unused const Partition &partition, ...@@ -241,7 +242,7 @@ command_available(gcc_unused const Partition &partition,
static CommandResult static CommandResult
PrintAvailableCommands(Response &r, const Partition &partition, PrintAvailableCommands(Response &r, const Partition &partition,
unsigned permission) unsigned permission) noexcept
{ {
for (unsigned i = 0; i < num_commands; ++i) { for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i]; const struct command *cmd = &commands[i];
...@@ -255,7 +256,7 @@ PrintAvailableCommands(Response &r, const Partition &partition, ...@@ -255,7 +256,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
} }
static CommandResult static CommandResult
PrintUnavailableCommands(Response &r, unsigned permission) PrintUnavailableCommands(Response &r, unsigned permission) noexcept
{ {
for (unsigned i = 0; i < num_commands; ++i) { for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i]; const struct command *cmd = &commands[i];
...@@ -282,7 +283,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r) ...@@ -282,7 +283,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
} }
void void
command_init() command_init() noexcept
{ {
#ifndef NDEBUG #ifndef NDEBUG
/* ensure that the command list is sorted */ /* ensure that the command list is sorted */
...@@ -291,8 +292,9 @@ command_init() ...@@ -291,8 +292,9 @@ command_init()
#endif #endif
} }
gcc_pure
static const struct command * static const struct command *
command_lookup(const char *name) command_lookup(const char *name) noexcept
{ {
unsigned a = 0, b = num_commands, i; unsigned a = 0, b = num_commands, i;
...@@ -314,7 +316,7 @@ command_lookup(const char *name) ...@@ -314,7 +316,7 @@ command_lookup(const char *name)
static bool static bool
command_check_request(const struct command *cmd, Response &r, command_check_request(const struct command *cmd, Response &r,
unsigned permission, Request args) unsigned permission, Request args) noexcept
{ {
if (cmd->permission != (permission & cmd->permission)) { if (cmd->permission != (permission & cmd->permission)) {
r.FormatError(ACK_ERROR_PERMISSION, r.FormatError(ACK_ERROR_PERMISSION,
...@@ -348,7 +350,7 @@ command_check_request(const struct command *cmd, Response &r, ...@@ -348,7 +350,7 @@ command_check_request(const struct command *cmd, Response &r,
static const struct command * static const struct command *
command_checked_lookup(Response &r, unsigned permission, 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); const struct command *cmd = command_lookup(cmd_name);
if (cmd == nullptr) { if (cmd == nullptr) {
...@@ -366,8 +368,8 @@ command_checked_lookup(Response &r, unsigned permission, ...@@ -366,8 +368,8 @@ command_checked_lookup(Response &r, unsigned permission,
} }
CommandResult CommandResult
command_process(Client &client, unsigned num, char *line) command_process(Client &client, unsigned num, char *line) noexcept
try { {
Response r(client, num); Response r(client, num);
/* get the command name (first word on the line) */ /* get the command name (first word on the line) */
...@@ -395,34 +397,33 @@ try { ...@@ -395,34 +397,33 @@ try {
char *argv[COMMAND_ARGV_MAX]; char *argv[COMMAND_ARGV_MAX];
Request args(argv, 0); 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;
}
char *a = tokenizer.NextParam(); while (true) {
if (a == nullptr) if (args.size == COMMAND_ARGV_MAX) {
break; r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
}
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 = /* look up and invoke the command handler */
command_checked_lookup(r, client.GetPermission(),
cmd_name, args);
CommandResult ret = cmd const struct command *cmd =
? cmd->handler(client, args, r) command_checked_lookup(r, client.GetPermission(),
: CommandResult::ERROR; cmd_name, args);
if (cmd == nullptr)
return CommandResult::ERROR;
return ret; return cmd->handler(client, args, r);
} catch (const std::exception &e) { } catch (...) {
Response r(client, num); PrintError(r, std::current_exception());
PrintError(r, std::current_exception()); return CommandResult::ERROR;
return CommandResult::ERROR; }
} }
...@@ -25,12 +25,9 @@ ...@@ -25,12 +25,9 @@
class Client; class Client;
void void
command_init(); command_init() noexcept;
void
command_finish();
CommandResult CommandResult
command_process(Client &client, unsigned num, char *line); command_process(Client &client, unsigned num, char *line) noexcept;
#endif #endif
...@@ -138,6 +138,18 @@ DecoderControl::Seek(std::unique_lock<Mutex> &lock, SongTime t) ...@@ -138,6 +138,18 @@ DecoderControl::Seek(std::unique_lock<Mutex> &lock, SongTime t)
seek_error = false; seek_error = false;
SynchronousCommandLocked(lock, DecoderCommand::SEEK); 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) if (seek_error)
throw std::runtime_error("Decoder failed to seek"); throw std::runtime_error("Decoder failed to seek");
} }
......
...@@ -418,6 +418,11 @@ static void ...@@ -418,6 +418,11 @@ static void
decoder_run_song(DecoderControl &dc, decoder_run_song(DecoderControl &dc,
const DetachedSong &song, const char *uri, Path path_fs) 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(), DecoderBridge bridge(dc, dc.start_time.IsPositive(),
/* pass the song tag only if it's /* pass the song tag only if it's
authoritative, i.e. if it's a local authoritative, i.e. if it's a local
......
...@@ -158,6 +158,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format, ...@@ -158,6 +158,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format,
} catch (...) { } catch (...) {
LogError(std::current_exception()); LogError(std::current_exception());
Failure(std::current_exception()); Failure(std::current_exception());
return;
} }
if (f != in_audio_format || f != output->out_audio_format) if (f != in_audio_format || f != output->out_audio_format)
...@@ -457,7 +458,7 @@ AudioOutputControl::Task() noexcept ...@@ -457,7 +458,7 @@ AudioOutputControl::Task() noexcept
case Command::RELEASE: case Command::RELEASE:
if (!open) { if (!open) {
/* the output has failed after /* the output has failed after
the PAUSE command was submitted; bail the RELEASE command was submitted; bail
out */ out */
CommandFinished(); CommandFinished();
break; break;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment