#include "store/settings.hpp" #include #include #include namespace velox::daemon::store { namespace { // Built-in defaults, one per SettingKey (Settings.schema.json / SettingKey.schema.json). // The schema itself carries no "default" keyword anywhere — these are what settings.get // falls back to for a key with no stored row, chosen per ADR 0012 where it speaks (the // connection.* buffer/segment keys) and otherwise the conservative, least-surprising // value for that key's own description. capture.monitoredExtensions defaults to the union // of every builtin category's extensions (0001_initial.sql's seed) rather than an // arbitrary list of its own, so the two stay in sync without a second place to edit. constexpr std::array, 43> kDefaults{{ {"general.launchOnLogin", "false"}, {"general.minimizeToTray", "false"}, {"general.showDropTarget", "true"}, {"general.confirmOnExit", "true"}, {"general.language", "\"system\""}, {"general.checkForUpdates", "true"}, {"capture.enabled", "true"}, {"capture.monitoredExtensions", "[\"exe\",\"msi\",\"deb\",\"rpm\",\"dmg\",\"appimage\",\"iso\",\"zip\",\"tar\",\"gz\"," "\"xz\",\"7z\",\"mp4\",\"mkv\",\"webm\",\"avi\",\"mov\",\"flv\",\"m4v\",\"ts\",\"mp3\"," "\"flac\",\"aac\",\"ogg\",\"opus\",\"wav\",\"m4a\",\"pdf\",\"doc\",\"docx\",\"xls\"," "\"xlsx\",\"ppt\",\"pptx\",\"odt\",\"epub\",\"jpg\",\"jpeg\",\"png\",\"gif\",\"webp\"," "\"svg\",\"bmp\",\"tiff\"]"}, {"capture.monitoredMimeTypes", "[]"}, {"capture.minSizeBytes", "0"}, {"capture.excludedHosts", "[]"}, {"capture.bypassModifier", "\"shift\""}, {"capture.autoStartTypes", "[]"}, {"saveTo.defaultDir", "\"~/Downloads\""}, {"saveTo.tempDir", "\"\""}, {"saveTo.allowedRoots", "[\"~/Downloads\"]"}, {"saveTo.fileExistsPolicy", "\"ask\""}, {"saveTo.createSubfolderPerSite", "false"}, {"connection.preset", "\"auto\""}, {"connection.maxSegmentsPerDownload", "8"}, {"connection.bufferBytes", "1048576"}, {"connection.maxTotalBufferBytes", "134217728"}, {"connection.maxActiveSegments", "32"}, {"connection.maxConcurrentDownloads", "5"}, {"connection.timeoutSec", "30"}, {"connection.maxRetries", "10"}, {"connection.retryBackoffSec", "5"}, {"downloads.speedLimitBps", "0"}, {"downloads.speedLimitEnabled", "false"}, {"downloads.virusScanCommand", "\"\""}, {"downloads.postDownloadCommand", "\"\""}, {"downloads.duplicatePolicy", "\"ask\""}, {"downloads.verifyChecksums", "true"}, {"proxy.mode", "\"system\""}, {"proxy.host", "\"\""}, {"proxy.port", "1"}, {"proxy.username", "\"\""}, {"proxy.bypassHosts", "[]"}, {"proxy.pacUrl", "\"\""}, {"sounds.enabled", "true"}, {"sounds.onComplete", "\"\""}, {"sounds.onQueueComplete", "\"\""}, {"sounds.onError", "\"\""}, }}; } // namespace std::optional Settings::default_for(std::string_view key) { for (const auto& [k, v] : kDefaults) { if (k == key) return v; } return std::nullopt; } DbResult> Settings::get_raw(std::string_view key) { auto st = db_.prepare("SELECT value FROM settings WHERE key = ?1"); if (!st) return std::unexpected(st.error()); if (auto r = st->bind(1, key); !r) return std::unexpected(r.error()); auto row = st->step(); if (!row) return std::unexpected(row.error()); if (*row) return std::optional{st->column_text(0)}; if (auto d = default_for(key)) return std::optional{std::string(*d)}; return std::optional{}; } DbResult Settings::set_raw(std::string_view key, std::string_view json_text) { auto st = db_.prepare( "INSERT INTO settings(key, value) VALUES(?1, ?2) " "ON CONFLICT(key) DO UPDATE SET value = excluded.value"); if (!st) return std::unexpected(st.error()); if (auto r = st->bind(1, key); !r) return std::unexpected(r.error()); if (auto r = st->bind(2, json_text); !r) return std::unexpected(r.error()); if (auto r = st->step(); !r) return std::unexpected(r.error()); return {}; } DbResult> Settings::overrides() { auto st = db_.prepare("SELECT key, value FROM settings ORDER BY key"); if (!st) return std::unexpected(st.error()); std::map out; for (;;) { auto row = st->step(); if (!row) return std::unexpected(row.error()); if (!*row) break; out.emplace(st->column_text(0), st->column_text(1)); } return out; } std::int64_t Settings::get_int(std::string_view key) { auto raw = get_raw(key); if (raw && *raw) { auto j = nlohmann::json::parse(**raw, nullptr, false); if (j.is_number_integer()) return j.get(); } if (auto d = default_for(key)) { auto j = nlohmann::json::parse(*d, nullptr, false); if (j.is_number_integer()) return j.get(); } return 0; } bool Settings::get_bool(std::string_view key) { auto raw = get_raw(key); if (raw && *raw) { auto j = nlohmann::json::parse(**raw, nullptr, false); if (j.is_boolean()) return j.get(); } if (auto d = default_for(key)) { auto j = nlohmann::json::parse(*d, nullptr, false); if (j.is_boolean()) return j.get(); } return false; } std::string Settings::get_string(std::string_view key) { auto raw = get_raw(key); if (raw && *raw) { auto j = nlohmann::json::parse(**raw, nullptr, false); if (j.is_string()) return j.get(); } if (auto d = default_for(key)) { auto j = nlohmann::json::parse(*d, nullptr, false); if (j.is_string()) return j.get(); } return {}; } std::vector Settings::get_string_array(std::string_view key) { auto raw = get_raw(key); const std::string text = (raw && *raw) ? **raw : default_for(key) ? std::string(*default_for(key)) : std::string("[]"); auto j = nlohmann::json::parse(text, nullptr, false); std::vector out; if (j.is_array()) { for (const auto& e : j) { if (e.is_string()) out.push_back(e.get()); } } return out; } } // namespace velox::daemon::store