use std::{fs::{self, File}, io::{Seek, Write}};
use chrono::{DateTime, Datelike, Local, Utc};
use log::{info, warn};
use crate::{database::Database, get_dir, thought::Thought};
pub fn export(markdown: bool, oats: bool, path: &str) {
if markdown {
export_markdown(path);
} else if oats {
export_oats(path);
} else {
export_ron(path);
}
}
fn export_oats(path: &str) {
info!("exporting thoughts as oats (`{path}`)...");
let database = Database::load(get_dir()).expect("database either corrupt or non-existent");
let mut file = File::create(path).unwrap();
file.write_all("oats".as_bytes()).unwrap();
file.write_all(&[0]).unwrap();
file.seek(std::io::SeekFrom::Start(4 + 1 + 8)).unwrap();
let mut stack_ptr: u64 = 4 + 1 + 8;
for bytes in database {
let thought = bincode::deserialize(&bytes).expect("thought database is corrupt");
let Thought { uid, thought, utc } = thought;
let length = 8 + 1 + thought.len() as u32 + if utc.is_some() { 8 } else { 0 };
file.write_all(&length.to_be_bytes()).unwrap();
file.write_all(&uid.to_be_bytes()).unwrap();
if let Some(utc) = utc {
file.write_all(&[2]).unwrap();
file.write_all(&utc.timestamp_millis().to_be_bytes()).unwrap();
} else {
file.write_all(&[0]).unwrap();
}
file.write_all(thought.as_bytes()).unwrap();
file.write_all(&length.to_be_bytes()).unwrap();
stack_ptr += length as u64 + 2 * 4;
}
file.seek(std::io::SeekFrom::Start(4 + 1)).unwrap();
file.write_all(&stack_ptr.to_be_bytes()).unwrap();
}
fn export_markdown(path: &str) {
info!("exporting thoughts as markdown (`{path}`)...");
let database = Database::load(get_dir()).expect("database either corrupt or non-existent");
let mut file = File::create(path).unwrap();
file.write_all("# Thoughts :D\n---\n".as_bytes()).unwrap();
let mut last: Option<DateTime<Utc>> = None;
for bytes in database {
let thought = bincode::deserialize(&bytes).expect("thought database is corrupt");
let Thought { uid: _, thought, utc } = thought;
if let Some(utc) = utc {
let last = last.unwrap_or(DateTime::from_timestamp_nanos(0));
if last.day() != utc.day() || last.month() != utc.month() || last.year() != utc.year() {
let utc: DateTime<Local> = DateTime::from(utc);
let format = &format!(
"%A, %-d{} of %B %Y `%I:%M %p`",
match utc.day() {
t if t % 10 == 1 && t % 100 != 11 => "st",
t if t % 10 == 2 && t % 100 != 12 => "nd",
t if t % 10 == 3 && t % 100 != 13 => "rd",
_ => "th",
}
);
let date = format!("## {}\n", utc.format(format));
file.write_all(date.as_bytes()).unwrap();
} else if (utc.time() - last.time()).num_minutes() > 16 { let time: DateTime<Local> = DateTime::from(utc);
let time = time.format("`%I:%M %p`");
let time = format!("#### {time}\n");
file.write_all(time.as_bytes()).unwrap();
}
}
last = utc;
let thought = format!("- {thought}\n");
file.write_all(thought.as_bytes()).unwrap();
}
file.flush().unwrap();
info!("successfully export thoughts as markdown!");
}
fn export_ron(path: &str) {
info!("exporting thoughts as RON (`{path}`)...");
let database = Database::load(get_dir()).expect("database either corrupt or non-existent");
let ron = database.into_iter()
.map(|bytes| {
bincode::deserialize::<Thought>(&bytes).expect("thought database is corrupt")
})
.collect::<Vec<_>>();
let ron = ron::to_string(&ron).unwrap();
fs::write(path, ron).unwrap();
info!("successfully export thoughts as RON!");
}
pub fn import(path: &str) {
info!("importing RON thoughts and combining with current database...");
let mut thoughts = if get_dir().exists() {
info!("thoughts database found");
Database::load(get_dir()).unwrap()
.map(|bytes| {
bincode::deserialize(&bytes)
.expect("thoughts database is corrupt")
})
.collect::<Vec<Thought>>()
} else {
warn!("thoughts database not found, initialising a new one");
Vec::new()
};
#[allow(clippy::expect_fun_call)]
let ron_thoughts: Vec<Thought> = ron::from_str(&fs::read_to_string(path).expect(&format!("while reading the contents of `{path}`"))).expect("RON thoughts are corrupt");
for rthought in ron_thoughts.into_iter() {
if !thoughts.iter().any(|thought| thought.uid == rthought.uid) {
thoughts.push(rthought);
}
}
thoughts.sort_unstable_by_key(|thought| thought.uid);
let _ = fs::remove_dir_all(get_dir());
let mut database = Database::new(get_dir()).expect("while initialising database");
for thought in thoughts.into_iter() {
database.push(
&bincode::serialize(&thought).unwrap()
).expect("while writing to thought database (warning: major data loss)");
} database.commit().expect("while writing to thought database (warning: major data loss)");
info!("successfully imported RON thoughts!");
}