#pragma once #include #include #include #include #include #include #include namespace radiantwave::db { class DatabaseManager; } namespace radiantwave::audio { // One entry from the audio checksums manifest. // // Supported line formats (in priority order): // '' // current // // legacy fallback // // hz is the actual frequency value of the track (e.g. 396), written by hand // or by a script that has no access to the frequency-id table; 0 means // "none / unset". The manifest author may also write the frequency id (or any // unrecognized number): only values that resolve to a known frequency are // honored, anything else falls back to the Hz parsed from the filename, so // the file's own name is always the safety net. struct AudioManifestEntry { std::string filename; std::string display_name; int solfeggio_frequency_hz = 0; std::string md5; }; // Downloads audio assets from files.radiantwavetech.com in the background // and keeps the database's audio_files table in sync with what is actually // on disk. // // Fetches checksums.md5, compares each entry against the local file's MD5, // and re-downloads anything that is missing or whose checksum differs. // Files present on disk but absent from the manifest are deleted, and the // audio_files table is reconciled on every pass (and after each individual // download) so the settings audio list reflects live progress: a file that // just finished downloading appears in the list immediately, and files not // yet downloaded are not listed. The worker runs a persistent loop: if // there is no internet at startup the status shows "Waiting for internet..." // and a download starts automatically once connectivity returns. When all // files verify it keeps re-checking the manifest periodically so changed // checksums are picked up while running. class AudioDownloader { public: AudioDownloader() = default; ~AudioDownloader(); AudioDownloader(const AudioDownloader &) = delete; AudioDownloader &operator=(const AudioDownloader &) = delete; // Spawns the background thread. Safe to call repeatedly; only one worker // runs at a time. The manifest is reconciled against `db` on every pass. void start(db::DatabaseManager &db); // Signals the worker to stop and joins the thread. void shutdown(); // Returns the current human-readable status for display (e.g. footer). std::string statusString() const; static constexpr const char *AUDIO_BASE_URL = "https://files.radiantwavetech.com/audio/"; static constexpr const char *MANIFEST_FILENAME = "checksums.md5"; private: void run(); bool fetchManifest(std::vector &out); bool downloadAndVerify(const std::string &filename, const std::string &expectedMd5, const std::string &displayName); void setStatus(const std::string &msg); void wait(std::chrono::seconds seconds); mutable std::mutex mutex_; std::condition_variable cv_; std::thread worker_; std::atomic stop_{false}; std::string status_; db::DatabaseManager *db_ = nullptr; }; // Parses checksums.md5 manifest text into entries. Blank lines and lines // starting with '#' are ignored. Lines that don't match a known format are // skipped. Returns false if no valid entries were found. bool parseManifest(const std::string &text, std::vector &out); // Maps a frequency value in Hz to its internal solfeggio frequency id using // the same table the color palettes are seeded from (174/285->1, 396->2, // 417->3, 432->4, 528->5, 639->6, 741->7, 852->8, 963->9). Returns 0 for // frequencies with no id (binaurals, AlphaWaves, OriginalAudio, ...). int frequencyIdForHz(int hz); // Parses the leading frequency value out of an audio filename like // "396Hz.wav" -> 396. Requires the digits to be followed by "Hz" // (case-insensitive); returns 0 when the name has no frequency (e.g. // "AlphaWaves.wav", "OriginalAudio.wav"). int hzFromFilename(const std::string &filename); // Computes the lowercase MD5 hex of a file, or an empty string on failure. std::string computeFileMd5(const std::string &path); } // namespace radiantwave::audio