From d85ff3ac8df980c3824111ff074f2ce200f411fc Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Fri, 6 Dec 2024 23:28:06 +0100 Subject: [PATCH] refactor(tasks): extract more modules --- src/kinds.rs | 2 +- src/tasks.rs | 232 ++------------------------------ src/tasks/children_traversal.rs | 160 ++++++++++++++++++++++ src/tasks/durations.rs | 83 ++++++++++++ src/tasks/tests.rs | 32 ++--- 5 files changed, 262 insertions(+), 247 deletions(-) create mode 100644 src/tasks/children_traversal.rs create mode 100644 src/tasks/durations.rs diff --git a/src/kinds.rs b/src/kinds.rs index 8179cbe..be705de 100644 --- a/src/kinds.rs +++ b/src/kinds.rs @@ -1,5 +1,5 @@ use crate::task::MARKER_PARENT; -use crate::tasks::nostr_users::NostrUsers; +use crate::tasks::NostrUsers; use crate::tasks::HIGH_PRIO; use itertools::Itertools; use nostr_sdk::{EventBuilder, EventId, Kind, PublicKey, Tag, TagKind, TagStandard}; diff --git a/src/tasks.rs b/src/tasks.rs index 81ea4cc..8c30dc4 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,6 +1,8 @@ -pub(crate) mod nostr_users; +mod nostr_users; #[cfg(test)] mod tests; +mod children_traversal; +mod durations; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; use std::fmt::{Display, Formatter}; @@ -17,7 +19,9 @@ use crate::helpers::{ }; use crate::kinds::*; use crate::task::{State, StateChange, Task, MARKER_DEPENDS, MARKER_PARENT, MARKER_PROPERTY}; -use crate::tasks::nostr_users::NostrUsers; +use crate::tasks::children_traversal::ChildrenTraversal; +use crate::tasks::durations::{referenced_events, timestamps, Durations}; +pub use crate::tasks::nostr_users::NostrUsers; use colored::Colorize; use itertools::Itertools; use log::{debug, error, info, trace, warn}; @@ -39,7 +43,7 @@ pub(crate) fn now() -> Timestamp { } type TaskMap = HashMap; -trait TaskMapMethods { +pub(super) trait TaskMapMethods { fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator + 'a; fn children_for<'a>(&'a self, id: Option) -> impl Iterator + 'a; fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator + 'a; @@ -52,7 +56,6 @@ impl TaskMapMethods for TaskMap { fn children_for<'a>(&'a self, id: Option) -> impl Iterator + 'a { self.values().filter(move |t| t.parent_id() == id.as_ref()) } - fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator + 'a { self.children_for(Some(id)).map(|t| t.get_id()) } @@ -369,7 +372,7 @@ impl TasksRelay { fn total_time_tracked(&self, id: EventId) -> u64 { let mut total = 0; - let children = ChildIterator::from(&self, id).get_all(); + let children = ChildrenTraversal::from(&self, id).get_all(); for user in self.history.values() { total += Durations::from(user.values(), &children) .sum::() @@ -1334,7 +1337,7 @@ impl TasksRelay { let ids = if state == State::Closed { // Close whole subtree - ChildIterator::from(self, id).get_all() + ChildrenTraversal::from(self, id).get_all() } else { vec![id] }; @@ -1575,226 +1578,10 @@ pub(crate) fn join_tasks<'a>( }) } -fn referenced_events(event: &Event) -> impl Iterator + '_ { - event.tags.iter().filter_map(|tag| match_event_tag(tag).map(|t| t.id)) -} - pub fn referenced_event(event: &Event) -> Option { referenced_events(event).next() } -/// Returns the id of a referenced event if it is contained in the provided ids list. -fn matching_tag_id<'a>(event: &'a Event, ids: &'a [EventId]) -> Option { - referenced_events(event).find(|id| ids.contains(id)) -} - -/// Filters out event timestamps to those that start or stop one of the given events -fn timestamps<'a>( - events: impl Iterator, - ids: &'a [EventId], -) -> impl Iterator)> { - events - .map(|event| (&event.created_at, matching_tag_id(event, ids))) - .dedup_by(|(_, e1), (_, e2)| e1 == e2) - .skip_while(|element| element.1.is_none()) -} - -/// Iterates Events to accumulate times tracked -/// Expects a sorted iterator -struct Durations<'a> { - events: Box + 'a>, - ids: &'a [EventId], - threshold: Option, -} -impl Durations<'_> { - fn from<'b>( - events: impl IntoIterator + 'b, - ids: &'b [EventId], - ) -> Durations<'b> { - Durations { - events: Box::new(events.into_iter()), - ids, - threshold: Some(Timestamp::now()), // TODO consider offset? - } - } -} -impl Iterator for Durations<'_> { - type Item = Duration; - - fn next(&mut self) -> Option { - let mut start: Option = None; - while let Some(event) = self.events.next() { - if matching_tag_id(event, self.ids).is_some() { - if self.threshold.is_some_and(|th| event.created_at > th) { - continue; - } - start = start.or(Some(event.created_at.as_u64())) - } else { - if let Some(stamp) = start { - return Some(Duration::from_secs(event.created_at.as_u64() - stamp)); - } - } - } - let now = self.threshold.unwrap_or(Timestamp::now()).as_u64(); - start.filter(|t| t < &now) - .map(|stamp| Duration::from_secs(now.saturating_sub(stamp))) - } -} - -#[derive(Clone, Debug, PartialEq)] -enum ChildIteratorFilter { - Reject = 0b00, - TakeSelf = 0b01, - TakeChildren = 0b10, - Take = 0b11, -} -impl ChildIteratorFilter { - fn takes_children(&self) -> bool { - self == &ChildIteratorFilter::Take || - self == &ChildIteratorFilter::TakeChildren - } - fn takes_self(&self) -> bool { - self == &ChildIteratorFilter::Take || - self == &ChildIteratorFilter::TakeSelf - } -} - -/// Breadth-First Iterator over Tasks and recursive children -struct ChildIterator<'a> { - tasks: &'a TaskMap, - /// Found Events - queue: Vec, - /// Index of the next element in the queue - index: usize, - /// Depth of the next element - depth: usize, - /// Element with the next depth boundary - next_depth_at: usize, -} -impl<'a> ChildIterator<'a> { - fn rooted(tasks: &'a TaskMap, id: Option<&EventId>) -> Self { - let mut queue = Vec::with_capacity(tasks.len()); - queue.append( - &mut tasks - .values() - .filter(move |t| t.parent_id() == id) - .map(|t| t.get_id()) - .collect_vec() - ); - Self::with_queue(tasks, queue) - } - - fn with_queue(tasks: &'a TaskMap, queue: Vec) -> Self { - ChildIterator { - tasks: &tasks, - next_depth_at: queue.len(), - index: 0, - depth: 1, - queue, - } - } - - fn from(tasks: &'a TasksRelay, id: EventId) -> Self { - let mut queue = Vec::with_capacity(64); - queue.push(id); - ChildIterator { - tasks: &tasks.tasks, - queue, - index: 0, - depth: 0, - next_depth_at: 1, - } - } - - /// Process until the given depth - /// Returns true if that depth was reached - fn process_depth(&mut self, depth: usize) -> bool { - while self.depth < depth { - if self.next().is_none() { - return false; - } - } - true - } - - /// Get all children - fn get_all(mut self) -> Vec { - while self.next().is_some() {} - self.queue - } - - /// Get all tasks until the specified depth - fn get_depth(mut self, depth: usize) -> Vec { - self.process_depth(depth); - self.queue - } - - fn check_depth(&mut self) { - if self.next_depth_at == self.index { - self.depth += 1; - self.next_depth_at = self.queue.len(); - } - } - - /// Get next id and advance, without adding children - fn next_task(&mut self) -> Option { - if self.index >= self.queue.len() { - return None; - } - let id = self.queue[self.index]; - self.index += 1; - Some(id) - } - - /// Get the next known task and run it through the filter - fn next_filtered(&mut self, filter: &F) -> Option<&'a Task> - where - F: Fn(&Task) -> ChildIteratorFilter, - { - self.next_task().and_then(|id| { - if let Some(task) = self.tasks.get(&id) { - let take = filter(task); - if take.takes_children() { - self.queue_children_of(&task); - } - if take.takes_self() { - self.check_depth(); - return Some(task); - } - } - self.check_depth(); - self.next_filtered(filter) - }) - } - - fn queue_children_of(&mut self, task: &'a Task) { - self.queue.extend(self.tasks.children_ids_for(task.get_id())); - } -} -impl FusedIterator for ChildIterator<'_> {} -impl<'a> Iterator for ChildIterator<'a> { - type Item = EventId; - - fn next(&mut self) -> Option { - self.next_task().inspect(|id| { - match self.tasks.get(id) { - None => { - // Unknown task, might still find children, just slower - for task in self.tasks.values() { - if task.parent_id().is_some_and(|i| i == id) { - self.queue.push(task.get_id()); - } - } - } - Some(task) => { - self.queue_children_of(&task); - } - } - self.check_depth(); - }) - } -} - struct ParentIterator<'a> { tasks: &'a TaskMap, current: Option, @@ -1809,3 +1596,4 @@ impl<'a> Iterator for ParentIterator<'a> { }) } } +impl FusedIterator for ParentIterator<'_> {} diff --git a/src/tasks/children_traversal.rs b/src/tasks/children_traversal.rs new file mode 100644 index 0000000..04fe772 --- /dev/null +++ b/src/tasks/children_traversal.rs @@ -0,0 +1,160 @@ +use std::iter::FusedIterator; +use itertools::Itertools; +use nostr_sdk::EventId; +use crate::task::Task; +use crate::tasks::{TaskMap, TaskMapMethods, TasksRelay}; + +#[derive(Clone, Debug, PartialEq)] +enum TraversalFilter { + Reject = 0b00, + TakeSelf = 0b01, + TakeChildren = 0b10, + Take = 0b11, +} +impl TraversalFilter { + fn takes_children(&self) -> bool { + self == &TraversalFilter::Take || + self == &TraversalFilter::TakeChildren + } + fn takes_self(&self) -> bool { + self == &TraversalFilter::Take || + self == &TraversalFilter::TakeSelf + } +} + +/// Breadth-First Iterator over tasks with recursive children +pub(super) struct ChildrenTraversal<'a> { + tasks: &'a TaskMap, + /// Found Events + queue: Vec, + /// Index of the next element in the queue + index: usize, + /// Depth of the next element + depth: usize, + /// Element with the next depth boundary + next_depth_at: usize, +} +impl<'a> ChildrenTraversal<'a> { + fn rooted(tasks: &'a TaskMap, id: Option<&EventId>) -> Self { + let mut queue = Vec::with_capacity(tasks.len()); + queue.append( + &mut tasks + .values() + .filter(move |t| t.parent_id() == id) + .map(|t| t.get_id()) + .collect_vec() + ); + Self::with_queue(tasks, queue) + } + + fn with_queue(tasks: &'a TaskMap, queue: Vec) -> Self { + ChildrenTraversal { + tasks: &tasks, + next_depth_at: queue.len(), + index: 0, + depth: 1, + queue, + } + } + + pub(super) fn from(tasks: &'a TasksRelay, id: EventId) -> Self { + let mut queue = Vec::with_capacity(64); + queue.push(id); + ChildrenTraversal { + tasks: &tasks.tasks, + queue, + index: 0, + depth: 0, + next_depth_at: 1, + } + } + + /// Process until the given depth + /// Returns true if that depth was reached + pub(super) fn process_depth(&mut self, depth: usize) -> bool { + while self.depth < depth { + if self.next().is_none() { + return false; + } + } + true + } + + /// Get all children + pub(super) fn get_all(mut self) -> Vec { + while self.next().is_some() {} + self.queue + } + + /// Get all tasks until the specified depth + pub(super) fn get_depth(mut self, depth: usize) -> Vec { + self.process_depth(depth); + self.queue + } + + fn check_depth(&mut self) { + if self.next_depth_at == self.index { + self.depth += 1; + self.next_depth_at = self.queue.len(); + } + } + + /// Get next id and advance, without adding children + fn next_task(&mut self) -> Option { + if self.index >= self.queue.len() { + return None; + } + let id = self.queue[self.index]; + self.index += 1; + Some(id) + } + + /// Get the next known task and run it through the filter + fn next_filtered(&mut self, filter: &F) -> Option<&'a Task> + where + F: Fn(&Task) -> TraversalFilter, + { + self.next_task().and_then(|id| { + if let Some(task) = self.tasks.get(&id) { + let take = filter(task); + if take.takes_children() { + self.queue_children_of(&task); + } + if take.takes_self() { + self.check_depth(); + return Some(task); + } + } + self.check_depth(); + self.next_filtered(filter) + }) + } + + fn queue_children_of(&mut self, task: &'a Task) { + self.queue.extend(self.tasks.children_ids_for(task.get_id())); + } +} +impl FusedIterator for ChildrenTraversal<'_> {} +impl<'a> Iterator for ChildrenTraversal<'a> { + type Item = EventId; + + fn next(&mut self) -> Option { + self.next_task().inspect(|id| { + match self.tasks.get(id) { + None => { + // Unknown task, might still find children, just slower + for task in self.tasks.values() { + if task.parent_id().is_some_and(|i| i == id) { + self.queue.push(task.get_id()); + } + } + } + Some(task) => { + self.queue_children_of(&task); + } + } + self.check_depth(); + }) + } +} + diff --git a/src/tasks/durations.rs b/src/tasks/durations.rs new file mode 100644 index 0000000..d6722e5 --- /dev/null +++ b/src/tasks/durations.rs @@ -0,0 +1,83 @@ +use std::time::Duration; +use itertools::Itertools; +use nostr_sdk::{Event, EventId, Timestamp}; +use crate::kinds::match_event_tag; + +pub(super) fn referenced_events(event: &Event) -> impl Iterator + '_ { + event.tags.iter().filter_map(|tag| match_event_tag(tag).map(|t| t.id)) +} + +/// Returns the id of a referenced event if it is contained in the provided ids list. +fn matching_tag_id<'a>(event: &'a Event, ids: &'a [EventId]) -> Option { + referenced_events(event).find(|id| ids.contains(id)) +} + +/// Filters out event timestamps to those that start or stop one of the given events +pub(super) fn timestamps<'a>( + events: impl Iterator, + ids: &'a [EventId], +) -> impl Iterator)> { + events + .map(|event| (&event.created_at, matching_tag_id(event, ids))) + .dedup_by(|(_, e1), (_, e2)| e1 == e2) + .skip_while(|element| element.1.is_none()) +} + +/// Iterates Events to accumulate times tracked +/// Expects a sorted iterator +pub(super) struct Durations<'a> { + events: Box + 'a>, + ids: &'a [EventId], + threshold: Option, +} +impl Durations<'_> { + pub(super) fn from<'b>( + events: impl IntoIterator + 'b, + ids: &'b [EventId], + ) -> Durations<'b> { + Durations { + events: Box::new(events.into_iter()), + ids, + threshold: Some(Timestamp::now()), // TODO consider offset? + } + } +} +impl Iterator for Durations<'_> { + type Item = Duration; + + fn next(&mut self) -> Option { + let mut start: Option = None; + while let Some(event) = self.events.next() { + if matching_tag_id(event, self.ids).is_some() { + if self.threshold.is_some_and(|th| event.created_at > th) { + continue; + } + start = start.or(Some(event.created_at.as_u64())) + } else { + if let Some(stamp) = start { + return Some(Duration::from_secs(event.created_at.as_u64() - stamp)); + } + } + } + let now = self.threshold.unwrap_or(Timestamp::now()).as_u64(); + start.filter(|t| t < &now) + .map(|stamp| Duration::from_secs(now.saturating_sub(stamp))) + } +} + +#[test] +#[ignore] +fn test_timestamps() { + let mut tasks = crate::tasks::tests::stub_tasks(); + let zero = EventId::all_zeros(); + + tasks.track_at(Timestamp::now() + 100, Some(zero)); + assert_eq!( + timestamps(tasks.get_own_events_history(), &[zero]) + .collect_vec() + .len(), + 2 + ) + // TODO Does not show both future and current tracking properly, need to split by current time +} + diff --git a/src/tasks/tests.rs b/src/tasks/tests.rs index 28fb29d..c60b69c 100644 --- a/src/tasks/tests.rs +++ b/src/tasks/tests.rs @@ -7,7 +7,7 @@ use itertools::Itertools; use nostr_sdk::{EventBuilder, EventId, Keys, Kind, Tag, Timestamp}; use std::collections::HashSet; -fn stub_tasks() -> TasksRelay { +pub(super) fn stub_tasks() -> TasksRelay { use nostr_sdk::Keys; use tokio::sync::mpsc; @@ -312,22 +312,6 @@ fn test_tracking() { // TODO test received events } -#[test] -#[ignore] -fn test_timestamps() { - let mut tasks = stub_tasks(); - let zero = EventId::all_zeros(); - - tasks.track_at(Timestamp::now() + 100, Some(zero)); - assert_eq!( - timestamps(tasks.get_own_events_history(), &[zero]) - .collect_vec() - .len(), - 2 - ) - // TODO Does not show both future and current tracking properly, need to split by current time -} - #[test] fn test_depth() { let mut tasks = stub_tasks(); @@ -364,13 +348,13 @@ fn test_depth() { tasks.view_depth = 2; assert_tasks_view!(tasks, [t111]); - assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_all().len(), 1); - assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_depth(0).len(), 1); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(0).len(), 1); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(1).len(), 3); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(2).len(), 4); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(9).len(), 4); - assert_eq!(ChildIterator::from(&tasks, t1).get_all().len(), 4); + assert_eq!(ChildrenTraversal::from(&tasks, EventId::all_zeros()).get_all().len(), 1); + assert_eq!(ChildrenTraversal::from(&tasks, EventId::all_zeros()).get_depth(0).len(), 1); + assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(0).len(), 1); + assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(1).len(), 3); + assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(2).len(), 4); + assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(9).len(), 4); + assert_eq!(ChildrenTraversal::from(&tasks, t1).get_all().len(), 4); tasks.move_up(); assert_position!(tasks, t1);