Compare commits
No commits in common. "45b8f9cf0fa9d9ee7c39d2383a92bed2df539cf9" and "619bcfbbad4269b1a81b3177550ecc9f51a5b4b8" have entirely different histories.
45b8f9cf0f
...
619bcfbbad
5 changed files with 22 additions and 90 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -961,7 +961,6 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"colog",
|
"colog",
|
||||||
"colored",
|
"colored",
|
||||||
"env_logger",
|
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
|
|
|
@ -16,7 +16,6 @@ xdg = "2.5"
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
env_logger = "0.11"
|
|
||||||
colog = "1.3"
|
colog = "1.3"
|
||||||
colored = "2.1"
|
colored = "2.1"
|
||||||
nostr-sdk = "0.33"
|
nostr-sdk = "0.33"
|
||||||
|
|
91
src/main.rs
91
src/main.rs
|
@ -9,21 +9,18 @@ use std::ops::Sub;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::RecvTimeoutError;
|
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
|
use chrono::DateTime;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use env_logger::Builder;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, LevelFilter, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
use crate::helpers::*;
|
use crate::helpers::*;
|
||||||
use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND};
|
use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND};
|
||||||
use crate::MostrMessage::AddTasks;
|
|
||||||
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State};
|
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State};
|
||||||
use crate::tasks::{PropertyCollection, StateFilter, Tasks};
|
use crate::tasks::{PropertyCollection, StateFilter, Tasks};
|
||||||
|
|
||||||
|
@ -32,20 +29,17 @@ mod task;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
mod kinds;
|
mod kinds;
|
||||||
|
|
||||||
const UNDO_DELAY: u64 = 60;
|
|
||||||
const INACTVITY_DELAY: u64 = 200;
|
|
||||||
|
|
||||||
type Events = Vec<Event>;
|
type Events = Vec<Event>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct EventSender {
|
struct EventSender {
|
||||||
url: Option<Url>,
|
url: Option<Url>,
|
||||||
tx: Sender<MostrMessage>,
|
tx: Sender<(Url, Events)>,
|
||||||
keys: Keys,
|
keys: Keys,
|
||||||
queue: RefCell<Events>,
|
queue: RefCell<Events>,
|
||||||
}
|
}
|
||||||
impl EventSender {
|
impl EventSender {
|
||||||
fn from(url: Option<Url>, tx: &Sender<MostrMessage>, keys: &Keys) -> Self {
|
fn from(url: Option<Url>, tx: &Sender<(Url, Events)>, keys: &Keys) -> Self {
|
||||||
EventSender {
|
EventSender {
|
||||||
url,
|
url,
|
||||||
tx: tx.clone(),
|
tx: tx.clone(),
|
||||||
|
@ -58,7 +52,7 @@ impl EventSender {
|
||||||
{
|
{
|
||||||
// Always flush if oldest event older than a minute or newer than now
|
// Always flush if oldest event older than a minute or newer than now
|
||||||
let borrow = self.queue.borrow();
|
let borrow = self.queue.borrow();
|
||||||
let min = Timestamp::now().sub(UNDO_DELAY);
|
let min = Timestamp::now().sub(60u64);
|
||||||
if borrow.iter().any(|e| e.created_at < min || e.created_at > Timestamp::now()) {
|
if borrow.iter().any(|e| e.created_at < min || e.created_at > Timestamp::now()) {
|
||||||
drop(borrow);
|
drop(borrow);
|
||||||
debug!("Flushing event queue because it is older than a minute");
|
debug!("Flushing event queue because it is older than a minute");
|
||||||
|
@ -80,7 +74,7 @@ impl EventSender {
|
||||||
debug!("Flushing {} events from queue", self.queue.borrow().len());
|
debug!("Flushing {} events from queue", self.queue.borrow().len());
|
||||||
let values = self.clear();
|
let values = self.clear();
|
||||||
self.url.as_ref().map(|url| {
|
self.url.as_ref().map(|url| {
|
||||||
or_print(self.tx.send(AddTasks(url.clone(), values)));
|
or_print(self.tx.send((url.clone(), values)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/// Sends all pending events if there is a non-tracking event
|
/// Sends all pending events if there is a non-tracking event
|
||||||
|
@ -104,29 +98,9 @@ impl Drop for EventSender {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
enum MostrMessage {
|
|
||||||
Flush,
|
|
||||||
NewRelay(Url),
|
|
||||||
AddTasks(Url, Vec<Event>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let mut args = args().skip(1).peekable();
|
colog::init();
|
||||||
if args.peek().is_some_and(|arg| arg == "--debug") {
|
|
||||||
args.next();
|
|
||||||
Builder::new()
|
|
||||||
.filter(None, LevelFilter::Debug)
|
|
||||||
.filter(Some("mostr"), LevelFilter::Trace)
|
|
||||||
.parse_default_env()
|
|
||||||
.init();
|
|
||||||
} else {
|
|
||||||
colog::default_builder()
|
|
||||||
.filter(Some("nostr-relay-pool"), LevelFilter::Error)
|
|
||||||
//.filter(Some("nostr-relay-pool::relay::internal"), LevelFilter::Off)
|
|
||||||
.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_dir = or_print(BaseDirectories::new())
|
let config_dir = or_print(BaseDirectories::new())
|
||||||
.and_then(|d| or_print(d.create_config_directory("mostr")))
|
.and_then(|d| or_print(d.create_config_directory("mostr")))
|
||||||
|
@ -209,7 +183,7 @@ async fn main() {
|
||||||
client.connect().await;
|
client.connect().await;
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel::<MostrMessage>();
|
let (tx, rx) = mpsc::channel();
|
||||||
let tasks_for_url = |url: Option<Url>| Tasks::from(url, &tx, &keys);
|
let tasks_for_url = |url: Option<Url>| Tasks::from(url, &tx, &keys);
|
||||||
let mut relays: HashMap<Url, Tasks> =
|
let mut relays: HashMap<Url, Tasks> =
|
||||||
client.relays().await.into_keys().map(|url| (url.clone(), tasks_for_url(Some(url)))).collect();
|
client.relays().await.into_keys().map(|url| (url.clone(), tasks_for_url(Some(url)))).collect();
|
||||||
|
@ -234,47 +208,12 @@ async fn main() {
|
||||||
.await;*/
|
.await;*/
|
||||||
|
|
||||||
let sender = tokio::spawn(async move {
|
let sender = tokio::spawn(async move {
|
||||||
let mut queue: Option<(Url, Vec<Event>)> = None;
|
while let Ok((url, events)) = rx.recv() {
|
||||||
|
trace!("Sending {:?}", events);
|
||||||
loop {
|
// TODO batch up further
|
||||||
// TODO invalid acknowledgement from bucket relay slows sending down
|
let _ = client.batch_event_to(vec![url], events, RelaySendOptions::new()).await;
|
||||||
match rx.recv_timeout(Duration::from_secs(INACTVITY_DELAY)) {
|
|
||||||
Ok(AddTasks(url, mut events)) => {
|
|
||||||
if 1 == 2 {
|
|
||||||
client.connect_relay("").await;
|
|
||||||
}
|
}
|
||||||
debug!("Queueing {:?}", &events);
|
info!("Shutting down sender thread");
|
||||||
if let Some((queue_url, mut queue_events)) = queue {
|
|
||||||
if queue_url == url {
|
|
||||||
queue_events.append(&mut events);
|
|
||||||
queue = Some((queue_url, queue_events));
|
|
||||||
} else {
|
|
||||||
info!("Sending {} events due to relay change", queue_events.len());
|
|
||||||
client.batch_event_to(vec![queue_url], queue_events, RelaySendOptions::new()).await;
|
|
||||||
queue = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if queue.is_none() {
|
|
||||||
events.reserve(events.len() + 10);
|
|
||||||
queue = Some((url, events))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(RecvTimeoutError::Timeout) => if let Some((url, events)) = queue {
|
|
||||||
info!("Sending {} events due to inactivity", events.len());
|
|
||||||
client.batch_event_to(vec![url], events, RelaySendOptions::new()).await;
|
|
||||||
queue = None;
|
|
||||||
}
|
|
||||||
arg => {
|
|
||||||
debug!("Finalizing nostr communication thread because of {:?}", arg);
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some((url, events)) = queue {
|
|
||||||
info!("Sending {} events before exiting", events.len());
|
|
||||||
client.batch_event_to(vec![url], events, RelaySendOptions::new()).await;
|
|
||||||
}
|
|
||||||
info!("Shutting down nostr communication thread");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut local_tasks = Tasks::from(None, &tx, &keys);
|
let mut local_tasks = Tasks::from(None, &tx, &keys);
|
||||||
|
@ -282,7 +221,7 @@ async fn main() {
|
||||||
|
|
||||||
{
|
{
|
||||||
let tasks = selected_relay.as_ref().and_then(|url| relays.get_mut(&url)).unwrap_or_else(|| &mut local_tasks);
|
let tasks = selected_relay.as_ref().and_then(|url| relays.get_mut(&url)).unwrap_or_else(|| &mut local_tasks);
|
||||||
for argument in args {
|
for argument in args().skip(1) {
|
||||||
tasks.make_task(&argument);
|
tasks.make_task(&argument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -493,9 +432,7 @@ async fn main() {
|
||||||
}
|
}
|
||||||
let slice = input[dots..].trim();
|
let slice = input[dots..].trim();
|
||||||
|
|
||||||
if pos != tasks.get_position() || slice.is_empty() {
|
|
||||||
tasks.move_to(pos);
|
tasks.move_to(pos);
|
||||||
}
|
|
||||||
if slice.is_empty() {
|
if slice.is_empty() {
|
||||||
if dots > 1 {
|
if dots > 1 {
|
||||||
info!("Moving up {} tasks", dots - 1)
|
info!("Moving up {} tasks", dots - 1)
|
||||||
|
|
|
@ -141,7 +141,7 @@ impl Task {
|
||||||
match property {
|
match property {
|
||||||
"id" => Some(self.event.id.to_string()),
|
"id" => Some(self.event.id.to_string()),
|
||||||
"parentid" => self.parent_id().map(|i| i.to_string()),
|
"parentid" => self.parent_id().map(|i| i.to_string()),
|
||||||
"status" => Some(self.state_or_default().get_label()),
|
"status" => Some(self.state_or_default().get_colored_label().to_string()),
|
||||||
"name" => Some(self.event.content.clone()),
|
"name" => Some(self.event.content.clone()),
|
||||||
"desc" => self.descriptions().last().cloned(),
|
"desc" => self.descriptions().last().cloned(),
|
||||||
"description" => Some(self.descriptions().join(" ")),
|
"description" => Some(self.descriptions().join(" ")),
|
||||||
|
|
13
src/tasks.rs
13
src/tasks.rs
|
@ -16,7 +16,7 @@ use nostr_sdk::{Event, EventBuilder, EventId, Keys, Kind, PublicKey, Tag, TagSta
|
||||||
use nostr_sdk::prelude::Marker;
|
use nostr_sdk::prelude::Marker;
|
||||||
use TagStandard::Hashtag;
|
use TagStandard::Hashtag;
|
||||||
|
|
||||||
use crate::{Events, EventSender, MostrMessage};
|
use crate::{Events, EventSender};
|
||||||
use crate::helpers::some_non_empty;
|
use crate::helpers::some_non_empty;
|
||||||
use crate::kinds::*;
|
use crate::kinds::*;
|
||||||
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State, Task, TaskState};
|
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State, Task, TaskState};
|
||||||
|
@ -105,7 +105,7 @@ impl Display for StateFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tasks {
|
impl Tasks {
|
||||||
pub(crate) fn from(url: Option<Url>, tx: &Sender<MostrMessage>, keys: &Keys) -> Self {
|
pub(crate) fn from(url: Option<Url>, tx: &Sender<(Url, Events)>, keys: &Keys) -> Self {
|
||||||
Self::with_sender(EventSender {
|
Self::with_sender(EventSender {
|
||||||
url,
|
url,
|
||||||
tx: tx.clone(),
|
tx: tx.clone(),
|
||||||
|
@ -127,7 +127,6 @@ impl Tasks {
|
||||||
],
|
],
|
||||||
sorting: VecDeque::from([
|
sorting: VecDeque::from([
|
||||||
"state".into(),
|
"state".into(),
|
||||||
"rtime".into(),
|
|
||||||
"name".into(),
|
"name".into(),
|
||||||
]),
|
]),
|
||||||
position: None, // TODO persist position
|
position: None, // TODO persist position
|
||||||
|
@ -403,7 +402,6 @@ impl Tasks {
|
||||||
writeln!(lock, "{}", self.properties.join("\t").bold())?;
|
writeln!(lock, "{}", self.properties.join("\t").bold())?;
|
||||||
let mut total_time = 0;
|
let mut total_time = 0;
|
||||||
let mut tasks = self.current_tasks();
|
let mut tasks = self.current_tasks();
|
||||||
let count = tasks.len();
|
|
||||||
tasks.sort_by_cached_key(|task| {
|
tasks.sort_by_cached_key(|task| {
|
||||||
self.sorting
|
self.sorting
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -414,16 +412,15 @@ impl Tasks {
|
||||||
writeln!(
|
writeln!(
|
||||||
lock,
|
lock,
|
||||||
"{}",
|
"{}",
|
||||||
self.properties.iter()
|
self.properties
|
||||||
|
.iter()
|
||||||
.map(|p| self.get_property(task, p.as_str()))
|
.map(|p| self.get_property(task, p.as_str()))
|
||||||
.join(" \t")
|
.join(" \t")
|
||||||
)?;
|
)?;
|
||||||
if self.depth < 2 || task.parent_id() == self.position.as_ref() {
|
|
||||||
total_time += self.total_time_tracked(task.event.id)
|
total_time += self.total_time_tracked(task.event.id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if total_time > 0 {
|
if total_time > 0 {
|
||||||
writeln!(lock, "{} visible tasks{}", count, display_time(" tracked a total of HHhMMm", total_time))?;
|
writeln!(lock, "{}", display_time("Total time tracked on visible tasks: HHh MMm", total_time))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue