xenua
2 years ago
2 changed files with 223 additions and 214 deletions
@ -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<HashMap<&str, String>, 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::<Result<HashMap<_, _>, Error>>()?; |
||||||
|
|
||||||
|
Ok(map) |
||||||
|
} |
||||||
|
|
||||||
|
struct TextPaste { |
||||||
|
pub text: String, |
||||||
|
pub meta: TextMeta, |
||||||
|
} |
||||||
|
|
||||||
|
struct TextMeta { |
||||||
|
language: Option<String>, |
||||||
|
} |
||||||
|
|
||||||
|
impl TextMeta { |
||||||
|
pub fn parse(from: &str) -> Result<Self, Error> { |
||||||
|
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<Self, Error> { |
||||||
|
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<Self, Error> { |
||||||
|
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<String, String>, |
||||||
|
text: DashMap<String, TextPaste>, |
||||||
|
files: DashMap<String, FileMeta>, |
||||||
|
users: DashMap<String, UserData>, |
||||||
|
} |
||||||
|
|
||||||
|
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(()) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue