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,
)
| 404 | /// Returns an error if the file can't be opened, the upload fails, or |
| 405 | /// the server rejects the change. |
| 406 | pub async fn upload_change_streamed( |
| 407 | &self, |
| 408 | hash: &str, |
| 409 | view: &str, |
| 410 | path: &std::path::Path, |
| 411 | ) -> RemoteResult<()> { |
| 412 | let url = format!("{}?insert={}&view={}", self.base_url, hash, view); |
| 413 | debug!("POST insert (streamed): {} from {:?}", url, path); |
| 414 | |
| 415 | let file = tokio::fs::File::open(path).await.map_err(|e| { |
| 416 | RemoteError::other(format!("Failed to open change file {:?}: {}", path, e)) |
| 417 | })?; |
| 418 | |
| 419 | let file_size = file |
| 420 | .metadata() |
| 421 | .await |
| 422 | .map_err(|e| { |
| 423 | RemoteError::other(format!("Failed to read metadata for {:?}: {}", path, e)) |
| 424 | })? |
| 425 | .len(); |
| 426 | |
| 427 | info!("Streaming upload of change {} ({} bytes)", hash, file_size); |
| 428 | |
| 429 | let stream = ReaderStream::new(file); |
| 430 | let body = reqwest::Body::wrap_stream(stream); |
| 431 | |
| 432 | let response = self |
| 433 | .client |
| 434 | .post(&url) |
| 435 | .header(CONTENT_TYPE, "application/octet-stream") |
| 436 | .header(CONTENT_LENGTH, file_size) |
| 437 | .body(body) |
| 438 | .send() |
| 439 | .await |
| 440 | .map_err(|e| RemoteError::connection_failed(&url, e))?; |
| 441 | |
| 442 | crate::check_min_version_header(response.headers()); |
| 443 | let status = response.status(); |
| 444 | |
| 445 | match status { |
| 446 | StatusCode::OK => { |
| 447 | debug!("Successfully streamed change {} from file", hash); |
| 448 | Ok(()) |
| 449 | } |
| 450 | StatusCode::NOT_FOUND => Err(RemoteError::repo_not_found(&url)), |
| 451 | StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => { |
| 452 | let msg = response.text().await.unwrap_or_default(); |
| 453 | Err(RemoteError::auth_failed(&url, msg)) |
| 454 | } |
| 455 | StatusCode::BAD_REQUEST | StatusCode::INTERNAL_SERVER_ERROR => { |
| 456 | let msg = response.text().await.unwrap_or_default(); |
| 457 | if msg.contains("missing") && msg.contains("dependenc") { |
| 458 | Err(RemoteError::missing_deps(vec![])) |
| 459 | } else { |
| 460 | Err(RemoteError::http(status.as_u16(), msg)) |
| 461 | } |
| 462 | } |
| 463 | _ => { |
no test coverage detected