diff options
Diffstat (limited to 'src/database/file')
-rw-r--r-- | src/database/file/mod.rs | 151 |
1 files changed, 102 insertions, 49 deletions
diff --git a/src/database/file/mod.rs b/src/database/file/mod.rs index d67c920..4fb7f47 100644 --- a/src/database/file/mod.rs +++ b/src/database/file/mod.rs @@ -1,17 +1,17 @@ +use crate::database::{filter_post, ErrorKind, Result, Storage, StorageError}; use async_std::fs::{File, OpenOptions}; -use async_std::io::{ErrorKind as IOErrorKind}; use async_std::io::prelude::*; +use async_std::io::ErrorKind as IOErrorKind; use async_std::task::spawn_blocking; use async_trait::async_trait; +use fd_lock::RwLock; use futures::stream; use futures_util::StreamExt; use futures_util::TryStreamExt; -use serde_json::json; -use crate::database::{ErrorKind, Result, Storage, StorageError, filter_post}; -use fd_lock::RwLock; use log::debug; -use std::path::{Path, PathBuf}; +use serde_json::json; use std::collections::HashMap; +use std::path::{Path, PathBuf}; impl From<std::io::Error> for StorageError { fn from(source: std::io::Error) -> Self { @@ -106,13 +106,24 @@ fn modify_post(post: &serde_json::Value, update: &serde_json::Value) -> Result<s let mut post = post.clone(); if let Some(delete) = update["delete"].as_array() { - remove_keys.extend(delete.iter().filter_map(|v| v.as_str()).map(|v| v.to_string())); + remove_keys.extend( + delete + .iter() + .filter_map(|v| v.as_str()) + .map(|v| v.to_string()), + ); } else if let Some(delete) = update["delete"].as_object() { for (k, v) in delete { if let Some(v) = v.as_array() { - remove_values.entry(k.to_string()).or_default().extend(v.clone()); + remove_values + .entry(k.to_string()) + .or_default() + .extend(v.clone()); } else { - return Err(StorageError::new(ErrorKind::BadRequest, "Malformed update object")); + return Err(StorageError::new( + ErrorKind::BadRequest, + "Malformed update object", + )); } } } @@ -121,7 +132,10 @@ fn modify_post(post: &serde_json::Value, update: &serde_json::Value) -> Result<s if v.is_array() { add_keys.insert(k.to_string(), v.clone()); } else { - return Err(StorageError::new(ErrorKind::BadRequest, "Malformed update object")); + return Err(StorageError::new( + ErrorKind::BadRequest, + "Malformed update object", + )); } } } @@ -161,7 +175,12 @@ fn modify_post(post: &serde_json::Value, update: &serde_json::Value) -> Result<s let k = &k; if let Some(prop) = props[k].as_array_mut() { if k == "children" { - v.as_array().unwrap().iter().cloned().rev().for_each(|v| prop.insert(0, v)); + v.as_array() + .unwrap() + .iter() + .cloned() + .rev() + .for_each(|v| prop.insert(0, v)); } else { prop.extend(v.as_array().unwrap().iter().cloned()); } @@ -169,10 +188,12 @@ fn modify_post(post: &serde_json::Value, update: &serde_json::Value) -> Result<s post["properties"][k] = v } } - Ok(post) + Ok(post) } #[derive(Clone)] +/// A backend using a folder with JSON files as a backing store. +/// Uses symbolic links to represent a many-to-one mapping of URLs to a post. pub struct FileStorage { root_dir: PathBuf, } @@ -185,10 +206,19 @@ impl FileStorage { } } -async fn hydrate_author<S: Storage>(feed: &mut serde_json::Value, user: &'_ Option<String>, storage: &S) { +async fn hydrate_author<S: Storage>( + feed: &mut serde_json::Value, + user: &'_ Option<String>, + storage: &S, +) { let url = feed["properties"]["uid"][0].as_str().unwrap(); if let Some(author) = feed["properties"]["author"].clone().as_array() { - if !feed["type"].as_array().unwrap().iter().any(|i| i == "h-card") { + if !feed["type"] + .as_array() + .unwrap() + .iter() + .any(|i| i == "h-card") + { let author_list: Vec<serde_json::Value> = stream::iter(author.iter()) .then(|i| async move { if let Some(i) = i.as_str() { @@ -196,18 +226,21 @@ async fn hydrate_author<S: Storage>(feed: &mut serde_json::Value, user: &'_ Opti Ok(post) => match post { Some(post) => match filter_post(post, user) { Some(author) => author, - None => json!(i) + None => json!(i), }, - None => json!(i) + None => json!(i), }, Err(e) => { log::error!("Error while hydrating post {}: {}", url, e); json!(i) } } - } else { i.clone() } + } else { + i.clone() + } }) - .collect::<Vec<_>>().await; + .collect::<Vec<_>>() + .await; feed["properties"].as_object_mut().unwrap()["author"] = json!(author_list); } } @@ -251,8 +284,8 @@ impl Storage for FileStorage { async fn put_post<'a>(&self, post: &'a serde_json::Value, user: &'a str) -> Result<()> { let key = post["properties"]["uid"][0] - .as_str() - .expect("Tried to save a post without UID"); + .as_str() + .expect("Tried to save a post without UID"); let path = url_to_path(&self.root_dir, key); debug!("Creating {:?}", path); @@ -288,26 +321,39 @@ impl Storage for FileStorage { let orig = path.clone(); spawn_blocking::<_, Result<()>>(move || { // We're supposed to have a parent here. - let basedir = link.parent().ok_or(StorageError::new(ErrorKind::Backend, "Failed to calculate parent directory when creating a symlink"))?; + let basedir = link.parent().ok_or(StorageError::new( + ErrorKind::Backend, + "Failed to calculate parent directory when creating a symlink", + ))?; let relative = path_relative_from(&orig, &basedir).unwrap(); println!("{:?} - {:?} = {:?}", &orig, &basedir, &relative); println!("Created a symlink at {:?}", &link); let symlink_result; #[cfg(unix)] - { symlink_result = std::os::unix::fs::symlink(relative, link); } + { + symlink_result = std::os::unix::fs::symlink(relative, link); + } // Wow it even supports windows. Not sure if I need it to run on Windows but oh well #[cfg(windows)] - { symlink_result = std::os::windows::fs::symlink_file(relative, link); } + { + symlink_result = std::os::windows::fs::symlink_file(relative, link); + } match symlink_result { Ok(()) => Ok(()), - Err(e) => Err(e.into()) + Err(e) => Err(e.into()), } - }).await?; + }) + .await?; } } } - if post["type"].as_array().unwrap().iter().any(|s| s.as_str() == Some("h-feed")) { + if post["type"] + .as_array() + .unwrap() + .iter() + .any(|s| s.as_str() == Some("h-feed")) + { println!("Adding to channel list..."); // Add the h-feed to the channel list let mut path = relative_path::RelativePathBuf::new(); @@ -320,7 +366,8 @@ impl Storage for FileStorage { .write(true) .truncate(false) .create(true) - .open(&path).await?; + .open(&path) + .await?; let mut lock = get_lockable_file(file).await; let mut guard = lock.write()?; @@ -338,11 +385,13 @@ impl Storage for FileStorage { name: post["properties"]["name"][0] .as_str() .map(|s| s.to_string()) - .unwrap_or_else(|| String::default()) + .unwrap_or_else(|| String::default()), }); guard.seek(std::io::SeekFrom::Start(0)).await?; guard.set_len(0).await?; - guard.write_all(serde_json::to_string(&channels)?.as_bytes()).await?; + guard + .write_all(serde_json::to_string(&channels)?.as_bytes()) + .await?; } Ok(()) @@ -375,10 +424,7 @@ impl Storage for FileStorage { Ok(()) } - async fn get_channels<'a>( - &self, - user: &'a str, - ) -> Result<Vec<super::MicropubChannel>> { + async fn get_channels<'a>(&self, user: &'a str) -> Result<Vec<super::MicropubChannel>> { let mut path = relative_path::RelativePathBuf::new(); path.push(user.to_string()); path.push("channels"); @@ -393,11 +439,11 @@ impl Storage for FileStorage { (&mut &*guard).read_to_string(&mut content).await?; // This should not happen, but if it does, let's handle it gracefully instead of failing. if content.len() == 0 { - return Ok(vec![]) + return Ok(vec![]); } let channels: Vec<super::MicropubChannel> = serde_json::from_str(&content)?; Ok(channels) - }, + } Err(e) => { if e.kind() == IOErrorKind::NotFound { Ok(vec![]) @@ -419,7 +465,8 @@ impl Storage for FileStorage { if let Some(mut feed) = filter_post(feed, user) { if feed["children"].is_array() { let children = feed["children"].as_array().unwrap().clone(); - let mut posts_iter = children.into_iter() + let mut posts_iter = children + .into_iter() .map(|s: serde_json::Value| s.as_str().unwrap().to_string()); if after.is_some() { loop { @@ -430,24 +477,25 @@ impl Storage for FileStorage { } } let posts = stream::iter(posts_iter) - .map(|url: String| async move { - self.get_post(&url).await - }) + .map(|url: String| async move { self.get_post(&url).await }) .buffered(std::cmp::min(3, limit)) - // Hack to unwrap the Option and sieve out broken links - // Broken links return None, and Stream::filter_map skips Nones. + // Hack to unwrap the Option and sieve out broken links + // Broken links return None, and Stream::filter_map skips Nones. .try_filter_map(|post: Option<serde_json::Value>| async move { Ok(post) }) - .and_then(|mut post| async move { hydrate_author(&mut post, user, self).await; Ok(post) }) + .and_then(|mut post| async move { + hydrate_author(&mut post, user, self).await; + Ok(post) + }) .try_filter_map(|post| async move { Ok(filter_post(post, user)) }) - .take(limit); match posts.try_collect::<Vec<serde_json::Value>>().await { Ok(posts) => feed["children"] = serde_json::json!(posts), Err(err) => { return Err(StorageError::with_source( - ErrorKind::Other, "Feed assembly error", - Box::new(err) + ErrorKind::Other, + "Feed assembly error", + Box::new(err), )); } } @@ -469,7 +517,9 @@ impl Storage for FileStorage { let path = url_to_path(&self.root_dir, url); if let Err(e) = async_std::fs::remove_file(path).await { Err(e.into()) - } else { Ok(()) } + } else { + Ok(()) + } } async fn get_setting<'a>(&self, setting: &'a str, user: &'a str) -> Result<String> { @@ -489,10 +539,10 @@ impl Storage for FileStorage { let settings: HashMap<String, String> = serde_json::from_str(&content)?; // XXX consider returning string slices instead of cloning a string every time // it might come with a performance hit and/or memory usage inflation - settings.get(setting) + settings + .get(setting) .map(|s| s.clone()) .ok_or_else(|| StorageError::new(ErrorKind::Backend, "Setting not set")) - } async fn set_setting<'a>(&self, setting: &'a str, user: &'a str, value: &'a str) -> Result<()> { @@ -513,7 +563,8 @@ impl Storage for FileStorage { .read(true) .truncate(false) .create(true) - .open(&path).await?; + .open(&path) + .await?; let mut lock = get_lockable_file(file).await; log::debug!("Created a lock. Locking for writing..."); let mut guard = lock.write()?; @@ -530,7 +581,9 @@ impl Storage for FileStorage { settings.insert(setting.to_string(), value.to_string()); guard.seek(std::io::SeekFrom::Start(0)).await?; guard.set_len(0).await?; - guard.write_all(serde_json::to_string(&settings)?.as_bytes()).await?; + guard + .write_all(serde_json::to_string(&settings)?.as_bytes()) + .await?; Ok(()) } } |