diff --git a/peach-web/src/utils/flash.rs b/peach-web/src/utils/flash.rs new file mode 100644 index 0000000..96be9a5 --- /dev/null +++ b/peach-web/src/utils/flash.rs @@ -0,0 +1,44 @@ +use rouille::{input, Request, Response}; + +/// Flash message trait for `Request`. +pub trait FlashRequest { + /// Retrieve the flash message cookie values from a `Request`. + fn retrieve_flash(&self) -> (Option<&str>, Option<&str>); +} + +impl FlashRequest for Request { + fn retrieve_flash(&self) -> (Option<&str>, Option<&str>) { + // check for flash cookies + let flash_name = input::cookies(&self) + .find(|&(n, _)| n == "flash_name") + // return the value of the cookie (key is already known) + .map(|key_val| key_val.1); + let flash_msg = input::cookies(&self) + .find(|&(n, _)| n == "flash_msg") + .map(|key_val| key_val.1); + + (flash_name, flash_msg) + } +} + +/// Flash message trait for `Response`. +pub trait FlashResponse { + /// Add flash message cookies to a `Response`. + fn add_flash(self, flash_name: String, flash_msg: String) -> Response; + /// Reset flash message cookie values for a `Response`. + fn reset_flash(self) -> Response; +} + +impl FlashResponse for Response { + fn add_flash(self, flash_name: String, flash_msg: String) -> Response { + // set the flash cookie headers + self.with_additional_header("Set-Cookie", flash_name) + .with_additional_header("Set-Cookie", flash_msg) + } + + fn reset_flash(self) -> Response { + // set blank cookies to clear the flash msg from the previous request + self.with_additional_header("Set-Cookie", "flash_name=") + .with_additional_header("Set-Cookie", "flash_msg=") + } +} diff --git a/peach-web/src/utils/mod.rs b/peach-web/src/utils/mod.rs new file mode 100644 index 0000000..e02227e --- /dev/null +++ b/peach-web/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod flash; +pub mod sbot; +pub mod theme; diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs new file mode 100644 index 0000000..d916476 --- /dev/null +++ b/peach-web/src/utils/sbot.rs @@ -0,0 +1,206 @@ +use std::io::prelude::*; +use std::{collections::HashMap, error::Error}; +use std::{fs, fs::File, path::Path}; + +use async_std::task; +use dirs; +use futures::stream::TryStreamExt; +use golgi::{blobs, messages::SsbMessageValue, Sbot}; +use log::info; +use peach_lib::sbot::SbotConfig; +use temporary::Directory; + +use crate::error::PeachWebError; + +// SBOT HELPER FUNCTIONS + +pub async fn init_sbot_with_config( + sbot_config: &Option, +) -> Result { + // initialise sbot connection with ip:port and shscap from config file + let sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None).await? + } + None => Sbot::init(None, None).await?, + }; + + Ok(sbot_client) +} + +// FILEPATH FUNCTIONS +// return the path of the ssb-go directory +pub fn get_go_ssb_path() -> Result { + let go_ssb_path = match SbotConfig::read() { + Ok(conf) => conf.repo, + // return the default path if unable to read `config.toml` + Err(_) => { + // determine the home directory + let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + // add the go-ssb subdirectory + home_path.push(".ssb-go"); + // convert the PathBuf to a String + home_path + .into_os_string() + .into_string() + .map_err(|_| PeachWebError::OsString)? + } + }; + Ok(go_ssb_path) +} + +// check whether a blob is in the blobstore +pub async fn blob_is_stored_locally(blob_path: &str) -> Result { + let go_ssb_path = get_go_ssb_path()?; + let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); + let blob_exists_locally = Path::new(&complete_path).exists(); + Ok(blob_exists_locally) +} + +/* +// take the path to a file, add it to the blobstore and return the blob id +pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { + // create temporary directory and path + let temp_dir = Directory::new("blob")?; + // we performed a `file.name().is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = file.name().expect("retrieving filename from uploaded file"); + let temp_path = temp_dir.join(filename); + // write file to temporary path + file.persist_to(&temp_path).await?; + // open the file and read it into a buffer + let mut file = File::open(&temp_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + // hash the bytes representing the file + let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + // define the blobstore path and blob filename + let (blob_dir, blob_filename) = hex_hash.split_at(2); + let go_ssb_path = get_go_ssb_path()?; + let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + // create the blobstore sub-directory + fs::create_dir_all(&blobstore_sub_dir)?; + // copy the file to the blobstore + let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); + fs::copy(temp_path, blob_path)?; + Ok(blob_id) +} +*/ + +pub fn latest_sequence_number() -> Result> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // retrieve the local id + let id = sbot_client.whoami().await?; + + let history_stream = sbot_client.create_history_stream(id).await?; + let mut msgs: Vec = history_stream.try_collect().await?; + + // reverse the list of messages so we can easily reference the latest one + msgs.reverse(); + + // return the sequence number of the latest msg + Ok(msgs[0].sequence) + }) +} + +pub fn get_blocks_list() -> Result>, Box> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let blocks = sbot_client.get_blocks().await?; + + // we'll use this to store the profile info for each peer whom we block + let mut peer_list = Vec::new(); + + if !blocks.is_empty() { + for peer in blocks.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut peer_info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + peer_info.insert("id".to_string(), key.to_string()); + // we do not even attempt to find the blob for a blocked peer, + // since it may be vulgar to cause distress to the local peer. + peer_info.insert("blob_exists".to_string(), "false".to_string()); + // push profile info to peer_list vec + peer_list.push(peer_info) + } + } + + // return the list of blocked peers + Ok(peer_list) + }) +} + +pub fn get_follows_list() -> Result>, Box> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_list = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut peer_info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + peer_info.insert("id".to_string(), key.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = peer_info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + peer_info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + peer_info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => peer_info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + // push profile info to peer_list vec + peer_list.push(peer_info) + } + } + + // return the list of peers + Ok(peer_list) + }) +} + +pub fn get_friends_list() -> Result, Box> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // return the sequence number of the latest msg + //Ok(msgs[0].sequence) + + Ok(vec!["temp".to_string()]) + }) +} diff --git a/peach-web/src/utils/theme.rs b/peach-web/src/utils/theme.rs new file mode 100644 index 0000000..9f99492 --- /dev/null +++ b/peach-web/src/utils/theme.rs @@ -0,0 +1,134 @@ +use log::info; + +use crate::THEME; + +// THEME FUNCTIONS + +#[derive(Debug, Copy, Clone)] +pub enum Theme { + Light, + Dark, +} + +pub fn get_theme() -> String { + let current_theme = THEME.read().unwrap(); + match *current_theme { + Theme::Dark => "dark".to_string(), + _ => "light".to_string(), + } +} + +pub fn set_theme(theme: Theme) { + info!("set ui theme to: {:?}", theme); + let mut writable_theme = THEME.write().unwrap(); + *writable_theme = theme; +} + +// get_cookie + +// set_cookie + +/* +pub mod monitor; + +use std::io::prelude::*; +use std::{fs, fs::File, path::Path}; + +use dirs; +use golgi::blobs; +use log::info; +use peach_lib::sbot::SbotConfig; +use rocket::{ + fs::TempFile, + response::{Redirect, Responder}, + serde::Serialize, +}; +use rocket_dyn_templates::Template; +use temporary::Directory; + +use crate::{error::PeachWebError, THEME}; + +// FILEPATH FUNCTIONS + +// return the path of the ssb-go directory +pub fn get_go_ssb_path() -> Result { + let go_ssb_path = match SbotConfig::read() { + Ok(conf) => conf.repo, + // return the default path if unable to read `config.toml` + Err(_) => { + // determine the home directory + let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + // add the go-ssb subdirectory + home_path.push(".ssb-go"); + // convert the PathBuf to a String + home_path + .into_os_string() + .into_string() + .map_err(|_| PeachWebError::OsString)? + } + }; + + Ok(go_ssb_path) +} + +// check whether a blob is in the blobstore +pub async fn blob_is_stored_locally(blob_path: &str) -> Result { + let go_ssb_path = get_go_ssb_path()?; + let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); + let blob_exists_locally = Path::new(&complete_path).exists(); + + Ok(blob_exists_locally) +} + +// take the path to a file, add it to the blobstore and return the blob id +pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { + // create temporary directory and path + let temp_dir = Directory::new("blob")?; + // we performed a `file.name().is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = file.name().expect("retrieving filename from uploaded file"); + let temp_path = temp_dir.join(filename); + + // write file to temporary path + file.persist_to(&temp_path).await?; + + // open the file and read it into a buffer + let mut file = File::open(&temp_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + // hash the bytes representing the file + let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + + // define the blobstore path and blob filename + let (blob_dir, blob_filename) = hex_hash.split_at(2); + let go_ssb_path = get_go_ssb_path()?; + let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + + // create the blobstore sub-directory + fs::create_dir_all(&blobstore_sub_dir)?; + + // copy the file to the blobstore + let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); + fs::copy(temp_path, blob_path)?; + + Ok(blob_id) +} + +// HELPER FUNCTIONS + +#[derive(Debug, Serialize)] +pub struct FlashContext { + pub flash_name: Option, + pub flash_msg: Option, +} + +/// A helper enum which allows routes to either return a Template or a Redirect +/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066 +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Responder)] +pub enum TemplateOrRedirect { + Template(Template), + Redirect(Redirect), +} +*/