forked from janek/mostr
refactor(tasks): extract more modules
This commit is contained in:
parent
77bc359d8a
commit
d85ff3ac8d
5 changed files with 262 additions and 247 deletions
|
@ -1,5 +1,5 @@
|
||||||
use crate::task::MARKER_PARENT;
|
use crate::task::MARKER_PARENT;
|
||||||
use crate::tasks::nostr_users::NostrUsers;
|
use crate::tasks::NostrUsers;
|
||||||
use crate::tasks::HIGH_PRIO;
|
use crate::tasks::HIGH_PRIO;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::{EventBuilder, EventId, Kind, PublicKey, Tag, TagKind, TagStandard};
|
use nostr_sdk::{EventBuilder, EventId, Kind, PublicKey, Tag, TagKind, TagStandard};
|
||||||
|
|
232
src/tasks.rs
232
src/tasks.rs
|
@ -1,6 +1,8 @@
|
||||||
pub(crate) mod nostr_users;
|
mod nostr_users;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
mod children_traversal;
|
||||||
|
mod durations;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
|
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
@ -17,7 +19,9 @@ use crate::helpers::{
|
||||||
};
|
};
|
||||||
use crate::kinds::*;
|
use crate::kinds::*;
|
||||||
use crate::task::{State, StateChange, Task, MARKER_DEPENDS, MARKER_PARENT, MARKER_PROPERTY};
|
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 colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
@ -39,7 +43,7 @@ pub(crate) fn now() -> Timestamp {
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskMap = HashMap<EventId, Task>;
|
type TaskMap = HashMap<EventId, Task>;
|
||||||
trait TaskMapMethods {
|
pub(super) trait TaskMapMethods {
|
||||||
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a;
|
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a;
|
||||||
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a;
|
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a;
|
||||||
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=EventId> + 'a;
|
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=EventId> + 'a;
|
||||||
|
@ -52,7 +56,6 @@ impl TaskMapMethods for TaskMap {
|
||||||
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a {
|
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a {
|
||||||
self.values().filter(move |t| t.parent_id() == id.as_ref())
|
self.values().filter(move |t| t.parent_id() == id.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=EventId> + 'a {
|
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=EventId> + 'a {
|
||||||
self.children_for(Some(id)).map(|t| t.get_id())
|
self.children_for(Some(id)).map(|t| t.get_id())
|
||||||
}
|
}
|
||||||
|
@ -369,7 +372,7 @@ impl TasksRelay {
|
||||||
fn total_time_tracked(&self, id: EventId) -> u64 {
|
fn total_time_tracked(&self, id: EventId) -> u64 {
|
||||||
let mut total = 0;
|
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() {
|
for user in self.history.values() {
|
||||||
total += Durations::from(user.values(), &children)
|
total += Durations::from(user.values(), &children)
|
||||||
.sum::<Duration>()
|
.sum::<Duration>()
|
||||||
|
@ -1334,7 +1337,7 @@ impl TasksRelay {
|
||||||
let ids =
|
let ids =
|
||||||
if state == State::Closed {
|
if state == State::Closed {
|
||||||
// Close whole subtree
|
// Close whole subtree
|
||||||
ChildIterator::from(self, id).get_all()
|
ChildrenTraversal::from(self, id).get_all()
|
||||||
} else {
|
} else {
|
||||||
vec![id]
|
vec![id]
|
||||||
};
|
};
|
||||||
|
@ -1575,226 +1578,10 @@ pub(crate) fn join_tasks<'a>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn referenced_events(event: &Event) -> impl Iterator<Item=EventId> + '_ {
|
|
||||||
event.tags.iter().filter_map(|tag| match_event_tag(tag).map(|t| t.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn referenced_event(event: &Event) -> Option<EventId> {
|
pub fn referenced_event(event: &Event) -> Option<EventId> {
|
||||||
referenced_events(event).next()
|
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<EventId> {
|
|
||||||
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<Item=&'a Event>,
|
|
||||||
ids: &'a [EventId],
|
|
||||||
) -> impl Iterator<Item=(&Timestamp, Option<EventId>)> {
|
|
||||||
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<dyn Iterator<Item=&'a Event> + 'a>,
|
|
||||||
ids: &'a [EventId],
|
|
||||||
threshold: Option<Timestamp>,
|
|
||||||
}
|
|
||||||
impl Durations<'_> {
|
|
||||||
fn from<'b>(
|
|
||||||
events: impl IntoIterator<Item=&'b Event> + '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<Self::Item> {
|
|
||||||
let mut start: Option<u64> = 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<EventId>,
|
|
||||||
/// 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<EventId>) -> 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<EventId> {
|
|
||||||
while self.next().is_some() {}
|
|
||||||
self.queue
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all tasks until the specified depth
|
|
||||||
fn get_depth(mut self, depth: usize) -> Vec<EventId> {
|
|
||||||
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<EventId> {
|
|
||||||
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<F>(&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::Item> {
|
|
||||||
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> {
|
struct ParentIterator<'a> {
|
||||||
tasks: &'a TaskMap,
|
tasks: &'a TaskMap,
|
||||||
current: Option<EventId>,
|
current: Option<EventId>,
|
||||||
|
@ -1809,3 +1596,4 @@ impl<'a> Iterator for ParentIterator<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl FusedIterator for ParentIterator<'_> {}
|
||||||
|
|
160
src/tasks/children_traversal.rs
Normal file
160
src/tasks/children_traversal.rs
Normal file
|
@ -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<EventId>,
|
||||||
|
/// 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<EventId>) -> 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<EventId> {
|
||||||
|
while self.next().is_some() {}
|
||||||
|
self.queue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all tasks until the specified depth
|
||||||
|
pub(super) fn get_depth(mut self, depth: usize) -> Vec<EventId> {
|
||||||
|
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<EventId> {
|
||||||
|
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<F>(&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::Item> {
|
||||||
|
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();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
83
src/tasks/durations.rs
Normal file
83
src/tasks/durations.rs
Normal file
|
@ -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<Item=EventId> + '_ {
|
||||||
|
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<EventId> {
|
||||||
|
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<Item=&'a Event>,
|
||||||
|
ids: &'a [EventId],
|
||||||
|
) -> impl Iterator<Item=(&Timestamp, Option<EventId>)> {
|
||||||
|
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<dyn Iterator<Item=&'a Event> + 'a>,
|
||||||
|
ids: &'a [EventId],
|
||||||
|
threshold: Option<Timestamp>,
|
||||||
|
}
|
||||||
|
impl Durations<'_> {
|
||||||
|
pub(super) fn from<'b>(
|
||||||
|
events: impl IntoIterator<Item=&'b Event> + '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<Self::Item> {
|
||||||
|
let mut start: Option<u64> = 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
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use itertools::Itertools;
|
||||||
use nostr_sdk::{EventBuilder, EventId, Keys, Kind, Tag, Timestamp};
|
use nostr_sdk::{EventBuilder, EventId, Keys, Kind, Tag, Timestamp};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
fn stub_tasks() -> TasksRelay {
|
pub(super) fn stub_tasks() -> TasksRelay {
|
||||||
use nostr_sdk::Keys;
|
use nostr_sdk::Keys;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
@ -312,22 +312,6 @@ fn test_tracking() {
|
||||||
// TODO test received events
|
// 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]
|
#[test]
|
||||||
fn test_depth() {
|
fn test_depth() {
|
||||||
let mut tasks = stub_tasks();
|
let mut tasks = stub_tasks();
|
||||||
|
@ -364,13 +348,13 @@ fn test_depth() {
|
||||||
tasks.view_depth = 2;
|
tasks.view_depth = 2;
|
||||||
assert_tasks_view!(tasks, [t111]);
|
assert_tasks_view!(tasks, [t111]);
|
||||||
|
|
||||||
assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_all().len(), 1);
|
assert_eq!(ChildrenTraversal::from(&tasks, EventId::all_zeros()).get_all().len(), 1);
|
||||||
assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_depth(0).len(), 1);
|
assert_eq!(ChildrenTraversal::from(&tasks, EventId::all_zeros()).get_depth(0).len(), 1);
|
||||||
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(0).len(), 1);
|
assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(0).len(), 1);
|
||||||
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(1).len(), 3);
|
assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(1).len(), 3);
|
||||||
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(2).len(), 4);
|
assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(2).len(), 4);
|
||||||
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(9).len(), 4);
|
assert_eq!(ChildrenTraversal::from(&tasks, t1).get_depth(9).len(), 4);
|
||||||
assert_eq!(ChildIterator::from(&tasks, t1).get_all().len(), 4);
|
assert_eq!(ChildrenTraversal::from(&tasks, t1).get_all().len(), 4);
|
||||||
|
|
||||||
tasks.move_up();
|
tasks.move_up();
|
||||||
assert_position!(tasks, t1);
|
assert_position!(tasks, t1);
|
||||||
|
|
Loading…
Add table
Reference in a new issue