Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions src/co/Generator.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: BSD-2-Clause
// author: Max Kellermann <max.kellermann@gmail.com>

#pragma once

#include "UniqueHandle.hxx"

#include <cassert>
#include <exception>
#include <optional>
#include <utility>

namespace Co {

/**
* Task type for a coroutine that generates values.
*
* It is meant to be used in a range-based "for" loop.
*/
template<typename T>
class Generator {
public:
struct promise_type {
std::optional<T> value;
std::exception_ptr error;

bool IsReady() const noexcept {
return value || error;
}

[[nodiscard]]
Generator get_return_object() noexcept {
return Generator(std::coroutine_handle<promise_type>::from_promise(*this));
}

auto initial_suspend() noexcept {
/* don't suspend initially because we want the
first value to be available immediately, or
else operator*() below does not work */
return std::suspend_never{};
}

auto final_suspend() noexcept {
return std::suspend_always{};
}

void unhandled_exception() noexcept {
error = std::current_exception();
}

template<typename U>
std::suspend_always yield_value(U &&_value) {
value.emplace(std::forward<U>(_value));
return {};
}

void return_void() noexcept {}
};

private:
UniqueHandle<promise_type> coroutine;

[[nodiscard]]
explicit Generator(std::coroutine_handle<promise_type> _coroutine) noexcept
:coroutine(_coroutine)
{
}

struct end_iterator {};

struct iterator {
const std::coroutine_handle<promise_type> co;

bool operator==(const end_iterator &) const noexcept {
return co.done() && !co.promise().IsReady();
}

iterator &operator++() {
auto &promise = co.promise();
assert(!promise.error);
assert(promise.value);

promise.value.reset();
co.resume();
return *this;
}

T &&operator*() const {
auto &promise = co.promise();
assert(promise.IsReady());

if (promise.error)
std::rethrow_exception(co.promise().error);

return std::move(*promise.value);
}
};

public:
auto begin() const noexcept {
return iterator{coroutine.get()};
}

auto end() const noexcept{
return end_iterator{};
}
};

} // namespace Co
77 changes: 77 additions & 0 deletions src/co/UniqueHandle.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <max.kellermann@ionos.com>

#pragma once

#include <coroutine>
#include <utility>

namespace Co {

/**
* Manage a std::coroutine_handle<> which is destroyed by the
* destructor.
*/
template<typename Promise=void>
class UniqueHandle {
std::coroutine_handle<Promise> value;

public:
UniqueHandle() = default;

explicit constexpr UniqueHandle(std::coroutine_handle<Promise> h) noexcept
:value(h) {}

UniqueHandle(UniqueHandle<Promise> &&src) noexcept
:value(std::exchange(src.value, nullptr))
{
}

/* this overload allows casting a specialized handle to a
std::coroutine_handle<void> */
template<typename P>
requires(std::is_void_v<Promise> && !std::is_void_v<P>)
UniqueHandle(UniqueHandle<P> &&src) noexcept
:value(src.release())
{
}

~UniqueHandle() noexcept {
if (value)
value.destroy();
}

auto &operator=(UniqueHandle<Promise> &&src) noexcept {
using std::swap;
swap(value, src.value);
return *this;
}

operator bool() const noexcept {
return (bool)value;
}

const auto &get() const noexcept {
return value;
}

const auto *operator->() const noexcept {
return &value;
}

#ifdef __clang__
/* the non-const overload is only needed for clang, because in
libc++11, some methods are not "const" */
auto *operator->() noexcept {
return &value;
}
#endif

[[nodiscard]]
auto release() noexcept {
return std::exchange(value, nullptr);
}
};

} // namespace Co
100 changes: 21 additions & 79 deletions src/command/StickerCommands.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "db/DatabaseLock.hxx"
#include <fmt/format.h>
#include "song/Filter.hxx"
#include "co/Generator.hxx"

namespace {

Expand Down Expand Up @@ -101,63 +102,30 @@ class DomainHandler {

virtual CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window) {
auto data = CallbackContext{
.name = name,
.sticker_type = sticker_type,
.response = response,
.is_song = StringIsEqual("song", sticker_type)
};

auto callback = [](const char *found_uri, const char *found_value, void *user_data) {
auto context = reinterpret_cast<CallbackContext *>(user_data);
context->response.Fmt("{}: {}\n",
context->is_song ? "file" : context->sticker_type, found_uri);
sticker_print_value(context->response, context->name, found_value);
};

sticker_database.Find(sticker_type,
uri,
name,
op, value,
sort, descending, window,
callback, &data);
const bool is_song = StringIsEqual("song", sticker_type);

for (const auto &i : sticker_database.Find(sticker_type, uri,
name, op, value,
sort, descending, window)) {
response.Fmt("{}: {}\n", is_song ? "file" : sticker_type, i.uri);
sticker_print_value(response, name, i.value);
}

return CommandResult::OK;
}

virtual CommandResult Names() {
auto data = CallbackContext{
.name = "",
.sticker_type = sticker_type,
.response = response,
.is_song = StringIsEqual("song", sticker_type)
};

auto callback = [](const char *found_value, void *user_data) {
auto context = reinterpret_cast<CallbackContext *>(user_data);
context->response.Fmt("name: {}\n", found_value);
};

sticker_database.Names(callback, &data);
for (const char *name : sticker_database.Names())
response.Fmt("name: {}\n", name);

return CommandResult::OK;
}

CommandResult NamesTypes(const char *type) {
auto data = CallbackContext{
.name = "",
.sticker_type = sticker_type,
.response = response,
.is_song = StringIsEqual("song", sticker_type)
};

auto callback = [](const char *found_value, const char *found_type, void *user_data) {
auto context = reinterpret_cast<CallbackContext *>(user_data);
context->response.Fmt("name: {}\n", found_value);
context->response.Fmt("type: {}\n", found_type);
};

sticker_database.NamesTypes(type, callback, &data);
for (const auto &i : sticker_database.NamesTypes(type)) {
response.Fmt("name: {}\n", i.value);
response.Fmt("type: {}\n", i.type);
}

return CommandResult::OK;
}
Expand Down Expand Up @@ -188,14 +156,6 @@ class DomainHandler {
Response &response;
const Database &database;
StickerDatabase &sticker_database;

private:
struct CallbackContext {
const char *const name;
const char *const sticker_type;
Response &response;
const bool is_song;
};
};

/**
Expand All @@ -216,15 +176,12 @@ class SongHandler final : public DomainHandler {

CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value,
const char *sort, bool descending, RangeArg window) override {
struct sticker_song_find_data data = {
response,
name,
};

sticker_song_find(sticker_database, database, uri, data.name,
op, value,
sort, descending, window,
sticker_song_find_print_cb, &data);
for (const auto &i : sticker_song_find(sticker_database, database, uri, name,
op, value,
sort, descending, window)) {
song_print_uri(response, i.song);
sticker_print_value(response, name, i.value);
}

return CommandResult::OK;
}
Expand All @@ -238,21 +195,6 @@ class SongHandler final : public DomainHandler {
}

private:
struct sticker_song_find_data {
Response &r;
const char *name;
};

static void
sticker_song_find_print_cb(const LightSong &song, const char *value,
void *user_data)
{
auto *data = (struct sticker_song_find_data *)user_data;

song_print_uri(data->r, song);
sticker_print_value(data->r, data->name, value);
}

const LightSong* song = nullptr;
};

Expand Down
31 changes: 31 additions & 0 deletions src/lib/sqlite/Generator.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project

#include "Generator.hxx"
#include "Row.hxx"
#include "Util.hxx"

#include <sqlite3.h>

namespace Sqlite {

Co::Generator<Row>
GenerateRows(sqlite3_stmt &stmt)
{
while (true) {
int result = ExecuteBusy(&stmt);
switch (result) {
case SQLITE_ROW:
co_yield Row{stmt};
break;

case SQLITE_DONE:
co_return;

default:
throw SqliteError{&stmt, result, "sqlite3_step() failed"};
}
}
}

} // namespace Sqlite
17 changes: 17 additions & 0 deletions src/lib/sqlite/Generator.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project

#pragma once

#include "co/Generator.hxx"

struct sqlite3_stmt;

namespace Sqlite {

class Row;

Co::Generator<Row>
GenerateRows(sqlite3_stmt &stmt);

} // namespace Sqlite
Loading
Loading