//pub mod async_file_ext; use async_std::fs::{File, OpenOptions}; use async_std::io::{ErrorKind as IOErrorKind, BufReader}; use async_std::io::prelude::*; use async_std::task::spawn_blocking; use async_trait::async_trait; use crate::database::{ErrorKind, Result, Storage, StorageError}; use fd_lock::RwLock; use log::debug; use std::path::{Path, PathBuf}; impl From for StorageError { fn from(source: std::io::Error) -> Self { Self::with_source( match source.kind() { IOErrorKind::NotFound => ErrorKind::NotFound, _ => ErrorKind::Backend, }, "file I/O error", Box::new(source), ) } } async fn get_lockable_file(file: File) -> RwLock { debug!("Trying to create a file lock"); spawn_blocking(move || RwLock::new(file)).await } fn url_to_path(root: &Path, url: &str) -> PathBuf { let url = http_types::Url::parse(url).expect("Couldn't parse a URL"); let mut path: PathBuf = root.to_owned(); path.push(url.origin().ascii_serialization() + &url.path().to_string() + ".json"); path } #[derive(Clone)] pub struct FileStorage { root_dir: PathBuf, } impl FileStorage { pub async fn new(root_dir: PathBuf) -> Result { // TODO check if the dir is writable Ok(Self { root_dir }) } } #[async_trait] impl Storage for FileStorage { async fn post_exists(&self, url: &str) -> Result { let path = url_to_path(&self.root_dir, url); debug!("Checking if {:?} exists...", path); Ok(spawn_blocking(move || path.is_file()).await) } async fn get_post(&self, url: &str) -> Result> { let path = url_to_path(&self.root_dir, url); debug!("Opening {:?}", path); // We have to special-case in here because the function should return Ok(None) on 404 match File::open(path).await { Ok(f) => { let lock = get_lockable_file(f).await; let guard = lock.read()?; // HOW DOES THIS TYPECHECK?!!!!!!!! // Read::read(&mut self) requires a mutable reference // yet Read is implemented for &File // We can't get a &mut File from &File, can we? // And we need a &mut File to use Read::read_to_string() // Yet if we pass it to a BufReader it works?!! // // I hate magic // // TODO find a way to get rid of BufReader here let mut content = String::new(); let mut reader = BufReader::new(&*guard); reader.read_to_string(&mut content).await?; drop(reader); drop(guard); Ok(Some(serde_json::from_str(&content)?)) } Err(err) => { if err.kind() == IOErrorKind::NotFound { Ok(None) } else { Err(err.into()) } } } } 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"); let path = url_to_path(&self.root_dir, key); debug!("Creating {:?}", path); let parent = path.parent().unwrap().to_owned(); if !spawn_blocking(move || parent.is_dir()).await { async_std::fs::create_dir_all(path.parent().unwrap()).await?; } let f = OpenOptions::new() .write(true) .create_new(true) .open(&path) .await?; let mut lock = get_lockable_file(f).await; let mut guard = lock.write()?; (*guard).write(post.to_string().as_bytes()).await?; drop(guard); if post["properties"]["url"].is_array() { for url in post["properties"]["url"] .as_array() .unwrap() .iter() .map(|i| i.as_str().unwrap()) { // TODO consider using the symlink crate // to promote cross-platform compat on Windows // do we even need to support Windows?... if url != key && url.starts_with(user) { let link = url_to_path(&self.root_dir, url); debug!("Creating a symlink at {:?}", link); let orig = path.clone(); spawn_blocking(move || { std::os::unix::fs::symlink(orig, link) }).await?; } } } Ok(()) } async fn update_post<'a>(&self, url: &'a str, update: serde_json::Value) -> Result<()> { todo!() } async fn get_channels( &self, user: &crate::indieauth::User, ) -> Result> { todo!() } async fn read_feed_with_limit<'a>( &self, url: &'a str, after: &'a Option, limit: usize, user: &'a Option, ) -> Result> { todo!() } async fn delete_post<'a>(&self, url: &'a str) -> Result<()> { todo!() } async fn get_setting<'a>(&self, setting: &'a str, user: &'a str) -> Result { todo!() } async fn set_setting<'a>(&self, setting: &'a str, user: &'a str, value: &'a str) -> Result<()> { todo!() } }