xenua
2 years ago
2 changed files with 223 additions and 214 deletions
@ -0,0 +1,218 @@
@@ -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