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

Repository for job persistence (legacy SQLite interface) More...

#include <job_repository.h>

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

Public Member Functions

 job_repository (sqlite3 *db)
 
 ~job_repository ()
 
 job_repository (const job_repository &)=delete
 
auto operator= (const job_repository &) -> job_repository &=delete
 
 job_repository (job_repository &&) noexcept
 
auto operator= (job_repository &&) noexcept -> job_repository &
 
auto save (const client::job_record &job) -> VoidResult
 
auto find_by_id (std::string_view job_id) const -> std::optional< client::job_record >
 
auto find_by_pk (int64_t pk) const -> std::optional< client::job_record >
 
auto find_jobs (const job_query_options &options={}) const -> std::vector< client::job_record >
 
auto find_by_status (client::job_status status, size_t limit=100) const -> std::vector< client::job_record >
 
auto find_pending_jobs (size_t limit=10) const -> std::vector< client::job_record >
 
auto find_by_node (std::string_view node_id) const -> std::vector< client::job_record >
 
auto remove (std::string_view job_id) -> VoidResult
 
auto cleanup_old_jobs (std::chrono::hours max_age) -> Result< size_t >
 
auto exists (std::string_view job_id) const -> bool
 
auto update_status (std::string_view job_id, client::job_status status, std::string_view error_message="", std::string_view error_details="") -> VoidResult
 
auto update_progress (std::string_view job_id, const client::job_progress &progress) -> VoidResult
 
auto mark_started (std::string_view job_id) -> VoidResult
 
auto mark_completed (std::string_view job_id) -> VoidResult
 
auto mark_failed (std::string_view job_id, std::string_view error_message, std::string_view error_details="") -> VoidResult
 
auto increment_retry (std::string_view job_id) -> VoidResult
 
auto count () const -> size_t
 
auto count_by_status (client::job_status status) const -> size_t
 
auto count_completed_today () const -> size_t
 
auto count_failed_today () const -> size_t
 
auto is_valid () const noexcept -> bool
 

Private Member Functions

auto parse_row (void *stmt) const -> client::job_record
 

Static Private Member Functions

static auto serialize_instance_uids (const std::vector< std::string > &uids) -> std::string
 
static auto deserialize_instance_uids (std::string_view json) -> std::vector< std::string >
 
static auto serialize_metadata (const std::unordered_map< std::string, std::string > &metadata) -> std::string
 
static auto deserialize_metadata (std::string_view json) -> std::unordered_map< std::string, std::string >
 

Private Attributes

sqlite3 * db_ {nullptr}
 

Detailed Description

Repository for job persistence (legacy SQLite interface)

This is the legacy interface maintained for builds without database_system. New code should use the base_repository version when PACS_WITH_DATABASE_SYSTEM is defined.

Definition at line 331 of file job_repository.h.

Constructor & Destructor Documentation

◆ job_repository() [1/3]

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

Definition at line 1189 of file job_repository.cpp.

◆ ~job_repository()

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

◆ job_repository() [2/3]

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

◆ job_repository() [3/3]

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

Member Function Documentation

◆ cleanup_old_jobs()

Result< size_t > kcenon::pacs::storage::job_repository::cleanup_old_jobs ( std::chrono::hours max_age) -> Result<size_t>
nodiscard

Definition at line 1520 of file job_repository.cpp.

1521 {
1522 return VoidResult(kcenon::common::error_info{
1523 -1, "Failed to delete job: " + std::string(sqlite3_errmsg(db_)),
1524 "job_repository"});
1525 }
1526
1527 return kcenon::common::ok();
1528}
1529
1530Result<size_t> job_repository::cleanup_old_jobs(std::chrono::hours max_age) {
1531 if (!db_) {
1532 return kcenon::common::make_error<size_t>(
1533 -1, "Database not initialized", "job_repository");
1534 }
1535
1536 auto cutoff = std::chrono::system_clock::now() - max_age;
1537 auto cutoff_str = to_timestamp_string(cutoff);
1538
1539 static constexpr const char* sql = R"(
1540 DELETE FROM jobs
1541 WHERE status IN ('completed', 'failed', 'cancelled')
1542 AND completed_at < ?
1543 )";
1544
1545 sqlite3_stmt* stmt = nullptr;
1546 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1547 return kcenon::common::make_error<size_t>(
1548 -1,
1549 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1550 "job_repository");
1551 }
1552
1553 sqlite3_bind_text(stmt, 1, cutoff_str.c_str(), -1, SQLITE_TRANSIENT);
1554
1555 auto rc = sqlite3_step(stmt);
auto cleanup_old_jobs(std::chrono::hours max_age) -> Result< size_t >
kcenon::common::Result< T > Result
Result type alias for operations returning a value.

References db_.

◆ count()

size_t kcenon::pacs::storage::job_repository::count ( ) const -> size_t
nodiscard

Definition at line 1847 of file job_repository.cpp.

1857 {
1858 if (!db_) return 0;
1859
1860 static constexpr const char* sql = "SELECT COUNT(*) FROM jobs";
1861
1862 sqlite3_stmt* stmt = nullptr;
1863 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1864 return 0;

◆ count_by_status()

size_t kcenon::pacs::storage::job_repository::count_by_status ( client::job_status status) const -> size_t
nodiscard

Definition at line 1866 of file job_repository.cpp.

1868 {
1869 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1870 }
1871
1872 sqlite3_finalize(stmt);
1873 return result;
1874}
1875
1877 if (!db_) return 0;
1878
1879 static constexpr const char* sql =
1880 "SELECT COUNT(*) FROM jobs WHERE status = ?";
1881
1882 sqlite3_stmt* stmt = nullptr;
1883 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1884 return 0;
1885 }
1886
auto count_by_status(client::job_status status) const -> size_t
job_status
Current status of a job.
Definition job_types.h:90

◆ count_completed_today()

size_t kcenon::pacs::storage::job_repository::count_completed_today ( ) const -> size_t
nodiscard

Definition at line 1888 of file job_repository.cpp.

1890 {
1891 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1892 }
1893
1894 sqlite3_finalize(stmt);
1895 return result;
1896}
1897
1899 if (!db_) return 0;
1900
1901 static constexpr const char* sql = R"(
1902 SELECT COUNT(*) FROM jobs
1903 WHERE status = 'completed'
1904 AND date(completed_at) = date('now')
1905 )";
1906
1907 sqlite3_stmt* stmt = nullptr;
1908 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1909 return 0;

◆ count_failed_today()

size_t kcenon::pacs::storage::job_repository::count_failed_today ( ) const -> size_t
nodiscard

Definition at line 1911 of file job_repository.cpp.

1913 {
1914 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1915 }
1916
1917 sqlite3_finalize(stmt);
1918 return result;
1919}
1920
1922 if (!db_) return 0;
1923
1924 static constexpr const char* sql = R"(
1925 SELECT COUNT(*) FROM jobs
1926 WHERE status = 'failed'
1927 AND date(completed_at) = date('now')
1928 )";
1929
1930 sqlite3_stmt* stmt = nullptr;
1931 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1932 return 0;

◆ deserialize_instance_uids()

std::vector< std::string > kcenon::pacs::storage::job_repository::deserialize_instance_uids ( std::string_view json) -> std::vector<std::string>
staticnodiscardprivate

Definition at line 1070 of file job_repository.cpp.

1071 {
1072 std::vector<std::string> result;
1073 if (json.empty() || json == "[]") return result;
1074
1075 size_t pos = 0;
1076 while (pos < json.size()) {
1077 auto start = json.find('"', pos);
1078 if (start == std::string_view::npos) break;
1079
1080 size_t end = start + 1;
1081 while (end < json.size()) {
1082 if (json[end] == '\\' && end + 1 < json.size()) {
1083 end += 2;
1084 } else if (json[end] == '"') {
1085 break;
1086 } else {
1087 ++end;
1088 }
1089 }
1090
1091 if (end < json.size()) {
1092 std::string value{json.substr(start + 1, end - start - 1)};
1093 std::string unescaped;
1094 for (size_t i = 0; i < value.size(); ++i) {
1095 if (value[i] == '\\' && i + 1 < value.size()) {
1096 unescaped += value[++i];
1097 } else {
1098 unescaped += value[i];
1099 }
1100 }
1101 result.push_back(std::move(unescaped));
1102 }
1103
1104 pos = end + 1;
1105 }
1106
1107 return result;
1108}

◆ deserialize_metadata()

std::unordered_map< std::string, std::string > kcenon::pacs::storage::job_repository::deserialize_metadata ( std::string_view json) -> std::unordered_map<std::string, std::string>
staticnodiscardprivate

Definition at line 1146 of file job_repository.cpp.

1147 {
1148 std::unordered_map<std::string, std::string> result;
1149 if (json.empty() || json == "{}") return result;
1150
1151 size_t pos = 0;
1152 while (pos < json.size()) {
1153 auto key_start = json.find('"', pos);
1154 if (key_start == std::string_view::npos) break;
1155
1156 size_t key_end = key_start + 1;
1157 while (key_end < json.size() && json[key_end] != '"') {
1158 if (json[key_end] == '\\') ++key_end;
1159 ++key_end;
1160 }
1161 if (key_end >= json.size()) break;
1162
1163 std::string key{json.substr(key_start + 1, key_end - key_start - 1)};
1164
1165 auto val_start = json.find('"', key_end + 1);
1166 if (val_start == std::string_view::npos) break;
1167
1168 size_t val_end = val_start + 1;
1169 while (val_end < json.size() && json[val_end] != '"') {
1170 if (json[val_end] == '\\') ++val_end;
1171 ++val_end;
1172 }
1173
1174 if (val_end < json.size()) {
1175 std::string value{json.substr(val_start + 1, val_end - val_start - 1)};
1176 result[key] = value;
1177 }
1178
1179 pos = val_end + 1;
1180 }
1181
1182 return result;
1183}

◆ exists()

bool kcenon::pacs::storage::job_repository::exists ( std::string_view job_id) const -> bool
nodiscard

Definition at line 1557 of file job_repository.cpp.

1558 {
1559 return kcenon::common::make_error<size_t>(
1560 -1, "Failed to cleanup jobs: " + std::string(sqlite3_errmsg(db_)),
1561 "job_repository");
1562 }
1563
1564 return kcenon::common::ok(static_cast<size_t>(sqlite3_changes(db_)));
1565}
1566
1567bool job_repository::exists(std::string_view job_id) const {
1568 if (!db_) return false;
1569
1570 static constexpr const char* sql = "SELECT 1 FROM jobs WHERE job_id = ?";
1571
1572 sqlite3_stmt* stmt = nullptr;
1573 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
auto exists(std::string_view job_id) const -> bool

References db_.

◆ find_by_id()

std::optional< client::job_record > kcenon::pacs::storage::job_repository::find_by_id ( std::string_view job_id) const -> std::optional<client::job_record>
nodiscard

Definition at line 1314 of file job_repository.cpp.

1315 {
1316 if (!db_) return std::nullopt;
1317
1318 static constexpr const char* sql = R"(
1319 SELECT pk, job_id, type, status, priority,
1320 source_node_id, destination_node_id,
1321 patient_id, study_uid, series_uid, sop_instance_uid, instance_uids_json,
1322 total_items, completed_items, failed_items, skipped_items, bytes_transferred,
1323 current_item, current_item_description,
1324 error_message, error_details, retry_count, max_retries,
1325 created_by, metadata_json,
1326 created_at, queued_at, started_at, completed_at
1327 FROM jobs WHERE job_id = ?
1328 )";
1329
1330 sqlite3_stmt* stmt = nullptr;
1331 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1332 return std::nullopt;
1333 }
1334
1335 sqlite3_bind_text(stmt, 1, job_id.data(), static_cast<int>(job_id.size()),
1336 SQLITE_TRANSIENT);
1337
1338 std::optional<client::job_record> result;
1339 if (sqlite3_step(stmt) == SQLITE_ROW) {
1340 result = parse_row(stmt);
1341 }
1342
1343 sqlite3_finalize(stmt);
1344 return result;
1345}
auto parse_row(void *stmt) const -> client::job_record

References db_, and parse_row().

Here is the call graph for this function:

◆ find_by_node()

std::vector< client::job_record > kcenon::pacs::storage::job_repository::find_by_node ( std::string_view node_id) const -> std::vector<client::job_record>
nodiscard

Definition at line 1482 of file job_repository.cpp.

1484 {
1485 result.push_back(parse_row(stmt));
1486 }
1487

◆ find_by_pk()

std::optional< client::job_record > kcenon::pacs::storage::job_repository::find_by_pk ( int64_t pk) const -> std::optional<client::job_record>
nodiscard

Definition at line 1347 of file job_repository.cpp.

1347 {
1348 if (!db_) return std::nullopt;
1349
1350 static constexpr const char* sql = R"(
1351 SELECT pk, job_id, type, status, priority,
1352 source_node_id, destination_node_id,
1353 patient_id, study_uid, series_uid, sop_instance_uid, instance_uids_json,
1354 total_items, completed_items, failed_items, skipped_items, bytes_transferred,
1355 current_item, current_item_description,
1356 error_message, error_details, retry_count, max_retries,
1357 created_by, metadata_json,
1358 created_at, queued_at, started_at, completed_at
1359 FROM jobs WHERE pk = ?
1360 )";
1361
1362 sqlite3_stmt* stmt = nullptr;
1363 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1364 return std::nullopt;
1365 }
1366
1367 sqlite3_bind_int64(stmt, 1, pk);
1368
1369 std::optional<client::job_record> result;
1370 if (sqlite3_step(stmt) == SQLITE_ROW) {
1371 result = parse_row(stmt);
1372 }
1373
1374 sqlite3_finalize(stmt);
1375 return result;
1376}

References db_, and parse_row().

Here is the call graph for this function:

◆ find_by_status()

std::vector< client::job_record > kcenon::pacs::storage::job_repository::find_by_status ( client::job_status status,
size_t limit = 100 ) const -> std::vector<client::job_record>
nodiscard

Definition at line 1439 of file job_repository.cpp.

1441 {
1442 result.push_back(parse_row(stmt));
1443 }
1444
1445 sqlite3_finalize(stmt);

◆ find_jobs()

std::vector< client::job_record > kcenon::pacs::storage::job_repository::find_jobs ( const job_query_options & options = {}) const -> std::vector<client::job_record>
nodiscard

Definition at line 1378 of file job_repository.cpp.

1379 {
1380 std::vector<client::job_record> result;
1381 if (!db_) return result;
1382
1383 std::ostringstream sql;
1384 sql << R"(
1385 SELECT pk, job_id, type, status, priority,
1386 source_node_id, destination_node_id,
1387 patient_id, study_uid, series_uid, sop_instance_uid, instance_uids_json,
1388 total_items, completed_items, failed_items, skipped_items, bytes_transferred,
1389 current_item, current_item_description,
1390 error_message, error_details, retry_count, max_retries,
1391 created_by, metadata_json,
1392 created_at, queued_at, started_at, completed_at
1393 FROM jobs WHERE 1=1
1394 )";
1395
1396 std::vector<std::pair<int, std::string>> bindings;
1397 int param_idx = 1;
1398
1399 if (options.status.has_value()) {
1400 sql << " AND status = ?";
1401 bindings.emplace_back(
1402 param_idx++, std::string(client::to_string(options.status.value())));
1403 }
1404
1405 if (options.type.has_value()) {
1406 sql << " AND type = ?";
1407 bindings.emplace_back(param_idx++,
1408 std::string(client::to_string(options.type.value())));
1409 }
1410
1411 if (options.node_id.has_value()) {
1412 sql << " AND (source_node_id = ? OR destination_node_id = ?)";
1413 bindings.emplace_back(param_idx++, options.node_id.value());
1414 bindings.emplace_back(param_idx++, options.node_id.value());
1415 }
1416
1417 if (options.created_by.has_value()) {
1418 sql << " AND created_by = ?";
1419 bindings.emplace_back(param_idx++, options.created_by.value());
1420 }
1421
1422 if (options.order_by_priority) {
1423 sql << " ORDER BY priority DESC, created_at ASC";
1424 } else {
1425 sql << " ORDER BY created_at DESC";
1426 }
1427
1428 sql << " LIMIT " << options.limit << " OFFSET " << options.offset;
1429
1430 sqlite3_stmt* stmt = nullptr;
1431 auto sql_str = sql.str();
1432 if (sqlite3_prepare_v2(db_, sql_str.c_str(), -1, &stmt, nullptr) !=
1433 SQLITE_OK) {
1434 return result;
1435 }
1436
1437 for (const auto& [idx, value] : bindings) {
constexpr const char * to_string(job_type type) noexcept
Convert job_type to string representation.
Definition job_types.h:54

References db_, and kcenon::pacs::client::to_string().

Here is the call graph for this function:

◆ find_pending_jobs()

std::vector< client::job_record > kcenon::pacs::storage::job_repository::find_pending_jobs ( size_t limit = 10) const -> std::vector<client::job_record>
nodiscard

Definition at line 1447 of file job_repository.cpp.

1450 {
1451 job_query_options options;
1452 options.status = status;
1453 options.limit = limit;
1454 return find_jobs(options);
1455}
1456
1457std::vector<client::job_record> job_repository::find_pending_jobs(
1458 size_t limit) const {
1459 std::vector<client::job_record> result;
1460 if (!db_) return result;
1461
1462 static constexpr const char* sql = R"(
1463 SELECT pk, job_id, type, status, priority,
1464 source_node_id, destination_node_id,
1465 patient_id, study_uid, series_uid, sop_instance_uid, instance_uids_json,
1466 total_items, completed_items, failed_items, skipped_items, bytes_transferred,
1467 current_item, current_item_description,
1468 error_message, error_details, retry_count, max_retries,
1469 created_by, metadata_json,
1470 created_at, queued_at, started_at, completed_at
1471 FROM jobs
1472 WHERE status IN ('pending', 'queued')
1473 ORDER BY priority DESC, created_at ASC
1474 LIMIT ?
1475 )";
1476
1477 sqlite3_stmt* stmt = nullptr;
1478 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1479 return result;
1480 }
auto find_pending_jobs(size_t limit=10) const -> std::vector< client::job_record >
auto find_jobs(const job_query_options &options={}) const -> std::vector< client::job_record >
constexpr dicom_tag status
Status.

◆ increment_retry()

VoidResult kcenon::pacs::storage::job_repository::increment_retry ( std::string_view job_id) -> VoidResult
nodiscard

Definition at line 1810 of file job_repository.cpp.

1811 {
1812 return VoidResult(kcenon::common::error_info{
1813 -1, "Failed to mark failed: " + std::string(sqlite3_errmsg(db_)),
1814 "job_repository"});
1815 }
1816
1817 return kcenon::common::ok();
1818}
1819
1820VoidResult job_repository::increment_retry(std::string_view job_id) {
1821 if (!db_) {
1822 return VoidResult(kcenon::common::error_info{
1823 -1, "Database not initialized", "job_repository"});
1824 }
1825
1826 static constexpr const char* sql = R"(
1827 UPDATE jobs SET retry_count = retry_count + 1 WHERE job_id = ?
1828 )";
1829
1830 sqlite3_stmt* stmt = nullptr;
1831 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1832 return VoidResult(kcenon::common::error_info{
1833 -1,
1834 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1835 "job_repository"});
1836 }
1837
1838 sqlite3_bind_text(stmt, 1, job_id.data(), static_cast<int>(job_id.size()),
1839 SQLITE_TRANSIENT);
1840
1841 auto rc = sqlite3_step(stmt);
auto increment_retry(std::string_view job_id) -> VoidResult

References db_.

◆ is_valid()

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

Definition at line 1938 of file job_repository.cpp.

◆ mark_completed()

VoidResult kcenon::pacs::storage::job_repository::mark_completed ( std::string_view job_id) -> VoidResult
nodiscard

Definition at line 1729 of file job_repository.cpp.

1730 {
1731 return VoidResult(kcenon::common::error_info{
1732 -1, "Failed to mark started: " + std::string(sqlite3_errmsg(db_)),
1733 "job_repository"});
1734 }
1735
1736 return kcenon::common::ok();
1737}
1738
1739VoidResult job_repository::mark_completed(std::string_view job_id) {
1740 if (!db_) {
1741 return VoidResult(kcenon::common::error_info{
1742 -1, "Database not initialized", "job_repository"});
1743 }
1744
1745 static constexpr const char* sql = R"(
1746 UPDATE jobs SET
1747 status = 'completed',
1748 completed_at = CURRENT_TIMESTAMP
1749 WHERE job_id = ?
1750 )";
1751
1752 sqlite3_stmt* stmt = nullptr;
1753 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1754 return VoidResult(kcenon::common::error_info{
1755 -1,
1756 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1757 "job_repository"});
1758 }
1759
1760 sqlite3_bind_text(stmt, 1, job_id.data(), static_cast<int>(job_id.size()),
1761 SQLITE_TRANSIENT);
1762
1763 auto rc = sqlite3_step(stmt);
auto mark_completed(std::string_view job_id) -> VoidResult

References db_.

◆ mark_failed()

VoidResult kcenon::pacs::storage::job_repository::mark_failed ( std::string_view job_id,
std::string_view error_message,
std::string_view error_details = "" ) -> VoidResult
nodiscard

Definition at line 1765 of file job_repository.cpp.

1766 {
1767 return VoidResult(kcenon::common::error_info{
1768 -1, "Failed to mark completed: " + std::string(sqlite3_errmsg(db_)),
1769 "job_repository"});
1770 }
1771
1772 return kcenon::common::ok();
1773}
1774
1775VoidResult job_repository::mark_failed(std::string_view job_id,
1776 std::string_view error_message,
1777 std::string_view error_details) {
1778 if (!db_) {
1779 return VoidResult(kcenon::common::error_info{
1780 -1, "Database not initialized", "job_repository"});
1781 }
1782
1783 static constexpr const char* sql = R"(
1784 UPDATE jobs SET
1785 status = 'failed',
1786 error_message = ?,
1787 error_details = ?,
1788 retry_count = retry_count + 1,
1789 completed_at = CURRENT_TIMESTAMP
1790 WHERE job_id = ?
1791 )";
1792
1793 sqlite3_stmt* stmt = nullptr;
1794 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1795 return VoidResult(kcenon::common::error_info{
1796 -1,
1797 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1798 "job_repository"});
1799 }
1800
1801 sqlite3_bind_text(stmt, 1, error_message.data(),
1802 static_cast<int>(error_message.size()), SQLITE_TRANSIENT);
1803 sqlite3_bind_text(stmt, 2, error_details.data(),
1804 static_cast<int>(error_details.size()), SQLITE_TRANSIENT);
1805 sqlite3_bind_text(stmt, 3, job_id.data(), static_cast<int>(job_id.size()),
1806 SQLITE_TRANSIENT);
1807
1808 auto rc = sqlite3_step(stmt);
auto mark_failed(std::string_view job_id, std::string_view error_message, std::string_view error_details="") -> VoidResult

References db_.

◆ mark_started()

VoidResult kcenon::pacs::storage::job_repository::mark_started ( std::string_view job_id) -> VoidResult
nodiscard

Definition at line 1693 of file job_repository.cpp.

1694 {
1695 return VoidResult(kcenon::common::error_info{
1696 -1, "Failed to update progress: " + std::string(sqlite3_errmsg(db_)),
1697 "job_repository"});
1698 }
1699
1700 return kcenon::common::ok();
1701}
1702
1703VoidResult job_repository::mark_started(std::string_view job_id) {
1704 if (!db_) {
1705 return VoidResult(kcenon::common::error_info{
1706 -1, "Database not initialized", "job_repository"});
1707 }
1708
1709 static constexpr const char* sql = R"(
1710 UPDATE jobs SET
1711 status = 'running',
1712 started_at = CURRENT_TIMESTAMP
1713 WHERE job_id = ?
1714 )";
1715
1716 sqlite3_stmt* stmt = nullptr;
1717 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1718 return VoidResult(kcenon::common::error_info{
1719 -1,
1720 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1721 "job_repository"});
1722 }
1723
1724 sqlite3_bind_text(stmt, 1, job_id.data(), static_cast<int>(job_id.size()),
1725 SQLITE_TRANSIENT);
1726
1727 auto rc = sqlite3_step(stmt);
auto mark_started(std::string_view job_id) -> VoidResult

References db_.

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ parse_row()

client::job_record kcenon::pacs::storage::job_repository::parse_row ( void * stmt) const -> client::job_record
nodiscardprivate

Definition at line 1944 of file job_repository.cpp.

1948 { return db_ != nullptr; }
1949
1950// =============================================================================
1951// Private Implementation
1952// =============================================================================
1953
1954client::job_record job_repository::parse_row(void* stmt_ptr) const {
1955 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1956 client::job_record job;
1957
1958 int col = 0;
1959 job.pk = get_int64_column(stmt, col++);
1960 job.job_id = get_text_column(stmt, col++);
1961 job.type = client::job_type_from_string(get_text_column(stmt, col++));
1962 job.status = client::job_status_from_string(get_text_column(stmt, col++));
1963 job.priority = client::job_priority_from_int(get_int_column(stmt, col++));
1964
1965 job.source_node_id = get_text_column(stmt, col++);
1966 job.destination_node_id = get_text_column(stmt, col++);
1967
1968 job.patient_id = get_optional_text(stmt, col++);
1969 job.study_uid = get_optional_text(stmt, col++);
1970 job.series_uid = get_optional_text(stmt, col++);
1971 job.sop_instance_uid = get_optional_text(stmt, col++);
1972
1973 auto uids_json = get_text_column(stmt, col++);
1974 job.instance_uids = deserialize_instance_uids(uids_json);
1975
1976 job.progress.total_items =
1977 static_cast<size_t>(get_int64_column(stmt, col++));
1978 job.progress.completed_items =
1979 static_cast<size_t>(get_int64_column(stmt, col++));
1980 job.progress.failed_items =
1981 static_cast<size_t>(get_int64_column(stmt, col++));
1982 job.progress.skipped_items =
1983 static_cast<size_t>(get_int64_column(stmt, col++));
1984 job.progress.bytes_transferred =
1985 static_cast<size_t>(get_int64_column(stmt, col++));
1986 job.progress.current_item = get_text_column(stmt, col++);
1987 job.progress.current_item_description = get_text_column(stmt, col++);
1988 job.progress.calculate_percent();
1989
1990 job.error_message = get_text_column(stmt, col++);
1991 job.error_details = get_text_column(stmt, col++);
1992 job.retry_count = get_int_column(stmt, col++);
1993 job.max_retries = get_int_column(stmt, col++, 3);
1994
1995 job.created_by = get_text_column(stmt, col++);
1996
1997 auto metadata_json = get_text_column(stmt, col++);
1998 job.metadata = deserialize_metadata(metadata_json);
1999
2000 auto created_str = get_text_column(stmt, col++);
2001 job.created_at = from_timestamp_string(created_str.c_str());
2002
2003 auto queued_str = get_text_column(stmt, col++);
static auto deserialize_metadata(std::string_view json) -> std::unordered_map< std::string, std::string >
static auto deserialize_instance_uids(std::string_view json) -> std::vector< std::string >
job_priority job_priority_from_int(int value) noexcept
Parse job_priority from integer.
Definition job_types.h:194
job_type job_type_from_string(std::string_view str) noexcept
Parse job_type from string.
Definition job_types.h:72
job_status job_status_from_string(std::string_view str) noexcept
Parse job_status from string.
Definition job_types.h:123

References db_.

Referenced by find_by_id(), and find_by_pk().

Here is the caller graph for this function:

◆ remove()

VoidResult kcenon::pacs::storage::job_repository::remove ( std::string_view job_id) -> VoidResult
nodiscard

Definition at line 1489 of file job_repository.cpp.

1493 {
1494 job_query_options options;
1495 options.node_id = std::string(node_id);
1496 return find_jobs(options);
1497}
1498
1499VoidResult job_repository::remove(std::string_view job_id) {
1500 if (!db_) {
1501 return VoidResult(kcenon::common::error_info{
1502 -1, "Database not initialized", "job_repository"});
1503 }
1504
1505 static constexpr const char* sql = "DELETE FROM jobs WHERE job_id = ?";
1506
1507 sqlite3_stmt* stmt = nullptr;
1508 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1509 return VoidResult(kcenon::common::error_info{
1510 -1,
1511 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1512 "job_repository"});
1513 }
1514
1515 sqlite3_bind_text(stmt, 1, job_id.data(), static_cast<int>(job_id.size()),
1516 SQLITE_TRANSIENT);
1517
1518 auto rc = sqlite3_step(stmt);
auto remove(std::string_view job_id) -> VoidResult

◆ save()

VoidResult kcenon::pacs::storage::job_repository::save ( const client::job_record & job) -> VoidResult
nodiscard

Definition at line 1202 of file job_repository.cpp.

1202 {
1203 if (!db_) {
1204 return VoidResult(kcenon::common::error_info{
1205 -1, "Database not initialized", "job_repository"});
1206 }
1207
1208 static constexpr const char* sql = R"(
1209 INSERT INTO jobs (
1210 job_id, type, status, priority,
1211 source_node_id, destination_node_id,
1212 patient_id, study_uid, series_uid, sop_instance_uid, instance_uids_json,
1213 total_items, completed_items, failed_items, skipped_items, bytes_transferred,
1214 current_item, current_item_description,
1215 error_message, error_details, retry_count, max_retries,
1216 created_by, metadata_json,
1217 created_at, queued_at, started_at, completed_at
1218 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1219 ON CONFLICT(job_id) DO UPDATE SET
1220 status = excluded.status,
1221 priority = excluded.priority,
1222 total_items = excluded.total_items,
1223 completed_items = excluded.completed_items,
1224 failed_items = excluded.failed_items,
1225 skipped_items = excluded.skipped_items,
1226 bytes_transferred = excluded.bytes_transferred,
1227 current_item = excluded.current_item,
1228 current_item_description = excluded.current_item_description,
1229 error_message = excluded.error_message,
1230 error_details = excluded.error_details,
1231 retry_count = excluded.retry_count,
1232 queued_at = excluded.queued_at,
1233 started_at = excluded.started_at,
1234 completed_at = excluded.completed_at
1235 )";
1236
1237 sqlite3_stmt* stmt = nullptr;
1238 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1239 return VoidResult(kcenon::common::error_info{
1240 -1,
1241 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1242 "job_repository"});
1243 }
1244
1245 int idx = 1;
1246 sqlite3_bind_text(stmt, idx++, job.job_id.c_str(), -1, SQLITE_TRANSIENT);
1247 sqlite3_bind_text(stmt, idx++, client::to_string(job.type), -1,
1248 SQLITE_STATIC);
1249 sqlite3_bind_text(stmt, idx++, client::to_string(job.status), -1,
1250 SQLITE_STATIC);
1251 sqlite3_bind_int(stmt, idx++, static_cast<int>(job.priority));
1252
1253 sqlite3_bind_text(stmt, idx++, job.source_node_id.c_str(), -1,
1254 SQLITE_TRANSIENT);
1255 sqlite3_bind_text(stmt, idx++, job.destination_node_id.c_str(), -1,
1256 SQLITE_TRANSIENT);
1257
1258 bind_optional_text(stmt, idx++, job.patient_id);
1259 bind_optional_text(stmt, idx++, job.study_uid);
1260 bind_optional_text(stmt, idx++, job.series_uid);
1261 bind_optional_text(stmt, idx++, job.sop_instance_uid);
1262
1263 auto uids_json = serialize_instance_uids(job.instance_uids);
1264 sqlite3_bind_text(stmt, idx++, uids_json.c_str(), -1, SQLITE_TRANSIENT);
1265
1266 sqlite3_bind_int64(stmt, idx++,
1267 static_cast<int64_t>(job.progress.total_items));
1268 sqlite3_bind_int64(stmt, idx++,
1269 static_cast<int64_t>(job.progress.completed_items));
1270 sqlite3_bind_int64(stmt, idx++,
1271 static_cast<int64_t>(job.progress.failed_items));
1272 sqlite3_bind_int64(stmt, idx++,
1273 static_cast<int64_t>(job.progress.skipped_items));
1274 sqlite3_bind_int64(stmt, idx++,
1275 static_cast<int64_t>(job.progress.bytes_transferred));
1276
1277 sqlite3_bind_text(stmt, idx++, job.progress.current_item.c_str(), -1,
1278 SQLITE_TRANSIENT);
1279 sqlite3_bind_text(stmt, idx++,
1280 job.progress.current_item_description.c_str(), -1,
1281 SQLITE_TRANSIENT);
1282
1283 sqlite3_bind_text(stmt, idx++, job.error_message.c_str(), -1,
1284 SQLITE_TRANSIENT);
1285 sqlite3_bind_text(stmt, idx++, job.error_details.c_str(), -1,
1286 SQLITE_TRANSIENT);
1287 sqlite3_bind_int(stmt, idx++, job.retry_count);
1288 sqlite3_bind_int(stmt, idx++, job.max_retries);
1289
1290 sqlite3_bind_text(stmt, idx++, job.created_by.c_str(), -1, SQLITE_TRANSIENT);
1291
1292 auto metadata_json = serialize_metadata(job.metadata);
1293 sqlite3_bind_text(stmt, idx++, metadata_json.c_str(), -1, SQLITE_TRANSIENT);
1294
1295 auto created_str = to_timestamp_string(job.created_at);
1296 sqlite3_bind_text(stmt, idx++, created_str.c_str(), -1, SQLITE_TRANSIENT);
1297
1298 bind_optional_timestamp(stmt, idx++, job.queued_at);
1299 bind_optional_timestamp(stmt, idx++, job.started_at);
1300 bind_optional_timestamp(stmt, idx++, job.completed_at);
1301
1302 auto rc = sqlite3_step(stmt);
1303 sqlite3_finalize(stmt);
1304
1305 if (rc != SQLITE_DONE) {
1306 return VoidResult(kcenon::common::error_info{
1307 -1, "Failed to save job: " + std::string(sqlite3_errmsg(db_)),
1308 "job_repository"});
1309 }
1310
1311 return kcenon::common::ok();
1312}
static auto serialize_metadata(const std::unordered_map< std::string, std::string > &metadata) -> std::string
static auto serialize_instance_uids(const std::vector< std::string > &uids) -> std::string

References kcenon::pacs::client::to_string().

Here is the call graph for this function:

◆ serialize_instance_uids()

std::string kcenon::pacs::storage::job_repository::serialize_instance_uids ( const std::vector< std::string > & uids) -> std::string
staticnodiscardprivate

Definition at line 1047 of file job_repository.cpp.

1048 {
1049 if (uids.empty()) return "[]";
1050
1051 std::ostringstream oss;
1052 oss << "[";
1053 for (size_t i = 0; i < uids.size(); ++i) {
1054 if (i > 0) oss << ",";
1055 oss << "\"";
1056 for (char c : uids[i]) {
1057 if (c == '"')
1058 oss << "\\\"";
1059 else if (c == '\\')
1060 oss << "\\\\";
1061 else
1062 oss << c;
1063 }
1064 oss << "\"";
1065 }
1066 oss << "]";
1067 return oss.str();
1068}

◆ serialize_metadata()

std::string kcenon::pacs::storage::job_repository::serialize_metadata ( const std::unordered_map< std::string, std::string > & metadata) -> std::string
staticnodiscardprivate

Definition at line 1110 of file job_repository.cpp.

1111 {
1112 if (metadata.empty()) return "{}";
1113
1114 std::ostringstream oss;
1115 oss << "{";
1116 bool first = true;
1117 for (const auto& [key, value] : metadata) {
1118 if (!first) oss << ",";
1119 first = false;
1120
1121 oss << "\"";
1122 for (char c : key) {
1123 if (c == '"')
1124 oss << "\\\"";
1125 else if (c == '\\')
1126 oss << "\\\\";
1127 else
1128 oss << c;
1129 }
1130 oss << "\":\"";
1131
1132 for (char c : value) {
1133 if (c == '"')
1134 oss << "\\\"";
1135 else if (c == '\\')
1136 oss << "\\\\";
1137 else
1138 oss << c;
1139 }
1140 oss << "\"";
1141 }
1142 oss << "}";
1143 return oss.str();
1144}

◆ update_progress()

VoidResult kcenon::pacs::storage::job_repository::update_progress ( std::string_view job_id,
const client::job_progress & progress ) -> VoidResult
nodiscard

Definition at line 1636 of file job_repository.cpp.

1637 {
1638 return VoidResult(kcenon::common::error_info{
1639 -1, "Failed to update status: " + std::string(sqlite3_errmsg(db_)),
1640 "job_repository"});
1641 }
1642
1643 return kcenon::common::ok();
1644}
1645
1646VoidResult job_repository::update_progress(std::string_view job_id,
1647 const client::job_progress& progress) {
1648 if (!db_) {
1649 return VoidResult(kcenon::common::error_info{
1650 -1, "Database not initialized", "job_repository"});
1651 }
1652
1653 static constexpr const char* sql = R"(
1654 UPDATE jobs SET
1655 total_items = ?,
1656 completed_items = ?,
1657 failed_items = ?,
1658 skipped_items = ?,
1659 bytes_transferred = ?,
1660 current_item = ?,
1661 current_item_description = ?
1662 WHERE job_id = ?
1663 )";
1664
1665 sqlite3_stmt* stmt = nullptr;
1666 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1667 return VoidResult(kcenon::common::error_info{
1668 -1,
1669 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1670 "job_repository"});
1671 }
1672
1673 int idx = 1;
1674 sqlite3_bind_int64(stmt, idx++,
1675 static_cast<int64_t>(progress.total_items));
1676 sqlite3_bind_int64(stmt, idx++,
1677 static_cast<int64_t>(progress.completed_items));
1678 sqlite3_bind_int64(stmt, idx++,
1679 static_cast<int64_t>(progress.failed_items));
1680 sqlite3_bind_int64(stmt, idx++,
1681 static_cast<int64_t>(progress.skipped_items));
1682 sqlite3_bind_int64(stmt, idx++,
1683 static_cast<int64_t>(progress.bytes_transferred));
1684 sqlite3_bind_text(stmt, idx++, progress.current_item.c_str(), -1,
1685 SQLITE_TRANSIENT);
1686 sqlite3_bind_text(stmt, idx++, progress.current_item_description.c_str(),
1687 -1, SQLITE_TRANSIENT);
1688 sqlite3_bind_text(stmt, idx++, job_id.data(),
1689 static_cast<int>(job_id.size()), SQLITE_TRANSIENT);
1690
1691 auto rc = sqlite3_step(stmt);
auto update_progress(std::string_view job_id, const client::job_progress &progress) -> VoidResult

References db_.

◆ update_status()

VoidResult kcenon::pacs::storage::job_repository::update_status ( std::string_view job_id,
client::job_status status,
std::string_view error_message = "",
std::string_view error_details = "" ) -> VoidResult
nodiscard

Definition at line 1579 of file job_repository.cpp.

1592 {
1593 if (!db_) {
1594 return VoidResult(kcenon::common::error_info{
1595 -1, "Database not initialized", "job_repository"});
1596 }
1597
1598 const char* sql = nullptr;
1599 if (status == client::job_status::failed) {
1600 sql = R"(
1601 UPDATE jobs SET
1602 status = ?,
1603 error_message = ?,
1604 error_details = ?
1605 WHERE job_id = ?
1606 )";
1607 } else {
1608 sql = "UPDATE jobs SET status = ? WHERE job_id = ?";
1609 }
1610
1611 sqlite3_stmt* stmt = nullptr;
1612 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1613 return VoidResult(kcenon::common::error_info{
1614 -1,
1615 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1616 "job_repository"});
1617 }
1618
1619 int idx = 1;
1620 sqlite3_bind_text(stmt, idx++, client::to_string(status), -1, SQLITE_STATIC);
1621
1622 if (status == client::job_status::failed) {
1623 sqlite3_bind_text(stmt, idx++, error_message.data(),
1624 static_cast<int>(error_message.size()),
1625 SQLITE_TRANSIENT);
1626 sqlite3_bind_text(stmt, idx++, error_details.data(),
1627 static_cast<int>(error_details.size()),
1628 SQLITE_TRANSIENT);
1629 }
1630
1631 sqlite3_bind_text(stmt, idx++, job_id.data(),
1632 static_cast<int>(job_id.size()), SQLITE_TRANSIENT);
1633
1634 auto rc = sqlite3_step(stmt);
@ failed
Job failed with error.

Member Data Documentation

◆ db_

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

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