200 lines
8.2 KiB
C++
200 lines
8.2 KiB
C++
#pragma once
|
|
|
|
#include <sqlite3.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <mutex>
|
|
|
|
namespace radiantwave::db {
|
|
|
|
struct ProclamationRecord {
|
|
std::string id;
|
|
std::string title;
|
|
std::string content;
|
|
double solfeggio_freq_hz = 528.0;
|
|
std::string category = "general";
|
|
bool is_active = true;
|
|
bool selected = false;
|
|
int category_id = 0; // maps to ProclamationCategoryRecord::id
|
|
int group_id = 0; // maps to ProclamationGroupRecord::id (0 = ungrouped)
|
|
int solfeggio_frequency_id = 0; // maps to the solfeggio frequency preset
|
|
};
|
|
|
|
struct ProclamationCategoryRecord {
|
|
int id = 0;
|
|
std::string name;
|
|
int sort_order = 0;
|
|
};
|
|
|
|
// A group is a tag-like organizer: it is not owned by a category. A
|
|
// proclamation belongs to one category and may additionally belong to one
|
|
// group. Within the UI, a group appears under a category only when at least
|
|
// one proclamation pairs that (category_id, group_id).
|
|
struct ProclamationGroupRecord {
|
|
int id = 0;
|
|
std::string name;
|
|
int sort_order = 0;
|
|
};
|
|
|
|
struct AssetDownloadRecord {
|
|
std::string id;
|
|
std::string filename;
|
|
std::string url;
|
|
std::string status = "pending"; // pending, downloading, completed, failed
|
|
int64_t bytes_downloaded = 0;
|
|
int64_t total_bytes = 0;
|
|
std::string error_message = "";
|
|
};
|
|
|
|
struct AudioFileRecord {
|
|
int id = 0;
|
|
std::string display_name;
|
|
std::string filename;
|
|
int display_order = 0;
|
|
int solfeggio_frequency_id = 0; // 0 = not associated with a solfeggio frequency
|
|
};
|
|
|
|
// One scheduled listening block, mirrored from GET /schedule. `proclamation_ids`
|
|
// is the ordered play list for the block.
|
|
struct ScheduleBlockRecord {
|
|
std::string id;
|
|
int start_minutes = 0;
|
|
int end_minutes = 0;
|
|
bool overnight = false;
|
|
int duration_minutes = 0;
|
|
bool dynamic_audio = true; // override the device audio for this block
|
|
std::vector<std::string> proclamation_ids;
|
|
};
|
|
|
|
// A single color in a solfeggio frequency's color palette (RGBA).
|
|
struct PaletteColor {
|
|
uint8_t r = 0;
|
|
uint8_t g = 0;
|
|
uint8_t b = 0;
|
|
uint8_t a = 255;
|
|
};
|
|
|
|
// The ordered palette for one solfeggio frequency (as stored in the
|
|
// color_palettes table, keyed by color_index).
|
|
struct PaletteRecord {
|
|
int solfeggio_frequency_id = 0;
|
|
std::vector<PaletteColor> colors;
|
|
};
|
|
|
|
class DatabaseManager {
|
|
public:
|
|
DatabaseManager();
|
|
~DatabaseManager();
|
|
|
|
// Non-copyable
|
|
DatabaseManager(const DatabaseManager&) = delete;
|
|
DatabaseManager& operator=(const DatabaseManager&) = delete;
|
|
|
|
// Initialize & open database at path (default: ~/.local/radiantwave/radiantwave.db)
|
|
bool initialize(const std::string& custom_path = "");
|
|
|
|
// Close database connection
|
|
void close();
|
|
|
|
// True once initialize() has opened the database.
|
|
bool isOpen() const;
|
|
|
|
// ── Key-Value Storage (system_kv table) ───────────────────────────
|
|
bool setKV(const std::string& key, const std::string& value);
|
|
std::string getKV(const std::string& key, const std::string& defaultValue = "") const;
|
|
bool hasKV(const std::string& key);
|
|
bool deleteKV(const std::string& key);
|
|
|
|
// ── Proclamations Table ───────────────────────────────────────────
|
|
bool saveProclamation(const ProclamationRecord& record);
|
|
std::vector<ProclamationRecord> getActiveProclamations();
|
|
|
|
// Full sync: replaces all categories with the given set.
|
|
bool replaceCategories(const std::vector<ProclamationCategoryRecord>& records);
|
|
|
|
// Full sync: replaces all groups with the given set.
|
|
bool replaceGroups(const std::vector<ProclamationGroupRecord>& records);
|
|
|
|
// Full sync: upserts proclamations, deletes any not in `records`,
|
|
// preserves the existing `selected` state, and forces the proclamation
|
|
// identified by `defaultProclamationId` to remain selected.
|
|
bool syncProclamations(const std::vector<ProclamationRecord>& records,
|
|
const std::string& defaultProclamationId);
|
|
|
|
// Fetch all proclamations (regardless of is_active).
|
|
std::vector<ProclamationRecord> getAllProclamations();
|
|
|
|
// Fetch all categories ordered by sort_order then id.
|
|
std::vector<ProclamationCategoryRecord> getAllCategories();
|
|
|
|
// Fetch all groups ordered by sort_order then id.
|
|
std::vector<ProclamationGroupRecord> getAllGroups();
|
|
|
|
// Toggle a single proclamation's selected flag.
|
|
bool setProclamationSelected(const std::string& id, bool selected);
|
|
|
|
// Atomically replace the selection with `selectedIds`, then force the
|
|
// default proclamation to remain selected.
|
|
bool saveSelectedProclamations(const std::vector<std::string>& selectedIds,
|
|
const std::string& defaultProclamationId);
|
|
|
|
// Force the default proclamation to be flagged selected in the database.
|
|
bool ensureDefaultProclamationSelected(const std::string& defaultProclamationId);
|
|
|
|
// ── Asset Downloads Table ─────────────────────────────────────────
|
|
bool updateAssetDownload(const AssetDownloadRecord& record);
|
|
std::vector<AssetDownloadRecord> getPendingAssetDownloads();
|
|
|
|
// ── Audio Files Table ─────────────────────────────────────────────
|
|
// Reconciles audio_files to the given set (the on-disk audio files).
|
|
// Upserts by filename with the manifest as source of truth for
|
|
// display_name / display_order / solfeggio_frequency_id, and deletes
|
|
// any row whose filename is not in `records`.
|
|
bool syncAudioFiles(const std::vector<AudioFileRecord>& records);
|
|
std::vector<AudioFileRecord> getAudioFiles();
|
|
|
|
// ── Color Palettes Table ──────────────────────────────────────────
|
|
// Returns the 7-color palette for a solfeggio frequency id, ordered by
|
|
// color_index. Falls back to the 528 Hz palette when the id has no rows.
|
|
std::vector<PaletteColor> getPaletteForFrequencyId(int solfeggio_frequency_id);
|
|
|
|
// Full sync: replaces the entire color_palettes table with `records`
|
|
// (the portal is the source of truth for palettes). Pass an empty vector
|
|
// to clear all palettes. Colors are stored as "#RRGGBB" hex strings.
|
|
//
|
|
// NOTE: not currently called from the app flow. This is readiness code so
|
|
// palettes can be pushed from the portal once the API route exists.
|
|
bool replaceColorPalettes(const std::vector<PaletteRecord>& records);
|
|
|
|
// ── Schedule Blocks Table ─────────────────────────────────────────
|
|
// Full sync: replaces the stored schedule with `records` (the portal is the
|
|
// source of truth). Pass an empty vector to clear the schedule. Block order
|
|
// and per-block proclamation order are preserved exactly as given.
|
|
bool replaceScheduleBlocks(const std::vector<ScheduleBlockRecord>& records);
|
|
|
|
// Fetch all schedule blocks, ordered as stored, with their proclamation ids
|
|
// in play order.
|
|
std::vector<ScheduleBlockRecord> getScheduleBlocks();
|
|
|
|
// ── Event Logging Table ───────────────────────────────────────────
|
|
bool logEvent(const std::string& level, const std::string& component, const std::string& message);
|
|
|
|
private:
|
|
std::string resolvePath(const std::string& raw_path) const;
|
|
bool runMigrations();
|
|
bool migrateSubCategoriesToGroups();
|
|
bool executeSql(const std::string& sql);
|
|
bool columnExists(const std::string& table, const std::string& column);
|
|
bool ensureColumn(const std::string& table, const std::string& column,
|
|
const std::string& column_ddl);
|
|
void seedColorPalettes();
|
|
void bindProclamationRecord(sqlite3_stmt* stmt, const ProclamationRecord& record);
|
|
bool ensureDefaultProclamationSelectedUnlocked(const std::string& defaultProclamationId);
|
|
|
|
sqlite3* db_ = nullptr;
|
|
std::string db_path_;
|
|
mutable std::mutex mutex_;
|
|
};
|
|
|
|
} // namespace radiantwave::db
|