Add schedule sync and schedule-aware playback
This commit is contained in:
@@ -181,6 +181,23 @@ bool DatabaseManager::runMigrations() {
|
||||
hex TEXT NOT NULL,
|
||||
PRIMARY KEY (solfeggio_frequency_id, color_index)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schedule_blocks (
|
||||
id TEXT PRIMARY KEY,
|
||||
start_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
end_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
overnight INTEGER NOT NULL DEFAULT 0,
|
||||
duration_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
dynamic_audio INTEGER NOT NULL DEFAULT 1,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schedule_block_proclamations (
|
||||
block_id TEXT NOT NULL,
|
||||
position INTEGER NOT NULL,
|
||||
proclamation_id TEXT NOT NULL,
|
||||
PRIMARY KEY (block_id, position)
|
||||
);
|
||||
)";
|
||||
|
||||
bool ok = executeSql(schema);
|
||||
@@ -1083,4 +1100,115 @@ std::vector<AudioFileRecord> DatabaseManager::getAudioFiles() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ─── Schedule Blocks Table ─────────────────────────────────────────────
|
||||
|
||||
bool DatabaseManager::replaceScheduleBlocks(const std::vector<ScheduleBlockRecord>& records) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!db_) return false;
|
||||
|
||||
if (sqlite3_exec(db_, "BEGIN;", nullptr, nullptr, nullptr) != SQLITE_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
if (sqlite3_exec(db_, "DELETE FROM schedule_block_proclamations;", nullptr, nullptr, nullptr) != SQLITE_OK ||
|
||||
sqlite3_exec(db_, "DELETE FROM schedule_blocks;", nullptr, nullptr, nullptr) != SQLITE_OK) {
|
||||
sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* blockSql = "INSERT INTO schedule_blocks "
|
||||
"(id, start_minutes, end_minutes, overnight, duration_minutes, "
|
||||
" dynamic_audio, sort_order) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?);";
|
||||
const char* procSql = "INSERT INTO schedule_block_proclamations "
|
||||
"(block_id, position, proclamation_id) VALUES (?, ?, ?);";
|
||||
|
||||
for (size_t i = 0; i < records.size() && ok; ++i) {
|
||||
const ScheduleBlockRecord& rec = records[i];
|
||||
|
||||
sqlite3_stmt* blockStmt = nullptr;
|
||||
if (sqlite3_prepare_v2(db_, blockSql, -1, &blockStmt, nullptr) != SQLITE_OK) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
sqlite3_bind_text(blockStmt, 1, rec.id.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int(blockStmt, 2, rec.start_minutes);
|
||||
sqlite3_bind_int(blockStmt, 3, rec.end_minutes);
|
||||
sqlite3_bind_int(blockStmt, 4, rec.overnight ? 1 : 0);
|
||||
sqlite3_bind_int(blockStmt, 5, rec.duration_minutes);
|
||||
sqlite3_bind_int(blockStmt, 6, rec.dynamic_audio ? 1 : 0);
|
||||
sqlite3_bind_int(blockStmt, 7, static_cast<int>(i));
|
||||
if (sqlite3_step(blockStmt) != SQLITE_DONE) ok = false;
|
||||
sqlite3_finalize(blockStmt);
|
||||
if (!ok) break;
|
||||
|
||||
for (size_t p = 0; p < rec.proclamation_ids.size(); ++p) {
|
||||
sqlite3_stmt* procStmt = nullptr;
|
||||
if (sqlite3_prepare_v2(db_, procSql, -1, &procStmt, nullptr) != SQLITE_OK) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
sqlite3_bind_text(procStmt, 1, rec.id.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int(procStmt, 2, static_cast<int>(p));
|
||||
sqlite3_bind_text(procStmt, 3, rec.proclamation_ids[p].c_str(), -1, SQLITE_TRANSIENT);
|
||||
if (sqlite3_step(procStmt) != SQLITE_DONE) ok = false;
|
||||
sqlite3_finalize(procStmt);
|
||||
if (!ok) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, nullptr);
|
||||
} else {
|
||||
sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::vector<ScheduleBlockRecord> DatabaseManager::getScheduleBlocks() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<ScheduleBlockRecord> result;
|
||||
if (!db_) return result;
|
||||
|
||||
const char* sql = "SELECT id, start_minutes, end_minutes, overnight, duration_minutes, "
|
||||
"dynamic_audio FROM schedule_blocks ORDER BY sort_order ASC;";
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
ScheduleBlockRecord rec;
|
||||
rec.id = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
|
||||
rec.start_minutes = sqlite3_column_int(stmt, 1);
|
||||
rec.end_minutes = sqlite3_column_int(stmt, 2);
|
||||
rec.overnight = (sqlite3_column_int(stmt, 3) != 0);
|
||||
rec.duration_minutes = sqlite3_column_int(stmt, 4);
|
||||
rec.dynamic_audio = (sqlite3_column_int(stmt, 5) != 0);
|
||||
result.push_back(std::move(rec));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Attach each block's proclamation ids in play order.
|
||||
const char* procSql = "SELECT proclamation_id FROM schedule_block_proclamations "
|
||||
"WHERE block_id = ? ORDER BY position ASC;";
|
||||
for (auto& block : result) {
|
||||
sqlite3_stmt* procStmt = nullptr;
|
||||
if (sqlite3_prepare_v2(db_, procSql, -1, &procStmt, nullptr) != SQLITE_OK) {
|
||||
break;
|
||||
}
|
||||
sqlite3_bind_text(procStmt, 1, block.id.c_str(), -1, SQLITE_TRANSIENT);
|
||||
while (sqlite3_step(procStmt) == SQLITE_ROW) {
|
||||
const unsigned char* text = sqlite3_column_text(procStmt, 0);
|
||||
if (text) {
|
||||
block.proclamation_ids.push_back(reinterpret_cast<const char*>(text));
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(procStmt);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace radiantwave::db
|
||||
|
||||
Reference in New Issue
Block a user