19#ifdef PACS_WITH_DATABASE_SYSTEM
28 : base_repository(std::
move(db),
"remote_nodes",
"node_id") {}
34auto node_repository::parse_timestamp(
const std::string& str)
const
35 -> std::chrono::system_clock::time_point {
41 if (std::sscanf(str.c_str(),
"%d-%d-%d %d:%d:%d", &tm.tm_year, &tm.tm_mon,
42 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
50 auto time = _mkgmtime(&tm);
52 auto time = timegm(&tm);
55 return std::chrono::system_clock::from_time_t(time);
58auto node_repository::format_timestamp(
59 std::chrono::system_clock::time_point tp)
const -> std::string {
60 if (tp == std::chrono::system_clock::time_point{}) {
64 auto time = std::chrono::system_clock::to_time_t(tp);
73 std::strftime(buf,
sizeof(buf),
"%Y-%m-%d %H:%M:%S", &tm);
81auto node_repository::find_by_pk(int64_t pk) -> result_type {
82 if (!db() || !db()->is_connected()) {
83 return result_type(kcenon::common::error_info{
84 -1,
"Database not connected",
"storage"});
87 auto builder = query_builder();
88 builder.select(select_columns())
93 auto result = storage_session().select(builder.build());
94 if (result.is_err()) {
95 return result_type(result.error());
98 if (result.value().empty()) {
99 return result_type(kcenon::common::error_info{
100 -1,
"Node not found with pk=" + std::to_string(pk),
"storage"});
103 return result_type(map_row_to_entity(result.value()[0]));
106auto node_repository::find_all_nodes() -> list_result_type {
107 if (!db() || !db()->is_connected()) {
108 return list_result_type(kcenon::common::error_info{
109 -1,
"Database not connected",
"storage"});
112 auto builder = query_builder();
113 builder.select(select_columns())
115 .order_by(
"name", database::sort_order::asc);
117 auto result = storage_session().select(builder.build());
118 if (result.is_err()) {
119 return list_result_type(result.error());
122 std::vector<client::remote_node> nodes;
123 nodes.reserve(result.value().size());
124 for (
const auto& row : result.value()) {
125 nodes.push_back(map_row_to_entity(row));
128 return list_result_type(std::move(nodes));
131auto node_repository::find_by_status(client::node_status status)
132 -> list_result_type {
133 if (!db() || !db()->is_connected()) {
134 return list_result_type(kcenon::common::error_info{
135 -1,
"Database not connected",
"storage"});
138 auto builder = query_builder();
139 builder.select(select_columns())
141 .where(
"status",
"=", std::string(client::to_string(status)))
142 .order_by(
"name", database::sort_order::asc);
144 auto result = storage_session().select(builder.build());
145 if (result.is_err()) {
146 return list_result_type(result.error());
149 std::vector<client::remote_node> nodes;
150 nodes.reserve(result.value().size());
151 for (
const auto& row : result.value()) {
152 nodes.push_back(map_row_to_entity(row));
155 return list_result_type(std::move(nodes));
162auto node_repository::update_status(
163 std::string_view node_id,
164 client::node_status status,
165 std::string_view error_message) -> VoidResult {
166 if (!db() || !db()->is_connected()) {
167 return VoidResult(kcenon::common::error_info{
168 -1,
"Database not connected",
"storage"});
171 if (status == client::node_status::error ||
172 status == client::node_status::offline) {
174 auto sql =
"UPDATE " + table_name() +
175 " SET status = '" + std::string(client::to_string(status)) +
176 "', last_error = CURRENT_TIMESTAMP, "
177 "last_error_message = '" + std::string(error_message) +
178 "', updated_at = CURRENT_TIMESTAMP "
179 "WHERE node_id = '" + std::string(node_id) +
"'";
180 auto result = storage_session().execute(sql);
181 if (result.is_err()) {
182 return VoidResult(result.error());
185 auto builder = query_builder();
186 builder.update(table_name())
187 .set(
"status", std::string(client::to_string(status)))
188 .where(
"node_id",
"=", std::string(node_id));
190 auto result = storage_session().execute(builder.build());
191 if (result.is_err()) {
192 return VoidResult(result.error());
196 return kcenon::common::ok();
199auto node_repository::update_last_verified(std::string_view node_id)
201 if (!db() || !db()->is_connected()) {
202 return VoidResult(kcenon::common::error_info{
203 -1,
"Database not connected",
"storage"});
206 auto sql =
"UPDATE " + table_name() +
207 " SET last_verified = CURRENT_TIMESTAMP, "
208 "updated_at = CURRENT_TIMESTAMP WHERE node_id = '" +
209 std::string(node_id) +
"'";
211 auto result = storage_session().execute(sql);
212 if (result.is_err()) {
213 return VoidResult(result.error());
216 return kcenon::common::ok();
223auto node_repository::map_row_to_entity(
const database_row& row)
const
224 -> client::remote_node {
225 client::remote_node node;
228 auto pk_it = row.find(
"pk");
229 if (pk_it != row.end() && !pk_it->second.empty()) {
230 node.pk = std::stoll(pk_it->second);
233 node.node_id = row.at(
"node_id");
234 node.name = row.at(
"name");
235 node.ae_title = row.at(
"ae_title");
236 node.host = row.at(
"host");
238 auto port_it = row.find(
"port");
239 if (port_it != row.end() && !port_it->second.empty()) {
240 node.port =
static_cast<uint16_t
>(std::stoi(port_it->second));
243 auto find_it = row.find(
"supports_find");
244 if (find_it != row.end() && !find_it->second.empty()) {
245 node.supports_find = (std::stoi(find_it->second) != 0);
248 auto move_it = row.find(
"supports_move");
249 if (move_it != row.end() && !move_it->second.empty()) {
250 node.supports_move = (std::stoi(move_it->second) != 0);
253 auto get_it = row.find(
"supports_get");
254 if (get_it != row.end() && !get_it->second.empty()) {
255 node.supports_get = (std::stoi(get_it->second) != 0);
258 auto store_it = row.find(
"supports_store");
259 if (store_it != row.end() && !store_it->second.empty()) {
260 node.supports_store = (std::stoi(store_it->second) != 0);
263 auto worklist_it = row.find(
"supports_worklist");
264 if (worklist_it != row.end() && !worklist_it->second.empty()) {
265 node.supports_worklist = (std::stoi(worklist_it->second) != 0);
268 auto conn_timeout_it = row.find(
"connection_timeout_sec");
269 if (conn_timeout_it != row.end() && !conn_timeout_it->second.empty()) {
270 node.connection_timeout = std::chrono::seconds(std::stoi(conn_timeout_it->second));
273 auto dimse_timeout_it = row.find(
"dimse_timeout_sec");
274 if (dimse_timeout_it != row.end() && !dimse_timeout_it->second.empty()) {
275 node.dimse_timeout = std::chrono::seconds(std::stoi(dimse_timeout_it->second));
278 auto max_assoc_it = row.find(
"max_associations");
279 if (max_assoc_it != row.end() && !max_assoc_it->second.empty()) {
280 node.max_associations =
static_cast<size_t>(std::stoll(max_assoc_it->second));
283 auto status_it = row.find(
"status");
284 if (status_it != row.end() && !status_it->second.empty()) {
285 node.status = client::node_status_from_string(status_it->second);
288 auto last_verified_it = row.find(
"last_verified");
289 if (last_verified_it != row.end() && !last_verified_it->second.empty()) {
290 node.last_verified = parse_timestamp(last_verified_it->second);
293 auto last_error_it = row.find(
"last_error");
294 if (last_error_it != row.end() && !last_error_it->second.empty()) {
295 node.last_error = parse_timestamp(last_error_it->second);
298 auto last_error_msg_it = row.find(
"last_error_message");
299 if (last_error_msg_it != row.end()) {
300 node.last_error_message = last_error_msg_it->second;
303 auto created_it = row.find(
"created_at");
304 if (created_it != row.end() && !created_it->second.empty()) {
305 node.created_at = parse_timestamp(created_it->second);
308 auto updated_it = row.find(
"updated_at");
309 if (updated_it != row.end() && !updated_it->second.empty()) {
310 node.updated_at = parse_timestamp(updated_it->second);
316auto node_repository::entity_to_row(
const client::remote_node& entity)
const
317 -> std::map<std::string, database_value> {
318 std::map<std::string, database_value> row;
320 row[
"node_id"] = entity.node_id;
321 row[
"name"] = entity.name;
322 row[
"ae_title"] = entity.ae_title;
323 row[
"host"] = entity.host;
324 row[
"port"] =
static_cast<int64_t
>(entity.port);
325 row[
"supports_find"] =
static_cast<int64_t
>(entity.supports_find ? 1 : 0);
326 row[
"supports_move"] =
static_cast<int64_t
>(entity.supports_move ? 1 : 0);
327 row[
"supports_get"] =
static_cast<int64_t
>(entity.supports_get ? 1 : 0);
328 row[
"supports_store"] =
static_cast<int64_t
>(entity.supports_store ? 1 : 0);
329 row[
"supports_worklist"] =
static_cast<int64_t
>(entity.supports_worklist ? 1 : 0);
330 row[
"connection_timeout_sec"] =
static_cast<int64_t
>(entity.connection_timeout.count());
331 row[
"dimse_timeout_sec"] =
static_cast<int64_t
>(entity.dimse_timeout.count());
332 row[
"max_associations"] =
static_cast<int64_t
>(entity.max_associations);
333 row[
"status"] = std::string(client::to_string(entity.status));
334 row[
"last_verified"] = format_timestamp(entity.last_verified);
335 row[
"last_error"] = format_timestamp(entity.last_error);
336 row[
"last_error_message"] = entity.last_error_message;
337 row[
"created_at"] = format_timestamp(entity.created_at);
338 row[
"updated_at"] = format_timestamp(entity.updated_at);
343auto node_repository::get_pk(
const client::remote_node& entity)
const
345 return entity.node_id;
348auto node_repository::has_pk(
const client::remote_node& entity)
const ->
bool {
349 return !entity.node_id.empty();
352auto node_repository::select_columns() const -> std::vector<std::
string> {
354 "pk",
"node_id",
"name",
"ae_title",
"host",
"port",
355 "supports_find",
"supports_move",
"supports_get",
356 "supports_store",
"supports_worklist",
357 "connection_timeout_sec",
"dimse_timeout_sec",
"max_associations",
358 "status",
"last_verified",
"last_error",
"last_error_message",
359 "created_at",
"updated_at"
382[[nodiscard]] std::string to_timestamp_string(
383 std::chrono::system_clock::time_point tp) {
384 if (tp == std::chrono::system_clock::time_point{}) {
387 auto time = std::chrono::system_clock::to_time_t(tp);
390 gmtime_s(&tm, &time);
392 gmtime_r(&time, &tm);
395 std::strftime(buf,
sizeof(buf),
"%Y-%m-%d %H:%M:%S", &tm);
400[[nodiscard]] std::chrono::system_clock::time_point from_timestamp_string(
402 if (!str || str[0] ==
'\0') {
406 if (std::sscanf(str,
"%d-%d-%d %d:%d:%d",
407 &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
408 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
414 auto time = _mkgmtime(&tm);
416 auto time = timegm(&tm);
418 return std::chrono::system_clock::from_time_t(time);
422[[nodiscard]] std::string get_text_column(sqlite3_stmt* stmt,
int col) {
423 auto text =
reinterpret_cast<const char*
>(sqlite3_column_text(stmt, col));
428[[nodiscard]]
int get_int_column(sqlite3_stmt* stmt,
int col,
int default_val = 0) {
429 if (sqlite3_column_type(stmt, col) == SQLITE_NULL) {
432 return sqlite3_column_int(stmt, col);
436[[nodiscard]] int64_t get_int64_column(sqlite3_stmt* stmt,
int col, int64_t default_val = 0) {
437 if (sqlite3_column_type(stmt, col) == SQLITE_NULL) {
440 return sqlite3_column_int64(stmt, col);
463 return kcenon::common::make_error<int64_t>(
464 -1,
"Database not initialized",
"node_repository");
467 static constexpr const char* sql = R
"(
468 INSERT INTO remote_nodes (
469 node_id, name, ae_title, host, port,
470 supports_find, supports_move, supports_get, supports_store, supports_worklist,
471 connection_timeout_sec, dimse_timeout_sec, max_associations,
472 status, last_verified, last_error, last_error_message,
473 created_at, updated_at
474 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
475 ON CONFLICT(node_id) DO UPDATE SET
476 name = excluded.name,
477 ae_title = excluded.ae_title,
478 host = excluded.host,
479 port = excluded.port,
480 supports_find = excluded.supports_find,
481 supports_move = excluded.supports_move,
482 supports_get = excluded.supports_get,
483 supports_store = excluded.supports_store,
484 supports_worklist = excluded.supports_worklist,
485 connection_timeout_sec = excluded.connection_timeout_sec,
486 dimse_timeout_sec = excluded.dimse_timeout_sec,
487 max_associations = excluded.max_associations,
488 updated_at = CURRENT_TIMESTAMP
492 sqlite3_stmt* stmt = nullptr;
493 if (sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
494 return kcenon::common::make_error<int64_t>(
495 -1,
"Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
500 sqlite3_bind_text(stmt, idx++, node.node_id.c_str(), -1, SQLITE_TRANSIENT);
501 sqlite3_bind_text(stmt, idx++, node.name.c_str(), -1, SQLITE_TRANSIENT);
502 sqlite3_bind_text(stmt, idx++, node.ae_title.c_str(), -1, SQLITE_TRANSIENT);
503 sqlite3_bind_text(stmt, idx++, node.host.c_str(), -1, SQLITE_TRANSIENT);
504 sqlite3_bind_int(stmt, idx++, node.port);
505 sqlite3_bind_int(stmt, idx++, node.supports_find ? 1 : 0);
506 sqlite3_bind_int(stmt, idx++, node.supports_move ? 1 : 0);
507 sqlite3_bind_int(stmt, idx++, node.supports_get ? 1 : 0);
508 sqlite3_bind_int(stmt, idx++, node.supports_store ? 1 : 0);
509 sqlite3_bind_int(stmt, idx++, node.supports_worklist ? 1 : 0);
510 sqlite3_bind_int(stmt, idx++,
static_cast<int>(node.connection_timeout.count()));
511 sqlite3_bind_int(stmt, idx++,
static_cast<int>(node.dimse_timeout.count()));
512 sqlite3_bind_int(stmt, idx++,
static_cast<int>(node.max_associations));
515 sqlite3_bind_text(stmt, idx++, status_str.c_str(), -1, SQLITE_TRANSIENT);
517 auto last_verified_str = to_timestamp_string(node.last_verified);
518 if (last_verified_str.empty()) {
519 sqlite3_bind_null(stmt, idx++);
521 sqlite3_bind_text(stmt, idx++, last_verified_str.c_str(), -1, SQLITE_TRANSIENT);
524 auto last_error_str = to_timestamp_string(node.last_error);
525 if (last_error_str.empty()) {
526 sqlite3_bind_null(stmt, idx++);
528 sqlite3_bind_text(stmt, idx++, last_error_str.c_str(), -1, SQLITE_TRANSIENT);
531 sqlite3_bind_text(stmt, idx++, node.last_error_message.c_str(), -1, SQLITE_TRANSIENT);
534 if (sqlite3_step(stmt) == SQLITE_ROW) {
535 pk = sqlite3_column_int64(stmt, 0);
537 auto err = std::string(sqlite3_errmsg(db_));
538 sqlite3_finalize(stmt);
539 return kcenon::common::make_error<int64_t>(-1,
"Failed to upsert: " + err,
"node_repository");
542 sqlite3_finalize(stmt);
543 return kcenon::common::ok(pk);
547 if (!
db_)
return std::nullopt;
549 static constexpr const char* sql = R
"(
550 SELECT pk, node_id, name, ae_title, host, port,
551 supports_find, supports_move, supports_get, supports_store, supports_worklist,
552 connection_timeout_sec, dimse_timeout_sec, max_associations,
553 status, last_verified, last_error, last_error_message,
554 created_at, updated_at
555 FROM remote_nodes WHERE node_id = ?
558 sqlite3_stmt* stmt = nullptr;
559 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
563 sqlite3_bind_text(stmt, 1, node_id.data(),
static_cast<int>(node_id.size()), SQLITE_TRANSIENT);
565 std::optional<client::remote_node> result;
566 if (sqlite3_step(stmt) == SQLITE_ROW) {
570 sqlite3_finalize(stmt);
575 if (!
db_)
return std::nullopt;
577 static constexpr const char* sql = R
"(
578 SELECT pk, node_id, name, ae_title, host, port,
579 supports_find, supports_move, supports_get, supports_store, supports_worklist,
580 connection_timeout_sec, dimse_timeout_sec, max_associations,
581 status, last_verified, last_error, last_error_message,
582 created_at, updated_at
583 FROM remote_nodes WHERE pk = ?
586 sqlite3_stmt* stmt = nullptr;
587 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
591 sqlite3_bind_int64(stmt, 1, pk);
593 std::optional<client::remote_node> result;
594 if (sqlite3_step(stmt) == SQLITE_ROW) {
598 sqlite3_finalize(stmt);
603 std::vector<client::remote_node> result;
604 if (!
db_)
return result;
606 static constexpr const char* sql = R
"(
607 SELECT pk, node_id, name, ae_title, host, port,
608 supports_find, supports_move, supports_get, supports_store, supports_worklist,
609 connection_timeout_sec, dimse_timeout_sec, max_associations,
610 status, last_verified, last_error, last_error_message,
611 created_at, updated_at
612 FROM remote_nodes ORDER BY name
615 sqlite3_stmt* stmt = nullptr;
616 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
620 while (sqlite3_step(stmt) == SQLITE_ROW) {
624 sqlite3_finalize(stmt);
630 std::vector<client::remote_node> result;
631 if (!
db_)
return result;
633 static constexpr const char* sql = R
"(
634 SELECT pk, node_id, name, ae_title, host, port,
635 supports_find, supports_move, supports_get, supports_store, supports_worklist,
636 connection_timeout_sec, dimse_timeout_sec, max_associations,
637 status, last_verified, last_error, last_error_message,
638 created_at, updated_at
639 FROM remote_nodes WHERE status = ? ORDER BY name
642 sqlite3_stmt* stmt = nullptr;
643 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
648 sqlite3_bind_text(stmt, 1, status_str.c_str(), -1, SQLITE_TRANSIENT);
650 while (sqlite3_step(stmt) == SQLITE_ROW) {
654 sqlite3_finalize(stmt);
660 return VoidResult(kcenon::common::error_info{-1,
"Database not initialized",
"node_repository"});
663 static constexpr const char* sql =
"DELETE FROM remote_nodes WHERE node_id = ?";
665 sqlite3_stmt* stmt =
nullptr;
666 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
667 return VoidResult(kcenon::common::error_info{
668 -1,
"Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
672 sqlite3_bind_text(stmt, 1, node_id.data(),
static_cast<int>(node_id.size()), SQLITE_TRANSIENT);
674 auto rc = sqlite3_step(stmt);
675 sqlite3_finalize(stmt);
677 if (rc != SQLITE_DONE) {
678 return VoidResult(kcenon::common::error_info{
679 -1,
"Failed to delete: " + std::string(sqlite3_errmsg(
db_)),
683 return kcenon::common::ok();
687 if (!
db_)
return false;
689 static constexpr const char* sql =
"SELECT 1 FROM remote_nodes WHERE node_id = ?";
691 sqlite3_stmt* stmt =
nullptr;
692 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
696 sqlite3_bind_text(stmt, 1, node_id.data(),
static_cast<int>(node_id.size()), SQLITE_TRANSIENT);
698 bool found = (sqlite3_step(stmt) == SQLITE_ROW);
699 sqlite3_finalize(stmt);
706 static constexpr const char* sql =
"SELECT COUNT(*) FROM remote_nodes";
708 sqlite3_stmt* stmt =
nullptr;
709 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
714 if (sqlite3_step(stmt) == SQLITE_ROW) {
715 result =
static_cast<size_t>(sqlite3_column_int64(stmt, 0));
718 sqlite3_finalize(stmt);
727 std::string_view node_id,
729 std::string_view error_message) {
732 return VoidResult(kcenon::common::error_info{-1,
"Database not initialized",
"node_repository"});
735 const char* sql =
nullptr;
738 UPDATE remote_nodes SET
740 last_error = CURRENT_TIMESTAMP,
741 last_error_message = ?,
742 updated_at = CURRENT_TIMESTAMP
747 UPDATE remote_nodes SET
749 updated_at = CURRENT_TIMESTAMP
754 sqlite3_stmt* stmt = nullptr;
755 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
756 return VoidResult(kcenon::common::error_info{
757 -1,
"Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
763 sqlite3_bind_text(stmt, idx++, status_str.c_str(), -1, SQLITE_TRANSIENT);
766 sqlite3_bind_text(stmt, idx++, error_message.data(),
767 static_cast<int>(error_message.size()), SQLITE_TRANSIENT);
770 sqlite3_bind_text(stmt, idx++, node_id.data(),
static_cast<int>(node_id.size()), SQLITE_TRANSIENT);
772 auto rc = sqlite3_step(stmt);
773 sqlite3_finalize(stmt);
775 if (rc != SQLITE_DONE) {
776 return VoidResult(kcenon::common::error_info{
777 -1,
"Failed to update status: " + std::string(sqlite3_errmsg(
db_)),
781 return kcenon::common::ok();
786 return VoidResult(kcenon::common::error_info{-1,
"Database not initialized",
"node_repository"});
789 static constexpr const char* sql = R
"(
790 UPDATE remote_nodes SET
791 last_verified = CURRENT_TIMESTAMP,
792 updated_at = CURRENT_TIMESTAMP
796 sqlite3_stmt* stmt = nullptr;
797 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
798 return VoidResult(kcenon::common::error_info{
799 -1,
"Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
803 sqlite3_bind_text(stmt, 1, node_id.data(),
static_cast<int>(node_id.size()), SQLITE_TRANSIENT);
805 auto rc = sqlite3_step(stmt);
806 sqlite3_finalize(stmt);
808 if (rc != SQLITE_DONE) {
809 return VoidResult(kcenon::common::error_info{
810 -1,
"Failed to update last_verified: " + std::string(sqlite3_errmsg(
db_)),
814 return kcenon::common::ok();
822 return db_ !=
nullptr;
830 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
834 node.
pk = get_int64_column(stmt, col++);
835 node.
node_id = get_text_column(stmt, col++);
836 node.
name = get_text_column(stmt, col++);
837 node.
ae_title = get_text_column(stmt, col++);
838 node.
host = get_text_column(stmt, col++);
839 node.
port =
static_cast<uint16_t
>(get_int_column(stmt, col++, 104));
848 node.
dimse_timeout = std::chrono::seconds(get_int_column(stmt, col++, 60));
849 node.
max_associations =
static_cast<size_t>(get_int_column(stmt, col++, 4));
851 auto status_str = get_text_column(stmt, col++);
854 auto last_verified_str = get_text_column(stmt, col++);
855 node.
last_verified = from_timestamp_string(last_verified_str.c_str());
857 auto last_error_str = get_text_column(stmt, col++);
858 node.
last_error = from_timestamp_string(last_error_str.c_str());
862 auto created_at_str = get_text_column(stmt, col++);
863 node.
created_at = from_timestamp_string(created_at_str.c_str());
865 auto updated_at_str = get_text_column(stmt, col++);
866 node.
updated_at = from_timestamp_string(updated_at_str.c_str());
Repository for remote node persistence (legacy SQLite interface)
auto find_by_status(client::node_status status) const -> std::vector< client::remote_node >
node_repository(sqlite3 *db)
auto is_valid() const noexcept -> bool
auto update_last_verified(std::string_view node_id) -> VoidResult
auto remove(std::string_view node_id) -> VoidResult
auto parse_row(void *stmt) const -> client::remote_node
auto find_all() const -> std::vector< client::remote_node >
auto find_by_pk(int64_t pk) const -> std::optional< client::remote_node >
auto exists(std::string_view node_id) const -> bool
auto count() const -> size_t
auto update_status(std::string_view node_id, client::node_status status, std::string_view error_message="") -> VoidResult
auto find_by_id(std::string_view node_id) const -> std::optional< client::remote_node >
node_status
Status of a remote PACS node.
@ offline
Node is not responding.
@ error
Node returned an error.
node_status node_status_from_string(std::string_view str) noexcept
Parse node_status from string.
constexpr const char * to_string(job_type type) noexcept
Convert job_type to string representation.
@ move
C-MOVE move request/response.
Repository for remote PACS node persistence using base_repository pattern.
bool supports_store
C-STORE support (Send)
std::chrono::system_clock::time_point updated_at
Last update timestamp.
int64_t pk
Primary key (0 if not persisted)
node_status status
Current connectivity status.
std::string ae_title
DICOM Application Entity Title.
std::string name
Human-readable display name.
uint16_t port
DICOM port (default: 104)
std::chrono::system_clock::time_point last_verified
Last successful verification.
std::string node_id
Unique identifier for this node.
bool supports_move
C-MOVE support (Retrieve)
std::chrono::system_clock::time_point last_error
Last error time.
size_t max_associations
Max concurrent associations.
bool supports_worklist
Modality Worklist support.
std::string last_error_message
Last error description.
bool supports_get
C-GET support (alternative retrieve)
bool supports_find
C-FIND support (Query)
std::chrono::seconds dimse_timeout
DIMSE operation timeout.
std::chrono::system_clock::time_point created_at
Creation timestamp.
std::string host
IP address or hostname.
std::chrono::seconds connection_timeout
TCP connection timeout.