forked from janek/mostr
feat: live updates via mpsc
This commit is contained in:
parent
27324f8601
commit
b30994c151
|
@ -14,6 +14,4 @@ nostril --envelope --content "realtime message" --kind 90002 | websocat ws://loc
|
||||||
|
|
||||||
## Plans
|
## Plans
|
||||||
|
|
||||||
- TUI
|
- TUI - Clear terminal?
|
||||||
- Send messages asynchronously
|
|
||||||
- How to clear terminal?
|
|
127
src/main.rs
127
src/main.rs
|
@ -1,9 +1,13 @@
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
use std::io::{stdin, stdout, Write};
|
use std::fmt::Display;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{Read, stdin, stdout, Write};
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::time::Duration;
|
use std::str::FromStr;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use nostr_sdk::async_utility::futures_util::TryFutureExt;
|
use nostr_sdk::async_utility::futures_util::TryFutureExt;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
@ -98,20 +102,23 @@ async fn main() {
|
||||||
|
|
||||||
client.connect().await;
|
client.connect().await;
|
||||||
|
|
||||||
let mut tasks: Tasks = Default::default();
|
let (tx, rx) = mpsc::channel::<Event>();
|
||||||
for argument in args().skip(1) {
|
let mut tasks: Tasks = Tasks::from(EventSender {
|
||||||
tasks.add_task(make_task(&argument, &[Tag::Hashtag("arg".to_string())]));
|
keys: MY_KEYS.clone(),
|
||||||
}
|
tx,
|
||||||
|
});
|
||||||
|
|
||||||
let sub_id: SubscriptionId = client.subscribe(vec![Filter::new()], None).await;
|
let sub_id: SubscriptionId = client.subscribe(vec![Filter::new()], None).await;
|
||||||
|
eprintln!("Subscribed with {}", sub_id);
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
|
|
||||||
println!("Finding existing events");
|
/*println!("Finding existing events");
|
||||||
let res = client
|
let _ = client
|
||||||
.get_events_of(vec![Filter::new()], None)
|
.get_events_of(vec![Filter::new()], Some(Duration::from_secs(5)))
|
||||||
.map_ok(|res| {
|
.map_ok(|res| {
|
||||||
println!("Found {} events", res.len());
|
println!("Found {} events", res.len());
|
||||||
let (mut task_events, props): (Vec<Event>, Vec<Event>) = res.into_iter().partition(|e| e.kind.as_u32() == 1621);
|
let (mut task_events, props): (Vec<Event>, Vec<Event>) =
|
||||||
|
res.into_iter().partition(|e| e.kind.as_u32() == 1621);
|
||||||
task_events.sort_unstable();
|
task_events.sort_unstable();
|
||||||
for event in task_events {
|
for event in task_events {
|
||||||
print_event(&event);
|
print_event(&event);
|
||||||
|
@ -122,13 +129,35 @@ async fn main() {
|
||||||
tasks.add_prop(&event);
|
tasks.add_prop(&event);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;*/
|
||||||
|
|
||||||
|
let sender = tokio::spawn(async move {
|
||||||
|
while let Ok(e) = rx.recv() {
|
||||||
|
//eprintln!("Sending {}", e.id);
|
||||||
|
let _ = client.send_event(e).await;
|
||||||
|
}
|
||||||
|
println!("Stopping listeners...");
|
||||||
|
client.unsubscribe_all().await;
|
||||||
|
});
|
||||||
|
for argument in args().skip(1) {
|
||||||
|
tasks.make_task(&argument);
|
||||||
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
tasks.print_current_tasks();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
while let Ok(notification) = notifications.try_recv() {
|
||||||
|
if let RelayPoolNotification::Event {
|
||||||
|
subscription_id,
|
||||||
|
event,
|
||||||
|
..
|
||||||
|
} = notification
|
||||||
|
{
|
||||||
|
print_event(&event);
|
||||||
|
tasks.add(*event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tasks.print_current_tasks();
|
||||||
|
|
||||||
print!(" {}> ", tasks.taskpath(tasks.get_position()));
|
print!(" {}> ", tasks.taskpath(tasks.get_position()));
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
match stdin().lines().next() {
|
match stdin().lines().next() {
|
||||||
|
@ -183,17 +212,19 @@ async fn main() {
|
||||||
if !slice.is_empty() {
|
if !slice.is_empty() {
|
||||||
pos = EventId::parse(slice).ok().or_else(|| {
|
pos = EventId::parse(slice).ok().or_else(|| {
|
||||||
tasks.move_to(pos);
|
tasks.move_to(pos);
|
||||||
let filtered: Vec<EventId> = tasks.current_tasks().iter().filter(|t| t.event.content.starts_with(slice)).map(|t| t.event.id).collect();
|
let filtered: Vec<EventId> = tasks
|
||||||
|
.current_tasks()
|
||||||
|
.iter()
|
||||||
|
.filter(|t| t.event.content.starts_with(slice))
|
||||||
|
.map(|t| t.event.id)
|
||||||
|
.collect();
|
||||||
match filtered.len() {
|
match filtered.len() {
|
||||||
0 => {
|
0 => {
|
||||||
// No match, new task
|
// No match, new task
|
||||||
let task = tasks.make_task(slice);
|
tasks.make_task(slice)
|
||||||
let ret = Some(task.id);
|
|
||||||
tasks.add_task(task);
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
// One match, select
|
// One match, activate
|
||||||
Some(filtered.first().unwrap().clone())
|
Some(filtered.first().unwrap().clone())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -212,59 +243,31 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
tasks.add_task(tasks.make_task(&input));
|
tasks.make_task(&input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Err(e)) => eprintln!("{}", e),
|
Some(Err(e)) => eprintln!("{}", e),
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Ok(notification) = notifications.try_recv() {
|
|
||||||
if let RelayPoolNotification::Event {
|
|
||||||
subscription_id,
|
|
||||||
event,
|
|
||||||
..
|
|
||||||
} = notification
|
|
||||||
{
|
|
||||||
print_event(&event);
|
|
||||||
tasks.add(*event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.print_current_tasks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.update_state("", |t| if t.pure_state() == State::Active { Some(State::Open) } else { None });
|
tasks.update_state("", |t| {
|
||||||
|
if t.pure_state() == State::Active {
|
||||||
|
Some(State::Open)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
drop(tasks);
|
||||||
|
|
||||||
println!();
|
eprintln!("Waiting for sync to relay...");
|
||||||
println!("Submitting events");
|
or_print(sender.await);
|
||||||
// TODO send via message passing
|
|
||||||
let _ = client
|
|
||||||
.batch_event(
|
|
||||||
tasks
|
|
||||||
.tasks
|
|
||||||
.into_values()
|
|
||||||
.flat_map(|t| {
|
|
||||||
let mut ev = t.props;
|
|
||||||
ev.push(t.event);
|
|
||||||
ev
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
RelaySendOptions::new().skip_send_confirmation(true),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_task(text: &str, tags: &[Tag]) -> Event {
|
|
||||||
make_event(Kind::from(1621), text, tags)
|
|
||||||
}
|
|
||||||
fn make_event(kind: Kind, text: &str, tags: &[Tag]) -> Event {
|
|
||||||
EventBuilder::new(kind, text, tags.to_vec())
|
|
||||||
.to_event(&MY_KEYS)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_event(event: &Event) {
|
fn print_event(event: &Event) {
|
||||||
println!("At {} found {} kind {} '{}' {:?}", event.created_at, event.id, event.kind, event.content, event.tags);
|
eprintln!(
|
||||||
|
"At {} found {} kind {} '{}' {:?}",
|
||||||
|
event.created_at, event.id, event.kind, event.content, event.tags
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
25
src/task.rs
25
src/task.rs
|
@ -1,18 +1,19 @@
|
||||||
use crate::make_event;
|
use std::collections::{BTreeSet, HashSet};
|
||||||
use nostr_sdk::{Event, EventId, Kind, Tag, Timestamp};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use nostr_sdk::{Event, EventId, Kind, Tag, Timestamp};
|
||||||
|
|
||||||
pub(crate) struct Task {
|
pub(crate) struct Task {
|
||||||
pub(crate) event: Event,
|
pub(crate) event: Event,
|
||||||
pub(crate) children: Vec<EventId>,
|
pub(crate) children: HashSet<EventId>,
|
||||||
pub(crate) props: Vec<Event>,
|
pub(crate) props: BTreeSet<Event>,
|
||||||
}
|
}
|
||||||
impl Task {
|
impl Task {
|
||||||
pub(crate) fn new(event: Event) -> Task {
|
pub(crate) fn new(event: Event) -> Task {
|
||||||
Task {
|
Task {
|
||||||
event,
|
event,
|
||||||
children: Vec::new(),
|
children: Default::default(),
|
||||||
props: Vec::new(),
|
props: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,16 +66,6 @@ impl Task {
|
||||||
self.state().map_or(State::Open, |s| s.state)
|
self.state().map_or(State::Open, |s| s.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_state(&mut self, state: State, comment: &str) -> Event {
|
|
||||||
let event = make_event(
|
|
||||||
state.kind(),
|
|
||||||
comment,
|
|
||||||
&[Tag::event(self.event.id)],
|
|
||||||
);
|
|
||||||
self.props.push(event.clone());
|
|
||||||
event
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_state(&self) -> TaskState {
|
fn default_state(&self) -> TaskState {
|
||||||
TaskState {
|
TaskState {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -149,7 +140,7 @@ pub(crate) enum State {
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
impl State {
|
impl State {
|
||||||
fn kind(&self) -> Kind {
|
pub(crate) fn kind(&self) -> Kind {
|
||||||
match self {
|
match self {
|
||||||
State::Open => Kind::from(1630),
|
State::Open => Kind::from(1630),
|
||||||
State::Done => Kind::from(1631),
|
State::Done => Kind::from(1631),
|
||||||
|
|
56
src/tasks.rs
56
src/tasks.rs
|
@ -1,8 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use nostr_sdk::{Event, EventId, Tag};
|
use nostr_sdk::{Event, EventBuilder, EventId, Kind, Tag};
|
||||||
|
|
||||||
use crate::make_task;
|
use crate::{EventSender, TASK_KIND};
|
||||||
use crate::task::{State, Task};
|
use crate::task::{State, Task};
|
||||||
|
|
||||||
type TaskMap = HashMap<EventId, Task>;
|
type TaskMap = HashMap<EventId, Task>;
|
||||||
|
@ -15,15 +15,17 @@ pub(crate) struct Tasks {
|
||||||
position: Option<EventId>,
|
position: Option<EventId>,
|
||||||
/// A filtered view of the current tasks
|
/// A filtered view of the current tasks
|
||||||
view: Vec<EventId>,
|
view: Vec<EventId>,
|
||||||
|
sender: EventSender
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Tasks {
|
impl Tasks {
|
||||||
fn default() -> Self {
|
pub(crate) fn from(sender: EventSender) -> Self {
|
||||||
Tasks {
|
Tasks {
|
||||||
tasks: Default::default(),
|
tasks: Default::default(),
|
||||||
properties: vec!["id".into(), "name".into(), "state".into()],
|
properties: vec!["id".into(), "name".into(), "state".into(), "ttime".into()],
|
||||||
position: None,
|
position: None,
|
||||||
view: Default::default(),
|
view: Default::default(),
|
||||||
|
sender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,10 +35,6 @@ impl Tasks {
|
||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_tasks(&self, tasks: &Vec<EventId>) -> Vec<&Task> {
|
|
||||||
tasks.iter().filter_map(|id| self.tasks.get(id)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Total time this task and its subtasks have been active
|
/// Total time this task and its subtasks have been active
|
||||||
fn total_time_tracked(&self, task: &EventId) -> u64 {
|
fn total_time_tracked(&self, task: &EventId) -> u64 {
|
||||||
self.tasks.get(task).map_or(0, |t| {
|
self.tasks.get(task).map_or(0, |t| {
|
||||||
|
@ -53,7 +51,7 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn current_tasks(&self) -> Vec<&Task> {
|
pub(crate) fn current_tasks(&self) -> Vec<&Task> {
|
||||||
let res = self.collect_tasks(&self.view);
|
let res: Vec<&Task> = self.view.iter().filter_map(|id| self.tasks.get(id)).collect();
|
||||||
if res.len() > 0 {
|
if res.len() > 0 {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +60,7 @@ impl Tasks {
|
||||||
|p| {
|
|p| {
|
||||||
self.tasks
|
self.tasks
|
||||||
.get(&p)
|
.get(&p)
|
||||||
.map_or(Vec::new(), |t| self.collect_tasks(&t.children))
|
.map_or(Vec::new(), |t| t.children.iter().filter_map(|id| self.tasks.get(id)).collect())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -86,11 +84,19 @@ impl Tasks {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_task(&self, input: &str) -> Event {
|
pub(crate) fn make_task(&mut self, input: &str) -> Option<EventId> {
|
||||||
|
self.sender.submit(self.build_task(input)).map(|e| {
|
||||||
|
let id = e.id;
|
||||||
|
self.add_task(e);
|
||||||
|
id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_task(&self, input: &str) -> EventBuilder {
|
||||||
let mut tags: Vec<Tag> = Vec::new();
|
let mut tags: Vec<Tag> = Vec::new();
|
||||||
self.position.inspect(|p| tags.push(Tag::event(*p)));
|
self.position.inspect(|p| tags.push(Tag::event(*p)));
|
||||||
return match input.split_once(": ") {
|
return match input.split_once(": ") {
|
||||||
None => make_task(&input, &tags),
|
None => EventBuilder::new(Kind::from(TASK_KIND), input, tags),
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
tags.append(
|
tags.append(
|
||||||
&mut s
|
&mut s
|
||||||
|
@ -99,7 +105,7 @@ impl Tasks {
|
||||||
.map(|t| Tag::Hashtag(t.to_string()))
|
.map(|t| Tag::Hashtag(t.to_string()))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
make_task(s.0, &tags)
|
EventBuilder::new(Kind::from(TASK_KIND), s.0, tags)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -121,12 +127,16 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_task(&mut self, event: Event) {
|
pub(crate) fn add_task(&mut self, event: Event) {
|
||||||
self.referenced_tasks(&event, |t| t.children.push(event.id));
|
self.referenced_tasks(&event, |t| { t.children.insert(event.id); });
|
||||||
self.tasks.insert(event.id, Task::new(event));
|
if self.tasks.contains_key(&event.id) {
|
||||||
|
//eprintln!("Did not insert duplicate event {}", event.id);
|
||||||
|
} else {
|
||||||
|
self.tasks.insert(event.id, Task::new(event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_prop(&mut self, event: &Event) {
|
pub(crate) fn add_prop(&mut self, event: &Event) {
|
||||||
self.referenced_tasks(&event, |t| t.props.push(event.clone()));
|
self.referenced_tasks(&event, |t| { t.props.insert(event.clone()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn move_up(&mut self) {
|
pub(crate) fn move_up(&mut self) {
|
||||||
|
@ -182,9 +192,15 @@ impl Tasks {
|
||||||
where
|
where
|
||||||
F: FnOnce(&Task) -> Option<State>,
|
F: FnOnce(&Task) -> Option<State>,
|
||||||
{
|
{
|
||||||
self.tasks.get_mut(id).and_then(|t| {
|
self.tasks.get_mut(id).and_then(|task| {
|
||||||
f(t).map(|s| {
|
f(task).and_then(|state| {
|
||||||
t.update_state(s, comment)
|
self.sender.submit(EventBuilder::new(
|
||||||
|
state.kind(),
|
||||||
|
comment,
|
||||||
|
vec![Tag::event(task.event.id)],
|
||||||
|
))
|
||||||
|
}).inspect(|e| {
|
||||||
|
task.props.insert(e.clone());
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue