From fa655bbe8acae9c7f5d46e977ebea01ac0572327 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 6 Apr 2021 17:06:11 +0200 Subject: Implement first CLI Signed-off-by: Matthias Beyer --- Cargo.toml | 26 +++++++++++------- src/backend/backend.rs | 7 +++++ src/backend/datetime.rs | 7 +++++ src/backend/mime.rs | 6 +++++ src/backend/node.rs | 1 + src/backend/payload.rs | 28 ++++++++++++++++++++ src/cli.rs | 46 ++++++++++++++++++++++++++++++++ src/consts.rs | 4 +++ src/main.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 184 insertions(+), 11 deletions(-) create mode 100644 src/cli.rs create mode 100644 src/consts.rs diff --git a/Cargo.toml b/Cargo.toml index f24ae6f..d557fb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,15 +17,21 @@ edition = "2018" [dependencies] # TODO: Replace with thiserror -anyhow = "1" +anyhow = "1" -async-trait = "0.1" -cid = "0.6" -chrono = "0.4" -daglib = { git = "https://git.sr.ht/~matthiasbeyer/daglib", branch = "master" } -ipfs-embed = "0.19" -libipld = "0.11" -libipld-cbor = "0.11" -mime = "0.3" -tokio = { version = "1", features = ["full"] } +async-trait = "0.1" +cid = "0.6" +chrono = "0.4" +clap-v3 = "3.0.0-beta.1" +daglib = { git = "https://git.sr.ht/~matthiasbeyer/daglib", branch = "master" } +ipfs-embed = "0.19" +libipld = "0.11" +libipld-cbor = "0.11" +libp2p = "0.36" +libp2p-bitswap = "*" +libp2p-core = "*" +libp2p-gossipsub = "*" +libp2p-ping = "*" +mime = "0.3" +tokio = { version = "1", features = ["full"] } diff --git a/src/backend/backend.rs b/src/backend/backend.rs index 3e00f0f..33a8906 100644 --- a/src/backend/backend.rs +++ b/src/backend/backend.rs @@ -43,6 +43,13 @@ impl IpfsEmbedBackend { ipfs_embed::Ipfs::new(config).await.map(Arc::new).map(|ipfs| IpfsEmbedBackend { ipfs }) } + pub async fn new_with_config(cfg: ipfs_embed::Config) -> Result { + ipfs_embed::Ipfs::new(cfg) + .await + .map(Arc::new) + .map(|ipfs| IpfsEmbedBackend { ipfs }) + } + pub async fn write_payload(&self, payload: &crate::backend::Payload) -> Result { let block = libipld::block::Block::encode(libipld::cbor::DagCborCodec, libipld::multihash::Code::Blake3_256, &payload)?; self.ipfs diff --git a/src/backend/datetime.rs b/src/backend/datetime.rs index 650a159..ec99282 100644 --- a/src/backend/datetime.rs +++ b/src/backend/datetime.rs @@ -3,6 +3,13 @@ use anyhow::Error; #[derive(Debug, Eq, PartialEq)] pub struct DateTime(chrono::DateTime); +impl From> for DateTime { + fn from(dt: chrono::DateTime) -> Self { + DateTime(dt) + } +} + + impl libipld::codec::Encode for DateTime { fn encode(&self, c: libipld_cbor::DagCborCodec, w: &mut W) -> libipld::error::Result<()> { self.0.to_rfc3339().encode(c, w).map_err(Error::from) diff --git a/src/backend/mime.rs b/src/backend/mime.rs index d65761e..9ea1cb8 100644 --- a/src/backend/mime.rs +++ b/src/backend/mime.rs @@ -3,6 +3,12 @@ use anyhow::Error; #[derive(Debug, Eq, PartialEq)] pub struct MimeType(mime::Mime); +impl From for MimeType { + fn from(mime: mime::Mime) -> Self { + MimeType(mime) + } +} + impl libipld::codec::Encode for MimeType { fn encode(&self, _c: C, w: &mut W) -> libipld::error::Result<()> { w.write_all(self.0.essence_str().as_bytes()).map_err(Error::from) diff --git a/src/backend/node.rs b/src/backend/node.rs index 800d503..a068405 100644 --- a/src/backend/node.rs +++ b/src/backend/node.rs @@ -33,4 +33,5 @@ impl Node { payload } } + } diff --git a/src/backend/payload.rs b/src/backend/payload.rs index fcddb50..856fcda 100644 --- a/src/backend/payload.rs +++ b/src/backend/payload.rs @@ -8,3 +8,31 @@ pub struct Payload { content: Vec, } +impl Payload { + pub fn new(mime: MimeType, timestamp: DateTime) -> Self { + Payload { mime, timestamp, content: Vec::new() } + } + + pub fn now_from_text(text: String) -> Payload { + let mime = MimeType::from(mime::TEXT_PLAIN_UTF_8); + let timestamp = DateTime::from(chrono::offset::Utc::now()); + + Self::new(mime, timestamp).with_content(text.as_bytes().to_vec()) + } + + pub fn with_content(mut self, v: Vec) -> Self { + self.content = v; + self + } + + pub fn with_mimetype(mut self, mime: MimeType) -> Self { + self.mime = mime; + self + } + + pub fn with_timestamp(mut self, ts: DateTime) -> Self { + self.timestamp = ts; + self + } +} + diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..4df70fa --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,46 @@ +use clap::crate_authors; +use clap::crate_version; +use clap::App; +use clap::Arg; + +pub fn app<'a>() -> App<'a> { + App::new("distrox") + .author(crate_authors!()) + .version(crate_version!()) + .about("Distributed social network") + + .subcommand(App::new("create-profile") + .author(crate_authors!()) + .version(crate_version!()) + .about("Create a new profile") + + .arg(Arg::with_name("content") + .index(1) + .multiple(false) + .takes_value(true) + .value_name("CONTENT") + .help("The text posting as first profile content") + ) + ) + + .subcommand(App::new("post") + .author(crate_authors!()) + .version(crate_version!()) + .about("Post to a profile") + .arg(Arg::with_name("head") + .index(1) + .multiple(false) + .takes_value(true) + .value_name("HEAD") + .help("Post with this HEAD as parent") + ) + + .arg(Arg::with_name("content") + .index(2) + .multiple(false) + .takes_value(true) + .value_name("TEXT") + .help("Post this TEXT as text/text") + ) + ) +} diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..e5c880c --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,4 @@ +pub fn v1() -> String { + String::from("1") +} + diff --git a/src/main.rs b/src/main.rs index ec907e8..ca89d15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,77 @@ +use std::path::PathBuf; + use anyhow::Result; +use daglib::DagBackend; + +extern crate clap_v3 as clap; mod backend; +mod cli; +mod consts; mod profile; #[tokio::main] async fn main() -> Result<()> { - Ok(()) + let app = crate::cli::app(); + + let mut backend = { + // Testing configuration for the IPFS node in the backend. + + let tmp = PathBuf::from("/tmp/distrox.tmp"); + let sconf = ipfs_embed::StorageConfig { + path: Some(tmp), + cache_size_blocks: 100_000, // blocks kepts before GC + cache_size_bytes: 1024 * 1024 * 1024, // 1GB before GC + gc_interval: std::time::Duration::from_secs(60 * 60), // hourly + gc_min_blocks: 0, + gc_target_duration: std::time::Duration::from_secs(60), // 1 minute + }; + + let nconf = ipfs_embed::NetworkConfig { + node_key: libp2p_core::identity::Keypair::generate_ed25519(), + node_name: String::from("distrox-devel"), + enable_mdns: false, // don't know what this is, yet + enable_kad: false, // don't know what this is, yet + allow_non_globals_in_dht: false, // don't know what this is, yet + psk: None, // Pre shared key for pnet. + ping: libp2p_ping::PingConfig::new(), // Ping config. + gossipsub: libp2p_gossipsub::GossipsubConfig::default(), // Gossipsub config. + bitswap: ipfs_embed::BitswapConfig::new(), // Bitswap config. + }; + + let ipfs_configuration = ipfs_embed::Config { + storage: sconf, + network: nconf, + }; + crate::backend::IpfsEmbedBackend::new_with_config(ipfs_configuration).await? + }; + + backend.ipfs().listen_on("/ip4/127.0.0.1/tcp/0".parse()?).await?; + + match app.get_matches().subcommand() { + ("create-profile", Some(mtch)) => { + let payload = mtch + .value_of("content") + .map(String::from) + .map(crate::backend::Payload::now_from_text) + .unwrap(); // Safe by clap + + let payload_cid = backend.write_payload(&payload).await?; + let node = crate::backend::Node::new(crate::consts::v1(), vec![], payload_cid); + + let id = backend.put(node).await?; + + println!("id = {:?}", id); + Ok(()) + }, + + ("post", Some(mtch)) => { + unimplemented!() + }, + + (other, _) => { + unimplemented!() + }, + } } + -- cgit v1.2.3