use super::errors;
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use std::ops::Add;
use std::path::PathBuf;
type DT = DateTime<Utc>;
#[derive(Debug, Serialize, Deserialize)]
pub struct Project {
name: String,
initial_date: DT,
sessions: Vec<Session>,
}
impl Project {
pub fn name(&self) -> &String {
&self.name
}
pub fn initial_date(&self) -> &DT {
&self.initial_date
}
pub fn sessions(&self) -> &Vec<Session> {
&self.sessions
}
pub(super) fn create(name: &str) -> errors::CtResult<Project> {
if super::has(name)? {
Err(errors::CtError::Own("Project already exists"))
} else {
Ok(Project {
name: name.to_owned(),
initial_date: Utc::now(),
sessions: vec![],
})
}
}
pub(super) fn from_json(json: &str) -> errors::CtResult<Project> {
serde_json::from_str(json).map_err(|e| e.into())
}
pub fn json(&self, pretty: bool) -> errors::CtResult<String> {
if pretty {
serde_json::to_string_pretty(self).map_err(|e| e.into())
} else {
serde_json::to_string(self).map_err(|e| e.into())
}
}
pub(super) fn start(&mut self) {
for s in self.sessions.iter() {
if s.end.is_none() {
return;
}
}
self.sessions.push(Session::new());
}
pub(super) fn stop(&mut self) {
let open: Vec<&mut Session> = self.sessions.iter_mut().filter(|s| s.is_open()).collect();
assert!(open.len() < 2);
for s in open {
s.close();
}
}
pub fn is_open(&self) -> bool {
self.sessions.iter().fold(false, |acc, x| acc | x.is_open())
}
pub fn duration(&self) -> Duration {
self.sessions
.iter()
.map(|s| s.timespan())
.fold(Duration::zero(), Duration::add)
}
pub fn session_count(&self) -> usize {
self.sessions.len()
}
pub fn load(path: &PathBuf) -> errors::CtResult<Project> {
assert!(path.exists());
assert!(path.is_file());
let json = std::fs::read_to_string(&path)?;
Project::from_json(json.as_str())
}
pub fn load_from_name(name: &str) -> errors::CtResult<Project> {
Self::load(&super::project_path(name)?)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub struct Session {
start: DT,
end: Option<DT>,
}
impl Session {
fn new() -> Session {
Session {
start: Utc::now(),
end: None,
}
}
pub fn is_open(&self) -> bool {
self.end.is_none()
}
fn close(&mut self) {
assert!(self.end == None);
self.end = Some(Utc::now());
}
pub fn timespan(&self) -> Duration {
let start = self.start;
let end = self.end.unwrap_or_else(Utc::now);
end.signed_duration_since(start)
}
pub fn start_time(&self) -> &DT {
&self.start
}
pub fn end_time(&self) -> Option<&DT> {
if let Some(t) = &self.end {
Some(t)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use chrono::{Duration, Utc};
use std::ops::Add;
use super::{Project, Session, DT};
struct TimeProvider {
state: i32,
num: i32,
base_time: DT,
timestep: Duration,
}
impl TimeProvider {
fn new(timestep: Duration) -> TimeProvider {
Self::lim(-1, timestep)
}
fn lim(num: i32, timestep: Duration) -> TimeProvider {
TimeProvider {
state: 0,
num,
base_time: Utc::now(),
timestep,
}
}
}
impl Iterator for TimeProvider {
type Item = DT;
fn next(&mut self) -> Option<Self::Item> {
if self.num <= 0 || self.state <= self.num {
self.state += 1;
self.base_time = self.base_time.add(self.timestep);
Some(self.base_time)
} else {
None
}
}
}
impl Project {
fn create_test(session_count: usize, timestep: Duration) -> Project {
let mut t = TimeProvider::new(timestep);
let initial_date = t.next().unwrap();
let mut sessions = Vec::with_capacity(session_count);
for _ in 1..=session_count {
sessions.push(Session::mock(&mut t));
}
Project {
name: "TEST".to_owned(),
initial_date,
sessions,
}
}
}
impl Session {
fn mock(t: &mut TimeProvider) -> Session {
if let Some(time) = t.next() {
Session {
start: time,
end: t.next(),
}
} else {
Session {
start: Utc::now(),
end: Some(Utc::now()),
}
}
}
}
#[test]
fn duration() {
let timestep = Duration::seconds(5);
let session_count = 10;
let p = Project::create_test(session_count, timestep);
assert_eq!(
Duration::seconds(timestep.num_seconds() * session_count as i64),
p.duration()
);
}
#[test]
fn multiple_start() {
let mut p = Project::create("TEST").unwrap();
p.start();
p.start();
p.start();
println!("Project has {} sessions", p.sessions.len());
assert_eq!(p.sessions.len(), 1);
p.stop();
let has_open_sessions = p.sessions.iter().fold(false, |acc, x| acc | x.is_open());
println!("Project has open sessions? {}", has_open_sessions);
assert!(!has_open_sessions);
println!("Test finished successfully");
}
#[test]
fn session_timespan() {
let dur = Duration::seconds(5);
let mut t = TimeProvider::new(dur);
let s = Session::mock(&mut t);
assert_eq!(dur, s.timespan());
}
}