use std::{ collections::HashMap, fs::{self, File}, io::{BufRead, BufReader}, path::Path, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::PageUuid; #[derive(Clone, Deserialize, Serialize, Debug)] pub struct Page { pub uuid: PageUuid, pub author: Uuid, pub title: String, pub current_version: DateTime, pub prev_versions: Vec>, content_offset: usize, } pub struct Pages { pub pages: HashMap, } const METADATA_DIVIDER: &'static str = ""; #[cfg(feature = "ssr")] impl Pages { pub fn init(pages_dir: &Path) -> Result { // Read dir let page_dirs = fs::read_dir(&pages_dir) .map_err(|_| "Could not open pages data directory".to_string())?; // Parse each let pages = page_dirs .map(|dir_entry| -> Result { let page_dir_path = dir_entry.as_ref().unwrap().path(); Pages::read_page(&page_dir_path) }) .collect::, String>>()?; // Build lookup Ok(Self { pages: pages .into_iter() .map(|page| (page.uuid.clone(), page)) .collect::>(), }) } fn read_page(page_dir: &Path) -> Result { let current_page = page_dir .join("current") .canonicalize() .map_err(|_| "Could not canonicalize page location".to_string())?; let mut reader = BufReader::new( File::open(¤t_page).map_err(|_| "Could not open page file".to_string())?, ); let page_uuid = PageUuid( Uuid::try_parse( &page_dir .file_name() .ok_or("Could not read page directory".to_string())? .to_str() .unwrap(), ) .map_err(|_| "Could not parse page UUID".to_string())?, ); let mut metadata_string = String::new(); let mut current_line = String::new(); let mut content_offset = 0; 'readloop: while let Ok(size) = reader.read_line(&mut current_line) { content_offset += size; if size == 0 { return Err("Page file is invalid".to_string()); } if current_line.trim() == METADATA_DIVIDER { break 'readloop; } metadata_string.push_str(¤t_line); current_line.truncate(0); } #[derive(Deserialize)] struct PageMetadata { title: String, author: String, prev_versions: Option>, } let metadata: PageMetadata = toml::from_str(&metadata_string).map_err(|err| { println!("{err:?}"); "Page metadata is invalid".to_string() })?; let current_version = DateTime::parse_from_rfc3339( current_page .file_name() .unwrap() .to_str() .unwrap() .replace("_", ":") .as_str(), ) .map_err(|_| "Invalid date format".to_string())? .to_utc(); let prev_versions = metadata .prev_versions .unwrap_or(Vec::new()) .iter() .filter_map(|str| { DateTime::parse_from_rfc3339(str.replace("_", ":").as_str()) .ok() .map(|timestamp| timestamp.to_utc()) }) .collect::>(); Ok(Page { uuid: page_uuid, author: Uuid::try_parse(&metadata.author) .map_err(|_| "Could not parse author UUID".to_string())?, title: metadata.title, current_version, prev_versions, content_offset, }) } pub fn get_page(&self, uuid: PageUuid) -> Option { todo!() } pub fn create_page(&self, page: Page) { todo!() } pub fn update_page(&self, page: Page) { todo!() } pub fn delete_page(&self, uuid: PageUuid) -> Result<(), String> { todo!() } }