+112
-15
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <vamp_zero/errors.hpp>
|
||||
#include <vamp_zero/library.hpp>
|
||||
#include <vamp_zero/score.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -108,6 +109,31 @@ auto now_ms() -> std::int64_t {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
auto execute_delete_stale_catalog_rows(noto::connection& connection, std::string_view table, std::vector<std::string> const& ids) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto sql = std::string{"DELETE FROM "};
|
||||
sql += table;
|
||||
sql += " WHERE catalog_managed = 1";
|
||||
auto params = std::vector<noto::sqlite_value>{};
|
||||
params.reserve(ids.size());
|
||||
if (!ids.empty()) {
|
||||
sql += " AND id NOT IN (";
|
||||
for (auto index = std::size_t{}; index < ids.size(); ++index) {
|
||||
if (index != 0U) {
|
||||
sql += ", ";
|
||||
}
|
||||
sql += "?";
|
||||
sql += std::to_string(index + 1U);
|
||||
params.emplace_back(ids[index]);
|
||||
}
|
||||
sql += ")";
|
||||
}
|
||||
auto deleted = co_await connection.async_execute(std::move(sql), std::move(params));
|
||||
if (!deleted) {
|
||||
co_return std::unexpected(deleted.error());
|
||||
}
|
||||
co_return std::expected<void, modulus::error>{};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto vamp_zero::split_migration_statements(std::string const& input) -> std::expected<std::vector<std::string>, modulus::error> {
|
||||
@@ -203,6 +229,10 @@ auto vamp_zero::async_migrate_database(boost::asio::any_io_executor executor, va
|
||||
if (!practice_hierarchy_statements) {
|
||||
co_return std::unexpected(practice_hierarchy_statements.error());
|
||||
}
|
||||
auto catalog_managed_statements = read_migration(config, "005_catalog_managed_rows.sql");
|
||||
if (!catalog_managed_statements) {
|
||||
co_return std::unexpected(catalog_managed_statements.error());
|
||||
}
|
||||
auto migrator = noto::migrator{connection};
|
||||
auto migrations = std::vector<noto::migration>{};
|
||||
auto foundation = noto::migration{};
|
||||
@@ -237,12 +267,20 @@ auto vamp_zero::async_migrate_database(boost::asio::any_io_executor executor, va
|
||||
co_return co_await execute_statements(connection, statements);
|
||||
};
|
||||
migrations.push_back(std::move(practice_hierarchy));
|
||||
auto catalog_managed = noto::migration{};
|
||||
catalog_managed.id = "005_catalog_managed_rows";
|
||||
catalog_managed.description = "mark rows managed by bundled catalog import";
|
||||
catalog_managed.checksum = "005_catalog_managed_rows_v1";
|
||||
catalog_managed.up = [statements = std::move(*catalog_managed_statements)](noto::connection& connection) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
co_return co_await execute_statements(connection, statements);
|
||||
};
|
||||
migrations.push_back(std::move(catalog_managed));
|
||||
|
||||
auto migrated = co_await migrator.async_migrate(std::move(migrations));
|
||||
if (!migrated) {
|
||||
co_return std::unexpected(migrated.error());
|
||||
}
|
||||
auto version = co_await connection.async_set_user_version(4);
|
||||
auto version = co_await connection.async_set_user_version(5);
|
||||
if (!version) {
|
||||
co_return std::unexpected(version.error());
|
||||
}
|
||||
@@ -268,21 +306,14 @@ auto vamp_zero::async_import_catalogs(boost::asio::any_io_executor executor, vam
|
||||
co_return std::unexpected(begin.error());
|
||||
}
|
||||
auto imported = std::expected<void, modulus::error>{};
|
||||
auto cleared = co_await connection.async_execute("DELETE FROM songs");
|
||||
if (!cleared) {
|
||||
imported = std::unexpected(cleared.error());
|
||||
}
|
||||
if (imported) {
|
||||
auto cleared_practice = co_await connection.async_execute("DELETE FROM practice_exercises");
|
||||
if (!cleared_practice) {
|
||||
imported = std::unexpected(cleared_practice.error());
|
||||
}
|
||||
}
|
||||
auto timestamp = now_ms();
|
||||
auto song_ids = std::vector<std::string>{};
|
||||
song_ids.reserve(performance->parts.size());
|
||||
for (auto const& part : performance->parts) {
|
||||
if (!imported) {
|
||||
break;
|
||||
}
|
||||
song_ids.push_back(part.id);
|
||||
auto params = std::vector<noto::sqlite_value>{
|
||||
noto::sqlite_value{part.id},
|
||||
noto::sqlite_value{part.title},
|
||||
@@ -294,16 +325,41 @@ auto vamp_zero::async_import_catalogs(boost::asio::any_io_executor executor, vam
|
||||
noto::sqlite_value{part.source_path},
|
||||
noto::sqlite_value{part.key_signature},
|
||||
};
|
||||
auto inserted = co_await connection.async_execute("INSERT INTO songs(id, title, composer, source_json, created_at_ms, updated_at_ms, part, source_path, key_signature) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", std::move(params));
|
||||
auto inserted = co_await connection.async_execute(
|
||||
R"sql(
|
||||
INSERT INTO songs(id, title, composer, source_json, created_at_ms, updated_at_ms, part, source_path, key_signature, catalog_managed)
|
||||
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 1)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
composer = excluded.composer,
|
||||
source_json = excluded.source_json,
|
||||
part = excluded.part,
|
||||
source_path = excluded.source_path,
|
||||
key_signature = excluded.key_signature,
|
||||
catalog_managed = 1,
|
||||
updated_at_ms = CASE
|
||||
WHEN songs.title != excluded.title
|
||||
OR songs.composer != excluded.composer
|
||||
OR songs.source_json != excluded.source_json
|
||||
OR songs.part != excluded.part
|
||||
OR songs.source_path != excluded.source_path
|
||||
OR songs.key_signature != excluded.key_signature
|
||||
THEN excluded.updated_at_ms
|
||||
ELSE songs.updated_at_ms
|
||||
END
|
||||
)sql",
|
||||
std::move(params));
|
||||
if (!inserted) {
|
||||
imported = std::unexpected(inserted.error());
|
||||
}
|
||||
}
|
||||
auto practice_ids = std::vector<std::string>{};
|
||||
for (auto const& exercise : practice->exercises) {
|
||||
if (!imported) {
|
||||
break;
|
||||
}
|
||||
for (auto const& variant : exercise.variants) {
|
||||
practice_ids.push_back(variant.id);
|
||||
auto params = std::vector<noto::sqlite_value>{
|
||||
noto::sqlite_value{variant.id},
|
||||
noto::sqlite_value{exercise.practice_type_id},
|
||||
@@ -318,13 +374,54 @@ auto vamp_zero::async_import_catalogs(boost::asio::any_io_executor executor, vam
|
||||
noto::sqlite_value{timestamp},
|
||||
noto::sqlite_value{timestamp},
|
||||
};
|
||||
auto inserted = co_await connection.async_execute("INSERT INTO practice_exercises(id, practice_type_id, practice_type_title, lesson_group_id, lesson_group_title, title, composer, key_signature, source_json, source_path, created_at_ms, updated_at_ms) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)", std::move(params));
|
||||
auto inserted = co_await connection.async_execute(
|
||||
R"sql(
|
||||
INSERT INTO practice_exercises(id, practice_type_id, practice_type_title, lesson_group_id, lesson_group_title, title, composer, key_signature, source_json, source_path, created_at_ms, updated_at_ms, catalog_managed)
|
||||
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, 1)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
practice_type_id = excluded.practice_type_id,
|
||||
practice_type_title = excluded.practice_type_title,
|
||||
lesson_group_id = excluded.lesson_group_id,
|
||||
lesson_group_title = excluded.lesson_group_title,
|
||||
title = excluded.title,
|
||||
composer = excluded.composer,
|
||||
key_signature = excluded.key_signature,
|
||||
source_json = excluded.source_json,
|
||||
source_path = excluded.source_path,
|
||||
catalog_managed = 1,
|
||||
updated_at_ms = CASE
|
||||
WHEN practice_exercises.practice_type_id != excluded.practice_type_id
|
||||
OR practice_exercises.practice_type_title != excluded.practice_type_title
|
||||
OR practice_exercises.lesson_group_id != excluded.lesson_group_id
|
||||
OR practice_exercises.lesson_group_title != excluded.lesson_group_title
|
||||
OR practice_exercises.title != excluded.title
|
||||
OR practice_exercises.composer != excluded.composer
|
||||
OR practice_exercises.key_signature != excluded.key_signature
|
||||
OR practice_exercises.source_json != excluded.source_json
|
||||
OR practice_exercises.source_path != excluded.source_path
|
||||
THEN excluded.updated_at_ms
|
||||
ELSE practice_exercises.updated_at_ms
|
||||
END
|
||||
)sql",
|
||||
std::move(params));
|
||||
if (!inserted) {
|
||||
imported = std::unexpected(inserted.error());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (imported) {
|
||||
auto deleted = co_await execute_delete_stale_catalog_rows(connection, "songs", song_ids);
|
||||
if (!deleted) {
|
||||
imported = std::unexpected(deleted.error());
|
||||
}
|
||||
}
|
||||
if (imported) {
|
||||
auto deleted = co_await execute_delete_stale_catalog_rows(connection, "practice_exercises", practice_ids);
|
||||
if (!deleted) {
|
||||
imported = std::unexpected(deleted.error());
|
||||
}
|
||||
}
|
||||
if (imported) {
|
||||
auto committed = co_await connection.async_execute("COMMIT");
|
||||
if (!committed) {
|
||||
@@ -380,7 +477,7 @@ auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor exec
|
||||
.source_path = text_value(row[4]),
|
||||
.source_json = std::move(source_json),
|
||||
.measure_count = parsed->measures.size(),
|
||||
.total_pages = parsed->measures.empty() ? 1U : (parsed->measures.size() + 11U) / 12U,
|
||||
.total_pages = vamp_zero::score_page_count(*parsed),
|
||||
});
|
||||
}
|
||||
auto closed = co_await connection.async_close();
|
||||
@@ -428,7 +525,7 @@ auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executo
|
||||
.source_path = source_path,
|
||||
.source_json = source_json,
|
||||
.measure_count = parsed->measures.size(),
|
||||
.total_pages = parsed->measures.empty() ? 1U : (parsed->measures.size() + 11U) / 12U,
|
||||
.total_pages = vamp_zero::score_page_count(*parsed),
|
||||
};
|
||||
exercises.push_back({
|
||||
.id = logical_practice_exercise_id(type_id, lesson_id, source_path, text_value(row[5])),
|
||||
|
||||
Reference in New Issue
Block a user