docbox_core/files/
generated.rs1use crate::{files::create_generated_file_key, storage::TenantStorageLayer};
4use anyhow::Context;
5use bytes::Bytes;
6use futures::{
7 StreamExt,
8 stream::{FuturesOrdered, FuturesUnordered},
9};
10use mime::Mime;
11use tracing::{Instrument, debug, error};
12
13use docbox_database::models::{
14 file::FileId,
15 generated_file::{CreateGeneratedFile, GeneratedFile, GeneratedFileId, GeneratedFileType},
16};
17
18#[derive(Debug)]
19pub struct QueuedUpload {
20 pub mime: Mime,
21 pub ty: GeneratedFileType,
22 pub bytes: Bytes,
23}
24
25impl QueuedUpload {
26 pub fn new(mime: Mime, ty: GeneratedFileType, bytes: Bytes) -> Self {
27 Self { mime, ty, bytes }
28 }
29}
30
31pub enum GeneratedFileDeleteResult {
32 Ok,
34 Err(Vec<GeneratedFileId>, anyhow::Error),
37}
38
39pub async fn delete_generated_files(
40 storage: &TenantStorageLayer,
41 files: &[GeneratedFile],
42) -> GeneratedFileDeleteResult {
43 let files_count = files.len();
44
45 let mut futures = files
46 .iter()
47 .map(|file| {
48 async {
49 let id = file.id;
50 let file_id = file.file_id;
51 let file_key = file.file_key.to_string();
52
53 debug!(%id, %file_id, %file_key, "uploading file to s3",);
54
55 if let Err(cause) = storage.delete_file(&file_key).await {
57 error!(%id, %file_id, %file_key, ?cause, "failed to delete generated file");
58 }
59
60 debug!("deleted file from s3");
61
62 anyhow::Ok(id)
63 }
64 })
65 .collect::<FuturesUnordered<_>>();
66
67 let mut deleted: Vec<GeneratedFileId> = Vec::with_capacity(files_count);
68
69 while let Some(result) = futures.next().await {
70 match result {
71 Ok(id) => deleted.push(id),
72 Err(err) => return GeneratedFileDeleteResult::Err(deleted, err),
73 }
74 }
75
76 GeneratedFileDeleteResult::Ok
77}
78
79pub async fn upload_generated_files(
82 storage: &TenantStorageLayer,
83 base_file_key: &str,
84 file_id: &FileId,
85 file_hash: &str,
86 queued_uploads: Vec<QueuedUpload>,
87) -> Vec<anyhow::Result<CreateGeneratedFile>> {
88 queued_uploads
89 .into_iter()
90 .map(|queued_upload| {
91 let file_id = *file_id;
93 let file_hash = file_hash.to_string();
94 let file_mime = queued_upload.mime.to_string();
95 let file_key = create_generated_file_key(base_file_key, &queued_upload.mime);
96 let span = tracing::info_span!("upload_generated_files", %file_id, %file_hash, %file_key, %file_mime);
97
98 async move {
99 storage
101 .upload_file(&file_key, file_mime, queued_upload.bytes)
102 .await
103 .context("failed to upload generated file")?;
104
105 anyhow::Ok(CreateGeneratedFile {
106 file_id,
107 hash: file_hash,
108 mime: queued_upload.mime.to_string(),
109 ty: queued_upload.ty,
110 file_key,
111 })
112 }
113 .instrument(span)
114 })
115 .collect::<FuturesOrdered<_>>()
116 .collect()
117 .await
118}