Files
radiantwave/src/db/database_manager.h
T

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