diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.rs | 43 | ||||
-rw-r--r-- | src/configuration.rs | 107 | ||||
-rw-r--r-- | src/gui.rs | 31 | ||||
-rw-r--r-- | src/main.rs | 115 | ||||
-rw-r--r-- | src/middleware.rs | 108 | ||||
-rw-r--r-- | src/model.rs | 146 | ||||
-rw-r--r-- | src/server.rs | 78 | ||||
-rw-r--r-- | src/types/block.rs | 85 | ||||
-rw-r--r-- | src/types/content.rs | 104 | ||||
-rw-r--r-- | src/types/mod.rs | 5 | ||||
-rw-r--r-- | src/types/payload.rs | 279 | ||||
-rw-r--r-- | src/types/util.rs | 172 | ||||
-rw-r--r-- | src/version.rs | 4 |
13 files changed, 0 insertions, 1277 deletions
diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index c91b653..0000000 --- a/src/cli.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::path::PathBuf; - -use structopt::StructOpt; -use anyhow::Error; - -#[derive(Debug, StructOpt)] -#[structopt(name = "distrox", about = "Distrox - The distributed social network")] -pub struct CLI { - #[structopt(short, long)] - debug: bool, - - #[structopt(short, long)] - trace: bool, - - #[structopt(short, long)] - port: Option<u16>, - - #[structopt(parse(from_os_str))] - configfile: Option<PathBuf>, - - #[structopt(subcommand)] - cmd: Option<Command> -} - -impl CLI { - pub fn cmd(&self) -> Option<&Command> { - self.cmd.as_ref() - } - - pub fn port(&self) -> Option<u16> { - self.port.as_ref().map(|p| *p) - } -} - -#[derive(Debug, PartialEq, StructOpt)] -#[structopt(about = "Start the server part (running in foreground")] -pub enum Command { - Server, -} - -pub fn cli() -> Result<CLI, Error> { - Ok(CLI::from_args()) -} diff --git a/src/configuration.rs b/src/configuration.rs deleted file mode 100644 index 5b39090..0000000 --- a/src/configuration.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::types::util::MimeType; - -// use chrono::Duration; - -/// Configuration read from a configuration file -#[derive(Serialize, Deserialize, Debug, AddGetter)] -pub struct Configuration { - #[serde(rename = "ipfs-api-url")] - #[get] - /// The URL of the API - api_url: String, - - #[serde(rename = "ipfs-api-port")] - #[get] - /// The Port of the API - api_port: u16, - - #[serde(rename = "app-port")] - #[get] - /// The Port of the App itself - app_port: u16, - - #[serde(rename = "autoserve-chains")] - #[get] - /// Whether to automatically "ipfs pin" chain objects - autoserve_chains: bool, - - #[serde(rename = "autoserve-text-posts")] - #[get] - /// Whether to automatically "ipfs pin" foreign posts if their content is text - autoserve_text_posts: bool, - - #[serde(rename = "serve-blocked")] - #[get] - /// Whether to serve content/chains from blocked profiles - serve_blocked: bool, - - #[serde(rename = "autoserve-followed")] - #[get] - /// Whether to automatically "ipfs pin" followed profiles - autoserve_followed: bool, - - #[serde(rename = "max-autoload-per-post")] - #[get] - /// Default amount of bytes which are loaded for each post - max_autoload_per_post: usize, - - #[serde(rename = "autoserve-blacklist")] - #[get] - /// List of Mimetypes which should not be served - autoserve_blacklist: Vec<MimeType>, - - #[serde(rename = "autoserve-whitelist")] - #[get] - /// List of Mimetypes which can be served - autoserve_whitelist: Vec<MimeType>, - - // #[serde(rename = "merge-timeout")] - // #[get] - // /// Timeout before merge should be attempted - // merge_timeout: Duration, - // - - /// Name under which to provide the local device. E.G. - /// Some("/ipfs/QmVrLsEDn27sScp3k23sgZNefVTjSAL3wpgW1iWPi4MgoY") - /// - /// If none, one will be generated and set - #[serde(rename = "device_name")] - #[get] - device_name: Option<String>, - - /// Key to sign stuff that comes from this device. - /// - /// Create by using `ipfs key gen <name>` - #[serde(rename = "device_key")] - #[get] - device_key: Option<String>, - - /// Devices for the profile - /// E.G: - /// ["/ipfs/QmVrLsEDn27sScp3k23sgZNefVTjSAL3wpgW1iWPi4MgoY"] - #[serde(rename = "devices")] - #[get] - devices: Vec<String>, -} - -impl Default for Configuration { - fn default() -> Self { - Configuration { - api_url : String::from("127.0.0.1"), - api_port : 5001, - app_port : 5002, - autoserve_chains : true, - autoserve_text_posts : true, - serve_blocked : false, - autoserve_followed : true, - max_autoload_per_post : 1024 * 1024, - autoserve_blacklist : Vec::new(), - autoserve_whitelist : Vec::new(), - // merge_timeout : Duration::minutes(15), - device_name : None, - device_key : None, - devices : Vec::new(), - } - } -} - diff --git a/src/gui.rs b/src/gui.rs deleted file mode 100644 index 1d5281e..0000000 --- a/src/gui.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt::Debug; - -use anyhow::Error; -use anyhow::Result; -use web_view::WVResult; -use web_view::WebView; - -use crate::cli::*; -use crate::types::util::*; - -pub fn run_gui(adr: String) -> Result<()> { - let webview_content = web_view::Content::Url(format!("http://{}", adr)); - - web_view::builder() - .title("My Project") - .content(webview_content) - .resizable(true) - .debug(true) - .user_data(()) - .invoke_handler(invoke_handler) - .build() - .map_err(Error::from)? - .run() - .map_err(Error::from) -} - -fn invoke_handler<T: Debug>(webview: &mut WebView<T>, s: &str) -> WVResult { - debug!("invoke-handler: {:?}, {:?}", webview, s); - Ok(()) -} - diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 80e6ec9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,115 +0,0 @@ -#![allow(warnings)] - -extern crate ipfs_api; -extern crate chrono; -extern crate mime; -extern crate futures; -extern crate serde; -extern crate serde_json; -extern crate uuid; -extern crate clap; -extern crate toml; -extern crate config; -extern crate hyper; -extern crate env_logger; -extern crate itertools; -extern crate xdg; -extern crate handlebars; -extern crate web_view; -extern crate actix_rt; -extern crate actix_web; -extern crate failure; -extern crate pidlock; - -#[macro_use] extern crate anyhow; -#[macro_use] extern crate is_match; -#[macro_use] extern crate serde_derive; -#[macro_use] extern crate log; -#[macro_use] extern crate tokio; -#[macro_use] extern crate add_getters_setters; -#[macro_use] extern crate structopt; - -mod cli; -mod configuration; -mod gui; -mod model; -mod middleware; -mod server; -mod types; -mod version; - -use std::collections::BTreeMap; -use std::str::FromStr; -use std::ops::Deref; -use std::sync::Arc; -use std::path::PathBuf; - -use chrono::NaiveDateTime; -use futures::future::Future; -use futures::future::FutureExt; -use futures::future::TryFutureExt; -use serde_json::to_string_pretty as serde_json_to_string_pretty; -use serde_json::from_str as serde_json_from_str; -use anyhow::Result; -use anyhow::Error; -use env_logger::Env; - -use crate::cli::*; -use crate::configuration::Configuration; -use crate::model::Model; -use crate::middleware::Middleware; -use crate::types::block::Block; -use crate::types::content::Content; -use crate::types::payload::Payload; -use crate::types::util::IPFSHash; -use crate::types::util::IPNSHash; -use crate::types::util::MimeType; -use crate::types::util::Timestamp; -use crate::types::util::Version; - -use std::process::exit; - -#[actix_rt::main] -async fn main() -> Result<()> { - let cli = cli()?; - let _ = env_logger::from_env(Env::default().default_filter_or("info")).init(); - debug!("Logger initialized"); - - let config_file_name = PathBuf::from("distrox.toml"); - let config: Configuration = { - let configfile = xdg::BaseDirectories::with_prefix("distrox")? - .find_config_file(&config_file_name) - .ok_or_else(|| anyhow!("No configuration found"))?; - - let configstr = ::std::fs::read_to_string(&configfile)?; - ::toml::from_str(&configstr)? - }; - - let port = cli.port().unwrap_or_else(|| *config.get_app_port()); - let adr = format!("127.0.0.1:{}", port); - - let mut server_lock = crate::server::mk_lock(); - let server_running = crate::server::is_running(&server_lock); - let start_server = crate::server::do_start(&cli); - - match (server_running, start_server) { - (true, false) => crate::gui::run_gui(adr), - (false, false) => { - // fork() - info!("Spawning server thread..."); - let path = std::env::current_exe()?; - let mut child = std::process::Command::new(path).arg("server").spawn()?; - let r = crate::gui::run_gui(adr); - child.kill()?; - r - }, - - (false, true) => crate::server::run_server(config, server_lock, adr).await, - - (true, true) => { - info!("Server is already running. Doing nothing."); - return Ok(()) - }, - } -} - diff --git a/src/middleware.rs b/src/middleware.rs deleted file mode 100644 index 61fe716..0000000 --- a/src/middleware.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::collections::BTreeMap; - -use anyhow::Error; -use futures::Stream; -use futures::stream; - -use crate::types::util::IPFSHash; -use crate::types::util::MimeType; -use crate::types::util::IPNSHash; -use crate::types::block::Block; -use crate::model::Model; -use crate::types::content::Content; -use crate::types::payload::Payload; -use crate::types::util::Timestamp; -use crate::version::protocol_version; - -/// The "Middleware" type implements the actual application logic -#[derive(Debug, Clone)] -pub struct Middleware { - repo: Model, - device_name: IPNSHash, - publishing_key: String -} - -impl Middleware { - - pub fn load(device_name: IPNSHash, publishing_key: String, host: &str, port: u16) -> Result<Self, Error> { - Model::new(host, port).map(|repo| Middleware { repo, device_name, publishing_key }) - } - - pub async fn new_profile(repo: Model, publishing_key: &str, names: Vec<String>) -> Result<Self, Error> { - let payload = Payload::Profile { - names, - picture: None, - more: BTreeMap::new(), - }; - let timestamp = Timestamp::now(); - let content = Content::new(vec![], Some(timestamp), payload); - - let content_hash = repo.put_content(content).await?; - let head = repo.put_block(Block::new(protocol_version(), vec![], content_hash)).await?; - let device_name = repo.publish(&publishing_key, &head).await?; - - Ok(Middleware { repo, device_name, publishing_key: publishing_key.to_string() }) - } - - pub async fn new_post(&self, - content: Vec<u8>, - mime: MimeType, - reply_to: Option<IPFSHash>, - comments_will_be_propagated: Option<bool>, - comments_propagated_until: Option<Timestamp>) - -> Result<(), Error> - { - let content_hash = self.repo.put_raw_bytes(content).await?; - - let payload = Payload::Post { - content_format: mime, - content: content_hash, - reply_to, - comments_will_be_propagated, - comments_propagated_until, - }; - let timestamp = Timestamp::now(); - let content = Content::new(vec![], Some(timestamp), payload); - - let content_hash = self.repo.put_content(content).await?; - - let head = self.repo.put_block(Block::new(protocol_version(), vec![], content_hash)).await?; - let device_name = self.repo.publish(&self.publishing_key, &head).await?; - - Ok(()) - } - - pub fn blocks(&self, head: IPFSHash) -> impl Stream<Item = Result<Block, Error>> { - stream::unfold((self.repo.clone(), vec![head]), move |(repo, mut state)| { - async { - if let Some(hash) = state.pop() { - match repo - .get_block(hash) - .await - .map(move |block| { - block.parents().iter().for_each(|parent| { - state.push(parent.clone()) - }); - - (block, state) - }) - .map(Some) - .transpose() - { - Some(Ok((item, state))) => Some((Ok(item), (repo, state))), - Some(Err(e)) => Some((Err(e), (repo, vec![]))), - None => None, - } - } else { - None - } - } - }) - } - - pub async fn blocks_of(&self, ipns: IPNSHash) -> Result<impl Stream<Item = Result<Block, Error>>, Error> { - self.repo.resolve(ipns).await.map(|ipfs| self.blocks(ipfs)) - } - -} - diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index 85fcf22..0000000 --- a/src/model.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! The "model" module implements the Database-layer of the application - -use std::io::Cursor; -use std::ops::Deref; -use std::result::Result as RResult; -use std::sync::Arc; - -use anyhow::Error; -use chrono::NaiveDateTime; -use failure::Fail; -use futures::future::Future; -use futures::future::FutureExt; -use futures::stream::Stream; -use futures::stream::StreamExt; -use futures::stream::TryStreamExt; -use ipfs_api::IpfsClient; -use ipfs_api::TryFromUri; -use serde::Serialize; -use serde::de::DeserializeOwned; -use serde_json::from_str as serde_json_from_str; -use serde_json::to_string as serde_json_to_str; - -use crate::types::block::Block; -use crate::types::content::Content; -use crate::types::payload::Payload; -use crate::types::util::IPFSHash; -use crate::types::util::IPNSHash; - -#[derive(Clone)] -pub struct Model(IpfsClient); - -impl std::fmt::Debug for Model{ - fn fmt(&self, f: &mut std::fmt::Formatter) -> RResult<(), std::fmt::Error> { - write!(f, "Model") - } -} - -impl Model { - pub fn new(host: &str, port: u16) -> Result<Model, Error> { - debug!("Creating new Model object: {}:{}", host, port); - IpfsClient::from_str(&format!("{}:{}", host, port)) - .map(Model) - .map_err(|e| Error::from(e.compat())) - } - - - // - // - // Low-level interface to the ipfs-api - // - // - - pub(crate) async fn get_raw_bytes<H: AsRef<IPFSHash>>(&self, hash: H) -> Result<Vec<u8>, Error> { - debug!("Get: {}", hash.as_ref()); - self.0 - .cat(hash.as_ref()) - .map_ok(|b| b.to_vec()) - .try_concat() - .map(|r| r.map_err(|e| anyhow!("UNIMPLEMENTED!()"))) - .await - } - - pub(crate) async fn put_raw_bytes(&self, data: Vec<u8>) -> Result<IPFSHash, Error> { - debug!("Put: {:?}", data); - self.0 - .add(Cursor::new(data)) - .await - .map(|res| IPFSHash::from(res.hash)) - .map_err(|e| anyhow!("UNIMPLEMENTED!()")) - } - - pub(crate) async fn publish(&self, key: &str, hash: &str) -> Result<IPNSHash, Error> { - debug!("Publish: {:?} -> {:?}", key, hash); - self.0 - .name_publish(hash, false, None, None, Some(key)) - .await - .map(|res| IPNSHash::from(res.value)) - .map_err(|e| anyhow!("UNIMPLEMENTED!()")) - } - - pub(crate) async fn resolve(&self, ipns: IPNSHash) -> Result<IPFSHash, Error> { - self.0 - .name_resolve(Some(&ipns), true, false) - .await - .map(|res| IPFSHash::from(res.path)) - .map_err(|e| anyhow!("UNIMPLEMENTED!()")) - } - - // - // - // Generic typed interface - // - // - - pub(crate) async fn get_typed<H, D>(&self, hash: H) -> Result<D, Error> - where H: AsRef<IPFSHash>, - D: DeserializeOwned - { - self.get_raw_bytes(hash) - .await - .and_then(|data| { - debug!("Got data, building object: {:?}", data); - - serde_json::from_slice(&data).map_err(|e| Error::from(e.compat())) - }) - } - - pub(crate) async fn put_typed<S, Ser>(&self, data: &S) -> Result<IPFSHash, Error> - where S: AsRef<Ser>, - Ser: Serialize - { - let data = serde_json_to_str(data.as_ref())?; - self.put_raw_bytes(data.into_bytes()).await - } - - // - // - // Typed interface - // - // - - pub async fn get_block<H>(&self, hash: H) -> Result<Block, Error> - where H: AsRef<IPFSHash> - { - self.get_typed(hash).await - } - - pub async fn put_block<B>(&self, b: B) -> Result<IPFSHash, Error> - where B: AsRef<Block> - { - self.put_typed(b.as_ref()).await - } - - pub async fn get_content<H>(&self, hash: H) -> Result<Content, Error> - where H: AsRef<IPFSHash> - { - self.get_typed(hash).await - } - - pub async fn put_content<C>(&self, c: C) -> Result<IPFSHash, Error> - where C: AsRef<Content> - { - self.put_typed(c.as_ref()).await - } - -} diff --git a/src/server.rs b/src/server.rs deleted file mode 100644 index b1fa237..0000000 --- a/src/server.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::path::PathBuf; - -use anyhow::Error; -use anyhow::Result; -use pidlock::{Pidlock, PidlockState}; -use actix_web::HttpResponse; -use actix_web::Responder; -use actix_web::http::StatusCode; -use actix_web::body::Body; - -use crate::cli::*; -use crate::configuration::Configuration; -use crate::middleware::Middleware; -use crate::types::util::*; - -pub fn mk_lock() -> Pidlock { - pidlock::Pidlock::new("/tmp/distrox_server.pid") -} - -pub fn do_start(cli: &CLI) -> bool { - cli.cmd().map(|cmd| Command::Server == *cmd).unwrap_or(false) -} - -pub fn is_running(server_lock: &Pidlock) -> bool { - PathBuf::from("/tmp/distrox_server.pid").exists() -} - - -pub async fn run_server(config: Configuration, mut server_lock: Pidlock, adr: String) -> Result<()> { - let app = { - let device_name = config.get_device_name(); - let device_key = config.get_device_key(); - - if let (Some(name), Some(key)) = (device_name, device_key) { - let name = IPNSHash::from(name.clone()); - let key = key.clone(); - let api_url = config.get_api_url().clone(); - let api_port = config.get_api_port().clone(); - - Middleware::load(name, key, &api_url, api_port) - } else { - // ask user for name(s) - // boot repository - // load Middleware object - unimplemented!() - } - }?; - - info!("Starting server"); - let _ = server_lock.acquire().map_err(|_| anyhow!("Error while getting the PID lock"))?; - - info!("Got PID lock for server"); - actix_web::HttpServer::new(|| { - actix_web::App::new() - .route("*", actix_web::web::get().to(index)) - }) - .bind(adr.clone()) - .expect(&format!("Could not bind to address {}", adr)) - .run() - .await; - - info!("Server shutdown"); - info!("Releasing PID lock for server"); - server_lock.release().map_err(|_| anyhow!("Error while releasing the PID lock")) -} - -async fn index() -> impl Responder { - debug!("serve index"); - let s = format!("{pre}{style}{index}{post}", - pre = include_str!("../assets/index_pre.html"), - style = include_str!("../assets/style.css"), - index = include_str!("../assets/index.html"), - post = include_str!("../assets/index_post.html"), - ); - - HttpResponse::build(StatusCode::OK).body(Body::from_slice(s.as_bytes())) -} - diff --git a/src/types/block.rs b/src/types/block.rs deleted file mode 100644 index c70ceef..0000000 --- a/src/types/block.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::types::util::IPFSHash; -use crate::types::util::Version; -use crate::types::content::Content; -use crate::types::payload::*; -use crate::types::content::LoadedContent; -use crate::model::Model; - -use anyhow::Error; - -#[derive(Serialize, Deserialize, Debug)] -pub struct Block { - /// The version of the API in use - #[serde(rename = "v")] - version: Version, - - /// The parents of this Profile instance - /// - /// Multiple are required for merging. - #[serde(rename = "parents")] - #[serde(default)] - parents: Vec<IPFSHash>, - - /// The content of this block, pointed to by IPFS hash. - #[serde(rename = "content")] - content: IPFSHash, -} - -impl Block { - pub fn new(version: Version, parents: Vec<IPFSHash>, content: IPFSHash) -> Self { - Block { version, parents, content } - } - - pub fn version(&self) -> &Version { - &self.version - } - - pub fn parents(&self) -> &Vec<IPFSHash> { - &self.parents - } - - pub fn content(&self) -> &IPFSHash { - &self.content - } - - pub async fn load(self, r: &Model) -> Result<LoadedBlock, Error> { - Ok({ - LoadedBlock { - version: self.version, - parents: self.parents, - content: r.get_content(&self.content) - .await? - .load(r) - .await? - } - }) - } -} - -impl AsRef<Block> for Block { - fn as_ref(&self) -> &Self { - &self - } -} - -#[derive(Debug)] -pub struct LoadedBlock { - version: Version, - parents: Vec<IPFSHash>, - content: LoadedContent, -} - -impl LoadedBlock { - pub fn version(&self) -> &Version { - &self.version - } - - pub fn parents(&self) -> &Vec<IPFSHash> { - &self.parents - } - - pub fn content(&self) -> &LoadedContent { - &self.content - } -} - diff --git a/src/types/content.rs b/src/types/content.rs deleted file mode 100644 index 8b6eb4d..0000000 --- a/src/types/content.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::collections::BTreeMap; - -use anyhow::Error; - -use crate::types::util::IPFSHash; -use crate::types::util::IPNSHash; -use crate::types::util::MimeType; -use crate::types::util::Timestamp; -use crate::types::payload::Payload; -use crate::types::payload::LoadedPayload; -use crate::model::Model; - -#[derive(Serialize, Deserialize, Debug)] -pub struct Content { - - // - // - // Metadata about the content - // -------------------------- - // - // This metadata should be added to each block version. It is a small amount of bytes, but it - // makes the aggregation much simpler. - // - // In v2 of the API, we might change this and put all this meta-information into variants of - // `Payload`, if we find that aggregation is fast enough. - // - - /// A list of IPNS hashes which are posting to this chain (so if a client has one profile - /// node, it can find the latest profile nodes from all devices a user posts from) - #[serde(rename = "devices")] - devices: Vec<IPNSHash>, - - /// Timestamp (UNIX timestamp) when this was created. Can be left out. - #[serde(rename = "timestamp")] - #[serde(default)] - timestamp: Option<Timestamp>, - - /// The payload of the content block - #[serde(rename = "payload")] - payload: Payload, - -} - -impl Content { - - pub fn new(devices: Vec<IPNSHash>, timestamp: Option<Timestamp>, payload: Payload) -> Content { - Content { devices, timestamp, payload } - } - - pub fn devices(&self) -> &Vec<IPNSHash> { - &self.devices - } - - pub fn timestamp(&self) -> Option<&Timestamp> { - self.timestamp.as_ref() - } - - pub fn payload(&self) -> &Payload { - &self.payload - } - - pub(crate) fn push_device(&mut self, dev: IPNSHash) { - self.devices.push(dev); - } - - pub async fn load(self, r: &Model) -> Result<LoadedContent, Error> { - Ok({ - LoadedContent { - devices: self.devices, - timestamp: self.timestamp, - payload: self.payload.load(r).await? - } - }) - } - -} - -impl AsRef<Content> for Content { - fn as_ref(&self) -> &Self { - &self - } -} - -#[derive(Debug)] -pub struct LoadedContent { - devices: Vec<IPNSHash>, - timestamp: Option<Timestamp>, - payload: LoadedPayload, -} - -impl LoadedContent { - pub fn devices(&self) -> &Vec<IPNSHash> { - &self.devices - } - - pub fn timestamp(&self) -> Option<&Timestamp> { - self.timestamp.as_ref() - } - - pub fn payload(&self) -> &LoadedPayload { - &self.payload - } -} - diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index 06ac03a..0000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod block; -pub mod content; -pub mod payload; -pub mod util; - diff --git a/src/types/payload.rs b/src/types/payload.rs deleted file mode 100644 index 47f28a6..0000000 --- a/src/types/payload.rs +++ /dev/null @@ -1,279 +0,0 @@ -use std::collections::BTreeMap; - -use anyhow::Error; - -use crate::types::util::IPFSHash; -use crate::types::util::IPNSHash; -use crate::types::util::MimeType; -use crate::types::util::Timestamp; -use crate::model::Model; - - -/// The Payload type represents the Payload of a Content object -/// -/// The Payload type contains several variants, as an update (new block) may be either just a -/// metadata update (like a merge) or something more meaningful. -/// -/// For example, the payload might be a `Payload::Post`, Alice has posted new kitten pictures! -/// Or a `Payload::ProfileUpdate` which contains information about Alice herself -/// -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "payload_type")] -pub enum Payload { - - /// A variant that represents no payload - /// - /// This normally happens when merging two chains. - None, - - /// A post - /// - /// A post can be anything which contains data. The data itself is put behind an IPFS hash, the - /// Payload::Post only contains meta-information like payload size, content format (MIME) and - /// optionally a list of IPFS hashes which are related to the post itself. - Post { - /// Format of the actual content - #[serde(rename = "content-format")] - content_format: MimeType, - - /// IPFS hash pointing to the actual content - #[serde(rename = "content")] - content: IPFSHash, - - /// If this post is a reply to another post, this field can be used to point to the - /// replied-to post. - #[serde(rename = "reply-to")] - #[serde(default)] - reply_to: Option<IPFSHash>, - - // - // - // Metadata for the post which should be visible to others - // - // - - /// A flag whether comments to this post will be propagated - /// - /// From a technical POV, comments can be done anyways, but the client which published the - /// comment has to "accept" comments (via `PostComments`) to make them visible to others. - /// This flag indicates whether the client will do that (either manually or automatically, - /// which is not indicated here). - /// - /// # Available values - /// - /// A value of `Some(false)` means that comments will not be propagated, so others will not - /// see them. A UI may not show "add comment"-buttons if this is set to `Some(false)`. - /// - /// A value of `Some(true)` means that comments will eventually be propagated. This means - /// that the author might accept them by hand or tell their client to automatically accept - /// all comments. This distinction is client-side implementation specific. - /// - /// A value of `None` indicates no setting here, which means that the client might or might - /// not propagate any comments. - #[serde(rename = "comments-will-be-propagated")] - #[serde(default)] - comments_will_be_propagated: Option<bool>, - - /// A value which describes until what date/time comments will be propagated - /// - /// This is a hint for other users whether comments will be propagated or not. - /// A UI might not show a "Reply" button after that date. - #[serde(rename = "comments-propagated-until")] - #[serde(default)] - comments_propagated_until: Option<Timestamp>, - }, - - /// Comments for a post - /// - /// Propagating answers to a post must be done by the author of the post itself. - /// This variant is for publishing a message "These are the comments on my post <hash>". - /// - /// Always all comments should be published, not just the new ones! - AttachedPostComments { - /// The Hash of the Block for the Post which the comments are for - #[serde(rename = "comments-for")] - comments_for: IPFSHash, - - /// Hashes of direct answers to the post pointed to by "comments_for" - /// This list always represents a full list of all answers. As comments are added, old - /// versions of this object should be ignored by clients if newer variants for the same `comments_for`-object are published. - #[serde(rename = "refs")] - #[serde(default)] - refs: Vec<IPFSHash>, - }, - - /// A variant describing a profile - /// - /// A profile contains the whole set of data which is considered "current" for the - /// profile. Older versions of this shall then be ignored by clients. - Profile { - - /// The self-assigned names of a user. - #[serde(rename = "names")] - names: Vec<String>, - - /// An optional user profile picture - /// - /// The picture itself is located behind a IPFS hash. If the hash does not resolve to a - /// picture, clients should ignore it. - /// - /// A profile may only contain _one_ profile picture in the current version of the - /// protocol. - #[serde(rename = "picture")] - #[serde(default)] - picture: Option<IPFSHash>, - - /// A "more" field where arbitrary data can be stored. Like "Biography", "Hobbies", - /// "Political opinion" or even pictures, ... - /// - /// The stored data can be of any type. - #[serde(rename = "user-defined")] - #[serde(default)] - more: BTreeMap<String, Userdata>, - }, -} - -impl Payload { - /// Helper function - pub fn is_none(&self) -> bool { - match self { - &Payload::None => true, - _ => false, - } - } - - /// Helper function - pub fn is_post(&self) -> bool { - match self { - &Payload::Post {..} => true, - _ => false, - } - } - - /// Helper function - pub fn is_attached_post_comments(&self) -> bool { - match self { - &Payload::AttachedPostComments {..} => true, - _ => false, - } - } - - /// Helper function - pub fn is_profile(&self) -> bool { - match self { - &Payload::Profile {..} => true, - _ => false, - } - } - - pub async fn load(self, r: &Model) -> Result<LoadedPayload, Error> { - match self { - Payload::None => Ok(LoadedPayload::None), - Payload::Post { - content_format, - |