Upload a change file by streaming from disk. Uses reqwest's streaming body support to avoid loading the entire file into memory. The `Content-Length` header is set from file metadata so the server can validate the upload without buffering. # Arguments `hash` - The base32-encoded hash of the change. `view` - The target view name. `path` - Path to the `.change` file on disk. # Errors Returns a
(
&self,
hash: &str,
view: &str,
path: &std::path::Path,
)
| 301 | /// Returns an error if the file can't be opened, the upload fails, or |
| 302 | /// the server rejects the change. |
| 303 | pub async fn upload_change_streamed( |
| 304 | &self, |
| 305 | hash: &str, |
| 306 | view: &str, |
| 307 | path: &std::path::Path, |
| 308 | ) -> RemoteResult<()> { |
| 309 | let url = format!("{}?insert={}&view={}", self.base_url, hash, view); |
| 310 | debug!("POST insert (streamed): {} from {:?}", url, path); |
| 311 | |
| 312 | let file = tokio::fs::File::open(path).await.map_err(|e| { |
| 313 | RemoteError::other(format!("Failed to open change file {:?}: {}", path, e)) |
| 314 | })?; |
| 315 | |
| 316 | let file_size = file |
| 317 | .metadata() |
| 318 | .await |
| 319 | .map_err(|e| { |
| 320 | RemoteError::other(format!("Failed to read metadata for {:?}: {}", path, e)) |
| 321 | })? |
| 322 | .len(); |
| 323 | |
| 324 | info!("Streaming upload of change {} ({} bytes)", hash, file_size); |
| 325 | |
| 326 | let stream = ReaderStream::new(file); |
| 327 | let body = reqwest::Body::wrap_stream(stream); |
| 328 | |
| 329 | let response = self |
| 330 | .client |
| 331 | .post(&url) |
| 332 | .header(CONTENT_TYPE, "application/octet-stream") |
| 333 | .header(CONTENT_LENGTH, file_size) |
| 334 | .body(body) |
| 335 | .send() |
| 336 | .await |
| 337 | .map_err(|e| RemoteError::connection_failed(&url, e))?; |
| 338 | |
| 339 | crate::check_min_version_header(response.headers()); |
| 340 | let status = response.status(); |
| 341 | |
| 342 | match status { |
| 343 | StatusCode::OK => { |
| 344 | debug!("Successfully streamed change {} from file", hash); |
| 345 | Ok(()) |
| 346 | } |
| 347 | StatusCode::NOT_FOUND => Err(RemoteError::repo_not_found(&url)), |
| 348 | StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => { |
| 349 | let msg = response.text().await.unwrap_or_default(); |
| 350 | Err(RemoteError::auth_failed(&url, msg)) |
| 351 | } |
| 352 | StatusCode::BAD_REQUEST | StatusCode::INTERNAL_SERVER_ERROR => { |
| 353 | let msg = response.text().await.unwrap_or_default(); |
| 354 | if msg.contains("missing") && msg.contains("dependenc") { |
| 355 | Err(RemoteError::missing_deps(vec![])) |
| 356 | } else { |
| 357 | Err(RemoteError::http(status.as_u16(), msg)) |
| 358 | } |
| 359 | } |
| 360 | _ => { |