diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..c928cda --- /dev/null +++ b/src/data.rs @@ -0,0 +1,218 @@ +use std::{ + collections::HashMap, + fs::{read_to_string, DirBuilder, File}, + io::{self, BufRead}, + path::PathBuf, +}; + +#[cfg(target_family = "unix")] +use std::os::unix::fs::DirBuilderExt; + +use dashmap::DashMap; + +const TEXT_DIR: String = "text".to_string(); +const FILE_DIR: String = "files".to_string(); +const REDIRECTS_FILE: String = "redirects".to_string(); +const STATS_FILE: String = "stats".to_string(); +const USERS_FILE: String = "users".to_string(); + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("io error")] + IO(#[from] std::io::Error), + #[error("metadata error")] + Metadata, + #[error("some shit missing: {0}")] + ShitMissing(String), + #[error("filesystem error")] + FS, +} + +fn meta_str_to_map(from: &str) -> Result, Error> { + let map = from + .lines() + .map(|line| line.trim()) + .filter(|line| line.len() > 0) + .map(|line| { + line.split_once(": ") + .ok_or(Error::Metadata) + .and_then(|(k, v)| Ok((k, v.to_string()))) + }) + .collect::, Error>>()?; + + Ok(map) +} + +struct TextPaste { + pub text: String, + pub meta: TextMeta, +} + +struct TextMeta { + language: Option, +} + +impl TextMeta { + pub fn parse(from: &str) -> Result { + let map = meta_str_to_map(from)?; + Ok(Self { + language: map.remove("language"), + }) + } +} + +struct FileMeta { + file_name: String, +} + +impl FileMeta { + pub fn parse(from: &str) -> Result { + let map = meta_str_to_map(from)?; + + let file_name = map.remove("file_name").ok_or(Error::Metadata)?; + Ok(Self { file_name }) + } +} + +struct UserData { + pub username: String, + pub pw_hash: String, + pub is_admin: bool, +} +impl UserData { + pub fn parse(line: &str, is_admin: bool) -> Result { + let (username, pw_hash) = line.split_once(": ").ok_or(Error::Metadata)?; + Ok(Self { + username: username.trim().to_string(), + pw_hash: pw_hash.trim().to_string(), + is_admin, + }) + } +} + +pub struct DataStorage { + base_dir: PathBuf, + redirects: DashMap, + text: DashMap, + files: DashMap, + users: DashMap, +} + +impl DataStorage { + pub fn new(base_dir: PathBuf) -> Self { + let redirects = DashMap::new(); + let text = DashMap::new(); + let files = DashMap::new(); + let users = DashMap::new(); + DataStorage { + base_dir, + redirects, + text, + files, + users, + } + } + + pub fn init(&mut self) -> Result<(), Error> { + self.ensure_folder_structure()?; + self.load_redirects()?; + self.load_text()?; + self.load_files()?; + self.load_users()?; + + Ok(()) + } + + fn ensure_folder_structure(&self) -> Result<(), Error> { + let dir_builder = DirBuilder::new().recursive(true); + if cfg!(target_family = "unix") { + dir_builder = dir_builder.mode(0o775); + } + + dir_builder.create(self.base_dir)?; + dir_builder.create(self.base_dir.join(FILE_DIR))?; + dir_builder.create(self.base_dir.join(TEXT_DIR))?; + + Ok(()) + } + + fn load_redirects(&mut self) -> Result<(), Error> { + let f = File::options() + .create(true) + .read(true) + .open(self.base_dir.join(REDIRECTS_FILE))?; + + // TODO: replace with meta_str_to_map and then self.redirects.extend + + for line in io::BufReader::new(f).lines() { + line?; + let (id, url) = line?.split_once(": ").ok_or(Error::Metadata)?; + self.redirects + .insert(id.trim().to_string(), url.trim().to_string()); + // if there's a better way to do this let me know + } + + Ok(()) + } + + fn load_text(&mut self) -> Result<(), Error> { + for thing in std::fs::read_dir(self.base_dir.join(TEXT_DIR)).expect("fs error") { + let dir_entry = thing?; // i'm so good at naming things + let id = dir_entry.file_name().into_string().or(Err(Error::FS))?; + let (raw_metadata, text) = read_to_string(dir_entry.path())? + .split_once("---") + .expect("invalid text format"); + let meta = TextMeta::parse(raw_metadata)?; + self.text.insert( + id, + TextPaste { + text: text.to_string(), + meta, + }, + ); + } + Ok(()) + } + + fn load_files(&mut self) -> Result<(), Error> { + for thing in std::fs::read_dir(self.base_dir.join(FILE_DIR)).expect("fs error") { + let dir_entry = thing?; // i'm so good at naming things + let filename = dir_entry + .file_name() + .into_string() + .expect("fucked up OS string"); + if filename.ends_with(".meta") { + let id = filename.trim_end_matches(".meta").to_string(); + let raw_metadata = read_to_string(dir_entry.path())?; + let metadata = FileMeta::parse(raw_metadata.as_str())?; + self.files.insert(id, metadata); + } + } + self.assert_all_files_present()?; + Ok(()) + } + + fn assert_all_files_present(&self) -> Result<(), Error> { + let files_folder = self.base_dir.join(FILE_DIR); + for (id, _) in self.files.into_iter() { + files_folder + .join(id) + .try_exists() + .or(Err(Error::ShitMissing(id)))?; + } + Ok(()) + } + + fn load_users(&mut self) -> Result<(), Error> { + let is_admin = true; + for line in read_to_string(self.base_dir.join(USERS_FILE))?.lines() { + if line.starts_with("---") { + is_admin = false; + break; + } + let user = UserData::parse(line, is_admin)?; + self.users.insert(user.username, user); + } + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 560c139..7877b8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,15 @@ -use std::{ - collections::HashMap, - fs::{read_to_string, DirBuilder, File}, - io::{self, BufRead}, - net::SocketAddr, - path::PathBuf, -}; +mod data; +mod down; +mod up; -#[cfg(target_family = "unix")] -use std::os::unix::fs::DirBuilderExt; +use std::net::SocketAddr; use axum::{routing::get, Router}; -use dashmap::DashMap; -use serde::{Deserialize, Serialize}; +use data::DataStorage; use lazy_static::lazy_static; use tera::Tera; -mod down; -mod up; - const TEXT_DIR: String = "text".to_string(); const FILE_DIR: String = "files".to_string(); const REDIRECTS_FILE: String = "redirects".to_string(); @@ -34,206 +25,6 @@ lazy_static! { }; } -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("io error")] - IO(#[from] std::io::Error), - #[error("metadata error")] - Metadata, - #[error("some shit missing: {0}")] - ShitMissing(String), - #[error("filesystem error")] - FS, -} - -fn meta_str_to_map(from: &str) -> Result, Error> { - let map = from - .lines() - .map(|line| line.trim()) - .filter(|line| line.len() > 0) - .map(|line| { - line.split_once(": ") - .ok_or(Error::Metadata) - .and_then(|(k, v)| Ok((k, v.to_string()))) - }) - .collect::, Error>>()?; - - Ok(map) -} - -struct TextPaste { - pub text: String, - pub meta: TextMeta, -} - -struct TextMeta { - language: Option, -} - -impl TextMeta { - pub fn parse(from: &str) -> Result { - let map = meta_str_to_map(from)?; - // meh, this prolly doesnt perform well but idc its only init code - Ok(Self { - language: map.remove("language"), - }) - } -} - -struct FileMeta { - file_name: String, -} - -impl FileMeta { - pub fn parse(from: &str) -> Result { - let map = meta_str_to_map(from)?; - - let file_name = map.remove("file_name").ok_or(Error::Metadata)?; - Ok(Self { file_name }) - } -} - -struct UserData { - pub username: String, - pub pw_hash: String, - pub is_admin: bool, -} -impl UserData { - pub fn parse(line: &str, is_admin: bool) -> Result { - let (username, pw_hash) = line.split_once(": ").ok_or(Error::Metadata)?; - Ok(Self { - username: username.trim().to_string(), - pw_hash: pw_hash.trim().to_string(), - is_admin, - }) - } -} - -struct DataStorage { - base_dir: PathBuf, - redirects: DashMap, - text: DashMap, - files: DashMap, - users: DashMap, -} - -impl DataStorage { - pub fn new(base_dir: PathBuf) -> Self { - let redirects = DashMap::new(); - let text = DashMap::new(); - let files = DashMap::new(); - let users = DashMap::new(); - DataStorage { - base_dir, - redirects, - text, - files, - users, - } - } - - pub fn init(&mut self) -> Result<(), Error> { - self.ensure_folder_structure()?; - self.load_redirects()?; - self.load_text()?; - self.load_files()?; - self.load_users()?; - - Ok(()) - } - - fn ensure_folder_structure(&self) -> Result<(), Error> { - let dir_builder = DirBuilder::new().recursive(true); - if cfg!(target_family = "unix") { - dir_builder = dir_builder.mode(0o775); - } - - dir_builder.create(self.base_dir)?; - dir_builder.create(self.base_dir.join(FILE_DIR))?; - dir_builder.create(self.base_dir.join(TEXT_DIR))?; - - Ok(()) - } - - fn load_redirects(&mut self) -> Result<(), Error> { - let f = File::options() - .create(true) - .read(true) - .open(self.base_dir.join(REDIRECTS_FILE))?; - - for line in io::BufReader::new(f).lines() { - line?; - let (id, url) = line?.split_once(": ").ok_or(Error::Metadata)?; - self.redirects - .insert(id.trim().to_string(), url.trim().to_string()); - // if there's a better way to do this let me know - } - - Ok(()) - } - - fn load_text(&mut self) -> Result<(), Error> { - for thing in std::fs::read_dir(self.base_dir.join(TEXT_DIR)).expect("fs error") { - let dir_entry = thing?; // i'm so good at naming things - let id = dir_entry.file_name().into_string().or(Err(Error::FS))?; - let (raw_metadata, text) = read_to_string(dir_entry.path())? - .split_once("---") - .expect("invalid text format"); - let meta = TextMeta::parse(raw_metadata)?; - self.text.insert( - id, - TextPaste { - text: text.to_string(), - meta, - }, - ); - } - Ok(()) - } - - fn load_files(&mut self) -> Result<(), Error> { - for thing in std::fs::read_dir(self.base_dir.join(FILE_DIR)).expect("fs error") { - let dir_entry = thing?; // i'm so good at naming things - let filename = dir_entry - .file_name() - .into_string() - .expect("fucked up OS string"); - if filename.ends_with(".meta") { - let id = filename.trim_end_matches(".meta").to_string(); - let raw_metadata = read_to_string(dir_entry.path())?; - let metadata = FileMeta::parse(raw_metadata.as_str())?; - self.files.insert(id, metadata); - } - } - self.assert_all_files_present()?; - Ok(()) - } - - fn assert_all_files_present(&self) -> Result<(), Error> { - let files_folder = self.base_dir.join(FILE_DIR); - for (id, _) in self.files.into_iter() { - files_folder - .join(id) - .try_exists() - .or(Err(Error::ShitMissing(id)))?; - } - Ok(()) - } - - fn load_users(&mut self) -> Result<(), Error> { - let is_admin = true; - for line in read_to_string(self.base_dir.join(USERS_FILE))?.lines() { - if line.starts_with("---") { - is_admin = false; - break; - } - let user = UserData::parse(line, is_admin)?; - self.users.insert(user.username, user); - } - Ok(()) - } -} - struct AppState { alphabet: Vec, id_length: usize,