rust-ipfs 0.16.0

IPFS node implementation
Documentation
use futures::StreamExt;
use rust_ipfs::builder::DefaultIpfsBuilder;
use rust_ipfs::mfs::{MfsKind, WriteOptions};
use rust_ipfs::unixfs::UnixfsStatus;
use rust_ipfs::Ipfs;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt::init();

    let ipfs: Ipfs = DefaultIpfsBuilder::new().start().await?;

    let mfs = ipfs.mfs();

    println!("> mkdir");
    mfs.mkdir("/docs/notes", true).await?;
    mfs.mkdir("/media", false).await?;

    println!("> write files");
    mfs.write("/docs/readme.txt", b"hello mfs", true).await?;
    mfs.write("/docs/notes/todo.txt", b"buy milk\nwrite docs\n", true)
        .await?;

    println!("> write from a stream");
    let chunks = vec![
        Ok::<_, std::io::Error>(bytes::Bytes::from_static(b"streamed ")),
        Ok(bytes::Bytes::from_static(b"line by ")),
        Ok(bytes::Bytes::from_static(b"line\n")),
    ];
    mfs.write_stream("/docs/streamed.txt", futures::stream::iter(chunks), false)
        .await?;
    println!(
        "  {}",
        String::from_utf8_lossy(&mfs.read("/docs/streamed.txt").await?)
    );

    println!("> ls /");
    print_ls(&mfs, "/").await?;
    println!("> ls /docs");
    print_ls(&mfs, "/docs").await?;

    println!("> read /docs/readme.txt");
    println!(
        "  {}",
        String::from_utf8_lossy(&mfs.read("/docs/readme.txt").await?)
    );

    println!("> read /docs/streamed.txt as a stream");
    let stream = mfs.read_stream("/docs/streamed.txt");
    futures::pin_mut!(stream);
    while let Some(chunk) = stream.next().await {
        print!("  chunk: {}", String::from_utf8_lossy(&chunk?));
    }

    println!("> stat /docs/notes/todo.txt");
    let st = mfs.stat("/docs/notes/todo.txt").await?;
    println!("  cid={} size={} {:?}", st.cid, st.size, st.kind);

    println!("> random-access write (offset 6)");
    mfs.write_with(
        "/docs/readme.txt",
        b"MFS!!",
        WriteOptions {
            offset: 6,
            ..Default::default()
        },
    )
    .await?;
    println!(
        "  {}",
        String::from_utf8_lossy(&mfs.read("/docs/readme.txt").await?)
    );

    println!("> append past EOF");
    let len = mfs.stat("/docs/readme.txt").await?.size;
    mfs.write_with(
        "/docs/readme.txt",
        b" + appended",
        WriteOptions {
            offset: len,
            ..Default::default()
        },
    )
    .await?;
    println!(
        "  {}",
        String::from_utf8_lossy(&mfs.read("/docs/readme.txt").await?)
    );

    println!("> truncate to 9 bytes");
    mfs.truncate("/docs/readme.txt", 9).await?;
    println!(
        "  {}",
        String::from_utf8_lossy(&mfs.read("/docs/readme.txt").await?)
    );

    println!("> cp within mfs");
    mfs.cp("/docs/readme.txt", "/media/copy.txt", false).await?;
    println!(
        "  {}",
        String::from_utf8_lossy(&mfs.read("/media/copy.txt").await?)
    );

    println!("> cp from /ipfs (import external content)");
    let imported = add_to_ipfs(
        &ipfs,
        "greeting.txt",
        b"added through ipfs and imported into mfs",
    )
    .await?;
    mfs.cp(&imported, "/media/imported.txt", false).await?;
    println!(
        "  {}",
        String::from_utf8_lossy(&mfs.read("/media/imported.txt").await?)
    );

    println!("> mv");
    mfs.mv("/media/copy.txt", "/media/renamed.txt", false)
        .await?;
    print_ls(&mfs, "/media").await?;

    println!("> rm -r /docs");
    mfs.rm("/docs", true).await?;
    print_ls(&mfs, "/").await?;

    println!("> root cid");
    println!("  {:?}", mfs.root().await?);

    ipfs.exit_daemon().await;
    Ok(())
}

async fn print_ls(mfs: &rust_ipfs::mfs::Mfs, path: &str) -> anyhow::Result<()> {
    for entry in mfs.ls(path).await? {
        let kind = match entry.kind {
            MfsKind::File { size } => format!("file, {size} bytes"),
            MfsKind::Directory => "dir".to_string(),
            MfsKind::Symlink => "symlink".to_string(),
        };
        println!("  {:<16} [{}]  {}", entry.name, kind, entry.cid);
    }
    Ok(())
}

async fn add_to_ipfs(ipfs: &Ipfs, name: &str, data: &[u8]) -> anyhow::Result<String> {
    let data = data.to_vec();
    let stream = futures::stream::once(async move { Ok::<_, std::io::Error>(data) }).boxed();
    let mut add = ipfs.unixfs().add((name.to_string(), stream));

    let mut path = None;
    while let Some(status) = add.next().await {
        match status {
            UnixfsStatus::CompletedStatus { path: p, .. } => path = Some(p),
            UnixfsStatus::FailedStatus { error, .. } => anyhow::bail!(error),
            UnixfsStatus::ProgressStatus { .. } => {}
        }
    }
    Ok(path.expect("add completed").to_string())
}