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:
commit
e6600b8562
7
NEWS
7
NEWS
@ -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
|
|||||||
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[] = {
|
|||||||
|
|
||||||
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,
|
|||||||
|
|
||||||
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,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()
|
|||||||
#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)
|
|||||||
|
|
||||||
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,
|
|||||||
|
|
||||||
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,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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) {
|
while (true) {
|
||||||
if (args.size == COMMAND_ARGV_MAX) {
|
if (args.size == COMMAND_ARGV_MAX) {
|
||||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *a = tokenizer.NextParam();
|
||||||
|
if (a == nullptr)
|
||||||
|
break;
|
||||||
|
|
||||||
|
argv[args.size++] = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *a = tokenizer.NextParam();
|
/* look up and invoke the command handler */
|
||||||
if (a == nullptr)
|
|
||||||
break;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|||||||
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
|
|||||||
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,
|
|||||||
} 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
|
|||||||
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;
|
||||||
|
Loading…
Reference in New Issue
Block a user