shardline_server/local_backend/
files.rs1use axum::body::Bytes;
2#[cfg(test)]
3use shardline_index::FileRecord;
4use shardline_protocol::{ByteRange, RepositoryScope};
5use shardline_storage::ObjectStore;
6use tokio::task;
7
8use super::LocalBackend;
9use crate::{
10 ServerError, ShardMetadataLimits,
11 model::UploadFileResponse,
12 object_store::{read_full_object, reconstruct_file_record_bytes},
13 upload_ingest::RequestBodyReader,
14 validation::validate_identifier,
15 xet_adapter::{FileReconstructionResponse, ShardUploadResponse, build_reconstruction_response},
16};
17
18impl LocalBackend {
19 pub async fn upload_file(
26 &self,
27 file_id: &str,
28 body: Bytes,
29 repository_scope: Option<&RepositoryScope>,
30 ) -> Result<UploadFileResponse, ServerError> {
31 self.upload_file_stream(
32 file_id,
33 RequestBodyReader::from_bytes(body),
34 repository_scope,
35 None,
36 )
37 .await
38 }
39
40 pub(crate) async fn upload_file_stream(
47 &self,
48 file_id: &str,
49 mut body: RequestBodyReader,
50 repository_scope: Option<&RepositoryScope>,
51 expected_sha256: Option<&str>,
52 ) -> Result<UploadFileResponse, ServerError> {
53 validate_identifier(file_id)?;
54
55 let object_store = self.object_store();
56 let mut ingestor = crate::upload_ingest::FileUploadIngestor::new_with_parallelism(
57 self.chunk_size,
58 expected_sha256.is_some(),
59 self.upload_max_in_flight_chunks,
60 );
61 while let Some(bytes) = body.next_bytes().await? {
62 ingestor.ingest_body_chunk(&object_store, &bytes).await?;
63 }
64
65 let (record, response) = ingestor
66 .finish(&object_store, file_id, repository_scope, expected_sha256)
67 .await?;
68 self.record_store
69 .commit_file_version_metadata(&record)
70 .await?;
71
72 Ok(response)
73 }
74
75 pub(crate) async fn upload_shard_stream(
82 &self,
83 mut body: RequestBodyReader,
84 repository_scope: Option<&RepositoryScope>,
85 shard_metadata_limits: ShardMetadataLimits,
86 ) -> Result<ShardUploadResponse, ServerError> {
87 let uploaded_body = crate::upload_ingest::read_body_to_bytes(&mut body).await?;
88 let record_store = self.record_store.clone();
89 let object_store = self.object_store();
90 crate::xet_adapter::register_uploaded_shard_bytes(
91 &object_store,
92 &uploaded_body,
93 repository_scope,
94 shard_metadata_limits,
95 move |records, mappings| async move {
96 record_store
97 .commit_native_shard_metadata(&records, &mappings)
98 .await?;
99 Ok(())
100 },
101 )
102 .await
103 .map_err(ServerError::from)
104 }
105
106 pub async fn reconstruction(
113 &self,
114 file_id: &str,
115 content_hash: Option<&str>,
116 requested_range: Option<ByteRange>,
117 repository_scope: Option<&RepositoryScope>,
118 ) -> Result<FileReconstructionResponse, ServerError> {
119 let record = self
120 .read_record(file_id, content_hash, repository_scope)
121 .await?;
122 Ok(build_reconstruction_response(
123 self.public_base_url(),
124 &record,
125 requested_range,
126 )?)
127 }
128
129 pub async fn file_total_bytes(
136 &self,
137 file_id: &str,
138 content_hash: Option<&str>,
139 repository_scope: Option<&RepositoryScope>,
140 ) -> Result<u64, ServerError> {
141 let record = self
142 .read_record(file_id, content_hash, repository_scope)
143 .await?;
144 Ok(record.total_bytes)
145 }
146
147 #[cfg(test)]
154 pub(crate) async fn file_record(
155 &self,
156 file_id: &str,
157 content_hash: Option<&str>,
158 repository_scope: Option<&RepositoryScope>,
159 ) -> Result<FileRecord, ServerError> {
160 self.read_record(file_id, content_hash, repository_scope)
161 .await
162 }
163
164 pub async fn download_file(
170 &self,
171 file_id: &str,
172 content_hash: Option<&str>,
173 repository_scope: Option<&RepositoryScope>,
174 ) -> Result<Vec<u8>, ServerError> {
175 let record = self
176 .read_record(file_id, content_hash, repository_scope)
177 .await?;
178 let object_store = self.object_store();
179 let server_frontends = self.server_frontends.clone();
180 task::spawn_blocking(move || {
181 reconstruct_file_record_bytes(&object_store, &server_frontends, &record)
182 })
183 .await
184 .map_err(ServerError::BlockingTask)?
185 }
186
187 pub async fn read_chunk(&self, hash_hex: &str) -> Result<Vec<u8>, ServerError> {
193 let object_store = self.object_store();
194 let object_key = crate::chunk_store::chunk_object_key(hash_hex)?;
195 let metadata = object_store.metadata(&object_key)?;
196 let Some(metadata) = metadata else {
197 return Err(ServerError::NotFound);
198 };
199
200 task::spawn_blocking(move || {
201 read_full_object(&object_store, &object_key, metadata.length())
202 })
203 .await
204 .map_err(ServerError::BlockingTask)?
205 }
206
207 pub async fn read_chunk_for_file_version(
215 &self,
216 hash_hex: &str,
217 file_id: &str,
218 content_hash: &str,
219 repository_scope: Option<&RepositoryScope>,
220 ) -> Result<Vec<u8>, ServerError> {
221 let record = self
222 .read_record(file_id, Some(content_hash), repository_scope)
223 .await?;
224 if !record.chunks.iter().any(|chunk| chunk.hash == hash_hex) {
225 return Err(ServerError::NotFound);
226 }
227
228 self.read_chunk(hash_hex).await
229 }
230
231 pub async fn chunk_length(&self, hash_hex: &str) -> Result<u64, ServerError> {
237 let object_store = self.object_store();
238 let object_key = crate::chunk_store::chunk_object_key(hash_hex)?;
239 let metadata = object_store.metadata(&object_key)?;
240 let Some(metadata) = metadata else {
241 return Err(ServerError::NotFound);
242 };
243
244 Ok(metadata.length())
245 }
246}