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,
)
| 435 | /// Returns an error if the file can't be opened, the upload fails, or |
| 436 | /// the server rejects the change. |
| 437 | pub async fn upload_change_streamed( |
| 438 | &self, |
| 439 | hash: &str, |
| 440 | view: &str, |
| 441 | path: &std::path::Path, |
| 442 | ) -> RemoteResult<()> { |
| 443 | let url = format!("{}?insert={}&view={}", self.base_url, hash, view); |
| 444 | debug!("POST insert (streamed): {} from {:?}", url, path); |
| 445 | |
| 446 | let file = tokio::fs::File::open(path).await.map_err(|e| { |
| 447 | RemoteError::other(format!("Failed to open change file {:?}: {}", path, e)) |
| 448 | })?; |
| 449 | |
| 450 | let file_size = file |
| 451 | .metadata() |
| 452 | .await |
| 453 | .map_err(|e| { |
| 454 | RemoteError::other(format!("Failed to read metadata for {:?}: {}", path, e)) |
| 455 | })? |
| 456 | .len(); |
| 457 | |
| 458 | info!("Streaming upload of change {} ({} bytes)", hash, file_size); |
| 459 | |
| 460 | let stream = ReaderStream::new(file); |
| 461 | let body = reqwest::Body::wrap_stream(stream); |
| 462 | |
| 463 | let response = self |
| 464 | .client |
| 465 | .post(&url) |
| 466 | .header(CONTENT_TYPE, "application/octet-stream") |
| 467 | .header(CONTENT_LENGTH, file_size) |
| 468 | .body(body) |
| 469 | .send() |
| 470 | .await |
| 471 | .map_err(|e| RemoteError::connection_failed(&url, e))?; |
| 472 | |
| 473 | crate::check_min_version_header(response.headers()); |
| 474 | let status = response.status(); |
| 475 | |
| 476 | match status { |
| 477 | StatusCode::OK => { |
| 478 | debug!("Successfully streamed change {} from file", hash); |
| 479 | Ok(()) |
| 480 | } |
| 481 | StatusCode::NOT_FOUND => Err(RemoteError::repo_not_found(&url)), |
| 482 | StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => { |
| 483 | let msg = response.text().await.unwrap_or_default(); |
| 484 | Err(RemoteError::auth_failed(&url, msg)) |
| 485 | } |
| 486 | StatusCode::BAD_REQUEST | StatusCode::INTERNAL_SERVER_ERROR => { |
| 487 | let msg = response.text().await.unwrap_or_default(); |
| 488 | if msg.contains("missing") && msg.contains("dependenc") { |
| 489 | let hashes = extract_change_hashes(&msg); |
| 490 | if hashes.is_empty() { |
| 491 | Err(RemoteError::http(status.as_u16(), msg)) |
| 492 | } else { |
| 493 | Err(RemoteError::missing_deps(hashes)) |
| 494 | } |
no test coverage detected