daemon: store/ query layer — tasks + settings (prep for the download.add vertical slice)
The read/write surface the dispatcher handlers need, so download.add persists and download.list / download.get project real rows when the engine lands. - store/tasks — TaskRow (1:1 with the schema), insert / get / remove / set_state / count, and list(filter, sort, offset, limit) that does all the WHERE / ORDER BY / LIMIT in SQL (M1 DoD: a 1000-row list never materialised client-side). Filter covers states / category / queue / case-insensitive filename+url substring / date range; sort is a whitelisted column + direction with NULLs last, default newest-first; the enum spellings in a state IN (...) come from proto::to_string, never from user text. to_summary() projects a row onto proto::TaskSummary including the flattened error block when the task failed / retry_wait / auto-paused. - store/settings — key -> JSON-text with a built-in default table mirroring Settings.schema.json / ADR 0012; get_raw / set_raw / overrides plus typed get_int / get_string / get_string_array for the governor config and saveTo.allowedRoots. Full settings.get/set wire projection lands with those handlers. - veloxd_store now links velox::proto + nlohmann_json for the projection. Test veloxd.store_tasks: insert/get round trip, PK duplicate rejected, the error-block projection, list total+paging+sort+every filter, set_state pause_reason clear-on-unpause, remove, and settings default vs override. ASan+UBSan and TSan clean; 34 daemon/cli tests green. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
#include "store/settings.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace velox::daemon::store {
|
||||
|
||||
namespace {
|
||||
|
||||
// Built-in defaults, mirroring Settings.schema.json / ADR 0012. Only the keys the daemon
|
||||
// currently reads or is likely to need before the full settings.get handler lands; the
|
||||
// rest resolve through the schema's own defaults at that layer.
|
||||
constexpr std::array<std::pair<std::string_view, std::string_view>, 14> kDefaults{{
|
||||
{"connection.maxSegmentsPerDownload", "8"},
|
||||
{"connection.bufferBytes", "1048576"},
|
||||
{"connection.maxConcurrentDownloads", "5"},
|
||||
{"connection.maxActiveSegments", "32"},
|
||||
{"connection.maxTotalBufferBytes", "134217728"},
|
||||
{"connection.timeoutSec", "30"},
|
||||
{"connection.maxRetries", "10"},
|
||||
{"connection.retryBackoffSec", "5"},
|
||||
{"saveTo.defaultDir", "\"~/Downloads\""},
|
||||
{"saveTo.allowedRoots", "[\"~/Downloads\"]"},
|
||||
{"saveTo.createSubfolderPerSite", "false"},
|
||||
{"downloads.speedLimitBps", "0"},
|
||||
{"downloads.speedLimitEnabled", "false"},
|
||||
{"downloads.duplicatePolicy", "\"ask\""},
|
||||
}};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<std::string_view> Settings::default_for(std::string_view key) {
|
||||
for (const auto& [k, v] : kDefaults) {
|
||||
if (k == key) return v;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DbResult<std::optional<std::string>> 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<std::string>{st->column_text(0)};
|
||||
if (auto d = default_for(key)) return std::optional<std::string>{std::string(*d)};
|
||||
return std::optional<std::string>{};
|
||||
}
|
||||
|
||||
DbResult<void> 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<std::map<std::string, std::string>> Settings::overrides() {
|
||||
auto st = db_.prepare("SELECT key, value FROM settings ORDER BY key");
|
||||
if (!st) return std::unexpected(st.error());
|
||||
std::map<std::string, std::string> 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<std::int64_t>();
|
||||
}
|
||||
if (auto d = default_for(key)) {
|
||||
auto j = nlohmann::json::parse(*d, nullptr, false);
|
||||
if (j.is_number_integer()) return j.get<std::int64_t>();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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<std::string>();
|
||||
}
|
||||
if (auto d = default_for(key)) {
|
||||
auto j = nlohmann::json::parse(*d, nullptr, false);
|
||||
if (j.is_string()) return j.get<std::string>();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> 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<std::string> out;
|
||||
if (j.is_array()) {
|
||||
for (const auto& e : j) {
|
||||
if (e.is_string()) out.push_back(e.get<std::string>());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace velox::daemon::store
|
||||
Reference in New Issue
Block a user