use std::collections::HashMap;
use std::sync::mpsc::Sender;
use chrono::{DateTime, Local, Utc};
use itertools::Itertools;
use crate::dispatcher::Dispatcher;
use crate::domain::{Job, Pipeline, Project};
use crate::event::GlimEvent;
use crate::id::ProjectId;
pub struct ProjectStore {
sender: Sender<GlimEvent>,
projects: Vec<Project>,
project_id_lookup: HashMap<ProjectId, usize>,
sorted: Vec<Project>, }
impl ProjectStore {
pub fn new(sender: Sender<GlimEvent>) -> Self {
Self {
sender,
projects: Vec::new(),
project_id_lookup: HashMap::new(),
sorted: Vec::new(),
}
}
pub fn apply(&mut self, event: &GlimEvent) {
match event {
GlimEvent::OpenProjectDetails(id) => {
let project = self.find(*id).unwrap();
project.recent_pipelines()
.into_iter()
.filter(|p| p.jobs.is_none())
.for_each(|p| self.dispatch(GlimEvent::RequestJobs(project.id, p.id)));
},
GlimEvent::ReceivedProjects(projects) => {
let first_projects = self.sorted.is_empty();
projects.iter()
.map(|p| Project::from(p.clone()))
.for_each(|p| {
let project = p.clone();
self.sync_project(p);
let sender = self.sender.clone();
sender.dispatch(GlimEvent::ProjectUpdated(Box::new(project)))
});
self.sorted = self.sorted_projects();
if first_projects {
self.dispatch(GlimEvent::SelectedProject(self.sorted.first().unwrap().id));
}
},
GlimEvent::ReceivedPipelines(pipelines) => {
let project_id = pipelines[0].project_id;
let sender = self.sender.clone();
if let Some(project) = self.find_mut(project_id) {
let pipelines: Vec<Pipeline> = pipelines.iter()
.map(|p| Pipeline::from(p.clone()))
.collect();
pipelines.iter()
.filter(|&p| p.status.is_active() || p.has_active_jobs())
.for_each(|p| sender.dispatch(GlimEvent::RequestJobs(project_id, p.id)));
project.update_pipelines(pipelines);
sender.dispatch(GlimEvent::ProjectUpdated(Box::new(project.clone())))
}
self.sorted = self.sorted_projects();
},
GlimEvent::ReceivedJobs(project_id, pipeline_id, job_dtos) => {
let jobs: Vec<Job> = job_dtos.iter()
.map(|j| Job::from(j.clone()))
.collect();
let sender = self.sender.clone();
if let Some(project) = self.find_mut(*project_id) {
project.update_jobs(*pipeline_id, jobs);
project.update_commit(*pipeline_id, job_dtos.first().map(|j| j.commit.clone().into()).unwrap());
sender.dispatch(GlimEvent::ProjectUpdated(Box::new(project.clone())))
}
self.sorted = self.sorted_projects();
},
GlimEvent::SelectedProject(id) => {
let mut request_pipelines = false;
if let Some(project) = self.find_mut(*id) {
if project.pipelines.is_none() {
project.pipelines = Some(Vec::new());
request_pipelines = true;
}
};
if request_pipelines {
self.dispatch(GlimEvent::RequestPipelines(*id));
};
},
_ => {}
}
}
fn sorted_projects(&mut self) -> Vec<Project> {
self.projects.iter()
.sorted_by(|a, b| b.last_activity().cmp(&a.last_activity()))
.cloned()
.collect()
}
pub fn find(&self, id: ProjectId) -> Option<&Project> {
self.project_idx(id)
.map(|idx| &self.projects[idx])
}
pub fn projects(&self) -> &[Project] {
&self.sorted
}
fn find_mut(&mut self, id: ProjectId) -> Option<&mut Project> {
self.project_idx(id)
.map(|idx| &mut self.projects[idx])
}
fn project_idx(&self, id: ProjectId) -> Option<usize> {
self.project_id_lookup.get(&id).copied()
}
fn sync_project(&mut self, mut project: Project) {
let sender = self.sender.clone();
match self.find_mut(project.id) {
Some(existing_entry) => {
sender.dispatch(GlimEvent::RequestPipelines(project.id));
existing_entry.update_project(project.clone())
}
None => {
self.project_id_lookup.insert(project.id, self.projects.len());
if !is_older_than_7d(project.last_activity()) {
sender.dispatch(GlimEvent::RequestPipelines(project.id));
project.pipelines = Some(Vec::new());
}
self.projects.push(project);
}
}
}
}
fn is_older_than_7d(date: DateTime<Utc>) -> bool {
Utc::now()
.signed_duration_since(date)
.num_days() > 7
}
pub struct InternalLogsStore {
logs: Vec<(DateTime<Local>, String)>,
}
impl InternalLogsStore {
pub fn new() -> Self {
Self {
logs: Vec::new(),
}
}
pub fn apply(&mut self, event: &GlimEvent) {
if let Some(log) = match event {
GlimEvent::Log(s) => Some(s.to_owned()),
GlimEvent::ToggleColorDepth => Some("toggling color depth".to_string()),
GlimEvent::Shutdown =>
Some("shutting down...".to_string()),
GlimEvent::RequestProject(id) =>
Some(format!("refresh project_id={id}")),
GlimEvent::RequestProjects =>
Some("request all projects since last update".to_string()),
GlimEvent::RequestActiveJobs =>
Some("request active pipelines for all projects".to_string()),
GlimEvent::RequestPipelines(id) =>
Some(format!("request pipelines for project_id={id}")),
GlimEvent::RequestJobs(project_id, pipeline_id) =>
Some(format!("request jobs for project_id={project_id} pipeline_id={pipeline_id}")),
GlimEvent::ReceivedProjects(projects) =>
Some(format!("received {:?} projects", projects.len())),
GlimEvent::ReceivedPipelines(pipelines) =>
Some(format!("received {:?} pipelines", pipelines.len())),
GlimEvent::ReceivedJobs(project_id, _, jobs) =>
Some(format!("received {:?} jobs for project_id={project_id}", jobs.len())),
GlimEvent::OpenProjectDetails(id) =>
Some(format!("showing project_id={id} details")),
GlimEvent::CloseProjectDetails =>
Some("closing project details popup".to_string()),
GlimEvent::OpenPipelineActions(id, pipeline_id) =>
Some(format!("showing pipeline {pipeline_id}'s actions for project_id={id}")),
GlimEvent::Error(s) =>
Some(s.to_string()),
GlimEvent::SelectedProject(id) =>
Some(format!("selected project_id={id}")),
GlimEvent::SelectedPipeline(id) =>
Some(format!("selected pipeline_id={id}")),
GlimEvent::BrowseToProject(id) =>
Some(format!("open project_id={id} in browser")),
GlimEvent::BrowseToPipeline(_, id) =>
Some(format!("open pipeline_id={id} in browser")),
GlimEvent::BrowseToJob(_, _, job_id) =>
Some(format!("open job_id={job_id} in browser")),
GlimEvent::DownloadErrorLog(_, id) =>
Some(format!("download job log for failed pipeline_id={id}")),
GlimEvent::JobLogDownloaded(_, id, _) => Some(format!("downloaded log for job_id={id}")),
GlimEvent::DisplayConfig => Some("display config".to_string()),
GlimEvent::ApplyConfiguration => Some("applying new configuration".to_string()),
GlimEvent::UpdateConfig(_) => Some("updating configuration".to_string()),
GlimEvent::CloseConfig => None,
GlimEvent::ClosePipelineActions => None,
GlimEvent::GlitchOverride(_) => None,
GlimEvent::Tick => None,
GlimEvent::ProjectUpdated(_) => None,
GlimEvent::Key(_) => None,
GlimEvent::SelectNextProject => None,
GlimEvent::ShowLastNotification => None,
GlimEvent::SelectPreviousProject => None,
GlimEvent::ToggleInternalLogs => None,
} {
self.logs.push((Local::now(), log));
}
if self.logs.len() > 200 {
self.logs = self.logs.iter().dropping(150).cloned().collect();
}
}
pub fn logs(&self) -> Vec<(DateTime<Local>, &str)> {
self.logs.iter().map(|(dt, s)| (*dt, s.as_str())).collect()
}
}
impl Dispatcher for ProjectStore {
fn dispatch(&self, event: GlimEvent) {
self.sender.send(event).unwrap();
}
}