From deb68740a3cbefa96f26c1197f344b45d1ac55d0 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 6 Dec 2021 16:44:01 +0100 Subject: Split profile module into smaller submodules Signed-off-by: Matthias Beyer --- src/profile.rs | 281 --------------------------------------------------- src/profile/mod.rs | 167 ++++++++++++++++++++++++++++++ src/profile/state.rs | 129 +++++++++++++++++++++++ 3 files changed, 296 insertions(+), 281 deletions(-) delete mode 100644 src/profile.rs create mode 100644 src/profile/mod.rs create mode 100644 src/profile/state.rs (limited to 'src') diff --git a/src/profile.rs b/src/profile.rs deleted file mode 100644 index 205f3b4..0000000 --- a/src/profile.rs +++ /dev/null @@ -1,281 +0,0 @@ -use std::path::Path; -use std::path::PathBuf; -use std::convert::TryFrom; -use std::convert::TryInto; - -use anyhow::Context; -use anyhow::Result; -use tokio::io::AsyncReadExt; -use tokio::io::AsyncWriteExt; - -use crate::client::Client; -use crate::config::Config; -use crate::ipfs_client::IpfsClient; - -#[derive(Debug)] -pub struct Profile { - state: ProfileState, - client: Client, -} - -impl Profile { - pub async fn create(state_dir: &StateDir, name: &str, config: Config) -> Result { - let bootstrap = vec![]; // TODO - let mdns = false; // TODO - let keypair = ipfs::Keypair::generate_ed25519(); - - let options = ipfs::IpfsOptions { - ipfs_path: Self::ipfs_path(state_dir, name).await?, - keypair, - bootstrap, - mdns, - kad_protocol: None, - listening_addrs: vec![], - span: Some(tracing::trace_span!("distrox-ipfs")), - }; - - let keypair = options.keypair.clone(); - let (ipfs, fut): (ipfs::Ipfs<_>, _) = ipfs::UninitializedIpfs::<_>::new(options) - .start() - .await?; - tokio::task::spawn(fut); - Self::new(ipfs, config, name.to_string(), keypair).await - } - - async fn new_inmemory(config: Config, name: &str) -> Result { - let mut opts = ipfs::IpfsOptions::inmemory_with_generated_keys(); - opts.mdns = false; - let keypair = opts.keypair.clone(); - let (ipfs, fut): (ipfs::Ipfs<_>, _) = ipfs::UninitializedIpfs::<_>::new(opts).start().await.unwrap(); - tokio::task::spawn(fut); - Self::new(ipfs, config, format!("inmemory-{}", name), keypair).await - } - - async fn new(ipfs: IpfsClient, config: Config, profile_name: String, keypair: libp2p::identity::Keypair) -> Result { - let client = Client::new(ipfs, config); - let profile_head = Self::post_hello_world(&client, &profile_name).await?; - let state = ProfileState { - profile_head, - profile_name, - keypair, - }; - Ok(Profile { state, client }) - } - - async fn post_hello_world(client: &Client, name: &str) -> Result { - let text = format!("Hello world, I am {}", name); - client.post_text_node(vec![], text).await - } - - async fn ipfs_path(state_dir: &StateDir, name: &str) -> Result { - let path = state_dir.ipfs(); - tokio::fs::create_dir_all(&path).await?; - Ok(path) - } - - pub fn config_path(name: &str) -> String { - format!("distrox-{}", name) - } - - pub fn config_file_path(name: &str) -> Result { - xdg::BaseDirectories::with_prefix("distrox") - .map_err(anyhow::Error::from) - .and_then(|dirs| { - let name = Self::config_path(name); - dirs.place_config_file(name) - .map_err(anyhow::Error::from) - }) - } - - pub fn state_dir_path(name: &str) -> Result { - log::debug!("Getting state directory path"); - xdg::BaseDirectories::with_prefix("distrox") - .context("Fetching 'distrox' XDG base directory") - .map_err(anyhow::Error::from) - .and_then(|dirs| { - dirs.create_state_directory(name) - .map(StateDir::from) - .with_context(|| format!("Creating 'distrox' XDG state directory for '{}'", name)) - .map_err(anyhow::Error::from) - }) - } - - pub async fn save(&self) -> Result<()> { - let state_dir_path = Self::state_dir_path(&self.state.profile_name)?; - log::trace!("Saving to {:?}", state_dir_path.display()); - ProfileStateSaveable::new(&self.state) - .context("Serializing profile state")? - .save_to_disk(&state_dir_path) - .await - .context("Saving state to disk") - .map_err(anyhow::Error::from) - } - - pub async fn load(config: Config, name: &str) -> Result { - let state_dir_path = Self::state_dir_path(name)?; - log::trace!("state_dir_path = {:?}", state_dir_path.display()); - let state: ProfileState = ProfileStateSaveable::load_from_disk(&state_dir_path) - .await? - .try_into() - .context("Parsing profile state")?; - log::debug!("Loading state finished"); - - let bootstrap = vec![]; // TODO - let mdns = false; // TODO - let keypair = state.keypair.clone(); - - log::debug!("Configuring IPFS backend"); - let options = ipfs::IpfsOptions { - ipfs_path: Self::ipfs_path(&state_dir_path, name).await?, - keypair, - bootstrap, - mdns, - kad_protocol: None, - listening_addrs: vec![], - span: Some(tracing::trace_span!("distrox-ipfs")), - }; - - log::debug!("Starting IPFS backend"); - let (ipfs, fut): (ipfs::Ipfs<_>, _) = ipfs::UninitializedIpfs::<_>::new(options) - .start() - .await?; - tokio::task::spawn(fut); - - log::debug!("Profile loading finished"); - Ok(Profile { - state, - client: Client::new(ipfs, config), - }) - } - - pub async fn exit(self) -> Result<()> { - self.client.exit().await - } - -} - -#[derive(Debug)] -pub struct StateDir(PathBuf); - -impl StateDir { - pub fn ipfs(&self) -> PathBuf { - self.0.join("ipfs") - } - - pub fn profile_state(&self) -> PathBuf { - self.0.join("profile_state") - } - - pub fn display(&self) -> std::path::Display { - self.0.display() - } -} - -impl From for StateDir { - fn from(p: PathBuf) -> Self { - Self(p) - } -} - -#[derive(getset::Getters)] -pub struct ProfileState { - #[getset(get = "pub")] - profile_head: cid::Cid, - - #[getset(get = "pub")] - profile_name: String, - - #[getset(get = "pub")] - keypair: libp2p::identity::Keypair, -} - -impl std::fmt::Debug for ProfileState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ProfileState {{ name = {}, head = {:?} }}", self.profile_name, self.profile_head) - } -} - -#[derive(Debug, serde::Serialize, serde::Deserialize, getset::Getters)] -struct ProfileStateSaveable { - profile_head: Vec, - profile_name: String, - keypair: Vec, -} - -impl ProfileStateSaveable { - fn new(s: &ProfileState) -> Result { - Ok(Self { - profile_head: s.profile_head.to_bytes(), - profile_name: s.profile_name.clone(), - keypair: match s.keypair { - libp2p::identity::Keypair::Ed25519(ref kp) => Vec::from(kp.encode()), - _ => anyhow::bail!("Only keypair type ed25519 supported"), - } - }) - } - - pub async fn save_to_disk(&self, state_dir_path: &StateDir) -> Result<()> { - let state_s = serde_json::to_string(&self).context("Serializing state")?; - tokio::fs::OpenOptions::new() - .create_new(false) // do not _always_ create a new file - .create(true) - .truncate(true) - .write(true) - .open(&state_dir_path.profile_state()) - .await - .with_context(|| format!("Opening {}", state_dir_path.profile_state().display()))? - .write_all(state_s.as_bytes()) - .await - .map(|_| ()) - .with_context(|| format!("Writing to {}", state_dir_path.profile_state().display())) - .map_err(anyhow::Error::from) - } - - pub async fn load_from_disk(state_dir_path: &StateDir) -> Result { - log::trace!("Loading from disk: {:?}", state_dir_path.profile_state().display()); - let reader = tokio::fs::OpenOptions::new() - .read(true) - .open(&state_dir_path.profile_state()) - .await - .context("Opening state file")? - .into_std() - .await; - - log::trace!("Parsing state file"); - serde_json::from_reader(reader) - .context("Parsing state file") - .map_err(anyhow::Error::from) - } - -} - -impl TryInto for ProfileStateSaveable { - type Error = anyhow::Error; - - fn try_into(mut self) -> Result { - Ok(ProfileState { - profile_head: cid::Cid::try_from(self.profile_head)?, - profile_name: self.profile_name, - keypair: { - let kp = libp2p::identity::ed25519::Keypair::decode(&mut self.keypair)?; - libp2p::identity::Keypair::Ed25519(kp) - }, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::client::Client; - use crate::config::Config; - use crate::ipfs_client::IpfsClient; - - #[tokio::test] - async fn test_create_profile() { - let _ = env_logger::try_init(); - let profile = Profile::new_inmemory(Config::default(), "test-create-profile").await; - assert!(profile.is_ok()); - } - -} diff --git a/src/profile/mod.rs b/src/profile/mod.rs new file mode 100644 index 0000000..7078adb --- /dev/null +++ b/src/profile/mod.rs @@ -0,0 +1,167 @@ +use std::path::PathBuf; +use std::convert::TryInto; + +use anyhow::Context; +use anyhow::Result; + +use crate::client::Client; +use crate::config::Config; +use crate::ipfs_client::IpfsClient; + +mod state; +use state::*; + +#[derive(Debug)] +pub struct Profile { + state: ProfileState, + client: Client, +} + +impl Profile { + pub async fn create(state_dir: &StateDir, name: &str, config: Config) -> Result { + let bootstrap = vec![]; // TODO + let mdns = false; // TODO + let keypair = ipfs::Keypair::generate_ed25519(); + + let options = ipfs::IpfsOptions { + ipfs_path: Self::ipfs_path(state_dir, name).await?, + keypair, + bootstrap, + mdns, + kad_protocol: None, + listening_addrs: vec![], + span: Some(tracing::trace_span!("distrox-ipfs")), + }; + + let keypair = options.keypair.clone(); + let (ipfs, fut): (ipfs::Ipfs<_>, _) = ipfs::UninitializedIpfs::<_>::new(options) + .start() + .await?; + tokio::task::spawn(fut); + Self::new(ipfs, config, name.to_string(), keypair).await + } + + async fn new_inmemory(config: Config, name: &str) -> Result { + let mut opts = ipfs::IpfsOptions::inmemory_with_generated_keys(); + opts.mdns = false; + let keypair = opts.keypair.clone(); + let (ipfs, fut): (ipfs::Ipfs<_>, _) = ipfs::UninitializedIpfs::<_>::new(opts).start().await.unwrap(); + tokio::task::spawn(fut); + Self::new(ipfs, config, format!("inmemory-{}", name), keypair).await + } + + async fn new(ipfs: IpfsClient, config: Config, profile_name: String, keypair: libp2p::identity::Keypair) -> Result { + let client = Client::new(ipfs, config); + let profile_head = Self::post_hello_world(&client, &profile_name).await?; + let state = ProfileState::new(profile_head, profile_name, keypair); + Ok(Profile { state, client }) + } + + async fn post_hello_world(client: &Client, name: &str) -> Result { + let text = format!("Hello world, I am {}", name); + client.post_text_node(vec![], text).await + } + + async fn ipfs_path(state_dir: &StateDir, name: &str) -> Result { + let path = state_dir.ipfs(); + tokio::fs::create_dir_all(&path).await?; + Ok(path) + } + + pub fn config_path(name: &str) -> String { + format!("distrox-{}", name) + } + + pub fn config_file_path(name: &str) -> Result { + xdg::BaseDirectories::with_prefix("distrox") + .map_err(anyhow::Error::from) + .and_then(|dirs| { + let name = Self::config_path(name); + dirs.place_config_file(name) + .map_err(anyhow::Error::from) + }) + } + + pub fn state_dir_path(name: &str) -> Result { + log::debug!("Getting state directory path"); + xdg::BaseDirectories::with_prefix("distrox") + .context("Fetching 'distrox' XDG base directory") + .map_err(anyhow::Error::from) + .and_then(|dirs| { + dirs.create_state_directory(name) + .map(StateDir::from) + .with_context(|| format!("Creating 'distrox' XDG state directory for '{}'", name)) + .map_err(anyhow::Error::from) + }) + } + + pub async fn save(&self) -> Result<()> { + let state_dir_path = Self::state_dir_path(self.state.profile_name())?; + log::trace!("Saving to {:?}", state_dir_path.display()); + ProfileStateSaveable::new(&self.state) + .context("Serializing profile state")? + .save_to_disk(&state_dir_path) + .await + .context("Saving state to disk") + .map_err(anyhow::Error::from) + } + + pub async fn load(config: Config, name: &str) -> Result { + let state_dir_path = Self::state_dir_path(name)?; + log::trace!("state_dir_path = {:?}", state_dir_path.display()); + let state: ProfileState = ProfileStateSaveable::load_from_disk(&state_dir_path) + .await? + .try_into() + .context("Parsing profile state")?; + log::debug!("Loading state finished"); + + let bootstrap = vec![]; // TODO + let mdns = false; // TODO + let keypair = state.keypair().clone(); + + log::debug!("Configuring IPFS backend"); + let options = ipfs::IpfsOptions { + ipfs_path: Self::ipfs_path(&state_dir_path, name).await?, + keypair, + bootstrap, + mdns, + kad_protocol: None, + listening_addrs: vec![], + span: Some(tracing::trace_span!("distrox-ipfs")), + }; + + log::debug!("Starting IPFS backend"); + let (ipfs, fut): (ipfs::Ipfs<_>, _) = ipfs::UninitializedIpfs::<_>::new(options) + .start() + .await?; + tokio::task::spawn(fut); + + log::debug!("Profile loading finished"); + Ok(Profile { + state, + client: Client::new(ipfs, config), + }) + } + + pub async fn exit(self) -> Result<()> { + self.client.exit().await + } + +} + + +#[cfg(test)] +mod tests { + use super::*; + use crate::client::Client; + use crate::config::Config; + use crate::ipfs_client::IpfsClient; + + #[tokio::test] + async fn test_create_profile() { + let _ = env_logger::try_init(); + let profile = Profile::new_inmemory(Config::default(), "test-create-profile").await; + assert!(profile.is_ok()); + } + +} diff --git a/src/profile/state.rs b/src/profile/state.rs new file mode 100644 index 0000000..a84ff23 --- /dev/null +++ b/src/profile/state.rs @@ -0,0 +1,129 @@ +use std::path::PathBuf; +use std::convert::TryFrom; +use std::convert::TryInto; + +use anyhow::Context; +use anyhow::Result; +use tokio::io::AsyncWriteExt; + +#[derive(Debug)] +pub struct StateDir(PathBuf); + +impl StateDir { + pub fn ipfs(&self) -> PathBuf { + self.0.join("ipfs") + } + + pub fn profile_state(&self) -> PathBuf { + self.0.join("profile_state") + } + + pub fn display(&self) -> std::path::Display { + self.0.display() + } +} + +impl From for StateDir { + fn from(p: PathBuf) -> Self { + Self(p) + } +} + +#[derive(getset::Getters)] +pub struct ProfileState { + #[getset(get = "pub")] + profile_head: cid::Cid, + + #[getset(get = "pub")] + profile_name: String, + + #[getset(get = "pub")] + keypair: libp2p::identity::Keypair, +} + +impl ProfileState { + pub(super) fn new(profile_head: cid::Cid, profile_name: String, keypair: libp2p::identity::Keypair) -> Self { + Self { + profile_head, + profile_name, + keypair + } + } +} + +impl std::fmt::Debug for ProfileState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ProfileState {{ name = {}, head = {:?} }}", self.profile_name, self.profile_head) + } +} + +#[derive(Debug, serde::Serialize, serde::Deserialize, getset::Getters)] +pub(super) struct ProfileStateSaveable { + profile_head: Vec, + profile_name: String, + keypair: Vec, +} + +impl ProfileStateSaveable { + pub(super) fn new(s: &ProfileState) -> Result { + Ok(Self { + profile_head: s.profile_head.to_bytes(), + profile_name: s.profile_name.clone(), + keypair: match s.keypair { + libp2p::identity::Keypair::Ed25519(ref kp) => Vec::from(kp.encode()), + _ => anyhow::bail!("Only keypair type ed25519 supported"), + } + }) + } + + pub async fn save_to_disk(&self, state_dir_path: &StateDir) -> Result<()> { + let state_s = serde_json::to_string(&self).context("Serializing state")?; + tokio::fs::OpenOptions::new() + .create_new(false) // do not _always_ create a new file + .create(true) + .truncate(true) + .write(true) + .open(&state_dir_path.profile_state()) + .await + .with_context(|| format!("Opening {}", state_dir_path.profile_state().display()))? + .write_all(state_s.as_bytes()) + .await + .map(|_| ()) + .with_context(|| format!("Writing to {}", state_dir_path.profile_state().display())) + .map_err(anyhow::Error::from) + } + + pub async fn load_from_disk(state_dir_path: &StateDir) -> Result { + log::trace!("Loading from disk: {:?}", state_dir_path.profile_state().display()); + let reader = tokio::fs::OpenOptions::new() + .read(true) + .open(&state_dir_path.profile_state()) + .await + .context("Opening state file")? + .into_std() + .await; + + log::trace!("Parsing state file"); + serde_json::from_reader(reader) + .context("Parsing state file") + .map_err(anyhow::Error::from) + } + +} + +impl TryInto for ProfileStateSaveable { + type Error = anyhow::Error; + + fn try_into(mut self) -> Result { + Ok(ProfileState { + profile_head: cid::Cid::try_from(self.profile_head)?, + profile_name: self.profile_name, + keypair: { + let kp = libp2p::identity::ed25519::Keypair::decode(&mut self.keypair)?; + libp2p::identity::Keypair::Ed25519(kp) + }, + }) + } +} + + -- cgit v1.2.3