PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::storage::viewer_state_repository Class Reference

Repository for viewer state persistence (legacy SQLite interface) More...

#include <viewer_state_repository.h>

Collaboration diagram for kcenon::pacs::storage::viewer_state_repository:
Collaboration graph

Public Member Functions

 viewer_state_repository (sqlite3 *db)
 
 ~viewer_state_repository ()
 
 viewer_state_repository (const viewer_state_repository &)=delete
 
auto operator= (const viewer_state_repository &) -> viewer_state_repository &=delete
 
 viewer_state_repository (viewer_state_repository &&) noexcept
 
auto operator= (viewer_state_repository &&) noexcept -> viewer_state_repository &
 
auto save_state (const viewer_state_record &record) -> VoidResult
 
auto find_state_by_id (std::string_view state_id) const -> std::optional< viewer_state_record >
 
auto find_states_by_study (std::string_view study_uid) const -> std::vector< viewer_state_record >
 
auto search_states (const viewer_state_query &query) const -> std::vector< viewer_state_record >
 
auto remove_state (std::string_view state_id) -> VoidResult
 
auto count_states () const -> size_t
 
auto record_study_access (std::string_view user_id, std::string_view study_uid) -> VoidResult
 
auto get_recent_studies (std::string_view user_id, size_t limit=20) const -> std::vector< recent_study_record >
 
auto clear_recent_studies (std::string_view user_id) -> VoidResult
 
auto count_recent_studies (std::string_view user_id) const -> size_t
 
auto is_valid () const noexcept -> bool
 

Private Member Functions

auto parse_state_row (void *stmt) const -> viewer_state_record
 
auto parse_recent_study_row (void *stmt) const -> recent_study_record
 

Private Attributes

sqlite3 * db_ {nullptr}
 

Detailed Description

Repository for viewer state persistence (legacy SQLite interface)

This is the legacy interface maintained for builds without database_system.

Definition at line 229 of file viewer_state_repository.h.

Constructor & Destructor Documentation

◆ viewer_state_repository() [1/3]

kcenon::pacs::storage::viewer_state_repository::viewer_state_repository ( sqlite3 * db)
explicit

◆ ~viewer_state_repository()

kcenon::pacs::storage::viewer_state_repository::~viewer_state_repository ( )
default

◆ viewer_state_repository() [2/3]

kcenon::pacs::storage::viewer_state_repository::viewer_state_repository ( const viewer_state_repository & )
delete

◆ viewer_state_repository() [3/3]

kcenon::pacs::storage::viewer_state_repository::viewer_state_repository ( viewer_state_repository && )
defaultnoexcept

Member Function Documentation

◆ clear_recent_studies()

VoidResult kcenon::pacs::storage::viewer_state_repository::clear_recent_studies ( std::string_view user_id) -> VoidResult
nodiscard

Definition at line 693 of file viewer_state_repository.cpp.

696 {
697 if (!db_) {
698 return VoidResult(kcenon::common::error_info{
699 -1, "Database not initialized", "viewer_state_repository"});
700 }
701
702 static constexpr const char* sql = "DELETE FROM recent_studies WHERE user_id = ?";
703
704 sqlite3_stmt* stmt = nullptr;
705 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
706 return VoidResult(kcenon::common::error_info{
707 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
708 "viewer_state_repository"});
709 }
710
711 sqlite3_bind_text(stmt, 1, user_id.data(),
712 static_cast<int>(user_id.size()), SQLITE_TRANSIENT);
713
714 auto rc = sqlite3_step(stmt);
715 sqlite3_finalize(stmt);
716
717 if (rc != SQLITE_DONE) {
718 return VoidResult(kcenon::common::error_info{
719 -1, "Failed to clear recent studies: " + std::string(sqlite3_errmsg(db_)),
720 "viewer_state_repository"});
721 }

◆ count_recent_studies()

size_t kcenon::pacs::storage::viewer_state_repository::count_recent_studies ( std::string_view user_id) const -> size_t
nodiscard

Definition at line 723 of file viewer_state_repository.cpp.

726 {
727 if (!db_) return 0;
728
729 static constexpr const char* sql =
730 "SELECT COUNT(*) FROM recent_studies WHERE user_id = ?";
731
732 sqlite3_stmt* stmt = nullptr;
733 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
734 return 0;
735 }
736
737 sqlite3_bind_text(stmt, 1, user_id.data(),
738 static_cast<int>(user_id.size()), SQLITE_TRANSIENT);
739
740 size_t result = 0;
741 if (sqlite3_step(stmt) == SQLITE_ROW) {
742 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
743 }
744

◆ count_states()

size_t kcenon::pacs::storage::viewer_state_repository::count_states ( ) const -> size_t
nodiscard

Definition at line 597 of file viewer_state_repository.cpp.

600 {
601 if (!db_) return 0;
602
603 static constexpr const char* sql = "SELECT COUNT(*) FROM viewer_states";
604
605 sqlite3_stmt* stmt = nullptr;
606 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
607 return 0;
608 }
609
610 size_t result = 0;
611 if (sqlite3_step(stmt) == SQLITE_ROW) {
612 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
613 }
614

◆ find_state_by_id()

std::optional< viewer_state_record > kcenon::pacs::storage::viewer_state_repository::find_state_by_id ( std::string_view state_id) const -> std::optional<viewer_state_record>
nodiscard

Definition at line 489 of file viewer_state_repository.cpp.

490 {
491 if (!db_) return std::nullopt;
492
493 static constexpr const char* sql = R"(
494 SELECT pk, state_id, study_uid, user_id, state_json, created_at, updated_at
495 FROM viewer_states WHERE state_id = ?
496 )";
497
498 sqlite3_stmt* stmt = nullptr;
499 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
500 return std::nullopt;
501 }
502
503 sqlite3_bind_text(stmt, 1, state_id.data(),
504 static_cast<int>(state_id.size()), SQLITE_TRANSIENT);
505
506 std::optional<viewer_state_record> result;
507 if (sqlite3_step(stmt) == SQLITE_ROW) {
508 result = parse_state_row(stmt);
509 }
510
511 sqlite3_finalize(stmt);
512 return result;
513}
auto parse_state_row(void *stmt) const -> viewer_state_record

References db_, and parse_state_row().

Here is the call graph for this function:

◆ find_states_by_study()

std::vector< viewer_state_record > kcenon::pacs::storage::viewer_state_repository::find_states_by_study ( std::string_view study_uid) const -> std::vector<viewer_state_record>
nodiscard

Definition at line 515 of file viewer_state_repository.cpp.

516 {
517 viewer_state_query query;
518 query.study_uid = std::string(study_uid);
519 return search_states(query);
520}
auto search_states(const viewer_state_query &query) const -> std::vector< viewer_state_record >
const atna_coded_value query
Query (110112)

References search_states().

Here is the call graph for this function:

◆ get_recent_studies()

std::vector< recent_study_record > kcenon::pacs::storage::viewer_state_repository::get_recent_studies ( std::string_view user_id,
size_t limit = 20 ) const -> std::vector<recent_study_record>
nodiscard

Definition at line 662 of file viewer_state_repository.cpp.

667 {
668 std::vector<recent_study_record> result;
669 if (!db_) return result;
670
671 static constexpr const char* sql = R"(
672 SELECT pk, user_id, study_uid, accessed_at
673 FROM recent_studies
674 WHERE user_id = ?
675 ORDER BY accessed_at DESC, pk DESC
676 LIMIT ?
677 )";
678
679 sqlite3_stmt* stmt = nullptr;
680 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
681 return result;
682 }
683
684 sqlite3_bind_text(stmt, 1, user_id.data(),
685 static_cast<int>(user_id.size()), SQLITE_TRANSIENT);
686 sqlite3_bind_int64(stmt, 2, static_cast<int64_t>(limit));
687
688 while (sqlite3_step(stmt) == SQLITE_ROW) {
689 result.push_back(parse_recent_study_row(stmt));
690 }
691
auto parse_recent_study_row(void *stmt) const -> recent_study_record

◆ is_valid()

bool kcenon::pacs::storage::viewer_state_repository::is_valid ( ) const -> bool
nodiscardnoexcept

Definition at line 750 of file viewer_state_repository.cpp.

References db_.

◆ operator=() [1/2]

auto kcenon::pacs::storage::viewer_state_repository::operator= ( const viewer_state_repository & ) -> viewer_state_repository &=delete
delete

◆ operator=() [2/2]

auto kcenon::pacs::storage::viewer_state_repository::operator= ( viewer_state_repository && ) -> viewer_state_repository &
defaultnoexcept

◆ parse_recent_study_row()

recent_study_record kcenon::pacs::storage::viewer_state_repository::parse_recent_study_row ( void * stmt) const -> recent_study_record
nodiscardprivate

Definition at line 778 of file viewer_state_repository.cpp.

781 {
782 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
783 recent_study_record record;
784
785 int col = 0;
786 record.pk = get_int64_column(stmt, col++);
787 record.user_id = get_text_column(stmt, col++);
788 record.study_uid = get_text_column(stmt, col++);
789
790 auto accessed_str = get_text_column(stmt, col++);
791 record.accessed_at = from_timestamp_string(accessed_str.c_str());

◆ parse_state_row()

viewer_state_record kcenon::pacs::storage::viewer_state_repository::parse_state_row ( void * stmt) const -> viewer_state_record
nodiscardprivate

Definition at line 758 of file viewer_state_repository.cpp.

761 {
762 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
763 viewer_state_record record;
764
765 int col = 0;
766 record.pk = get_int64_column(stmt, col++);
767 record.state_id = get_text_column(stmt, col++);
768 record.study_uid = get_text_column(stmt, col++);
769 record.user_id = get_text_column(stmt, col++);
770 record.state_json = get_text_column(stmt, col++);
771
772 auto created_str = get_text_column(stmt, col++);
773 record.created_at = from_timestamp_string(created_str.c_str());
774
775 auto updated_str = get_text_column(stmt, col++);
776 record.updated_at = from_timestamp_string(updated_str.c_str());

Referenced by find_state_by_id(), and search_states().

Here is the caller graph for this function:

◆ record_study_access()

VoidResult kcenon::pacs::storage::viewer_state_repository::record_study_access ( std::string_view user_id,
std::string_view study_uid ) -> VoidResult
nodiscard

Definition at line 620 of file viewer_state_repository.cpp.

625 {
626 if (!db_) {
627 return VoidResult(kcenon::common::error_info{
628 -1, "Database not initialized", "viewer_state_repository"});
629 }
630
631 static constexpr const char* sql = R"(
632 INSERT INTO recent_studies (user_id, study_uid, accessed_at)
633 VALUES (?, ?, ?)
634 ON CONFLICT(user_id, study_uid) DO UPDATE SET
635 accessed_at = excluded.accessed_at
636 )";
637
638 sqlite3_stmt* stmt = nullptr;
639 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
640 return VoidResult(kcenon::common::error_info{
641 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
642 "viewer_state_repository"});
643 }
644
645 auto now_str = to_timestamp_string(std::chrono::system_clock::now());
646
647 sqlite3_bind_text(stmt, 1, user_id.data(),
648 static_cast<int>(user_id.size()), SQLITE_TRANSIENT);
649 sqlite3_bind_text(stmt, 2, study_uid.data(),
650 static_cast<int>(study_uid.size()), SQLITE_TRANSIENT);
651 sqlite3_bind_text(stmt, 3, now_str.c_str(), -1, SQLITE_TRANSIENT);
652
653 auto rc = sqlite3_step(stmt);
654 sqlite3_finalize(stmt);
655
656 if (rc != SQLITE_DONE) {
657 return VoidResult(kcenon::common::error_info{
658 -1, "Failed to record study access: " + std::string(sqlite3_errmsg(db_)),
659 "viewer_state_repository"});
660 }

References db_.

◆ remove_state()

VoidResult kcenon::pacs::storage::viewer_state_repository::remove_state ( std::string_view state_id) -> VoidResult
nodiscard

Definition at line 567 of file viewer_state_repository.cpp.

570 {
571 if (!db_) {
572 return VoidResult(kcenon::common::error_info{
573 -1, "Database not initialized", "viewer_state_repository"});
574 }
575
576 static constexpr const char* sql = "DELETE FROM viewer_states WHERE state_id = ?";
577
578 sqlite3_stmt* stmt = nullptr;
579 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
580 return VoidResult(kcenon::common::error_info{
581 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
582 "viewer_state_repository"});
583 }
584
585 sqlite3_bind_text(stmt, 1, state_id.data(),
586 static_cast<int>(state_id.size()), SQLITE_TRANSIENT);
587
588 auto rc = sqlite3_step(stmt);
589 sqlite3_finalize(stmt);
590
591 if (rc != SQLITE_DONE) {
592 return VoidResult(kcenon::common::error_info{
593 -1, "Failed to delete viewer state: " + std::string(sqlite3_errmsg(db_)),
594 "viewer_state_repository"});
595 }

◆ save_state()

VoidResult kcenon::pacs::storage::viewer_state_repository::save_state ( const viewer_state_record & record) -> VoidResult
nodiscard

Definition at line 445 of file viewer_state_repository.cpp.

445 {
446 if (!db_) {
447 return VoidResult(kcenon::common::error_info{
448 -1, "Database not initialized", "viewer_state_repository"});
449 }
450
451 static constexpr const char* sql = R"(
452 INSERT INTO viewer_states (
453 state_id, study_uid, user_id, state_json, created_at, updated_at
454 ) VALUES (?, ?, ?, ?, ?, ?)
455 ON CONFLICT(state_id) DO UPDATE SET
456 state_json = excluded.state_json,
457 updated_at = excluded.updated_at
458 )";
459
460 sqlite3_stmt* stmt = nullptr;
461 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
462 return VoidResult(kcenon::common::error_info{
463 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
464 "viewer_state_repository"});
465 }
466
467 auto now_str = to_timestamp_string(std::chrono::system_clock::now());
468
469 int idx = 1;
470 sqlite3_bind_text(stmt, idx++, record.state_id.c_str(), -1, SQLITE_TRANSIENT);
471 sqlite3_bind_text(stmt, idx++, record.study_uid.c_str(), -1, SQLITE_TRANSIENT);
472 sqlite3_bind_text(stmt, idx++, record.user_id.c_str(), -1, SQLITE_TRANSIENT);
473 sqlite3_bind_text(stmt, idx++, record.state_json.c_str(), -1, SQLITE_TRANSIENT);
474 sqlite3_bind_text(stmt, idx++, now_str.c_str(), -1, SQLITE_TRANSIENT);
475 sqlite3_bind_text(stmt, idx++, now_str.c_str(), -1, SQLITE_TRANSIENT);
476
477 auto rc = sqlite3_step(stmt);
478 sqlite3_finalize(stmt);
479
480 if (rc != SQLITE_DONE) {
481 return VoidResult(kcenon::common::error_info{
482 -1, "Failed to save viewer state: " + std::string(sqlite3_errmsg(db_)),
483 "viewer_state_repository"});
484 }
485
486 return kcenon::common::ok();
487}

◆ search_states()

std::vector< viewer_state_record > kcenon::pacs::storage::viewer_state_repository::search_states ( const viewer_state_query & query) const -> std::vector<viewer_state_record>
nodiscard

Definition at line 522 of file viewer_state_repository.cpp.

523 {
524 std::vector<viewer_state_record> result;
525 if (!db_) return result;
526
527 std::ostringstream sql;
528 sql << R"(
529 SELECT pk, state_id, study_uid, user_id, state_json, created_at, updated_at
530 FROM viewer_states WHERE 1=1
531 )";
532
533 std::vector<std::pair<int, std::string>> bindings;
534 int param_idx = 1;
535
536 if (query.study_uid.has_value()) {
537 sql << " AND study_uid = ?";
538 bindings.emplace_back(param_idx++, query.study_uid.value());
539 }
540
541 if (query.user_id.has_value()) {
542 sql << " AND user_id = ?";
543 bindings.emplace_back(param_idx++, query.user_id.value());
544 }
545
546 sql << " ORDER BY updated_at DESC";
547
548 if (query.limit > 0) {
549 sql << " LIMIT " << query.limit << " OFFSET " << query.offset;
550 }
551
552 sqlite3_stmt* stmt = nullptr;
553 auto sql_str = sql.str();
554 if (sqlite3_prepare_v2(db_, sql_str.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
555 return result;
556 }
557
558 for (const auto& [idx, value] : bindings) {
559 sqlite3_bind_text(stmt, idx, value.c_str(), -1, SQLITE_TRANSIENT);
560 }
561
562 while (sqlite3_step(stmt) == SQLITE_ROW) {
563 result.push_back(parse_state_row(stmt));
564 }
565

References db_, and parse_state_row().

Referenced by find_states_by_study().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ db_

sqlite3* kcenon::pacs::storage::viewer_state_repository::db_ {nullptr}
private

Definition at line 266 of file viewer_state_repository.h.

266{nullptr};

Referenced by find_state_by_id(), is_valid(), record_study_access(), and search_states().


The documentation for this class was generated from the following files: