671 lines
23 KiB
Rust
671 lines
23 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
error::Error,
|
|
fs,
|
|
fs::File,
|
|
io::prelude::*,
|
|
path::Path,
|
|
process::{Command, Output},
|
|
};
|
|
|
|
use async_std::task;
|
|
use dirs;
|
|
use futures::stream::TryStreamExt;
|
|
use log::debug;
|
|
use peach_lib::config_manager;
|
|
use peach_lib::sbot::SbotConfig;
|
|
use peach_lib::sbot::init_sbot;
|
|
use peach_lib::ssb_messages::SsbMessageKVT;
|
|
use rouille::input::post::BufferedFile;
|
|
use temporary::Directory;
|
|
use peach_lib::serde_json::json;
|
|
use peach_lib::tilde_client::TildeClient;
|
|
use crate::{error::PeachWebError, utils::sbot};
|
|
|
|
// SBOT HELPER FUNCTIONS
|
|
|
|
/// Executes a systemctl command for the solar-sbot.service process.
|
|
pub fn systemctl_sbot_cmd(cmd: &str) -> Result<Output, PeachWebError> {
|
|
let output = Command::new("sudo")
|
|
.arg("systemctl")
|
|
.arg(cmd)
|
|
.arg(config_manager::get_config_value("TILDE_SBOT_SERVICE")?)
|
|
.output()?;
|
|
Ok(output)
|
|
}
|
|
|
|
/// Executes a systemctl stop command followed by start command.
|
|
/// Returns a redirect with a flash message stating the output of the restart attempt.
|
|
pub fn restart_sbot_process() -> (String, String) {
|
|
debug!("Restarting solar-sbot.service");
|
|
match systemctl_sbot_cmd("stop") {
|
|
// if stop was successful, try to start the process
|
|
Ok(_) => match systemctl_sbot_cmd("start") {
|
|
Ok(_) => (
|
|
"success".to_string(),
|
|
"Updated configuration and restarted the sbot process".to_string(),
|
|
),
|
|
Err(err) => (
|
|
"error".to_string(),
|
|
format!(
|
|
"Updated configuration but failed to start the sbot process: {}",
|
|
err
|
|
),
|
|
),
|
|
},
|
|
Err(err) => (
|
|
"error".to_string(),
|
|
format!(
|
|
"Updated configuration but failed to stop the sbot process: {}",
|
|
err
|
|
),
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Initialise an sbot client with the given configuration parameters.
|
|
pub async fn init_sbot_client() -> Result<TildeClient, PeachWebError> {
|
|
debug!("Initialising an sbot client with configuration parameters");
|
|
// initialise sbot connection with ip:port and shscap from config file
|
|
let key_path = format!(
|
|
"{}/secret.toml",
|
|
config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
|
);
|
|
let sbot_client = init_sbot().await?;
|
|
Ok(sbot_client)
|
|
}
|
|
|
|
// SCUTTLEBUTT FUNCTIONS
|
|
|
|
/// Ensure that the given public key is a valid ed25519 key.
|
|
///
|
|
/// Return an error string if the key is invalid.
|
|
pub fn validate_public_key(public_key: &str) -> Result<(), String> {
|
|
// ensure the id starts with the correct sigil link
|
|
if !public_key.starts_with('@') {
|
|
return Err("Invalid key: expected '@' sigil as first character".to_string());
|
|
}
|
|
|
|
// find the dot index denoting the start of the algorithm definition tag
|
|
let dot_index = match public_key.rfind('.') {
|
|
Some(index) => index,
|
|
None => return Err("Invalid key: no dot index was found".to_string()),
|
|
};
|
|
|
|
// check hashing algorithm (must end with ".ed25519")
|
|
if !&public_key.ends_with(".ed25519") {
|
|
return Err("Invalid key: hashing algorithm must be ed25519".to_string());
|
|
}
|
|
|
|
// obtain the base64 portion (substring) of the public key
|
|
let base64_str = &public_key[1..dot_index];
|
|
|
|
// length of a base64 encoded ed25519 public key
|
|
if base64_str.len() != 44 {
|
|
return Err("Invalid key: base64 data length is incorrect".to_string());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Calculate the latest sequence number for the local profile.
|
|
///
|
|
/// Retrieves a list of all messages authored by the local public key,
|
|
/// reverses the list and reads the sequence number of the most recently
|
|
/// authored message. This gives us the size of the database in terms of
|
|
/// the total number of locally-authored messages.
|
|
pub fn latest_sequence_number() -> Result<u64, PeachWebError> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client().await?;
|
|
|
|
Err(PeachWebError::NotYetImplemented)
|
|
|
|
// retrieve the local id
|
|
// let id = sbot_client.whoami().await?;
|
|
|
|
// let history_stream = sbot_client.feed(&id).await?;
|
|
|
|
// let mut msgs: Vec<SsbMessageKVT> = history_stream.try_collect().await?;
|
|
//
|
|
// // there will be zero messages when the sbot is run for the first time
|
|
// if msgs.is_empty() {
|
|
// Ok(0)
|
|
// } else {
|
|
// // 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].value.sequence)
|
|
// }
|
|
})
|
|
}
|
|
|
|
pub fn create_invite(uses: u16) -> Result<String, PeachWebError> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client().await?;
|
|
|
|
debug!("Generating Scuttlebutt invite code");
|
|
let mut invite_code = sbot_client.create_invite(uses as i32).await?;
|
|
|
|
// // TODO: insert domain into invite if one is configured
|
|
// let domain = config_manager::get_config_value("EXTERNAL_DOMAIN")?;
|
|
// if !domain.is_empty() {
|
|
// invite_code = domain + &invite_code[4..];
|
|
// }
|
|
//
|
|
Ok(invite_code)
|
|
})
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Profile {
|
|
// is this the local profile or the profile of a peer?
|
|
pub is_local_profile: bool,
|
|
// an ssb_id which may or may not be the local public key
|
|
pub id: Option<String>,
|
|
pub name: Option<String>,
|
|
pub description: Option<String>,
|
|
pub image: Option<String>,
|
|
// the path to the blob defined in the `image` field (aka the profile picture)
|
|
pub blob_path: Option<String>,
|
|
// whether or not the blob exists in the blobstore (ie. is saved on disk)
|
|
pub blob_exists: bool,
|
|
// relationship state (if the profile being viewed is not for the local public key)
|
|
pub following: Option<bool>,
|
|
pub blocking: Option<bool>,
|
|
}
|
|
|
|
impl Profile {
|
|
pub fn default() -> Self {
|
|
Profile {
|
|
is_local_profile: true,
|
|
id: None,
|
|
name: None,
|
|
description: None,
|
|
image: None,
|
|
blob_path: None,
|
|
blob_exists: false,
|
|
following: None,
|
|
blocking: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Retrieve the profile info for the given public key.
|
|
pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error>> {
|
|
|
|
task::block_on(async {
|
|
let sbot_client = init_sbot_client().await?;
|
|
|
|
let local_id = sbot_client.whoami().await?;
|
|
|
|
let mut profile = Profile::default();
|
|
|
|
// if an ssb_id has been provided, we assume that the profile info
|
|
// being retrieved is for a peer (ie. not for our local profile)
|
|
let id = if let Some(peer_id) = ssb_id {
|
|
// we are not dealing with the local profile
|
|
profile.is_local_profile = false;
|
|
|
|
// query follow state
|
|
profile.following = Some(sbot_client.is_following(&local_id, &peer_id).await?);
|
|
|
|
// TODO: implement this check in solar_client so that this can be a real value
|
|
profile.blocking = Some(false);
|
|
|
|
peer_id
|
|
} else {
|
|
// if an ssb_id has not been provided, retrieve the local id using whoami
|
|
profile.is_local_profile = true;
|
|
|
|
local_id
|
|
};
|
|
|
|
// retrieve the profile info for the given id
|
|
let info = get_peer_info(&id).await?;
|
|
// set each profile field accordingly
|
|
for (key, val) in info {
|
|
match key.as_str() {
|
|
"name" => profile.name = Some(val),
|
|
"description" => profile.description = Some(val),
|
|
"image" => profile.image = Some(val),
|
|
_ => (),
|
|
}
|
|
}
|
|
//
|
|
// assign the ssb public key
|
|
// (could be for the local profile or a peer)
|
|
profile.id = Some(id);
|
|
|
|
// TODO: blobs support
|
|
// // determine the path to the blob defined by the value of `profile.image`
|
|
// if let Some(ref blob_id) = profile.image {
|
|
// profile.blob_path = match blobs::get_blob_path(blob_id) {
|
|
// Ok(path) => {
|
|
// // if we get the path, check if the blob is in the blobstore.
|
|
// // this allows us to default to a placeholder image in the template
|
|
// if let Ok(exists) = blob_is_stored_locally(&path).await {
|
|
// profile.blob_exists = exists
|
|
// };
|
|
//
|
|
// Some(path)
|
|
// }
|
|
// Err(_) => None,
|
|
// }
|
|
// }
|
|
|
|
Ok(profile)
|
|
})
|
|
}
|
|
|
|
/// Update the profile info for the local public key.
|
|
///
|
|
/// Profile info includes name, description and image.
|
|
pub fn update_profile_info(
|
|
current_name: String,
|
|
current_description: String,
|
|
new_name: Option<String>,
|
|
new_description: Option<String>,
|
|
image: Option<BufferedFile>,
|
|
) -> Result<String, PeachWebError> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client()
|
|
.await?;
|
|
|
|
Err(PeachWebError::NotYetImplemented)
|
|
// // track whether the name, description or image have been updated
|
|
// let mut name_updated: bool = false;
|
|
// let mut description_updated: bool = false;
|
|
// let mut image_updated: bool = false;
|
|
//
|
|
// // check if a new_name value has been submitted in the form
|
|
// if let Some(name) = new_name {
|
|
// // only update the name if it has changed
|
|
// if name != current_name {
|
|
// debug!("Publishing a new Scuttlebutt profile name");
|
|
// if let Err(e) = sbot_client.publish_name(&name).await {
|
|
// return Err(format!("Failed to update name: {}", e));
|
|
// } else {
|
|
// name_updated = true
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if let Some(description) = new_description {
|
|
// // only update the description if it has changed
|
|
// if description != current_description {
|
|
// debug!("Publishing a new Scuttlebutt profile description");
|
|
// if let Err(e) = sbot_client.publish_description(&description).await {
|
|
// return Err(format!("Failed to update description: {}", e));
|
|
// } else {
|
|
// description_updated = true
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// // only update the image if a file was uploaded
|
|
// if let Some(img) = image {
|
|
// // only write the blob if it has a filename and data > 0 bytes
|
|
// if img.filename.is_some() && !img.data.is_empty() {
|
|
// match write_blob_to_store(img).await {
|
|
// Ok(blob_id) => {
|
|
// // if the file was successfully added to the blobstore,
|
|
// // publish an about image message with the blob id
|
|
// if let Err(e) = sbot_client.publish_image(&blob_id).await {
|
|
// return Err(format!("Failed to update image: {}", e));
|
|
// } else {
|
|
// image_updated = true
|
|
// }
|
|
// }
|
|
// Err(e) => return Err(format!("Failed to add image to blobstore: {}", e)),
|
|
// }
|
|
// } else {
|
|
// image_updated = false
|
|
// }
|
|
// }
|
|
//
|
|
// if name_updated || description_updated || image_updated {
|
|
// Ok("Profile updated".to_string())
|
|
// } else {
|
|
// // no updates were made but no errors were encountered either
|
|
// Ok("Profile info unchanged".to_string())
|
|
// }
|
|
})
|
|
}
|
|
|
|
/// Follow a peer.
|
|
pub fn follow_peer(public_key: &str) -> Result<String, PeachWebError> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client()
|
|
.await?;
|
|
|
|
debug!("Following a Scuttlebutt peer");
|
|
Err(PeachWebError::NotYetImplemented)
|
|
})
|
|
}
|
|
|
|
/// Unfollow a peer.
|
|
pub fn unfollow_peer(public_key: &str) -> Result<String, PeachWebError> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client()
|
|
.await?;
|
|
|
|
Err(PeachWebError::NotYetImplemented)
|
|
// debug!("Unfollowing a Scuttlebutt peer");
|
|
// match sbot_client.unfollow(public_key).await {
|
|
// Ok(_) => Ok("Unfollowed peer".to_string()),
|
|
// Err(e) => Err(format!("Failed to unfollow peer: {}", e)),
|
|
// }
|
|
})
|
|
}
|
|
|
|
/// Block a peer.
|
|
pub fn block_peer(public_key: &str) -> Result<String, String> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client()
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
debug!("Blocking a Scuttlebutt peer");
|
|
match sbot_client.create_block(public_key).await {
|
|
Ok(_) => Ok("Blocked peer".to_string()),
|
|
Err(e) => Err(format!("Failed to block peer: {}", e)),
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Unblock a peer.
|
|
pub fn unblock_peer(public_key: &str) -> Result<String, PeachWebError> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client()
|
|
.await?;
|
|
|
|
debug!("Unblocking a Scuttlebutt peer");
|
|
Err(PeachWebError::NotYetImplemented)
|
|
// match sbot_client.unblock(public_key).await {
|
|
// Ok(_) => Ok("Unblocked peer".to_string()),
|
|
// Err(e) => Err(format!("Failed to unblock peer: {}", e)),
|
|
// }
|
|
})
|
|
}
|
|
|
|
/// Retrieve a list of peers blocked by the local public key.
|
|
pub fn get_blocks_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
|
// populate this vec to return
|
|
let mut to_return: Vec<HashMap<String, String>> = Vec::new();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client().await?;
|
|
|
|
let self_id = sbot_client.whoami().await?;
|
|
|
|
let blocks = sbot_client.get_blocks(&self_id).await?;
|
|
|
|
if !blocks.is_empty() {
|
|
for peer in blocks.iter() {
|
|
// trim whitespace (including newline characters) and
|
|
// remove the inverted-commas around the id
|
|
// TODO: is this necessary?
|
|
let key = peer.trim().replace('"', "");
|
|
let peer_info = get_peer_info(&key).await?;
|
|
|
|
// push profile info to peer_list vec
|
|
to_return.push(peer_info)
|
|
}
|
|
}
|
|
|
|
// return the list of peers
|
|
Ok(to_return)
|
|
})
|
|
}
|
|
|
|
|
|
pub async fn get_peer_info(key: &str) -> Result<HashMap<String, String>, Box<dyn Error>> {
|
|
let mut sbot_client = init_sbot_client().await?;
|
|
// key,value dict of info about this peer
|
|
let tilde_profile_info = sbot_client.get_profile_info(key).await.map_err(|err| {
|
|
println!("error getting profile info: {}", err);
|
|
err
|
|
}
|
|
)?;
|
|
let mut peer_info = HashMap::new();
|
|
tilde_profile_info.get("name").and_then(|val| val.as_str()).map(|val| {
|
|
peer_info.insert("name".to_string(), val.to_string());
|
|
});
|
|
tilde_profile_info.get("description").and_then(|val| val.as_str()).map(|val| {
|
|
peer_info.insert("description".to_string(), val.to_string());
|
|
});
|
|
|
|
// 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
|
|
// TODO: blob support
|
|
// 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 => {
|
|
// peer_info.insert("blob_exists".to_string(), "true".to_string())
|
|
// }
|
|
// _ => peer_info.insert("blob_exists".to_string(), "false".to_string()),
|
|
// };
|
|
// }
|
|
// }
|
|
Ok(peer_info)
|
|
}
|
|
|
|
/// Retrieve a list of peers followed by the local public key.
|
|
pub fn get_follows_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
|
|
|
// populate this vec to return
|
|
let mut to_return: Vec<HashMap<String, String>> = Vec::new();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client().await?;
|
|
|
|
let self_id = sbot_client.whoami().await?;
|
|
|
|
let follows = sbot_client.get_follows(&self_id).await?;
|
|
|
|
if !follows.is_empty() {
|
|
for peer in follows.iter() {
|
|
// trim whitespace (including newline characters) and
|
|
// remove the inverted-commas around the id
|
|
// TODO: is this necessary?
|
|
let key = peer.trim().replace('"', "");
|
|
let peer_info = get_peer_info(&key).await?;
|
|
|
|
// push profile info to peer_list vec
|
|
to_return.push(peer_info)
|
|
}
|
|
}
|
|
|
|
// return the list of peers
|
|
Ok(to_return)
|
|
})
|
|
}
|
|
|
|
/// Retrieve a list of peers friended by the local public key.
|
|
pub fn get_friends_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
|
|
|
// populate this vec to return
|
|
let mut to_return: Vec<HashMap<String, String>> = Vec::new();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client().await?;
|
|
|
|
let self_id = sbot_client.whoami().await?;
|
|
|
|
let friends = sbot_client.get_friends(&self_id).await?;
|
|
|
|
if !friends.is_empty() {
|
|
for peer in friends.iter() {
|
|
// trim whitespace (including newline characters) and
|
|
// remove the inverted-commas around the id
|
|
// TODO: is this necessary?
|
|
let key = peer.trim().replace('"', "");
|
|
let peer_info = get_peer_info(&key).await?;
|
|
|
|
// push profile info to peer_list vec
|
|
to_return.push(peer_info)
|
|
}
|
|
}
|
|
|
|
// return the list of peers
|
|
Ok(to_return)
|
|
})
|
|
}
|
|
|
|
/// Retrieve the local public key (id).
|
|
pub fn get_local_id() -> Result<String, Box<dyn Error>> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client().await?;
|
|
|
|
let local_id = sbot_client.whoami().await?;
|
|
|
|
Ok(local_id)
|
|
})
|
|
}
|
|
|
|
/// Publish a public post.
|
|
pub fn publish_public_post(text: String) -> Result<String, String> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client()
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
debug!("Publishing a new Scuttlebutt public post");
|
|
let post = json!({
|
|
"type": "post",
|
|
"text": &text,
|
|
});
|
|
match sbot_client.publish(post).await {
|
|
Ok(_) => Ok("Published post".to_string()),
|
|
Err(e) => Err(format!("Failed to publish post: {}", e)),
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Publish a private message.
|
|
pub fn publish_private_msg(text: String, recipients: Vec<String>) -> Result<String, PeachWebError> {
|
|
// retrieve latest solar-sbot configuration parameters
|
|
let sbot_config = SbotConfig::read().ok();
|
|
|
|
task::block_on(async {
|
|
let mut sbot_client = init_sbot_client()
|
|
.await?;
|
|
|
|
Err(PeachWebError::NotYetImplemented)
|
|
// debug!("Publishing a new Scuttlebutt private message");
|
|
// match sbot_client
|
|
// .publish_private(text.to_string(), recipients)
|
|
// .await
|
|
// {
|
|
// Ok(_) => Ok("Published private message".to_string()),
|
|
// Err(e) => Err(format!("Failed to publish private message: {}", e)),
|
|
// }
|
|
})
|
|
}
|
|
|
|
// FILEPATH FUNCTIONS
|
|
|
|
/// Return the path of the ssb-go directory.
|
|
pub fn get_go_ssb_path() -> Result<String, PeachWebError> {
|
|
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(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<bool, PeachWebError> {
|
|
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(image: BufferedFile) -> Result<String, PeachWebError> {
|
|
// we performed a `image.filename.is_some()` check before calling `write_blob_to_store`
|
|
// so it should be safe to do a simple unwrap here
|
|
let filename = image
|
|
.filename
|
|
.expect("retrieving filename from uploaded file");
|
|
|
|
// create temporary directory and path
|
|
let temp_dir = Directory::new("blob")?;
|
|
let temp_path = temp_dir.join(filename);
|
|
|
|
// write file to temporary path
|
|
fs::write(&temp_path, &image.data)?;
|
|
|
|
// 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)?;
|
|
|
|
Err(PeachWebError::NotYetImplemented)
|
|
// TODO: not yet implemented
|
|
|
|
// // 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)
|
|
}
|