PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
prefetch_history_repository.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
13
14#include <chrono>
15#include <cstdio>
16#include <sstream>
17
18#ifdef PACS_WITH_DATABASE_SYSTEM
19
20namespace kcenon::pacs::storage {
21
22namespace {
23
24[[nodiscard]] std::string to_timestamp_string(
25 std::chrono::system_clock::time_point tp) {
26 if (tp == std::chrono::system_clock::time_point{}) {
27 return "";
28 }
29 auto time = std::chrono::system_clock::to_time_t(tp);
30 std::tm tm{};
31#ifdef _WIN32
32 gmtime_s(&tm, &time);
33#else
34 gmtime_r(&time, &tm);
35#endif
36 char buf[32];
37 std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
38 return buf;
39}
40
41[[nodiscard]] std::chrono::system_clock::time_point from_timestamp_string(
42 const std::string& str) {
43 if (str.empty()) {
44 return {};
45 }
46 std::tm tm{};
47 if (std::sscanf(str.c_str(),
48 "%d-%d-%d %d:%d:%d",
49 &tm.tm_year,
50 &tm.tm_mon,
51 &tm.tm_mday,
52 &tm.tm_hour,
53 &tm.tm_min,
54 &tm.tm_sec) < 6) {
55 return {};
56 }
57 tm.tm_year -= 1900;
58 tm.tm_mon -= 1;
59#ifdef _WIN32
60 auto time = _mkgmtime(&tm);
61#else
62 auto time = timegm(&tm);
63#endif
64 return std::chrono::system_clock::from_time_t(time);
65}
66
67} // namespace
68
69prefetch_history_repository::prefetch_history_repository(
70 std::shared_ptr<pacs_database_adapter> db)
71 : base_repository(std::move(db), "prefetch_history", "pk") {}
72
73auto prefetch_history_repository::find_by_patient(
74 std::string_view patient_id,
75 size_t limit) -> list_result_type {
76 if (!db() || !db()->is_connected()) {
77 return list_result_type(kcenon::common::error_info{
78 -1, "Database not connected", "prefetch_history_repository"});
79 }
80
81 auto builder = storage_session().create_query_builder();
82 builder.select(select_columns())
83 .from(table_name())
84 .where("patient_id", "=", std::string(patient_id))
85 .order_by("prefetched_at", database::sort_order::desc)
86 .limit(limit);
87
88 auto result = storage_session().select(builder.build());
89 if (result.is_err()) {
90 return list_result_type(result.error());
91 }
92
93 std::vector<client::prefetch_history> entities;
94 entities.reserve(result.value().size());
95 for (const auto& row : result.value()) {
96 entities.push_back(map_row_to_entity(row));
97 }
98
99 return list_result_type(std::move(entities));
100}
101
102auto prefetch_history_repository::find_by_study(std::string_view study_uid)
103 -> list_result_type {
104 return find_where("study_uid", "=", std::string(study_uid));
105}
106
107auto prefetch_history_repository::find_by_rule(
108 std::string_view rule_id,
109 size_t limit) -> list_result_type {
110 if (!db() || !db()->is_connected()) {
111 return list_result_type(kcenon::common::error_info{
112 -1, "Database not connected", "prefetch_history_repository"});
113 }
114
115 auto builder = storage_session().create_query_builder();
116 builder.select(select_columns())
117 .from(table_name())
118 .where("rule_id", "=", std::string(rule_id))
119 .order_by("prefetched_at", database::sort_order::desc)
120 .limit(limit);
121
122 auto result = storage_session().select(builder.build());
123 if (result.is_err()) {
124 return list_result_type(result.error());
125 }
126
127 std::vector<client::prefetch_history> entities;
128 entities.reserve(result.value().size());
129 for (const auto& row : result.value()) {
130 entities.push_back(map_row_to_entity(row));
131 }
132
133 return list_result_type(std::move(entities));
134}
135
136auto prefetch_history_repository::find_by_status(
137 std::string_view status,
138 size_t limit) -> list_result_type {
139 if (!db() || !db()->is_connected()) {
140 return list_result_type(kcenon::common::error_info{
141 -1, "Database not connected", "prefetch_history_repository"});
142 }
143
144 auto builder = storage_session().create_query_builder();
145 builder.select(select_columns())
146 .from(table_name())
147 .where("status", "=", std::string(status))
148 .order_by("prefetched_at", database::sort_order::desc)
149 .limit(limit);
150
151 auto result = storage_session().select(builder.build());
152 if (result.is_err()) {
153 return list_result_type(result.error());
154 }
155
156 std::vector<client::prefetch_history> entities;
157 entities.reserve(result.value().size());
158 for (const auto& row : result.value()) {
159 entities.push_back(map_row_to_entity(row));
160 }
161
162 return list_result_type(std::move(entities));
163}
164
165auto prefetch_history_repository::find_recent(size_t limit) -> list_result_type {
166 if (!db() || !db()->is_connected()) {
167 return list_result_type(kcenon::common::error_info{
168 -1, "Database not connected", "prefetch_history_repository"});
169 }
170
171 auto builder = storage_session().create_query_builder();
172 builder.select(select_columns())
173 .from(table_name())
174 .order_by("prefetched_at", database::sort_order::desc)
175 .limit(limit);
176
177 auto result = storage_session().select(builder.build());
178 if (result.is_err()) {
179 return list_result_type(result.error());
180 }
181
182 std::vector<client::prefetch_history> entities;
183 entities.reserve(result.value().size());
184 for (const auto& row : result.value()) {
185 entities.push_back(map_row_to_entity(row));
186 }
187
188 return list_result_type(std::move(entities));
189}
190
191auto prefetch_history_repository::is_study_prefetched(
192 std::string_view study_uid) -> Result<bool> {
193 if (!db() || !db()->is_connected()) {
194 return Result<bool>(kcenon::common::error_info{
195 -1, "Database not connected", "prefetch_history_repository"});
196 }
197
198 std::ostringstream sql;
199 sql << "SELECT COUNT(*) as count FROM prefetch_history "
200 << "WHERE study_uid = '" << study_uid << "' "
201 << "AND status IN ('completed', 'pending')";
202
203 auto result = storage_session().select(sql.str());
204 if (result.is_err()) {
205 return Result<bool>(result.error());
206 }
207
208 if (result.value().empty()) {
209 return Result<bool>(false);
210 }
211
212 return Result<bool>(std::stoull(result.value()[0].at("count")) > 0);
213}
214
215auto prefetch_history_repository::count_by_status_on_current_date(
216 std::string_view status) -> Result<size_t> {
217 if (!db() || !db()->is_connected()) {
218 return Result<size_t>(kcenon::common::error_info{
219 -1, "Database not connected", "prefetch_history_repository"});
220 }
221
222 std::ostringstream sql;
223 sql << "SELECT COUNT(*) as count FROM prefetch_history "
224 << "WHERE status = '" << status << "' "
225 << "AND date(prefetched_at) = date('now')";
226
227 auto result = storage_session().select(sql.str());
228 if (result.is_err()) {
229 return Result<size_t>(result.error());
230 }
231
232 if (result.value().empty()) {
233 return Result<size_t>(static_cast<size_t>(0));
234 }
235
236 return Result<size_t>(std::stoull(result.value()[0].at("count")));
237}
238
239auto prefetch_history_repository::update_status(
240 int64_t pk,
241 std::string_view status) -> VoidResult {
242 if (!db() || !db()->is_connected()) {
243 return VoidResult(kcenon::common::error_info{
244 -1, "Database not connected", "prefetch_history_repository"});
245 }
246
247 std::ostringstream sql;
248 sql << "UPDATE prefetch_history SET status = '" << status
249 << "' WHERE pk = " << pk;
250
251 auto result = storage_session().update(sql.str());
252 if (result.is_err()) {
253 return VoidResult(result.error());
254 }
255
256 return kcenon::common::ok();
257}
258
259auto prefetch_history_repository::cleanup_old(std::chrono::hours max_age)
260 -> Result<size_t> {
261 if (!db() || !db()->is_connected()) {
262 return Result<size_t>(kcenon::common::error_info{
263 -1, "Database not connected", "prefetch_history_repository"});
264 }
265
266 std::ostringstream sql;
267 sql << R"(
268 DELETE FROM prefetch_history
269 WHERE prefetched_at < datetime('now', '-)"
270 << max_age.count() << R"( hours'))";
271
272 auto result = storage_session().remove(sql.str());
273 if (result.is_err()) {
274 return Result<size_t>(result.error());
275 }
276
277 return Result<size_t>(static_cast<size_t>(result.value()));
278}
279
280auto prefetch_history_repository::map_row_to_entity(
281 const database_row& row) const -> client::prefetch_history {
282 client::prefetch_history history;
283
284 history.pk = std::stoll(row.at("pk"));
285 history.patient_id = row.at("patient_id");
286 history.study_uid = row.at("study_uid");
287 history.rule_id = row.at("rule_id");
288 history.source_node_id = row.at("source_node_id");
289 history.job_id = row.at("job_id");
290 history.status = row.at("status");
291 history.prefetched_at = parse_timestamp(row.at("prefetched_at"));
292
293 return history;
294}
295
296auto prefetch_history_repository::entity_to_row(
297 const client::prefetch_history& entity) const
298 -> std::map<std::string, database_value> {
299 return {{"patient_id", entity.patient_id},
300 {"study_uid", entity.study_uid},
301 {"rule_id", entity.rule_id},
302 {"source_node_id", entity.source_node_id},
303 {"job_id", entity.job_id},
304 {"status", entity.status},
305 {"prefetched_at", format_timestamp(entity.prefetched_at)}};
306}
307
308auto prefetch_history_repository::get_pk(
309 const client::prefetch_history& entity) const -> int64_t {
310 return entity.pk;
311}
312
313auto prefetch_history_repository::has_pk(
314 const client::prefetch_history& entity) const -> bool {
315 return entity.pk > 0;
316}
317
318auto prefetch_history_repository::select_columns() const
319 -> std::vector<std::string> {
320 return {"pk",
321 "patient_id",
322 "study_uid",
323 "rule_id",
324 "source_node_id",
325 "job_id",
326 "status",
327 "prefetched_at"};
328}
329
330auto prefetch_history_repository::parse_timestamp(const std::string& str) const
331 -> std::chrono::system_clock::time_point {
332 return from_timestamp_string(str);
333}
334
335auto prefetch_history_repository::format_timestamp(
336 std::chrono::system_clock::time_point tp) const -> std::string {
337 return to_timestamp_string(tp);
338}
339
340} // namespace kcenon::pacs::storage
341
342#endif // PACS_WITH_DATABASE_SYSTEM
constexpr dicom_tag status
Status.
@ move
C-MOVE move request/response.
Repository for prefetch history records using base_repository pattern.