Browse Source

refactor out the data storage backend

xenua 2 years ago
parent
commit
13e02cf0f2
  1. 218
      src/data.rs
  2. 219
      src/main.rs

218
src/data.rs

@ -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(())
}
}

219
src/main.rs

@ -1,24 +1,15 @@ @@ -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! { @@ -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<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)?;
// 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<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,
})
}
}
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))?;
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<char>,
id_length: usize,

Loading…
Cancel
Save