PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
ups_watch_scp.cpp
Go to the documentation of this file.
1
7
14
15namespace kcenon::pacs::services {
16
17// =============================================================================
18// Construction
19// =============================================================================
20
21ups_watch_scp::ups_watch_scp(std::shared_ptr<di::ILogger> logger)
22 : scp_service(std::move(logger)) {}
23
24// =============================================================================
25// Configuration
26// =============================================================================
27
31
35
40
42 event_callback_ = std::move(callback);
43}
44
45// =============================================================================
46// scp_service Interface Implementation
47// =============================================================================
48
49std::vector<std::string> ups_watch_scp::supported_sop_classes() const {
50 return {
51 std::string(ups_watch_sop_class_uid)
52 };
53}
54
57 uint8_t context_id,
58 const network::dimse::dimse_message& request) {
59
60 using namespace network::dimse;
61
62 if (request.command() != command_field::n_action_rq) {
65 "Expected N-ACTION-RQ but received " +
66 std::string(to_string(request.command())));
67 }
68
69 return handle_n_action(assoc, context_id, request);
70}
71
72std::string_view ups_watch_scp::service_name() const noexcept {
73 return "UPS Watch SCP";
74}
75
76// =============================================================================
77// Event Notification
78// =============================================================================
79
81 const std::string& workitem_uid,
82 const std::string& new_state) {
83
84 core::dicom_dataset event_info;
85 event_info.set_string(
87 encoding::vr_type::CS, new_state);
88
89 dispatch_event(ups_event_state_report, workitem_uid, event_info);
90}
91
93 const std::string& workitem_uid,
94 const std::string& reason) {
95
96 core::dicom_dataset event_info;
97 if (!reason.empty()) {
98 event_info.set_string(
100 encoding::vr_type::LT, reason);
101 }
102
103 dispatch_event(ups_event_cancel_requested, workitem_uid, event_info);
104}
105
107 const std::string& workitem_uid,
108 int progress_percent) {
109
110 core::dicom_dataset event_info;
111 event_info.set_string(
113 encoding::vr_type::DS, std::to_string(progress_percent));
114
115 dispatch_event(ups_event_progress_report, workitem_uid, event_info);
116}
117
118// =============================================================================
119// Statistics
120// =============================================================================
121
123 return subscriptions_created_.load();
124}
125
127 return subscriptions_removed_.load();
128}
129
130size_t ups_watch_scp::events_sent() const noexcept {
131 return events_sent_.load();
132}
133
139
140// =============================================================================
141// Private Implementation - N-ACTION Dispatch
142// =============================================================================
143
146 uint8_t context_id,
147 const network::dimse::dimse_message& request) {
148
149 using namespace network::dimse;
150
151 // Get Action Type ID
152 auto action_type = request.action_type_id();
153 if (!action_type.has_value()) {
156 "Missing Action Type ID in N-ACTION request");
157 }
158
159 // Get SOP Instance UID (workitem UID or global subscription UID)
160 auto sop_instance_uid = request.requested_sop_instance_uid();
161 if (sop_instance_uid.empty()) {
162 sop_instance_uid = request.affected_sop_instance_uid();
163 }
164
165 uint16_t action_id = action_type.value();
166 uint16_t message_id = request.message_id();
167
168 switch (action_id) {
170 return handle_subscribe(
171 assoc, context_id, message_id, sop_instance_uid);
172
174 return handle_unsubscribe(
175 assoc, context_id, message_id, sop_instance_uid);
176
179 assoc, context_id, message_id);
180
181 default:
183 assoc, context_id, message_id,
184 sop_instance_uid, action_id,
185 status_error_no_such_action_type);
186 }
187}
188
189// =============================================================================
190// Private Implementation - Subscribe
191// =============================================================================
192
195 uint8_t context_id,
196 uint16_t message_id,
197 const std::string& sop_instance_uid) {
198
199 using namespace network::dimse;
200
201 if (!subscribe_handler_) {
204 "No subscribe handler configured for UPS Watch SCP");
205 }
206
207 // Determine subscriber AE from the association
208 std::string subscriber_ae{assoc.calling_ae()};
209
210 // Determine if this is a global or workitem-specific subscription
211 std::string workitem_uid;
212 if (sop_instance_uid != ups_global_subscription_instance_uid) {
213 workitem_uid = sop_instance_uid;
214 }
215
216 // Deletion lock defaults to false (could be extracted from dataset)
217 bool deletion_lock = false;
218
219 logger_->debug("UPS Watch: Subscribe request from " + subscriber_ae +
220 (workitem_uid.empty() ? " (global)" :
221 " for workitem " + workitem_uid));
222
223 auto result = subscribe_handler_(subscriber_ae, workitem_uid, deletion_lock);
224 if (result.is_err()) {
226 assoc, context_id, message_id,
227 sop_instance_uid, ups_watch_action_subscribe,
228 status_error_unable_to_process);
229 }
230
232
233 logger_->info("UPS Watch: Subscription created for " + subscriber_ae);
234
236 assoc, context_id, message_id,
237 sop_instance_uid, ups_watch_action_subscribe,
238 status_success);
239}
240
241// =============================================================================
242// Private Implementation - Unsubscribe
243// =============================================================================
244
247 uint8_t context_id,
248 uint16_t message_id,
249 const std::string& sop_instance_uid) {
250
251 using namespace network::dimse;
252
256 "No unsubscribe handler configured for UPS Watch SCP");
257 }
258
259 std::string subscriber_ae{assoc.calling_ae()};
260
261 // Determine if this is a global or workitem-specific unsubscription
262 std::string workitem_uid;
263 if (sop_instance_uid != ups_global_subscription_instance_uid) {
264 workitem_uid = sop_instance_uid;
265 }
266
267 logger_->debug("UPS Watch: Unsubscribe request from " + subscriber_ae +
268 (workitem_uid.empty() ? " (global)" :
269 " for workitem " + workitem_uid));
270
271 auto result = unsubscribe_handler_(subscriber_ae, workitem_uid);
272 if (result.is_err()) {
274 assoc, context_id, message_id,
275 sop_instance_uid, ups_watch_action_unsubscribe,
276 status_error_unable_to_process);
277 }
278
280
281 logger_->info("UPS Watch: Subscription removed for " + subscriber_ae);
282
284 assoc, context_id, message_id,
285 sop_instance_uid, ups_watch_action_unsubscribe,
286 status_success);
287}
288
289// =============================================================================
290// Private Implementation - Suspend Global
291// =============================================================================
292
295 uint8_t context_id,
296 uint16_t message_id) {
297
298 using namespace network::dimse;
299
303 "No unsubscribe handler configured for UPS Watch SCP");
304 }
305
306 std::string subscriber_ae{assoc.calling_ae()};
307
308 logger_->debug("UPS Watch: Suspend global subscription from " +
309 subscriber_ae);
310
311 // Suspend global = unsubscribe from global (empty workitem_uid)
312 auto result = unsubscribe_handler_(subscriber_ae, "");
313 if (result.is_err()) {
315 assoc, context_id, message_id,
318 status_error_unable_to_process);
319 }
320
322
323 logger_->info("UPS Watch: Global subscription suspended for " +
324 subscriber_ae);
325
327 assoc, context_id, message_id,
330 status_success);
331}
332
333// =============================================================================
334// Private Implementation - Response Helper
335// =============================================================================
336
339 uint8_t context_id,
340 uint16_t message_id,
341 const std::string& sop_instance_uid,
342 uint16_t action_type_id,
344
345 using namespace network::dimse;
346
347 auto response = make_n_action_rsp(
348 message_id,
350 sop_instance_uid,
351 action_type_id,
352 status);
353
354 return assoc.send_dimse(context_id, response);
355}
356
357// =============================================================================
358// Private Implementation - Event Dispatch
359// =============================================================================
360
362 uint16_t event_type_id,
363 const std::string& workitem_uid,
364 const core::dicom_dataset& event_info) {
365
367 return;
368 }
369
370 // Get subscribers for this workitem
371 auto subscribers_result = get_subscribers_handler_(workitem_uid);
372 if (subscribers_result.is_err()) {
373 logger_->warn("UPS Watch: Failed to get subscribers for " +
374 workitem_uid);
375 return;
376 }
377
378 const auto& subscribers = subscribers_result.value();
379
380 for (const auto& subscriber_ae : subscribers) {
381 event_callback_(subscriber_ae, event_type_id,
382 workitem_uid, event_info);
383 ++events_sent_;
384 }
385
386 if (!subscribers.empty()) {
387 logger_->info("UPS Watch: Dispatched event type " +
388 std::to_string(event_type_id) + " to " +
389 std::to_string(subscribers.size()) +
390 " subscribers for workitem " + workitem_uid);
391 }
392}
393
394} // namespace kcenon::pacs::services
void set_string(dicom_tag tag, encoding::vr_type vr, std::string_view value)
Set a string value for the given tag.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
std::string_view calling_ae() const noexcept
Get calling AE title.
auto message_id() const noexcept -> uint16_t
Get the message ID.
auto requested_sop_instance_uid() const -> std::string
Get the Requested SOP Instance UID (for N-SET, N-GET, N-ACTION, N-DELETE)
auto action_type_id() const -> std::optional< uint16_t >
Get the Action Type ID (for N-ACTION)
auto affected_sop_instance_uid() const -> std::string
Get the Affected SOP Instance UID.
auto command() const noexcept -> command_field
Get the command field.
std::shared_ptr< di::ILogger > logger_
Logger instance for service logging.
ups_unsubscribe_handler unsubscribe_handler_
std::atomic< size_t > subscriptions_removed_
void set_subscribe_handler(ups_subscribe_handler handler)
void dispatch_event(uint16_t event_type_id, const std::string &workitem_uid, const core::dicom_dataset &event_info)
ups_watch_scp(std::shared_ptr< di::ILogger > logger=nullptr)
Construct UPS Watch SCP with optional logger.
std::string_view service_name() const noexcept override
Get the service name for logging/debugging.
void notify_state_change(const std::string &workitem_uid, const std::string &new_state)
Notify subscribers of a workitem state change.
void notify_cancel_requested(const std::string &workitem_uid, const std::string &reason)
Notify subscribers of a cancel request.
network::Result< std::monostate > handle_suspend_global(network::association &assoc, uint8_t context_id, uint16_t message_id)
ups_get_subscribers_handler get_subscribers_handler_
void notify_progress(const std::string &workitem_uid, int progress_percent)
Notify subscribers of progress update.
std::atomic< size_t > subscriptions_created_
ups_subscribe_handler subscribe_handler_
network::Result< std::monostate > handle_subscribe(network::association &assoc, uint8_t context_id, uint16_t message_id, const std::string &sop_instance_uid)
network::Result< std::monostate > send_n_action_response(network::association &assoc, uint8_t context_id, uint16_t message_id, const std::string &sop_instance_uid, uint16_t action_type_id, network::dimse::status_code status)
network::Result< std::monostate > handle_n_action(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
size_t subscriptions_created() const noexcept
network::Result< std::monostate > handle_message(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request) override
Handle an incoming DIMSE message.
void set_event_callback(ups_event_callback callback)
size_t subscriptions_removed() const noexcept
void set_unsubscribe_handler(ups_unsubscribe_handler handler)
network::Result< std::monostate > handle_unsubscribe(network::association &assoc, uint8_t context_id, uint16_t message_id, const std::string &sop_instance_uid)
void set_get_subscribers_handler(ups_get_subscribers_handler handler)
std::vector< std::string > supported_sop_classes() const override
Get the list of SOP Class UIDs supported by this service.
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
@ DS
Decimal String (16 chars max)
@ CS
Code String (16 chars max, uppercase + digits + space + underscore)
@ LT
Long Text (10240 chars max)
constexpr int ups_unexpected_command
Definition result.h:210
constexpr int ups_invalid_action_type
Definition result.h:214
constexpr int ups_handler_not_set
Definition result.h:209
uint16_t status_code
DIMSE status code type alias.
constexpr core::dicom_tag procedure_step_progress
Procedure Step Progress (0074,1004)
constexpr core::dicom_tag procedure_step_state
Procedure Step State (0074,1000)
constexpr core::dicom_tag reason_for_cancellation
Reason for Cancellation (0074,1238)
std::function< network::Result< std::monostate >( const std::string &subscriber_ae, const std::string &workitem_uid, bool deletion_lock)> ups_subscribe_handler
Subscribe handler function type.
constexpr uint16_t ups_event_state_report
Event Type 1: UPS State Report — workitem state changed.
constexpr uint16_t ups_event_progress_report
Event Type 3: UPS Progress Report — progress percentage update.
constexpr std::string_view ups_watch_sop_class_uid
UPS Watch SOP Class UID (PS3.4 Table CC.2-1)
std::function< network::Result< std::monostate >( const std::string &subscriber_ae, const std::string &workitem_uid)> ups_unsubscribe_handler
Unsubscribe handler function type.
constexpr uint16_t ups_watch_action_suspend_global
N-ACTION Type 5: Suspend Global Subscription (PS3.4 CC.2.3.5)
constexpr std::string_view ups_global_subscription_instance_uid
UPS Global Subscription SOP Instance UID Used as the SOP Instance UID for global (non-workitem-specif...
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
constexpr uint16_t ups_event_cancel_requested
Event Type 2: UPS Cancel Requested — performer should stop processing.
std::function< network::Result< std::vector< std::string > >( const std::string &workitem_uid)> ups_get_subscribers_handler
Get subscribers handler function type.
std::function< void( const std::string &subscriber_ae, uint16_t event_type_id, const std::string &workitem_uid, const core::dicom_dataset &event_info)> ups_event_callback
Event notification callback type.
constexpr uint16_t ups_watch_action_unsubscribe
N-ACTION Type 4: Unsubscribe from Receiving UPS Event Reports (PS3.4 CC.2.3.4)
constexpr uint16_t ups_watch_action_subscribe
N-ACTION Type 3: Subscribe to Receive UPS Event Reports (PS3.4 CC.2.3.3)
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249
Result<T> type aliases and helpers for PACS system.
DIMSE status codes.
DICOM UPS (Unified Procedure Step) Push SCP service.
DICOM UPS Watch SCP service (subscription and event notification)