monorepo/fitnesstrax/app/src/app.rs

127 lines
3.9 KiB
Rust
Raw Normal View History

/*
Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of FitnessTrax.
FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
*/
use chrono::NaiveDate;
use emseries::{time_range, Record, RecordId, Series, Timestamp};
use ft_core::TraxRecord;
use std::{
path::PathBuf,
sync::{Arc, RwLock},
};
use tokio::runtime::Runtime;
pub enum AppError {
NoDatabase,
FailedToOpenDatabase,
Unhandled,
}
/// The real, headless application. This is where all of the logic will reside.
#[derive(Clone)]
pub struct App {
runtime: Arc<Runtime>,
database: Arc<RwLock<Option<Series<TraxRecord>>>>,
}
impl App {
pub fn new(db_path: Option<PathBuf>) -> Self {
let database = db_path.map(|path| Series::open(path).unwrap());
let runtime = Arc::new(
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap(),
);
let s = Self {
runtime,
database: Arc::new(RwLock::new(database)),
};
s
}
pub async fn records(
&self,
start: NaiveDate,
end: NaiveDate,
) -> Result<Vec<Record<TraxRecord>>, AppError> {
let db = self.database.clone();
self.runtime
.spawn_blocking(move || {
if let Some(ref db) = *db.read().unwrap() {
let records = db
.search(time_range(
Timestamp::Date(start),
true,
Timestamp::Date(end),
true,
))
.map(|record| record.clone())
.collect::<Vec<Record<TraxRecord>>>();
Ok(records)
} else {
Err(AppError::NoDatabase)
}
})
.await
.unwrap()
}
pub async fn put_record(&self, record: TraxRecord) -> Result<RecordId, AppError> {
let db = self.database.clone();
self.runtime
.spawn_blocking(move || {
if let Some(ref mut db) = *db.write().unwrap() {
let id = db.put(record).unwrap();
Ok(id)
} else {
Err(AppError::NoDatabase)
}
})
.await
.unwrap()
.map_err(|_| AppError::Unhandled)
}
pub async fn save_record(&self, record: Record<TraxRecord>) -> Result<(), AppError> {
let db = self.database.clone();
self.runtime
.spawn_blocking(move || {
if let Some(ref mut db) = *db.write().unwrap() {
db.update(record).map_err(|_| AppError::Unhandled)
} else {
Err(AppError::NoDatabase)
}
})
.await
.unwrap()
.map_err(|_| AppError::Unhandled)
}
pub async fn open_db(&self, path: PathBuf) -> Result<(), AppError> {
let db_ref = self.database.clone();
self.runtime
.spawn_blocking(move || {
let db = Series::open(path).map_err(|_| AppError::FailedToOpenDatabase)?;
*db_ref.write().unwrap() = Some(db);
Ok(())
})
.await
.unwrap()
}
}