1pub(crate) mod crypto;
2pub mod filters;
3pub mod sorts;
4
5pub(crate) use crypto::*;
6use dco3_crypto::DracoonCrypto;
7use dco3_crypto::DracoonRSACrypto;
8use dco3_crypto::PlainUserKeyPairContainer;
9use dco3_derive::FromResponse;
10pub use filters::*;
11pub use sorts::*;
12use tracing::{debug, error, warn};
13
14use std::fmt::Debug;
15use std::fmt::Display;
16use std::fmt::Formatter;
17use std::sync::Arc;
18use std::sync::Mutex;
19
20use crate::client::DracoonClient;
21use crate::{
22 client::{errors::DracoonClientError, models::DracoonErrorResponse},
23 models::{ObjectExpiration, Range, RangedItems},
24 utils::parse_body,
25 utils::FromResponse,
26};
27
28use async_trait::async_trait;
29use chrono::{DateTime, Utc};
30use dco3_crypto::FileKey;
31use dco3_crypto::PublicKeyContainer;
32use reqwest::{Response, StatusCode};
33use serde::{Deserialize, Serialize};
34
35use super::rooms::models::NodePermissionsBuilder;
36
37#[derive(Clone)]
38pub struct NodesEndpoint<S> {
39 client: Arc<DracoonClient<S>>,
40 state: std::marker::PhantomData<S>,
41}
42
43impl<S> NodesEndpoint<S> {
44 pub fn new(client: Arc<DracoonClient<S>>) -> Self {
45 Self {
46 client,
47 state: std::marker::PhantomData,
48 }
49 }
50
51 pub fn client(&self) -> &Arc<DracoonClient<S>> {
52 &self.client
53 }
54}
55
56pub type DownloadProgressCallback = Box<dyn FnMut(u64, u64) + Send + Sync>;
58
59pub type UploadProgressCallback = Box<dyn FnMut(u64, u64) + Send + Sync>;
61
62pub struct CloneableUploadProgressCallback(Arc<Mutex<UploadProgressCallback>>);
64
65impl Clone for CloneableUploadProgressCallback {
66 fn clone(&self) -> Self {
67 Self(self.0.clone())
68 }
69}
70
71impl CloneableUploadProgressCallback {
72 pub fn new<F>(callback: F) -> Self
73 where
74 F: 'static + FnMut(u64, u64) + Send + Sync,
75 {
76 Self(Arc::new(Mutex::new(Box::new(callback))))
77 }
78
79 pub fn call(&self, bytes_read: u64, total_size: u64) {
80 (self.0.lock().unwrap())(bytes_read, total_size);
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct FileMeta {
87 pub name: String,
88 pub size: u64,
89 pub timestamp_creation: Option<DateTime<Utc>>,
90 pub timestamp_modification: Option<DateTime<Utc>>,
91}
92
93#[derive(Default)]
94pub struct FileMetaBuilder {
95 name: String,
96 size: u64,
97 timestamp_creation: Option<DateTime<Utc>>,
98 timestamp_modification: Option<DateTime<Utc>>,
99}
100
101impl FileMeta {
102 pub fn builder(name: impl Into<String>, size: u64) -> FileMetaBuilder {
103 FileMetaBuilder::new(name, size)
104 }
105}
106
107impl FileMetaBuilder {
108 pub fn new(name: impl Into<String>, size: u64) -> Self {
109 Self {
110 name: name.into(),
111 size,
112 timestamp_creation: None,
113 timestamp_modification: None,
114 }
115 }
116
117 pub fn with_timestamp_creation(mut self, timestamp_creation: DateTime<Utc>) -> Self {
118 self.timestamp_creation = Some(timestamp_creation);
119 self
120 }
121
122 pub fn with_timestamp_modification(mut self, timestamp_modification: DateTime<Utc>) -> Self {
123 self.timestamp_modification = Some(timestamp_modification);
124 self
125 }
126
127 pub fn build(self) -> FileMeta {
128 FileMeta {
129 name: self.name,
130 size: self.size,
131 timestamp_creation: self.timestamp_creation,
132 timestamp_modification: self.timestamp_modification,
133 }
134 }
135}
136
137#[derive(Debug, Clone)]
139pub struct UploadOptions {
140 pub expiration: Option<ObjectExpiration>,
141 pub classification: Option<u8>,
142 pub keep_share_links: Option<bool>,
143 pub resolution_strategy: Option<ResolutionStrategy>,
144 pub file_meta: FileMeta,
145}
146
147impl UploadOptions {
148 pub fn builder(file_meta: FileMeta) -> UploadOptionsBuilder {
149 UploadOptionsBuilder::new(file_meta)
150 }
151}
152
153pub struct UploadOptionsBuilder {
154 file_meta: FileMeta,
155 expiration: Option<ObjectExpiration>,
156 classification: Option<u8>,
157 keep_share_links: Option<bool>,
158 resolution_strategy: Option<ResolutionStrategy>,
159}
160
161impl UploadOptionsBuilder {
162 pub fn new(file_meta: FileMeta) -> Self {
163 Self {
164 expiration: None,
165 classification: None,
166 keep_share_links: None,
167 resolution_strategy: None,
168 file_meta,
169 }
170 }
171
172 pub fn with_expiration(mut self, expiration: impl Into<ObjectExpiration>) -> Self {
173 self.expiration = Some(expiration.into());
174 self
175 }
176
177 pub fn with_classification(mut self, classification: u8) -> Self {
178 self.classification = Some(classification);
179 self
180 }
181
182 pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
183 self.keep_share_links = Some(keep_share_links);
184 self
185 }
186
187 pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
188 self.resolution_strategy = Some(resolution_strategy);
189 self
190 }
191
192 pub fn build(self) -> UploadOptions {
193 UploadOptions {
194 expiration: self.expiration,
195 classification: self.classification,
196 keep_share_links: self.keep_share_links,
197 resolution_strategy: self.resolution_strategy,
198 file_meta: self.file_meta,
199 }
200 }
201}
202
203pub type NodeList = RangedItems<Node>;
205
206impl NodeList {
207 pub fn get_files(&self) -> Vec<Node> {
208 self.items
209 .iter()
210 .filter(|node| node.node_type == NodeType::File)
211 .cloned()
212 .collect()
213 }
214
215 pub fn get_folders(&self) -> Vec<Node> {
216 self.items
217 .iter()
218 .filter(|node| node.node_type == NodeType::Folder)
219 .cloned()
220 .collect()
221 }
222
223 pub fn get_rooms(&self) -> Vec<Node> {
224 self.items
225 .iter()
226 .filter(|node| node.node_type == NodeType::Room)
227 .cloned()
228 .collect()
229 }
230}
231
232#[derive(Debug, Deserialize, Clone, FromResponse)]
234#[serde(rename_all = "camelCase")]
235pub struct Node {
236 pub id: u64,
237 pub reference_id: Option<u64>,
238 #[serde(rename = "type")]
239 pub node_type: NodeType,
240 pub name: String,
241 pub timestamp_creation: Option<DateTime<Utc>>,
242 pub timestamp_modification: Option<DateTime<Utc>>,
243 pub parent_id: Option<u64>,
244 pub parent_path: Option<String>,
245 pub created_at: Option<DateTime<Utc>>,
246 pub created_by: Option<UserInfo>,
247 pub updated_at: Option<DateTime<Utc>>,
248 pub updated_by: Option<UserInfo>,
249 pub expire_at: Option<DateTime<Utc>>,
250 pub hash: Option<String>,
251 pub file_type: Option<String>,
252 pub media_type: Option<String>,
253 pub size: Option<u64>,
254 pub classification: Option<u64>,
255 pub notes: Option<String>,
256 pub permissions: Option<NodePermissions>,
257 pub inherit_permissions: Option<bool>,
258 pub is_encrypted: Option<bool>,
259 pub encryption_info: Option<EncryptionInfo>,
260 pub cnt_deleted_versions: Option<u64>,
261 pub cnt_comments: Option<u64>,
262 pub cnt_upload_shares: Option<u64>,
263 pub cnt_download_shares: Option<u64>,
264 pub recycle_bin_retention_period: Option<u64>,
265 pub has_activities_log: Option<bool>,
266 pub quota: Option<u64>,
267 pub is_favorite: Option<bool>,
268 pub branch_version: Option<u64>,
269 pub media_token: Option<String>,
270 pub is_browsable: Option<bool>,
271 pub cnt_rooms: Option<u64>,
272 pub cnt_folders: Option<u64>,
273 pub cnt_files: Option<u64>,
274 pub auth_parent_id: Option<u64>,
275}
276
277#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
278pub enum NodeType {
279 #[serde(rename = "room")]
280 Room,
281 #[serde(rename = "folder")]
282 Folder,
283 #[serde(rename = "file")]
284 File,
285}
286
287impl From<NodeType> for String {
288 fn from(node_type: NodeType) -> Self {
289 match node_type {
290 NodeType::Room => "room".to_string(),
291 NodeType::Folder => "folder".to_string(),
292 NodeType::File => "file".to_string(),
293 }
294 }
295}
296
297impl From<&NodeType> for String {
298 fn from(node_type: &NodeType) -> Self {
299 match node_type {
300 NodeType::Room => "room".to_string(),
301 NodeType::Folder => "folder".to_string(),
302 NodeType::File => "file".to_string(),
303 }
304 }
305}
306
307#[derive(Debug, Serialize, Deserialize, Clone)]
309#[serde(rename_all = "camelCase")]
310#[allow(clippy::struct_excessive_bools)]
311pub struct NodePermissions {
312 pub manage: bool,
313 pub read: bool,
314 pub create: bool,
315 pub change: bool,
316 pub delete: bool,
317 pub manage_download_share: bool,
318 pub manage_upload_share: bool,
319 pub read_recycle_bin: bool,
320 pub restore_recycle_bin: bool,
321 pub delete_recycle_bin: bool,
322}
323
324impl NodePermissions {
325 pub fn builder() -> NodePermissionsBuilder {
326 NodePermissionsBuilder::new()
327 }
328
329 pub fn new_with_edit_permissions() -> Self {
330 Self {
331 manage: false,
332 read: true,
333 create: true,
334 change: true,
335 delete: true,
336 manage_download_share: true,
337 manage_upload_share: true,
338 read_recycle_bin: true,
339 restore_recycle_bin: true,
340 delete_recycle_bin: false,
341 }
342 }
343
344 pub fn new_with_read_permissions() -> Self {
345 Self {
346 manage: false,
347 read: true,
348 create: false,
349 change: false,
350 delete: false,
351 manage_download_share: false,
352 manage_upload_share: false,
353 read_recycle_bin: false,
354 restore_recycle_bin: false,
355 delete_recycle_bin: false,
356 }
357 }
358
359 pub fn new_with_manage_permissions() -> Self {
360 Self {
361 manage: true,
362 read: true,
363 create: true,
364 change: true,
365 delete: true,
366 manage_download_share: true,
367 manage_upload_share: true,
368 read_recycle_bin: true,
369 restore_recycle_bin: true,
370 delete_recycle_bin: true,
371 }
372 }
373}
374
375impl Display for NodePermissions {
376 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377 let mapping = [
378 (self.manage, 'm'),
379 (self.read, 'r'),
380 (self.create, 'w'),
381 (self.change, 'c'),
382 (self.delete, 'd'),
383 (self.manage_download_share, 'm'),
384 (self.manage_upload_share, 'm'),
385 (self.read_recycle_bin, 'r'),
386 (self.restore_recycle_bin, 'r'),
387 (self.delete_recycle_bin, 'd'),
388 ];
389
390 let mut perms = String::with_capacity(mapping.len());
391
392 for (i, &(flag, ch)) in mapping.iter().enumerate() {
393 perms.push(if flag { ch } else { '-' });
394
395 if i == 4 {
397 perms.push('-');
398 }
399 }
400
401 formatter.write_str(&perms)?;
402
403 Ok(())
404 }
405}
406
407#[derive(Debug, Serialize, Deserialize, Clone)]
409#[serde(rename_all = "camelCase")]
410pub struct EncryptionInfo {
411 user_key_state: String,
412 room_key_state: String,
413 data_space_key_state: String,
414}
415
416#[derive(Debug, Deserialize, Clone)]
418#[serde(rename_all = "camelCase")]
419pub struct UserInfo {
420 pub id: i64,
421 pub user_type: UserType,
422 pub user_name: Option<String>,
423 pub avatar_uuid: String,
424 pub first_name: Option<String>,
425 pub last_name: Option<String>,
426 pub email: Option<String>,
427}
428
429#[derive(Deserialize, Debug, Clone, PartialEq)]
430pub enum UserType {
431 #[serde(rename = "internal")]
432 Internal,
433 #[serde(rename = "external")]
434 External,
435 #[serde(rename = "system")]
436 System,
437 #[serde(rename = "deleted")]
438 Deleted,
439}
440
441#[async_trait]
442impl FromResponse for NodeList {
443 async fn from_response(res: Response) -> Result<Self, DracoonClientError> {
445 parse_body::<Self, DracoonErrorResponse>(res).await
446 }
447}
448
449#[derive(Serialize, Deserialize, Debug, FromResponse)]
451#[serde(rename_all = "camelCase")]
452pub struct DownloadUrlResponse {
453 pub download_url: String,
454}
455
456#[derive(Debug, Deserialize, PartialEq, Clone)]
458#[serde(rename_all = "PascalCase")]
459pub struct S3XmlError {
460 code: Option<String>,
461 request_id: Option<String>,
462 host_id: Option<String>,
463 message: Option<String>,
464 argument_name: Option<String>,
465}
466
467#[derive(Debug, PartialEq, Clone)]
469pub enum S3ErrorKind {
470 Xml(S3XmlError),
471 Protocol { code: &'static str, message: String },
472}
473
474#[derive(Debug, PartialEq, Clone)]
476pub struct S3ErrorResponse {
477 pub status: StatusCode,
478 pub kind: S3ErrorKind,
479}
480
481impl Display for S3ErrorResponse {
482 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
483 match &self.kind {
484 S3ErrorKind::Xml(error) => write!(
485 f,
486 "Error: {} ({})",
487 error
488 .message
489 .as_ref()
490 .unwrap_or(&String::from("Unknown S3 error")),
491 self.status,
492 ),
493 S3ErrorKind::Protocol { code, message } => {
494 write!(f, "Error: {message} [{code}] ({})", self.status)
495 }
496 }
497 }
498}
499
500impl S3ErrorResponse {
501 pub fn from_xml_error(status: StatusCode, error: S3XmlError) -> Self {
503 Self {
504 status,
505 kind: S3ErrorKind::Xml(error),
506 }
507 }
508
509 pub fn protocol(status: StatusCode, code: &'static str, message: impl Into<String>) -> Self {
511 Self {
512 status,
513 kind: S3ErrorKind::Protocol {
514 code,
515 message: message.into(),
516 },
517 }
518 }
519}
520
521#[async_trait]
522impl FromResponse for FileKey {
523 async fn from_response(res: Response) -> Result<Self, DracoonClientError> {
525 parse_body::<Self, DracoonErrorResponse>(res).await
526 }
527}
528
529#[derive(Debug, Deserialize, FromResponse)]
530#[serde(rename_all = "camelCase")]
531pub struct CreateFileUploadResponse {
532 pub upload_url: String,
533 pub upload_id: String,
534 pub token: String,
535}
536
537#[derive(Debug, Deserialize, Clone)]
538#[serde(rename_all = "camelCase")]
539pub struct PresignedUrl {
540 pub url: String,
541 pub part_number: u32,
542}
543
544#[derive(Debug, Deserialize, FromResponse, Clone)]
545#[serde(rename_all = "camelCase")]
546pub struct PresignedUrlList {
547 pub urls: Vec<PresignedUrl>,
548}
549
550#[derive(Debug, Deserialize, FromResponse, Clone)]
551#[serde(rename_all = "camelCase")]
552pub struct S3FileUploadStatus {
553 pub status: S3UploadStatus,
554 pub node: Option<Node>,
555 pub error_details: Option<DracoonErrorResponse>,
556}
557
558#[derive(Debug, Deserialize, PartialEq, Clone)]
559pub enum S3UploadStatus {
560 #[serde(rename = "transfer")]
561 Transfer,
562 #[serde(rename = "finishing")]
563 Finishing,
564 #[serde(rename = "done")]
565 Done,
566 #[serde(rename = "error")]
567 Error,
568}
569
570#[derive(Debug, Serialize, Clone)]
571#[serde(rename_all = "camelCase")]
572#[allow(non_snake_case)]
573pub struct CreateFileUploadRequest {
574 parent_id: u64,
575 name: String,
576 #[serde(skip_serializing_if = "Option::is_none")]
577 size: Option<u64>,
578 #[serde(skip_serializing_if = "Option::is_none")]
579 classification: Option<u8>,
580 #[serde(skip_serializing_if = "Option::is_none")]
581 expiration: Option<ObjectExpiration>,
582 #[serde(skip_serializing_if = "Option::is_none")]
583 direct_S3_upload: Option<bool>,
584 #[serde(skip_serializing_if = "Option::is_none")]
585 timestamp_creation: Option<String>,
586 #[serde(skip_serializing_if = "Option::is_none")]
587 timestamp_modification: Option<String>,
588}
589
590impl CreateFileUploadRequest {
591 pub fn builder(parent_id: u64, name: String) -> CreateFileUploadRequestBuilder {
592 CreateFileUploadRequestBuilder {
593 parent_id,
594 name,
595 size: None,
596 classification: None,
597 expiration: None,
598 direct_s3_upload: Some(true),
599 timestamp_creation: None,
600 timestamp_modification: None,
601 }
602 }
603
604 pub fn from_upload_options(
605 parent_id: u64,
606 upload_options: &UploadOptions,
607 is_s3_upload: Option<bool>,
608 ) -> Self {
609 let req = Self::builder(parent_id, upload_options.file_meta.name.clone())
610 .with_size(upload_options.file_meta.size)
611 .with_classification(upload_options.classification.unwrap_or(1))
612 .with_expiration(
613 upload_options
614 .expiration
615 .clone()
616 .unwrap_or_default()
617 .clone(),
618 );
619 let req = if let Some(timestamp_creation) = upload_options.file_meta.timestamp_creation {
620 req.with_timestamp_creation(timestamp_creation)
621 } else {
622 req
623 };
624
625 let mut req =
626 if let Some(timestamp_modification) = upload_options.file_meta.timestamp_modification {
627 req.with_timestamp_modification(timestamp_modification)
628 } else {
629 req
630 };
631
632 req.direct_s3_upload = is_s3_upload;
633
634 req.build()
635 }
636}
637
638pub struct CreateFileUploadRequestBuilder {
639 parent_id: u64,
640 name: String,
641 size: Option<u64>,
642 classification: Option<u8>,
643 expiration: Option<ObjectExpiration>,
644 direct_s3_upload: Option<bool>,
645 timestamp_creation: Option<String>,
646 timestamp_modification: Option<String>,
647}
648
649impl CreateFileUploadRequestBuilder {
650 pub fn with_size(mut self, size: u64) -> Self {
651 self.size = Some(size);
652 self
653 }
654
655 pub fn with_classification(mut self, classification: u8) -> Self {
656 self.classification = Some(classification);
657 self
658 }
659
660 pub fn with_expiration(mut self, expiration: impl Into<ObjectExpiration>) -> Self {
661 self.expiration = Some(expiration.into());
662 self
663 }
664 pub fn with_timestamp_creation(mut self, timestamp_creation: DateTime<Utc>) -> Self {
665 self.timestamp_creation = Some(timestamp_creation.to_rfc3339());
666 self
667 }
668 pub fn with_timestamp_modification(mut self, timestamp_modification: DateTime<Utc>) -> Self {
669 self.timestamp_modification = Some(timestamp_modification.to_rfc3339());
670 self
671 }
672
673 pub fn with_direct_s3_upload(mut self, direct_s3_upload: bool) -> Self {
674 if !direct_s3_upload {
675 self.direct_s3_upload = None;
676 } else {
677 self.direct_s3_upload = Some(direct_s3_upload);
678 }
679 self
680 }
681
682 pub fn build(self) -> CreateFileUploadRequest {
683 CreateFileUploadRequest {
684 parent_id: self.parent_id,
685 name: self.name,
686 size: self.size,
687 classification: self.classification,
688 expiration: self.expiration,
689 direct_S3_upload: self.direct_s3_upload,
690 timestamp_creation: self.timestamp_creation,
691 timestamp_modification: self.timestamp_modification,
692 }
693 }
694}
695
696#[derive(Debug, Serialize, Clone)]
697#[serde(rename_all = "camelCase")]
698pub struct GeneratePresignedUrlsRequest {
699 size: u64,
700 first_part_number: u32,
701 last_part_number: u32,
702}
703
704impl GeneratePresignedUrlsRequest {
705 pub fn new(size: u64, first_part_number: u32, last_part_number: u32) -> Self {
706 Self {
707 size,
708 first_part_number,
709 last_part_number,
710 }
711 }
712}
713
714#[derive(Debug, Serialize, Clone)]
715#[serde(rename_all = "camelCase")]
716pub struct CompleteS3FileUploadRequest {
717 parts: Vec<S3FileUploadPart>,
718 #[serde(skip_serializing_if = "Option::is_none")]
719 resolution_strategy: Option<ResolutionStrategy>,
720 #[serde(skip_serializing_if = "Option::is_none")]
721 file_name: Option<String>,
722 #[serde(skip_serializing_if = "Option::is_none")]
723 keep_share_links: Option<bool>,
724 #[serde(skip_serializing_if = "Option::is_none")]
725 file_key: Option<FileKey>,
726}
727
728pub struct CompleteS3FileUploadRequestBuilder {
729 parts: Vec<S3FileUploadPart>,
730 resolution_strategy: Option<ResolutionStrategy>,
731 file_name: Option<String>,
732 keep_share_links: Option<bool>,
733 file_key: Option<FileKey>,
734}
735
736impl CompleteS3FileUploadRequest {
737 pub fn builder(parts: Vec<S3FileUploadPart>) -> CompleteS3FileUploadRequestBuilder {
738 CompleteS3FileUploadRequestBuilder {
739 parts,
740 resolution_strategy: None,
741 file_name: None,
742 keep_share_links: None,
743 file_key: None,
744 }
745 }
746}
747
748impl CompleteS3FileUploadRequestBuilder {
749 pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
750 self.resolution_strategy = Some(resolution_strategy);
751 self
752 }
753
754 pub fn with_file_name(mut self, file_name: String) -> Self {
755 self.file_name = Some(file_name);
756 self
757 }
758
759 pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
760 self.keep_share_links = Some(keep_share_links);
761 self
762 }
763
764 pub fn with_file_key(mut self, file_key: FileKey) -> Self {
765 self.file_key = Some(file_key);
766 self
767 }
768
769 pub fn build(self) -> CompleteS3FileUploadRequest {
770 CompleteS3FileUploadRequest {
771 parts: self.parts,
772 resolution_strategy: self.resolution_strategy,
773 file_name: self.file_name,
774 keep_share_links: self.keep_share_links,
775 file_key: self.file_key,
776 }
777 }
778}
779
780#[derive(Debug, Serialize, Clone)]
781#[serde(rename_all = "camelCase")]
782pub struct CompleteUploadRequest {
783 #[serde(skip_serializing_if = "Option::is_none")]
784 resolution_strategy: Option<ResolutionStrategy>,
785 #[serde(skip_serializing_if = "Option::is_none")]
786 file_name: Option<String>,
787 #[serde(skip_serializing_if = "Option::is_none")]
788 keep_share_links: Option<bool>,
789 #[serde(skip_serializing_if = "Option::is_none")]
790 file_key: Option<FileKey>,
791}
792
793pub struct CompleteUploadRequestBuilder {
794 resolution_strategy: Option<ResolutionStrategy>,
795 file_name: Option<String>,
796 keep_share_links: Option<bool>,
797 file_key: Option<FileKey>,
798}
799
800impl CompleteUploadRequest {
801 pub fn builder() -> CompleteUploadRequestBuilder {
802 CompleteUploadRequestBuilder {
803 resolution_strategy: None,
804 file_name: None,
805 keep_share_links: None,
806 file_key: None,
807 }
808 }
809}
810
811impl CompleteUploadRequestBuilder {
812 pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
813 self.resolution_strategy = Some(resolution_strategy);
814 self
815 }
816
817 pub fn with_file_name(mut self, file_name: String) -> Self {
818 self.file_name = Some(file_name);
819 self
820 }
821
822 pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
823 self.keep_share_links = Some(keep_share_links);
824 self
825 }
826
827 pub fn with_file_key(mut self, file_key: FileKey) -> Self {
828 self.file_key = Some(file_key);
829 self
830 }
831
832 pub fn build(self) -> CompleteUploadRequest {
833 CompleteUploadRequest {
834 resolution_strategy: self.resolution_strategy,
835 file_name: self.file_name,
836 keep_share_links: self.keep_share_links,
837 file_key: self.file_key,
838 }
839 }
840}
841
842#[derive(Debug, Serialize, Clone, Default)]
843pub enum ResolutionStrategy {
844 #[default]
845 #[serde(rename = "autorename")]
846 AutoRename,
847 #[serde(rename = "overwrite")]
848 Overwrite,
849 #[serde(rename = "fail")]
850 Fail,
851}
852
853#[derive(Debug, Serialize, Clone)]
854#[serde(rename_all = "camelCase")]
855pub struct S3FileUploadPart {
856 part_number: u32,
857 part_etag: String,
858}
859
860impl S3FileUploadPart {
861 pub fn new(part_number: u32, part_etag: String) -> Self {
862 Self {
863 part_number,
864 part_etag,
865 }
866 }
867}
868
869#[derive(Debug, Serialize, Clone)]
870#[serde(rename_all = "camelCase")]
871pub struct DeleteNodesRequest {
872 node_ids: Vec<u64>,
873}
874
875impl From<Vec<u64>> for DeleteNodesRequest {
876 fn from(node_ids: Vec<u64>) -> Self {
877 Self { node_ids }
878 }
879}
880
881#[derive(Debug, Serialize, Clone)]
882#[serde(rename_all = "camelCase")]
883pub struct TransferNodesRequest {
884 items: Vec<TransferNode>,
885 #[serde(skip_serializing_if = "Option::is_none")]
886 resolution_strategy: Option<ResolutionStrategy>,
887 #[serde(skip_serializing_if = "Option::is_none")]
888 keep_share_links: Option<bool>,
889}
890
891#[derive(Debug, Serialize, Clone)]
892#[serde(rename_all = "camelCase")]
893pub struct TransferNode {
894 id: u64,
895 name: Option<String>,
896 timestamp_creation: Option<String>,
897 timestamp_modification: Option<String>,
898}
899
900impl From<u64> for TransferNode {
901 fn from(node_id: u64) -> Self {
902 Self {
903 id: node_id,
904 name: None,
905 timestamp_creation: None,
906 timestamp_modification: None,
907 }
908 }
909}
910
911impl From<Vec<u64>> for TransferNodesRequest {
912 fn from(node_ids: Vec<u64>) -> Self {
913 Self {
914 items: node_ids.into_iter().map(std::convert::Into::into).collect(),
915 resolution_strategy: None,
916 keep_share_links: None,
917 }
918 }
919}
920
921pub struct TransferNodesRequestBuilder {
922 items: Vec<TransferNode>,
923 resolution_strategy: Option<ResolutionStrategy>,
924 keep_share_links: Option<bool>,
925}
926
927impl TransferNodesRequest {
928 pub fn builder(items: Vec<TransferNode>) -> TransferNodesRequestBuilder {
929 TransferNodesRequestBuilder {
930 items,
931 resolution_strategy: None,
932 keep_share_links: None,
933 }
934 }
935
936 pub fn new_from_ids(node_ids: Vec<u64>) -> TransferNodesRequestBuilder {
937 TransferNodesRequestBuilder {
938 items: node_ids.into_iter().map(std::convert::Into::into).collect(),
939 resolution_strategy: None,
940 keep_share_links: None,
941 }
942 }
943
944 pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
945 self.resolution_strategy = Some(resolution_strategy);
946 self
947 }
948
949 pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
950 self.keep_share_links = Some(keep_share_links);
951 self
952 }
953
954 pub fn build(self) -> TransferNodesRequest {
955 TransferNodesRequest {
956 items: self.items,
957 resolution_strategy: self.resolution_strategy,
958 keep_share_links: self.keep_share_links,
959 }
960 }
961}
962
963pub struct TransferNodeBuilder {
964 id: u64,
965 name: Option<String>,
966 timestamp_creation: Option<String>,
967 timestamp_modification: Option<String>,
968}
969
970impl TransferNode {
971 pub fn builder(id: u64) -> TransferNodeBuilder {
972 TransferNodeBuilder {
973 id,
974 name: None,
975 timestamp_creation: None,
976 timestamp_modification: None,
977 }
978 }
979
980 pub fn with_name(mut self, name: String) -> Self {
981 self.name = Some(name);
982 self
983 }
984
985 pub fn with_timestamp_creation(mut self, timestamp_creation: String) -> Self {
986 self.timestamp_creation = Some(timestamp_creation);
987 self
988 }
989
990 pub fn with_timestamp_modification(mut self, timestamp_modification: String) -> Self {
991 self.timestamp_modification = Some(timestamp_modification);
992 self
993 }
994
995 pub fn build(self) -> TransferNode {
996 TransferNode {
997 id: self.id,
998 name: self.name,
999 timestamp_creation: self.timestamp_creation,
1000 timestamp_modification: self.timestamp_modification,
1001 }
1002 }
1003}
1004
1005#[derive(Debug, Serialize, Clone)]
1006#[serde(rename_all = "camelCase")]
1007pub struct CreateFolderRequest {
1008 name: String,
1009 parent_id: u64,
1010 #[serde(skip_serializing_if = "Option::is_none")]
1011 notes: Option<String>,
1012 #[serde(skip_serializing_if = "Option::is_none")]
1013 timestamp_creation: Option<String>,
1014 #[serde(skip_serializing_if = "Option::is_none")]
1015 timestamp_modification: Option<String>,
1016 #[serde(skip_serializing_if = "Option::is_none")]
1017 classification: Option<u8>,
1018}
1019
1020pub struct CreateFolderRequestBuilder {
1021 name: String,
1022 parent_id: u64,
1023 notes: Option<String>,
1024 timestamp_creation: Option<String>,
1025 timestamp_modification: Option<String>,
1026 classification: Option<u8>,
1027}
1028
1029impl CreateFolderRequest {
1030 pub fn builder(name: impl Into<String>, parent_id: u64) -> CreateFolderRequestBuilder {
1031 CreateFolderRequestBuilder {
1032 name: name.into(),
1033 parent_id,
1034 notes: None,
1035 timestamp_creation: None,
1036 timestamp_modification: None,
1037 classification: None,
1038 }
1039 }
1040}
1041
1042impl CreateFolderRequestBuilder {
1043 pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
1044 self.notes = Some(notes.into());
1045 self
1046 }
1047
1048 pub fn with_timestamp_creation(mut self, timestamp_creation: impl Into<String>) -> Self {
1049 self.timestamp_creation = Some(timestamp_creation.into());
1050 self
1051 }
1052
1053 pub fn with_timestamp_modification(
1054 mut self,
1055 timestamp_modification: impl Into<String>,
1056 ) -> Self {
1057 self.timestamp_modification = Some(timestamp_modification.into());
1058 self
1059 }
1060
1061 pub fn with_classification(mut self, classification: u8) -> Self {
1062 self.classification = Some(classification);
1063 self
1064 }
1065
1066 pub fn build(self) -> CreateFolderRequest {
1067 CreateFolderRequest {
1068 name: self.name,
1069 parent_id: self.parent_id,
1070 notes: self.notes,
1071 timestamp_creation: self.timestamp_creation,
1072 timestamp_modification: self.timestamp_modification,
1073 classification: self.classification,
1074 }
1075 }
1076}
1077
1078#[derive(Debug, Serialize, Clone)]
1079#[serde(rename_all = "camelCase")]
1080pub struct UpdateFolderRequest {
1081 #[serde(skip_serializing_if = "Option::is_none")]
1082 name: Option<String>,
1083 #[serde(skip_serializing_if = "Option::is_none")]
1084 notes: Option<String>,
1085 #[serde(skip_serializing_if = "Option::is_none")]
1086 timestamp_creation: Option<String>,
1087 #[serde(skip_serializing_if = "Option::is_none")]
1088 timestamp_modification: Option<String>,
1089 #[serde(skip_serializing_if = "Option::is_none")]
1090 classification: Option<u8>,
1091}
1092
1093pub struct UpdateFolderRequestBuilder {
1094 name: Option<String>,
1095 notes: Option<String>,
1096 timestamp_creation: Option<String>,
1097 timestamp_modification: Option<String>,
1098 classification: Option<u8>,
1099}
1100
1101impl UpdateFolderRequest {
1102 pub fn builder() -> UpdateFolderRequestBuilder {
1103 UpdateFolderRequestBuilder {
1104 name: None,
1105 notes: None,
1106 timestamp_creation: None,
1107 timestamp_modification: None,
1108 classification: None,
1109 }
1110 }
1111}
1112
1113impl UpdateFolderRequestBuilder {
1114 pub fn with_name(mut self, name: impl Into<String>) -> Self {
1115 self.name = Some(name.into());
1116 self
1117 }
1118
1119 pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
1120 self.notes = Some(notes.into());
1121 self
1122 }
1123
1124 pub fn with_timestamp_creation(mut self, timestamp_creation: impl Into<String>) -> Self {
1125 self.timestamp_creation = Some(timestamp_creation.into());
1126 self
1127 }
1128
1129 pub fn with_timestamp_modification(
1130 mut self,
1131 timestamp_modification: impl Into<String>,
1132 ) -> Self {
1133 self.timestamp_modification = Some(timestamp_modification.into());
1134 self
1135 }
1136
1137 pub fn with_classification(mut self, classification: u8) -> Self {
1138 self.classification = Some(classification);
1139 self
1140 }
1141
1142 pub fn build(self) -> UpdateFolderRequest {
1143 UpdateFolderRequest {
1144 name: self.name,
1145 notes: self.notes,
1146 timestamp_creation: self.timestamp_creation,
1147 timestamp_modification: self.timestamp_modification,
1148 classification: self.classification,
1149 }
1150 }
1151}
1152
1153#[derive(Debug, Deserialize, Clone)]
1154#[serde(rename_all = "camelCase")]
1155pub struct UserIdFileItem {
1156 pub user_id: u64,
1157 pub file_id: u64,
1158}
1159
1160#[derive(Debug, Deserialize, Clone)]
1161#[serde(rename_all = "camelCase")]
1162pub struct UserUserPublicKey {
1163 pub id: u64,
1164 pub public_key_container: PublicKeyContainer,
1165}
1166
1167#[derive(Debug, Deserialize, Clone)]
1168#[serde(rename_all = "camelCase")]
1169pub struct FileFileKeys {
1170 pub id: u64,
1171 pub file_key_container: FileKey,
1172}
1173
1174#[derive(Debug, Deserialize, FromResponse, Clone)]
1175#[serde(rename_all = "camelCase")]
1176pub struct MissingKeysResponse {
1177 pub range: Option<Range>,
1178 pub items: Vec<UserIdFileItem>,
1179 pub users: Vec<UserUserPublicKey>,
1180 pub files: Vec<FileFileKeys>,
1181}
1182
1183#[derive(Debug, Serialize, Default, Clone)]
1184#[serde(rename_all = "camelCase")]
1185pub struct UserFileKeySetBatchRequest {
1186 items: Vec<UserFileKeySetRequest>,
1187}
1188
1189impl UserFileKeySetBatchRequest {
1190 pub fn new() -> Self {
1191 UserFileKeySetBatchRequest { items: Vec::new() }
1192 }
1193
1194 pub fn add(&mut self, user_id: u64, file_id: u64, file_key: FileKey) {
1195 self.items
1196 .push(UserFileKeySetRequest::new(user_id, file_id, file_key));
1197 }
1198
1199 pub fn is_empty(&self) -> bool {
1200 self.items.is_empty()
1201 }
1202
1203 pub fn try_new_from_missing_keys(
1204 missing_keys: MissingKeysResponse,
1205 keypair: &PlainUserKeyPairContainer,
1206 ) -> Result<Self, DracoonClientError> {
1207 let mut reqs = Vec::new();
1208
1209 for item in missing_keys.items {
1210 let file_id = item.file_id;
1211 let user_id = item.user_id;
1212 let public_key = missing_keys
1213 .users
1214 .iter()
1215 .find(|u| u.id == user_id)
1216 .ok_or_else(|| {
1217 error!("User not found in response: {}", user_id);
1218 DracoonClientError::Unknown
1219 })?
1220 .public_key_container
1221 .clone();
1222 let file_key = missing_keys
1223 .files
1224 .iter()
1225 .find(|f| f.id == file_id)
1226 .ok_or_else(|| {
1227 error!("File not found in response: {}", file_id);
1228 DracoonClientError::Unknown
1229 })?
1230 .file_key_container
1231 .clone();
1232
1233 let plain_file_key = match DracoonCrypto::decrypt_file_key(file_key, keypair.clone()) {
1234 Ok(plain_file_key) => plain_file_key,
1235 Err(err) => {
1236 warn!(
1237 user_id,
1238 file_id,
1239 error = ?err,
1240 "Skipping missing-key redistribution because file key decryption failed",
1241 );
1242 continue;
1243 }
1244 };
1245 let file_key = match DracoonCrypto::encrypt_file_key(plain_file_key, public_key) {
1246 Ok(file_key) => file_key,
1247 Err(err) => {
1248 warn!(
1249 user_id,
1250 file_id,
1251 error = ?err,
1252 "Skipping missing-key redistribution because file key encryption failed",
1253 );
1254 continue;
1255 }
1256 };
1257
1258 reqs.push(UserFileKeySetRequest::new(user_id, file_id, file_key));
1259 }
1260
1261 debug!("Built {} key requests", reqs.len());
1262
1263 Ok(reqs.into())
1264 }
1265}
1266
1267impl From<Vec<UserFileKeySetRequest>> for UserFileKeySetBatchRequest {
1268 fn from(items: Vec<UserFileKeySetRequest>) -> Self {
1269 UserFileKeySetBatchRequest { items }
1270 }
1271}
1272
1273#[derive(Debug, Serialize, Clone)]
1274#[serde(rename_all = "camelCase")]
1275pub struct UserFileKeySetRequest {
1276 user_id: u64,
1277 file_id: u64,
1278 file_key: FileKey,
1279}
1280
1281impl UserFileKeySetRequest {
1282 pub fn new(user_id: u64, file_id: u64, file_key: FileKey) -> Self {
1283 UserFileKeySetRequest {
1284 user_id,
1285 file_id,
1286 file_key,
1287 }
1288 }
1289}
1290
1291#[derive(Debug, Clone)]
1292pub enum UseKey {
1293 RoomRescueKey,
1294 SystemRescueKey,
1295 PreviousUserKey,
1296 PreviousRoomRescueKey,
1297 PreviousSystemRescueKey,
1298}
1299
1300impl From<UseKey> for String {
1301 fn from(use_key: UseKey) -> Self {
1302 match use_key {
1303 UseKey::RoomRescueKey => "room_rescue_key".to_string(),
1304 UseKey::SystemRescueKey => "system_rescue_key".to_string(),
1305 UseKey::PreviousUserKey => "previous_user_key".to_string(),
1306 UseKey::PreviousRoomRescueKey => "previous_room_rescue_key".to_string(),
1307 UseKey::PreviousSystemRescueKey => "previous_system_rescue_key".to_string(),
1308 }
1309 }
1310}