Skip to main content

signer_remote/storage/
client.rs

1//! 存储客户端实现
2use std::{io::SeekFrom, path::PathBuf};
3
4use base64::Engine as _;
5use futures::stream::{self, StreamExt, TryStreamExt};
6use sha3::Digest;
7use signer_core::{SignerKeys, SignerUser};
8use tokio::io::{AsyncReadExt as _, AsyncSeekExt, AsyncWriteExt, BufReader};
9
10use crate::{
11    error::{RemoteError, RemoteResult},
12    remote::{HttpClient, HttpClientConfig},
13    storage::{
14        StorageBlobsCheckExistenceRequest, StorageBlobsCheckExistenceResponse, StorageItemData,
15        StorageItemResponse,
16    },
17};
18
19pub struct SignerDaemonStorage {
20    addr: String,
21    keys: SignerKeys,
22    user: SignerUser,
23}
24
25#[derive(Debug, Clone)]
26struct ChunkDescriptor {
27    hash: String,
28    offset: u64,
29    length: u64,
30}
31
32impl SignerDaemonStorage {
33    pub fn new(addr: String, keys: SignerKeys, user: SignerUser) -> Self {
34        Self { addr, keys, user }
35    }
36
37    pub async fn upload(&self, path: PathBuf) -> RemoteResult<StorageItemResponse> {
38        let file = tokio::fs::File::open(&path)
39            .await
40            .map_err(RemoteError::Io)?;
41        let mut reader = BufReader::new(file);
42
43        // 1. Read file in chunks and compute hashes, but only store descriptors
44        let mut chunk_descriptors = Vec::new();
45        let mut current_offset = 0;
46
47        loop {
48            let mut buffer = vec![0; 16 * 1024 * 1024];
49            let n = reader.read(&mut buffer).await.map_err(RemoteError::Io)?;
50            if n == 0 {
51                break;
52            }
53            buffer.truncate(n);
54
55            let mut hasher = sha3::Sha3_256::new();
56            hasher.update(&buffer);
57            let hash = base64::prelude::BASE64_URL_SAFE.encode(hasher.finalize().to_vec());
58
59            chunk_descriptors.push(ChunkDescriptor {
60                hash,
61                offset: current_offset,
62                length: n as u64,
63            });
64
65            current_offset += n as u64;
66        }
67
68        let blob_hashes: Vec<String> = chunk_descriptors.iter().map(|d| d.hash.clone()).collect();
69
70        // 2. Compute item hash
71        let mut item_hasher = sha3::Sha3_256::new();
72        for hash in &blob_hashes {
73            item_hasher.update(hash.as_bytes());
74        }
75        let item_hash = base64::prelude::BASE64_URL_SAFE.encode(item_hasher.finalize().to_vec());
76
77        let config = HttpClientConfig::new(self.keys.clone(), self.user.clone(), self.addr.clone());
78        let client = HttpClient::new(config);
79
80        // 3. Check if item exists for the user
81        let head_response = client
82            .head(&format!(
83                "/api/storage/item/{}/{}",
84                &self.user.pub_key, &item_hash
85            ))
86            .await;
87
88        match head_response {
89            Ok(response) => {
90                if response.status().is_success() {
91                    return Ok(StorageItemResponse {
92                        pubkey: self.user.pub_key.clone(),
93                        hash: item_hash,
94                        file_name: path.file_name().unwrap().to_str().unwrap().to_string(),
95                    });
96                }
97            }
98            Err(_) => {
99                // 如果 HEAD 请求失败,继续执行后续逻辑
100            }
101        }
102
103        // 尝试创建项目,如果返回 421 状态码则表示项目已存在
104        let create_response = client
105            .post_raw(
106                "/api/storage/item",
107                &StorageItemData {
108                    chunks: blob_hashes.clone(),
109                    file_name: path.file_name().unwrap().to_str().unwrap().to_string(),
110                },
111            )
112            .await;
113
114        match create_response {
115            Ok(response) => {
116                if response.status() == reqwest::StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS {
117                    let item: StorageItemResponse = response
118                        .json()
119                        .await
120                        .map_err(|e| RemoteError::Internal(e.to_string()))?;
121                    return Ok(item);
122                }
123            }
124            Err(_) => {
125                // 继续执行后续逻辑
126            }
127        }
128
129        // 4. Check for missing blobs
130        let missing_blobs: StorageBlobsCheckExistenceResponse = client
131            .post(
132                "/api/storage/blobs/check-existence",
133                &StorageBlobsCheckExistenceRequest {
134                    hashes: blob_hashes.clone(),
135                },
136            )
137            .await?;
138
139        let missing_hashes: std::collections::HashSet<String> =
140            missing_blobs.missing_hashes.into_iter().collect();
141
142        // 5. Upload missing blobs by streaming from the file
143        let descriptors_to_upload: Vec<ChunkDescriptor> = chunk_descriptors
144            .into_iter()
145            .filter(|d| missing_hashes.contains(&d.hash))
146            .collect();
147
148        let upload_futs = descriptors_to_upload.into_iter().map(|desc| {
149            let keys = self.keys.clone();
150            let user = self.user.clone();
151            let addr = self.addr.clone();
152            let path = path.clone();
153            async move {
154                let mut file = tokio::fs::File::open(&path)
155                    .await
156                    .map_err(RemoteError::Io)?;
157                file.seek(SeekFrom::Start(desc.offset))
158                    .await
159                    .map_err(RemoteError::Io)?;
160                let mut chunk_reader = file.take(desc.length);
161                let mut chunk_data = Vec::with_capacity(desc.length as usize);
162                chunk_reader
163                    .read_to_end(&mut chunk_data)
164                    .await
165                    .map_err(RemoteError::Io)?;
166
167                let config = HttpClientConfig::new(keys, user, addr.clone());
168                let client = HttpClient::new(config);
169
170                let file_data = reqwest::multipart::Part::bytes(chunk_data);
171                let form_data = reqwest::multipart::Form::new().part("blob", file_data);
172
173                client
174                    .post_multipart("/api/storage/blob", form_data)
175                    .await?
176                    .error_for_status()
177                    .map_err(RemoteError::Network)?;
178
179                let result: RemoteResult<()> = Ok(());
180                result
181            }
182        });
183
184        stream::iter(upload_futs)
185            .buffer_unordered(10)
186            .try_collect::<Vec<()>>()
187            .await?;
188
189        // 6. Create the item
190        let item: StorageItemResponse = client
191            .post(
192                "/api/storage/item",
193                &StorageItemData {
194                    chunks: blob_hashes,
195                    file_name: path.file_name().unwrap().to_str().unwrap().to_string(),
196                },
197            )
198            .await?;
199
200        Ok(item)
201    }
202
203    pub async fn download(&self, key: StorageItemResponse, path: PathBuf) -> RemoteResult<()> {
204        let config = HttpClientConfig::new(self.keys.clone(), self.user.clone(), self.addr.clone());
205        let client = HttpClient::new(config);
206
207        let r = client
208            .get_raw(&format!("/api/storage/item/{}/{}", &key.pubkey, &key.hash))
209            .await?
210            .error_for_status()
211            .map_err(RemoteError::Network)?;
212
213        let mut f = tokio::fs::File::create(path)
214            .await
215            .map_err(RemoteError::Io)?;
216        let mut response_stream = r.bytes_stream();
217        while let Some(chunk) = response_stream
218            .try_next()
219            .await
220            .map_err(RemoteError::Network)?
221        {
222            f.write_all(&chunk).await.map_err(RemoteError::Io)?;
223        }
224
225        Ok(())
226    }
227}