summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2021-12-05 09:27:44 +0100
committerMatthias Beyer <mail@beyermatthias.de>2021-12-06 16:28:59 +0100
commit397805100d2fc48937652b4c54a8870501966301 (patch)
treea31dd23e3deb1bd920b57cd7cd3841e8e872359c /src
parent30df43d2d3dc4b53e69bb1666d5d8f3c2550abfc (diff)
Implement saveable and loadable profile state
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
Diffstat (limited to 'src')
-rw-r--r--src/profile.rs154
1 files changed, 141 insertions, 13 deletions
diff --git a/src/profile.rs b/src/profile.rs
index d6f5b4b..1b5f81c 100644
--- a/src/profile.rs
+++ b/src/profile.rs
@@ -1,8 +1,11 @@
use std::path::Path;
use std::path::PathBuf;
+use std::convert::TryFrom;
+use std::convert::TryInto;
use anyhow::Result;
use tokio::io::AsyncReadExt;
+use tokio::io::AsyncWriteExt;
use crate::client::Client;
use crate::config::Config;
@@ -10,6 +13,7 @@ use crate::ipfs_client::IpfsClient;
#[derive(Debug)]
pub struct Profile {
+ state: ProfileState,
client: Client,
}
@@ -18,7 +22,6 @@ impl Profile {
let bootstrap = vec![]; // TODO
let mdns = false; // TODO
let keypair = ipfs::Keypair::generate_ed25519();
- Self::write_to_statedir(state_dir, name, &keypair).await?;
let options = ipfs::IpfsOptions {
ipfs_path: Self::ipfs_path(state_dir, name).await?,
@@ -30,27 +33,37 @@ impl Profile {
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);
- Ok(Self::new(ipfs, config))
+ Self::new(ipfs, config, name.to_string(), keypair).await
}
- async fn new_inmemory(config: Config) -> Result<Self> {
+ async fn new_inmemory(config: Config, name: &str) -> Result<Self> {
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);
- Ok(Self::new(ipfs, config))
+ Self::new(ipfs, config, format!("inmemory-{}", name), keypair).await
}
- fn new(ipfs: IpfsClient, config: Config) -> Self {
- Profile { client: Client::new(ipfs, config) }
+ async fn new(ipfs: IpfsClient, config: Config, profile_name: String, keypair: libp2p::identity::Keypair) -> Result<Self> {
+ 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 write_to_statedir(_state_dir: &Path, _name: &str, _keypair: &ipfs::Keypair) -> Result<()> {
- unimplemented!()
+ async fn post_hello_world(client: &Client, name: &str) -> Result<cid::Cid> {
+ let text = format!("Hello world, I am {}", name);
+ client.post_text_node(vec![], text).await
}
async fn ipfs_path(state_dir: &Path, name: &str) -> Result<PathBuf> {
@@ -73,17 +86,132 @@ impl Profile {
})
}
- /// Load the Profile from disk and ensure the keys exist in IPFS
- pub async fn load_from_filesystem(_name: &str, _client: &Client) -> Result<Option<Self>> {
- unimplemented!()
+ fn state_dir_path(name: &str) -> Result<PathBuf> {
+ xdg::BaseDirectories::with_prefix("distrox")
+ .map_err(anyhow::Error::from)
+ .and_then(|dirs| {
+ dirs.create_state_directory(name).map_err(anyhow::Error::from)
+ })
+ }
+
+ pub async fn save(&self) -> Result<()> {
+ let state_dir_path = Self::state_dir_path(&self.state.profile_name)?;
+ ProfileStateSaveable::new(&self.state)?
+ .save_to_disk(&state_dir_path)
+ .await
+ }
+
+ pub async fn load(config: Config, name: &str) -> Result<Self> {
+ let state_dir_path = Self::state_dir_path(name)?;
+ let state: ProfileState = ProfileStateSaveable::load_from_disk(&state_dir_path)
+ .await?
+ .try_into()?;
+
+ let bootstrap = vec![]; // TODO
+ let mdns = false; // TODO
+ let keypair = state.keypair.clone();
+
+ 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")),
+ };
+
+ let (ipfs, fut): (ipfs::Ipfs<_>, _) = ipfs::UninitializedIpfs::<_>::new(options)
+ .start()
+ .await?;
+ tokio::task::spawn(fut);
+
+ Ok(Profile {
+ state,
+ client: Client::new(ipfs, config),
+ })
+ }
+
+}
+
+#[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<u8>,
+ profile_name: String,
+ keypair: Vec<u8>,
+}
+
+impl ProfileStateSaveable {
+ fn new(s: &ProfileState) -> Result<Self> {
+ 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: &Path) -> Result<()> {
+ let state_s = serde_json::to_string(&self)?;
+ tokio::fs::OpenOptions::new()
+ .create(true)
+ .truncate(true)
+ .open(state_dir_path.join("profile_state"))
+ .await?
+ .write_all(state_s.as_bytes())
+ .await
+ .map(|_| ())
+ .map_err(anyhow::Error::from)
}
- async fn load_from_reader<R: AsyncReadExt + std::marker::Unpin>(_r: R, _name: &str, _client: &Client) -> Result<Option<Self>> {
- unimplemented!()
+ pub async fn load_from_disk(state_dir_path: &Path) -> Result<Self> {
+ let reader = tokio::fs::OpenOptions::new()
+ .read(true)
+ .open(state_dir_path.join("profile_state"))
+ .await?
+ .into_std()
+ .await;
+
+ serde_json::from_reader(reader).map_err(anyhow::Error::from)
}
}
+impl TryInto<ProfileState> for ProfileStateSaveable {
+ type Error = anyhow::Error;
+
+ fn try_into(mut self) -> Result<ProfileState> {
+ 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::*;