1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//! Hermes IPFS VFS compatibility

use clap::{Parser, Subcommand};
use hermes_ipfs::{HermesIpfs, IpfsBuilder};
use lipsum::lipsum_with_rng;
use rand::thread_rng;
use rust_ipfs::IpfsPath;

/// CLI for a virtual filesystem.
#[derive(Debug, Parser)] // requires `derive` feature
#[command(name = "hermes-ipfs-cli")]
#[command(about = "Hermes IPFS Virtual File Manager", long_about = None)]
struct Cli {
    /// CLI commands
    #[command(subcommand)]
    command: Commands,
}

/// Commands for the CLI.
#[derive(Debug, Subcommand)]
enum Commands {
    /// List Files
    #[command(name = "ls")]
    ListFiles,
    /// Add a file with random content to IPFS
    #[command(name = "add")]
    AddFile,
    /// Print the contents from a file in IPFS
    #[command(name = "cat")]
    GetFile {
        /// IPFS Path
        ipfs_path_str: String,
    },
    /// Remove the file from being listed (will be garbage collected)
    #[command(name = "rm")]
    UnPinFile {
        /// IPFS Path
        ipfs_path_str: String,
    },
}

/// Example application.
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Cli::parse();
    let base_dir = dirs::data_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
    let ipfs_data_path = base_dir.as_path().join("hermes/ipfs");
    let builder = IpfsBuilder::new()
        .with_default()
        .set_default_listener()
        .set_disk_storage(ipfs_data_path);
    let hermes_node: HermesIpfs = builder.start().await?.into();
    match args.command {
        Commands::ListFiles => {
            println!("Listing files");
            let cids = hermes_node.list_pins().await?;
            for cid in &cids {
                println!("{}", IpfsPath::from(cid));
            }
        },
        Commands::AddFile => {
            println!("Adding file");
            let contents = lipsum_with_rng(thread_rng(), 42);
            let ipfs_path = hermes_node
                .add_ipfs_file(contents.into_bytes().into())
                .await?;
            println!("Added file: {ipfs_path}");
        },
        Commands::GetFile { ipfs_path_str } => {
            println!("Getting file");
            let ipfs_path: IpfsPath = ipfs_path_str.parse()?;
            let get_file_bytes = hermes_node.get_ipfs_file(ipfs_path.into()).await?;
            println!("* Got file, {} bytes:", get_file_bytes.len());
            let get_file = String::from_utf8(get_file_bytes)?;
            println!("* FILE CONTENTS:");
            println!("{get_file}\n");
        },
        Commands::UnPinFile { ipfs_path_str } => {
            println!("Un-pinning file {ipfs_path_str}");
            let ipfs_path: IpfsPath = ipfs_path_str.parse()?;
            let cid = ipfs_path.root().cid().ok_or(anyhow::anyhow!(
                "ERROR! Could not extract CID from IPFS path."
            ))?;
            hermes_node.remove_pin(cid).await?;
        },
    }
    hermes_node.stop().await;
    Ok(())
}