shadow_drive_sdk/
models.rs

1use bytes::Bytes;
2use reqwest::multipart::Part;
3use serde::Deserialize;
4use sha2::{Digest, Sha256};
5use std::path::Path;
6use tokio::{fs::File, io::AsyncReadExt};
7
8//re-export structs from Shadow Drive Smart Contract that are used in the SDK
9pub use shadow_drive_user_staking::instructions::{
10    decrease_storage::UnstakeInfo, initialize_account::UserInfo, store_file::File as FileAccount,
11};
12
13pub mod payload;
14pub mod storage_acct;
15
16use crate::{constants::FILE_SIZE_LIMIT, error::Error};
17use payload::Payload;
18
19pub type ShadowDriveResult<T> = Result<T, Error>;
20
21const BUFFER_SIZE: usize = 4096;
22
23#[derive(Clone, Debug, Deserialize)]
24pub struct ShdwDriveResponse {
25    pub txid: String,
26}
27
28#[derive(Clone, Debug, Deserialize)]
29pub struct StorageResponse {
30    pub message: String,
31    pub transaction_signature: String,
32    pub error: Option<String>,
33}
34
35#[derive(Clone, Debug, Deserialize)]
36pub struct CreateStorageAccountResponse {
37    pub shdw_bucket: Option<String>,
38    pub transaction_signature: String,
39}
40
41#[derive(Clone, Debug, Deserialize)]
42pub struct DeleteFileResponse {
43    pub message: String,
44    pub error: Option<String>,
45}
46
47#[derive(Clone, Debug, Deserialize)]
48pub struct GetBucketSizeResponse {
49    pub storage_used: u64,
50    pub error: Option<String>,
51}
52
53/// [`ShadowFile`] is the combination of a file name and a [`Payload`].
54#[derive(Debug, Clone)]
55pub struct ShadowFile {
56    pub name: String,
57    pub data: Payload,
58    content_type: String,
59}
60
61const FALLBACK_MIMETYPE: &'static str = "application/octet-stream";
62
63impl ShadowFile {
64    pub fn name(&self) -> &str {
65        &self.name
66    }
67
68    pub fn file<T: AsRef<Path>>(name: String, path: T) -> Self {
69        let content_type = match infer::get_from_path(path.as_ref()) {
70            // Successfully read file, fallback if infer fails
71            Ok(mime_option) => mime_option
72                .map(|mime| mime.mime_type())
73                .unwrap_or(FALLBACK_MIMETYPE)
74                .to_owned(),
75
76            // Fallback value
77            Err(_) => FALLBACK_MIMETYPE.to_owned(),
78        };
79        Self {
80            name,
81            content_type,
82            data: Payload::File(path.as_ref().to_owned()),
83        }
84    }
85
86    pub fn bytes<T: Into<Bytes>>(name: String, data: T) -> Self {
87        Self {
88            name,
89            content_type: FALLBACK_MIMETYPE.to_owned(),
90            data: Payload::Bytes(data.into()),
91        }
92    }
93
94    pub(crate) async fn sha256(&self) -> ShadowDriveResult<String> {
95        let result = match &self.data {
96            Payload::File(path) => {
97                let mut file = File::open(path).await.map_err(Error::FileSystemError)?;
98                let mut buf = [0u8; BUFFER_SIZE];
99                let mut hasher = Sha256::new();
100
101                loop {
102                    let bytes_read = file.read(&mut buf[..]).await?;
103
104                    if bytes_read != 0 {
105                        hasher.update(&buf[..bytes_read]);
106                    } else {
107                        break;
108                    }
109                }
110
111                hasher.finalize()
112            }
113            Payload::Bytes(data) => {
114                let mut hasher = Sha256::new();
115                hasher.update(&data);
116                hasher.finalize()
117            }
118        };
119        Ok(hex::encode(result))
120    }
121
122    pub(crate) async fn into_form_part(self) -> ShadowDriveResult<Part> {
123        let mut part = match self.data {
124            Payload::File(path) => {
125                let file = File::open(path).await.map_err(Error::FileSystemError)?;
126                let file_meta = file.metadata().await.map_err(Error::FileSystemError)?;
127
128                //make sure that the file is under the size limit
129                if file_meta.len() > FILE_SIZE_LIMIT {
130                    return Err(Error::FileTooLarge(self.name.clone()));
131                }
132
133                Part::stream_with_length(file, file_meta.len()).file_name(self.name)
134            }
135            Payload::Bytes(data) => {
136                //make sure that the file is under the size limit
137                if data.len() as u64 > FILE_SIZE_LIMIT {
138                    return Err(Error::FileTooLarge(self.name.clone()));
139                }
140
141                Part::stream_with_length(Bytes::clone(&data), data.len() as u64)
142                    .file_name(self.name)
143            }
144        };
145
146        part = part.mime_str(&self.content_type)?;
147        Ok(part)
148    }
149}
150
151#[derive(Clone, Debug, Deserialize)]
152pub struct ShadowUploadResponse {
153    #[serde(default)]
154    pub finalized_locations: Vec<String>,
155    pub message: String,
156    #[serde(default)]
157    pub upload_errors: Vec<UploadError>,
158}
159
160#[derive(Clone, Debug, Deserialize)]
161pub struct ShadowEditResponse {
162    #[serde(default)]
163    pub finalized_location: String,
164    #[serde(default)]
165    pub error: String,
166}
167
168#[derive(Clone, Debug, Deserialize)]
169pub struct UploadError {
170    pub file: String,
171    pub storage_account: String,
172    pub error: String,
173}
174
175#[allow(dead_code)]
176#[derive(Clone, Debug, Deserialize)]
177pub(crate) struct ShdwDriveBatchServerResponse {
178    pub _finalized_locations: Option<Vec<String>>,
179    pub transaction_signature: String,
180}
181
182#[derive(Clone, Debug, Deserialize)]
183pub enum BatchUploadStatus {
184    Uploaded,
185    AlreadyExists,
186    Error(String),
187}
188#[derive(Clone, Debug, Deserialize)]
189pub struct ShadowBatchUploadResponse {
190    pub file_name: String,
191    pub status: BatchUploadStatus,
192    pub location: Option<String>,
193    pub transaction_signature: Option<String>,
194}
195
196#[derive(Clone, Debug, Deserialize)]
197pub struct FileDataResponse {
198    pub file_data: FileData,
199}
200
201#[derive(Clone, Debug, Deserialize)]
202#[serde(rename_all = "kebab-case")]
203pub struct FileData {
204    pub owner_account_pubkey: String,
205    pub storage_account_pubkey: String,
206}
207
208#[derive(Clone, Debug, Deserialize)]
209pub struct ListObjectsResponse {
210    pub keys: Vec<String>,
211}