supreme_fs/game_storage/online/
upload.rs1use super::util::compute_chunk_size;
5use crate::game_storage::GameStorage;
6use crate::{HybridStorageError, SnapshotEvent};
7use sha2::{Digest, Sha256};
8use std::io;
9use std::sync::mpsc::Sender;
10
11impl GameStorage {
12 pub fn get_max_upload_size(&self) -> Result<usize, HybridStorageError> {
14 if let Some(api_url) = &self.api_url {
15 let client = self
16 .client
17 .as_ref()
18 .ok_or_else(|| io::Error::other("Online storage not configured"))?;
19
20 let url = format!("{api_url}?action=get_upload_limits");
21 if let Ok(response) = client.get(&url).call() {
22 #[derive(serde::Deserialize)]
23 struct UploadLimits {
24 max_upload_size: usize,
25 }
26
27 if let Ok(limits) = response.into_body().read_json::<UploadLimits>() {
28 return Ok(limits.max_upload_size);
29 }
30 }
31 }
32
33 Ok(8 * 1024 * 1024)
35 }
36
37 pub fn upload_file_unified(
39 &self,
40 subdirectory: &str,
41 data: &[u8],
42 file_type: &str, filename: Option<&str>,
44 tx: Option<Sender<SnapshotEvent>>,
45 ) -> Result<(), HybridStorageError> {
46 let client = self
47 .client
48 .as_ref()
49 .ok_or_else(|| io::Error::other("Online storage not configured"))?;
50 let api_url = self
51 .api_url
52 .as_ref()
53 .ok_or_else(|| io::Error::other("Online storage not configured"))?;
54
55 let hash = {
57 let mut hasher = Sha256::new();
58 hasher.update(data);
59 hex::encode(hasher.finalize())
60 };
61
62 let filename = match filename {
63 Some(f) => f.to_string(),
64 None => {
65 if file_type == "snapshot" {
66 format!("{hash}.json")
67 } else {
68 hash.clone()
69 }
70 }
71 };
72
73 let max_upload_size = self.get_max_upload_size()?;
75 let chunk_size = compute_chunk_size(max_upload_size);
76
77 let total_chunks = data.len().div_ceil(chunk_size);
78
79 if let Some(ref sender) = tx {
81 let _ = sender.send(SnapshotEvent::Log(format!(
82 "Data length: {} bytes, chunk size: {} bytes, total chunks: {}",
83 data.len(),
84 chunk_size,
85 total_chunks
86 )));
87 }
88
89 let upload_id = hash.clone();
91
92 if let Some(ref sender) = tx {
94 let _ = sender.send(SnapshotEvent::InitProgress(total_chunks));
95 let _ = sender.send(SnapshotEvent::Log(format!(
96 "Uploading {total_chunks} chunks (chunk size: {chunk_size} bytes)"
97 )));
98 }
99
100 for (chunk_index, chunk) in data.chunks(chunk_size).enumerate() {
102 let chunk_hash = {
104 let mut hasher = Sha256::new();
105 hasher.update(chunk);
106 hex::encode(hasher.finalize())
107 };
108
109 if let Some(ref sender) = tx {
111 let _ = sender.send(SnapshotEvent::SetStatus(format!(
112 "Uploading chunk {}/{}",
113 chunk_index + 1,
114 total_chunks
115 )));
116 }
117
118 use super::multipart::{MultipartForm, SendMultipart};
119
120 let part_filename = format!("chunk_{chunk_index}_{chunk_hash}");
121 let mut form = MultipartForm::new()
122 .bytes(
123 "chunk_file",
124 &part_filename,
125 "application/octet-stream",
126 chunk,
127 )
128 .text("chunk_index", chunk_index.to_string())
129 .text("total_chunks", total_chunks.to_string())
130 .text("original_filename", filename.clone())
131 .text("upload_id", upload_id.clone())
132 .text("chunk_hash", chunk_hash)
133 .text("file_type", file_type);
134
135 if file_type == "content" {
136 form = form.text("hash", filename.clone());
137 }
138
139 if let Some(token) = &self.token {
140 form = form.text("api_token", token.clone());
141 }
142
143 let response = client.post(api_url).send_multipart(form)?;
144
145 if !response.status().is_success() {
146 return Err(HybridStorageError::UploadFailed(
147 self.extract_error_message(response),
148 ));
149 }
150
151 if let Some(ref sender) = tx {
153 let _ = sender.send(SnapshotEvent::IncrementProgress);
154 }
155
156 #[derive(serde::Deserialize)]
158 struct ChunkResponse {
159 status: Option<String>,
160 missing_files: Option<Vec<String>>,
161 }
162
163 if let Ok(response_data) = response.into_body().read_json::<ChunkResponse>()
164 && response_data.status.as_deref() != Some("chunk_received")
165 {
166 if file_type == "snapshot" {
168 if let Some(missing) = response_data.missing_files {
169 let total_missing = missing.len();
170
171 if let Some(ref sender) = tx {
173 let msg = format!(
174 "Uploading {} missing content file{}...",
175 total_missing,
176 if total_missing == 1 { "" } else { "s" }
177 );
178 let _ = sender.send(SnapshotEvent::Log(msg.clone()));
179 let _ = sender.send(SnapshotEvent::SetStatus(msg));
180 }
181
182 for (index, hash) in missing.into_iter().enumerate() {
183 if let Some(ref sender) = tx {
185 let _ = sender.send(SnapshotEvent::SetStatus(format!(
186 "Uploading content file {}/{}",
187 index + 1,
188 total_missing
189 )));
190 }
191
192 let content_path_xz = self.contents_path().join(format!("{hash}.xz"));
194 let content_path_raw = self.contents_path().join(&hash);
195
196 if let Ok(content_data) = std::fs::read(&content_path_xz) {
197 let filename = format!("{hash}.xz");
198 if let Some(ref sender) = tx {
199 let _ = sender.send(SnapshotEvent::Log(format!(
200 "Uploading content file: {} ({} bytes)",
201 filename,
202 content_data.len()
203 )));
204 }
205 self.upload_content_file(&filename, &content_data)?;
206 } else if let Ok(content_data) = std::fs::read(&content_path_raw) {
207 if let Some(ref sender) = tx {
208 let _ = sender.send(SnapshotEvent::Log(format!(
209 "Uploading content file: {} ({} bytes)",
210 hash,
211 content_data.len()
212 )));
213 }
214 self.upload_content_file(&hash, &content_data)?;
215 }
216 }
217
218 if let Some(ref sender) = tx {
220 let _ = sender.send(SnapshotEvent::Log(format!(
221 "Uploaded {} content file{}",
222 total_missing,
223 if total_missing == 1 { "" } else { "s" }
224 )));
225 }
226 }
227
228 if let Some(ref sender) = tx {
230 let _ =
231 sender.send(SnapshotEvent::Log("Committing snapshot...".to_string()));
232 let _ = sender.send(SnapshotEvent::SetStatus(
233 "Committing snapshot...".to_string(),
234 ));
235 }
236 self.commit_snapshot_type(subdirectory, &filename)?;
237
238 if let Some(ref sender) = tx {
240 let _ =
241 sender.send(SnapshotEvent::SetStatus("Upload complete!".to_string()));
242 }
243 }
244
245 return Ok(());
246 }
247 }
248
249 Ok(())
250 }
251}