1use dicom_object::{FileDicomObject, InMemDicomObject};
3
4use futures_util::{stream::BoxStream, Stream, StreamExt};
5use rand::{distr::Alphanumeric, Rng};
6use reqwest::Body;
7use snafu::ResultExt;
8
9use crate::{DicomWebClient, DicomWebError, RequestFailedSnafu};
10
11pub struct WadoStowRequest {
13 client: DicomWebClient,
14 url: String,
15 instances: BoxStream<'static, FileDicomObject<InMemDicomObject>>,
16}
17
18impl WadoStowRequest {
19 fn new(client: DicomWebClient, url: String) -> Self {
20 WadoStowRequest {
21 client,
22 url,
23 instances: futures_util::stream::empty().boxed(),
24 }
25 }
26
27 pub fn with_instances(
28 mut self,
29 instances: impl Stream<Item = FileDicomObject<InMemDicomObject>> + Send + 'static,
30 ) -> Self {
31 self.instances = instances.boxed();
32 self
33 }
34
35 pub async fn run(self) -> Result<(), DicomWebError> {
36 let mut request = self.client.client.post(&self.url);
37
38 if let Some(username) = &self.client.username {
40 request = request.basic_auth(username, self.client.password.as_ref());
41 }
42 else if let Some(bearer_token) = &self.client.bearer_token {
44 request = request.bearer_auth(bearer_token);
45 }
46
47 let boundary: String = rand::rng()
48 .sample_iter(&Alphanumeric)
49 .take(8)
50 .map(char::from)
51 .collect();
52
53 let request = request.header(
54 "Content-Type",
55 format!(
56 "multipart/related; type=\"application/dicom\"; boundary={}",
57 boundary
58 ),
59 );
60
61 let boundary_clone = boundary.clone();
62
63 let multipart_stream = self.instances.map(move |instance| {
65 let mut multipart_item = Vec::new();
66 let mut buffer = Vec::new();
67 instance.clone().write_all(&mut buffer).unwrap();
68 multipart_item.extend_from_slice(b"--");
69 multipart_item.extend_from_slice(boundary.as_bytes());
70 multipart_item.extend_from_slice(b"\r\n");
71 multipart_item.extend_from_slice(b"Content-Type: application/dicom\r\n\r\n");
72 multipart_item.extend_from_slice(&buffer);
73 multipart_item.extend_from_slice(b"\r\n");
74 Ok::<_, std::io::Error>(multipart_item)
75 });
76
77 let multipart_stream = multipart_stream.chain(futures_util::stream::once(async move {
79 Ok(format!("--{}--\r\n", boundary_clone).into_bytes())
80 }));
81
82 let response = request
83 .body(Body::wrap_stream(multipart_stream))
84 .send()
85 .await
86 .context(RequestFailedSnafu { url: &self.url })?;
87
88 if !response.status().is_success() {
89 return Err(DicomWebError::HttpStatusFailure {
90 status_code: response.status(),
91 });
92 }
93
94 Ok(())
95 }
96}
97
98impl DicomWebClient {
99 pub fn store_instances(&self) -> WadoStowRequest {
101 let url = format!("{}/studies", self.stow_url);
102 WadoStowRequest::new(self.clone(), url)
103 }
104
105 pub fn store_instances_in_study(&self, study_instance_uid: &str) -> WadoStowRequest {
107 let url = format!("{}/studies/{}", self.stow_url, study_instance_uid);
108 WadoStowRequest::new(self.clone(), url)
109 }
110}