Skip to main content

sharepoint_cli/graph/
download.rs

1//! Streaming downloads.
2//!
3//! `download_to_writer` follows Graph's redirect to the pre-authenticated
4//! storage URL and streams the body to the caller's `AsyncWrite`. Used by
5//! `files download` (writes to file or stdout via `-`).
6
7use futures_util::StreamExt;
8use reqwest::Method;
9use tokio::io::{AsyncWrite, AsyncWriteExt};
10
11use super::GraphClient;
12use crate::error::{CliError, Result};
13
14/// Stream the contents of a drive item to `writer`. Returns total bytes written.
15pub async fn download_to_writer<W: AsyncWrite + Unpin>(
16    graph: &GraphClient,
17    drive_id: &str,
18    path: &str,
19    writer: &mut W,
20) -> Result<u64> {
21    let trimmed = path.trim_start_matches('/');
22    if trimmed.is_empty() {
23        return Err(CliError::Input("cannot download the drive root".into()));
24    }
25    let encoded = super::drives::encode_path_segments(trimmed);
26    let api_path = format!("/drives/{drive_id}/root:/{encoded}:/content");
27    let resp = graph.send(Method::GET, &api_path, None).await?;
28    let mut total: u64 = 0;
29    let mut stream = resp.bytes_stream();
30    while let Some(chunk) = stream.next().await {
31        let bytes = chunk.map_err(|e| CliError::Http(format!("download stream: {e}")))?;
32        writer
33            .write_all(&bytes)
34            .await
35            .map_err(|e| CliError::Other(format!("write download: {e}")))?;
36        total += bytes.len() as u64;
37    }
38    writer
39        .flush()
40        .await
41        .map_err(|e| CliError::Other(format!("flush download: {e}")))?;
42    Ok(total)
43}