minor refactors, add OutgoingMessage

This commit is contained in:
ranfdev
2024-11-21 12:28:58 +01:00
parent a758ffcd6e
commit b3d0aaf277
10 changed files with 184 additions and 208 deletions

View File

@ -0,0 +1,14 @@
macro_rules! send_command {
($self:expr, $command:expr) => {{
let (resp_tx, rx) = oneshot::channel();
$self
.command_tx
.send($command(resp_tx))
.await
.map_err(|_| anyhow::anyhow!("Actor mailbox error"))?;
rx.await
.map_err(|_| anyhow::anyhow!("Actor response error"))?
}};
}
pub(crate) use send_command;

View File

@ -1,3 +1,4 @@
mod actor_utils;
pub mod credentials; pub mod credentials;
mod http_client; mod http_client;
mod listener; mod listener;
@ -31,6 +32,8 @@ pub enum Error {
InvalidTopic(String), InvalidTopic(String),
#[error("invalid server base url {0:?}")] #[error("invalid server base url {0:?}")]
InvalidServer(#[from] url::ParseError), InvalidServer(#[from] url::ParseError),
#[error("multiple errors in subscription model: {0:?}")]
InvalidSubscription(Vec<Error>),
#[error("duplicate message")] #[error("duplicate message")]
DuplicateMessage, DuplicateMessage,
#[error("can't parse the minimum set of required fields from the message {0}")] #[error("can't parse the minimum set of required fields from the message {0}")]

View File

@ -36,7 +36,7 @@ pub enum ServerEvent {
topic: String, topic: String,
}, },
#[serde(rename = "message")] #[serde(rename = "message")]
Message(models::Message), Message(models::ReceivedMessage),
#[serde(rename = "keepalive")] #[serde(rename = "keepalive")]
KeepAlive { KeepAlive {
id: String, id: String,
@ -48,7 +48,7 @@ pub enum ServerEvent {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ListenerEvent { pub enum ListenerEvent {
Message(models::Message), Message(models::ReceivedMessage),
ConnectionStateChanged(ConnectionState), ConnectionStateChanged(ConnectionState),
} }
@ -281,7 +281,7 @@ impl ListenerHandle {
} }
// the response will be sent as an event in self.events // the response will be sent as an event in self.events
pub async fn request_state(&self) -> ConnectionState { pub async fn state(&self) -> ConnectionState {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
self.commands self.commands
.send(ListenerCommand::GetState(tx)) .send(ListenerCommand::GetState(tx))
@ -300,20 +300,6 @@ mod tests {
use super::*; use super::*;
// takes a list of pattern matches. It recvs events and then matches them
// against the macro parameters
macro_rules! assert_event_matches {
($listener:expr, $( $pattern:pat_param ),+ $(,)?) => {
$(
$listener.events.changed().await.unwrap();
let event = $listener.events.borrow().clone();
panic!("{:?}", &event);
assert!(matches!(event, $pattern));
)+
};
}
#[tokio::test] #[tokio::test]
async fn test_listener_reconnects_on_http_status_500() { async fn test_listener_reconnects_on_http_status_500() {
let local_set = LocalSet::new(); let local_set = LocalSet::new();

View File

@ -27,7 +27,7 @@ pub fn validate_topic(topic: &str) -> Result<&str, Error> {
} }
#[derive(Default, Clone, Debug, Serialize, Deserialize)] #[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct Message { pub struct ReceivedMessage {
pub id: String, pub id: String,
pub topic: String, pub topic: String,
pub expires: Option<u64>, pub expires: Option<u64>,
@ -59,7 +59,7 @@ pub struct Message {
pub actions: Vec<Action>, pub actions: Vec<Action>,
} }
impl Message { impl ReceivedMessage {
fn extend_with_emojis(&self, text: &mut String) { fn extend_with_emojis(&self, text: &mut String) {
// Add emojis // Add emojis
for t in &self.tags { for t in &self.tags {
@ -107,6 +107,37 @@ impl Message {
} }
} }
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct OutgoingMessage {
pub topic: String,
pub message: Option<String>,
#[serde(default = "Default::default")]
pub time: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<i8>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub attachment: Option<Attachment>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub call: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub actions: Vec<Action>,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MinMessage { pub struct MinMessage {
pub id: String, pub id: String,
@ -167,7 +198,7 @@ impl Subscription {
.push("auth"); .push("auth");
Ok(url) Ok(url)
} }
pub fn validate(self) -> Result<Self, Vec<crate::Error>> { pub fn validate(self) -> Result<Self, crate::Error> {
let mut errs = vec![]; let mut errs = vec![];
if let Err(e) = validate_topic(&self.topic) { if let Err(e) = validate_topic(&self.topic) {
errs.push(e); errs.push(e);
@ -176,7 +207,7 @@ impl Subscription {
errs.push(e); errs.push(e);
}; };
if !errs.is_empty() { if !errs.is_empty() {
return Err(errs); return Err(Error::InvalidSubscription(errs));
} }
Ok(self) Ok(self)
} }
@ -239,7 +270,7 @@ impl SubscriptionBuilder {
self self
} }
pub fn build(self) -> Result<Subscription, Vec<Error>> { pub fn build(self) -> Result<Subscription, Error> {
let res = Subscription { let res = Subscription {
server: self.server, server: self.server,
topic: self.topic, topic: self.topic,

View File

@ -1,3 +1,4 @@
use crate::actor_utils::send_command;
use crate::models::NullNetworkMonitor; use crate::models::NullNetworkMonitor;
use crate::models::NullNotifier; use crate::models::NullNotifier;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
@ -36,40 +37,39 @@ pub fn build_client() -> anyhow::Result<reqwest::Client> {
// Message types for the actor // Message types for the actor
#[derive()] #[derive()]
pub enum NtfyMessage { pub enum NtfyCommand {
Subscribe { Subscribe {
server: String, server: String,
topic: String, topic: String,
respond_to: oneshot::Sender<Result<SubscriptionHandle, Vec<anyhow::Error>>>, resp_tx: oneshot::Sender<Result<SubscriptionHandle, anyhow::Error>>,
}, },
Unsubscribe { Unsubscribe {
server: String, server: String,
topic: String, topic: String,
respond_to: oneshot::Sender<anyhow::Result<()>>, resp_tx: oneshot::Sender<anyhow::Result<()>>,
}, },
RefreshAll { RefreshAll {
respond_to: oneshot::Sender<anyhow::Result<()>>, resp_tx: oneshot::Sender<anyhow::Result<()>>,
}, },
ListSubscriptions { ListSubscriptions {
respond_to: oneshot::Sender<anyhow::Result<Vec<SubscriptionHandle>>>, resp_tx: oneshot::Sender<anyhow::Result<Vec<SubscriptionHandle>>>,
}, },
ListAccounts { ListAccounts {
respond_to: oneshot::Sender<anyhow::Result<Vec<Account>>>, resp_tx: oneshot::Sender<anyhow::Result<Vec<Account>>>,
}, },
WatchSubscribed { WatchSubscribed {
respond_to: oneshot::Sender<anyhow::Result<()>>, resp_tx: oneshot::Sender<anyhow::Result<()>>,
}, },
AddAccount { AddAccount {
server: String, server: String,
username: String, username: String,
password: String, password: String,
respond_to: oneshot::Sender<anyhow::Result<()>>, resp_tx: oneshot::Sender<anyhow::Result<()>>,
}, },
RemoveAccount { RemoveAccount {
server: String, server: String,
respond_to: oneshot::Sender<anyhow::Result<()>>, resp_tx: oneshot::Sender<anyhow::Result<()>>,
}, },
Shutdown,
} }
#[derive(Debug, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Hash, PartialEq, Eq)]
@ -81,12 +81,12 @@ pub struct WatchKey {
pub struct NtfyActor { pub struct NtfyActor {
listener_handles: Arc<RwLock<HashMap<WatchKey, SubscriptionHandle>>>, listener_handles: Arc<RwLock<HashMap<WatchKey, SubscriptionHandle>>>,
env: SharedEnv, env: SharedEnv,
command_rx: mpsc::Receiver<NtfyMessage>, command_rx: mpsc::Receiver<NtfyCommand>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct NtfyHandle { pub struct NtfyHandle {
command_tx: mpsc::Sender<NtfyMessage>, command_tx: mpsc::Sender<NtfyCommand>,
} }
impl NtfyActor { impl NtfyActor {
@ -108,19 +108,15 @@ impl NtfyActor {
&self, &self,
server: String, server: String,
topic: String, topic: String,
) -> Result<SubscriptionHandle, Vec<anyhow::Error>> { ) -> Result<SubscriptionHandle, anyhow::Error> {
let subscription = models::Subscription::builder(topic.clone()) let subscription = models::Subscription::builder(topic.clone())
.server(server.clone()) .server(server.clone())
.build() .build()?;
.map_err(|e| e.into_iter().map(|e| anyhow!(e)).collect::<Vec<_>>())?;
let mut db = self.env.db.clone(); let mut db = self.env.db.clone();
db.insert_subscription(subscription.clone()) db.insert_subscription(subscription.clone())?;
.map_err(|e| vec![anyhow!(e)])?;
self.listen(subscription) self.listen(subscription).await
.await
.map_err(|e| vec![anyhow!(e)])
} }
async fn handle_unsubscribe(&mut self, server: String, topic: String) -> anyhow::Result<()> { async fn handle_unsubscribe(&mut self, server: String, topic: String) -> anyhow::Result<()> {
@ -141,25 +137,25 @@ impl NtfyActor {
pub async fn run(&mut self) { pub async fn run(&mut self) {
while let Some(msg) = self.command_rx.recv().await { while let Some(msg) = self.command_rx.recv().await {
match msg { match msg {
NtfyMessage::Subscribe { NtfyCommand::Subscribe {
server, server,
topic, topic,
respond_to, resp_tx,
} => { } => {
let result = self.handle_subscribe(server, topic).await; let result = self.handle_subscribe(server, topic).await;
let _ = respond_to.send(result); let _ = resp_tx.send(result);
} }
NtfyMessage::Unsubscribe { NtfyCommand::Unsubscribe {
server, server,
topic, topic,
respond_to, resp_tx,
} => { } => {
let result = self.handle_unsubscribe(server, topic).await; let result = self.handle_unsubscribe(server, topic).await;
let _ = respond_to.send(result); let _ = resp_tx.send(result);
} }
NtfyMessage::RefreshAll { respond_to } => { NtfyCommand::RefreshAll { resp_tx } => {
let mut res = Ok(()); let mut res = Ok(());
for sub in self.listener_handles.read().await.values() { for sub in self.listener_handles.read().await.values() {
res = sub.restart().await; res = sub.restart().await;
@ -167,10 +163,10 @@ impl NtfyActor {
break; break;
} }
} }
let _ = respond_to.send(res); let _ = resp_tx.send(res);
} }
NtfyMessage::ListSubscriptions { respond_to } => { NtfyCommand::ListSubscriptions { resp_tx } => {
let subs = self let subs = self
.listener_handles .listener_handles
.read() .read()
@ -178,10 +174,10 @@ impl NtfyActor {
.values() .values()
.cloned() .cloned()
.collect(); .collect();
let _ = respond_to.send(Ok(subs)); let _ = resp_tx.send(Ok(subs));
} }
NtfyMessage::ListAccounts { respond_to } => { NtfyCommand::ListAccounts { resp_tx } => {
let accounts = self let accounts = self
.env .env
.credentials .credentials
@ -192,34 +188,32 @@ impl NtfyActor {
username: credential.username, username: credential.username,
}) })
.collect(); .collect();
let _ = respond_to.send(Ok(accounts)); let _ = resp_tx.send(Ok(accounts));
} }
NtfyMessage::WatchSubscribed { respond_to } => { NtfyCommand::WatchSubscribed { resp_tx } => {
let result = self.handle_watch_subscribed().await; let result = self.handle_watch_subscribed().await;
let _ = respond_to.send(result); let _ = resp_tx.send(result);
} }
NtfyMessage::AddAccount { NtfyCommand::AddAccount {
server, server,
username, username,
password, password,
respond_to, resp_tx,
} => { } => {
let result = self let result = self
.env .env
.credentials .credentials
.insert(&server, &username, &password) .insert(&server, &username, &password)
.await; .await;
let _ = respond_to.send(result); let _ = resp_tx.send(result);
} }
NtfyMessage::RemoveAccount { server, respond_to } => { NtfyCommand::RemoveAccount { server, resp_tx } => {
let result = self.env.credentials.delete(&server).await; let result = self.env.credentials.delete(&server).await;
let _ = respond_to.send(result); let _ = resp_tx.send(result);
} }
NtfyMessage::Shutdown => break,
} }
} }
} }
@ -274,73 +268,36 @@ impl NtfyHandle {
&self, &self,
server: &str, server: &str,
topic: &str, topic: &str,
) -> Result<SubscriptionHandle, Vec<anyhow::Error>> { ) -> Result<SubscriptionHandle, anyhow::Error> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::Subscribe {
self.command_tx server: server.to_string(),
.send(NtfyMessage::Subscribe { topic: topic.to_string(),
server: server.to_string(), resp_tx,
topic: topic.to_string(), })
respond_to: tx,
})
.await
.map_err(|_| vec![anyhow!("Actor mailbox error")])?;
rx.await
.map_err(|_| vec![anyhow!("Actor response error")])?
} }
pub async fn unsubscribe(&self, server: &str, topic: &str) -> anyhow::Result<()> { pub async fn unsubscribe(&self, server: &str, topic: &str) -> anyhow::Result<()> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::Unsubscribe {
self.command_tx server: server.to_string(),
.send(NtfyMessage::Unsubscribe { topic: topic.to_string(),
server: server.to_string(), resp_tx,
topic: topic.to_string(), })
respond_to: tx,
})
.await
.map_err(|_| anyhow!("Actor mailbox error"))?;
rx.await.map_err(|_| anyhow!("Actor response error"))?
} }
pub async fn refresh_all(&self) -> anyhow::Result<()> { pub async fn refresh_all(&self) -> anyhow::Result<()> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::RefreshAll { resp_tx })
self.command_tx
.send(NtfyMessage::RefreshAll { respond_to: tx })
.await
.map_err(|_| anyhow!("Actor mailbox error"))?;
rx.await.map_err(|_| anyhow!("Actor response error"))?
} }
pub async fn list_subscriptions(&self) -> anyhow::Result<Vec<SubscriptionHandle>> { pub async fn list_subscriptions(&self) -> anyhow::Result<Vec<SubscriptionHandle>> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::ListSubscriptions { resp_tx })
self.command_tx
.send(NtfyMessage::ListSubscriptions { respond_to: tx })
.await
.map_err(|_| anyhow!("Actor mailbox error"))?;
rx.await.map_err(|_| anyhow!("Actor response error"))?
} }
pub async fn list_accounts(&self) -> anyhow::Result<Vec<Account>> { pub async fn list_accounts(&self) -> anyhow::Result<Vec<Account>> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::ListAccounts { resp_tx })
self.command_tx
.send(NtfyMessage::ListAccounts { respond_to: tx })
.await
.map_err(|_| anyhow!("Actor mailbox error"))?;
rx.await.map_err(|_| anyhow!("Actor response error"))?
} }
pub async fn watch_subscribed(&self) -> anyhow::Result<()> { pub async fn watch_subscribed(&self) -> anyhow::Result<()> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::WatchSubscribed { resp_tx })
self.command_tx
.send(NtfyMessage::WatchSubscribed { respond_to: tx })
.await
.map_err(|_| anyhow!("Actor mailbox error"))?;
rx.await.map_err(|_| anyhow!("Actor response error"))?
} }
pub async fn add_account( pub async fn add_account(
@ -349,31 +306,19 @@ impl NtfyHandle {
username: &str, username: &str,
password: &str, password: &str,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::AddAccount {
self.command_tx server: server.to_string(),
.send(NtfyMessage::AddAccount { username: username.to_string(),
server: server.to_string(), password: password.to_string(),
username: username.to_string(), resp_tx,
password: password.to_string(), })
respond_to: tx,
})
.await
.map_err(|_| anyhow!("Actor mailbox error"))?;
rx.await.map_err(|_| anyhow!("Actor response error"))?
} }
pub async fn remove_account(&self, server: &str) -> anyhow::Result<()> { pub async fn remove_account(&self, server: &str) -> anyhow::Result<()> {
let (tx, rx) = oneshot::channel(); send_command!(self, |resp_tx| NtfyCommand::RemoveAccount {
self.command_tx server: server.to_string(),
.send(NtfyMessage::RemoveAccount { resp_tx,
server: server.to_string(), })
respond_to: tx,
})
.await
.map_err(|_| anyhow!("Actor mailbox error"))?;
rx.await.map_err(|_| anyhow!("Actor response error"))?
} }
} }
@ -438,7 +383,7 @@ pub fn start(
mod tests { mod tests {
use std::time::Duration; use std::time::Duration;
use models::Message; use models::{OutgoingMessage, ReceivedMessage};
use tokio::time::sleep; use tokio::time::sleep;
use crate::ListenerEvent; use crate::ListenerEvent;
@ -466,7 +411,7 @@ mod tests {
let subscription_handle = handle.subscribe(server, topic).await.unwrap(); let subscription_handle = handle.subscribe(server, topic).await.unwrap();
// Publish a message // Publish a message
let message = serde_json::to_string(&Message { let message = serde_json::to_string(&OutgoingMessage {
topic: topic.to_string(), topic: topic.to_string(),
..Default::default() ..Default::default()
}) })

View File

@ -1,6 +1,6 @@
use crate::listener::{ListenerEvent, ListenerHandle}; use crate::listener::{ListenerEvent, ListenerHandle};
use crate::message_repo::Db; use crate::message_repo::Db;
use crate::models::{self, Message, NotificationProxy}; use crate::models::{self, NotificationProxy, ReceivedMessage};
use crate::{Error, ServerEvent, SharedEnv}; use crate::{Error, ServerEvent, SharedEnv};
use std::future::Future; use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
@ -9,31 +9,58 @@ use tokio::sync::{broadcast, mpsc, oneshot, watch, RwLock};
use tokio::task::spawn_local; use tokio::task::spawn_local;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
enum SubscriptionCommand {
GetModel {
resp_tx: oneshot::Sender<models::Subscription>,
},
UpdateInfo {
new_model: models::Subscription,
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
Attach {
resp_tx: oneshot::Sender<(Vec<ListenerEvent>, broadcast::Receiver<ListenerEvent>)>,
},
Publish {
msg: String,
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
ClearNotifications {
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
UpdateReadUntil {
timestamp: u64,
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubscriptionHandle { pub struct SubscriptionHandle {
sender: mpsc::Sender<SubscriptionRequest>, command_tx: mpsc::Sender<SubscriptionCommand>,
listener: ListenerHandle, listener: ListenerHandle,
} }
impl SubscriptionHandle { impl SubscriptionHandle {
pub fn new(listener: ListenerHandle, model: models::Subscription, env: &SharedEnv) -> Self { pub fn new(listener: ListenerHandle, model: models::Subscription, env: &SharedEnv) -> Self {
let (sender, receiver) = mpsc::channel(32); let (command_tx, command_rx) = mpsc::channel(32);
let broadcast_tx = broadcast::channel(8).0; let broadcast_tx = broadcast::channel(8).0;
let actor = SubscriptionActor { let actor = SubscriptionActor {
listener: listener.clone(), listener: listener.clone(),
model, model,
receiver, command_rx,
env: env.clone(), env: env.clone(),
broadcast_tx: broadcast_tx.clone(), broadcast_tx: broadcast_tx.clone(),
}; };
spawn_local(actor.run()); spawn_local(actor.run());
Self { sender, listener } Self {
command_tx,
listener,
}
} }
pub async fn model(&self) -> models::Subscription { pub async fn model(&self) -> models::Subscription {
let (resp_tx, resp_rx) = oneshot::channel(); let (resp_tx, resp_rx) = oneshot::channel();
self.sender self.command_tx
.send(SubscriptionRequest::GetModel { resp_tx }) .send(SubscriptionCommand::GetModel { resp_tx })
.await .await
.unwrap(); .unwrap();
resp_rx.await.unwrap() resp_rx.await.unwrap()
@ -41,8 +68,8 @@ impl SubscriptionHandle {
pub async fn update_info(&self, new_model: models::Subscription) -> anyhow::Result<()> { pub async fn update_info(&self, new_model: models::Subscription) -> anyhow::Result<()> {
let (resp_tx, resp_rx) = oneshot::channel(); let (resp_tx, resp_rx) = oneshot::channel();
self.sender self.command_tx
.send(SubscriptionRequest::UpdateInfo { new_model, resp_tx }) .send(SubscriptionCommand::UpdateInfo { new_model, resp_tx })
.await?; .await?;
resp_rx.await.unwrap() resp_rx.await.unwrap()
} }
@ -68,8 +95,8 @@ impl SubscriptionHandle {
// The `ListenerHandle` is returned to receive new events. // The `ListenerHandle` is returned to receive new events.
pub async fn attach(&self) -> (Vec<ListenerEvent>, broadcast::Receiver<ListenerEvent>) { pub async fn attach(&self) -> (Vec<ListenerEvent>, broadcast::Receiver<ListenerEvent>) {
let (resp_tx, resp_rx) = oneshot::channel(); let (resp_tx, resp_rx) = oneshot::channel();
self.sender self.command_tx
.send(SubscriptionRequest::Attach { resp_tx }) .send(SubscriptionCommand::Attach { resp_tx })
.await .await
.unwrap(); .unwrap();
resp_rx.await.unwrap() resp_rx.await.unwrap()
@ -77,8 +104,8 @@ impl SubscriptionHandle {
pub async fn publish(&self, msg: String) -> anyhow::Result<()> { pub async fn publish(&self, msg: String) -> anyhow::Result<()> {
let (resp_tx, resp_rx) = oneshot::channel(); let (resp_tx, resp_rx) = oneshot::channel();
self.sender self.command_tx
.send(SubscriptionRequest::Publish { msg, resp_tx }) .send(SubscriptionCommand::Publish { msg, resp_tx })
.await .await
.unwrap(); .unwrap();
resp_rx.await.unwrap() resp_rx.await.unwrap()
@ -86,8 +113,8 @@ impl SubscriptionHandle {
pub async fn clear_notifications(&self) -> anyhow::Result<()> { pub async fn clear_notifications(&self) -> anyhow::Result<()> {
let (resp_tx, resp_rx) = oneshot::channel(); let (resp_tx, resp_rx) = oneshot::channel();
self.sender self.command_tx
.send(SubscriptionRequest::ClearNotifications { resp_tx }) .send(SubscriptionCommand::ClearNotifications { resp_tx })
.await .await
.unwrap(); .unwrap();
resp_rx.await.unwrap() resp_rx.await.unwrap()
@ -95,8 +122,8 @@ impl SubscriptionHandle {
pub async fn update_read_until(&self, timestamp: u64) -> anyhow::Result<()> { pub async fn update_read_until(&self, timestamp: u64) -> anyhow::Result<()> {
let (resp_tx, resp_rx) = oneshot::channel(); let (resp_tx, resp_rx) = oneshot::channel();
self.sender self.command_tx
.send(SubscriptionRequest::UpdateReadUntil { timestamp, resp_tx }) .send(SubscriptionCommand::UpdateReadUntil { timestamp, resp_tx })
.await .await
.unwrap(); .unwrap();
resp_rx.await.unwrap() resp_rx.await.unwrap()
@ -106,7 +133,7 @@ impl SubscriptionHandle {
struct SubscriptionActor { struct SubscriptionActor {
listener: ListenerHandle, listener: ListenerHandle,
model: models::Subscription, model: models::Subscription,
receiver: mpsc::Receiver<SubscriptionRequest>, command_rx: mpsc::Receiver<SubscriptionCommand>,
env: SharedEnv, env: SharedEnv,
broadcast_tx: broadcast::Sender<ListenerEvent>, broadcast_tx: broadcast::Sender<ListenerEvent>,
} }
@ -123,12 +150,12 @@ impl SubscriptionActor {
} }
} }
} }
Some(request) = self.receiver.recv() => { Some(command) = self.command_rx.recv() => {
match request { match command {
SubscriptionRequest::GetModel { resp_tx } => { SubscriptionCommand::GetModel { resp_tx } => {
let _ = resp_tx.send(self.model.clone()); let _ = resp_tx.send(self.model.clone());
} }
SubscriptionRequest::UpdateInfo { SubscriptionCommand::UpdateInfo {
mut new_model, mut new_model,
resp_tx, resp_tx,
} => { } => {
@ -140,10 +167,10 @@ impl SubscriptionActor {
} }
resp_tx.send(res.map_err(|e| e.into())); resp_tx.send(res.map_err(|e| e.into()));
} }
SubscriptionRequest::Publish {msg, resp_tx} => { SubscriptionCommand::Publish {msg, resp_tx} => {
let _ = resp_tx.send(self.publish(msg).await); let _ = resp_tx.send(self.publish(msg).await);
} }
SubscriptionRequest::Attach { resp_tx } => { SubscriptionCommand::Attach { resp_tx } => {
let messages = self let messages = self
.env .env
.db .db
@ -163,13 +190,13 @@ impl SubscriptionActor {
}) })
.map(ListenerEvent::Message) .map(ListenerEvent::Message)
.collect(); .collect();
previous_events.push(ListenerEvent::ConnectionStateChanged(self.listener.request_state().await)); previous_events.push(ListenerEvent::ConnectionStateChanged(self.listener.state().await));
let _ = resp_tx.send((previous_events, self.broadcast_tx.subscribe())); let _ = resp_tx.send((previous_events, self.broadcast_tx.subscribe()));
} }
SubscriptionRequest::ClearNotifications {resp_tx} => { SubscriptionCommand::ClearNotifications {resp_tx} => {
let _ = resp_tx.send(self.env.db.delete_messages(&self.model.server, &self.model.topic).map_err(|e| anyhow::anyhow!(e))); let _ = resp_tx.send(self.env.db.delete_messages(&self.model.server, &self.model.topic).map_err(|e| anyhow::anyhow!(e)));
} }
SubscriptionRequest::UpdateReadUntil { timestamp, resp_tx } => { SubscriptionCommand::UpdateReadUntil { timestamp, resp_tx } => {
let res = self.env.db.update_read_until(&self.model.server, &self.model.topic, timestamp); let res = self.env.db.update_read_until(&self.model.server, &self.model.topic, timestamp);
let _ = resp_tx.send(res.map_err(|e| anyhow::anyhow!(e))); let _ = resp_tx.send(res.map_err(|e| anyhow::anyhow!(e)));
} }
@ -192,7 +219,7 @@ impl SubscriptionActor {
res.error_for_status()?; res.error_for_status()?;
Ok(()) Ok(())
} }
fn handle_msg_event(&mut self, msg: Message) { fn handle_msg_event(&mut self, msg: ReceivedMessage) {
// Store in database // Store in database
let already_stored: bool = { let already_stored: bool = {
let json_ev = &serde_json::to_string(&msg).unwrap(); let json_ev = &serde_json::to_string(&msg).unwrap();
@ -231,27 +258,3 @@ impl SubscriptionActor {
} }
} }
} }
enum SubscriptionRequest {
GetModel {
resp_tx: oneshot::Sender<models::Subscription>,
},
UpdateInfo {
new_model: models::Subscription,
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
Attach {
resp_tx: oneshot::Sender<(Vec<ListenerEvent>, broadcast::Receiver<ListenerEvent>)>,
},
Publish {
msg: String,
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
ClearNotifications {
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
UpdateReadUntil {
timestamp: u64,
resp_tx: oneshot::Sender<anyhow::Result<()>>,
},
}

View File

@ -227,12 +227,12 @@ impl Subscription {
.await?; .await?;
Ok(()) Ok(())
} }
fn last_message(list: &gio::ListStore) -> Option<models::Message> { fn last_message(list: &gio::ListStore) -> Option<models::ReceivedMessage> {
let n = list.n_items(); let n = list.n_items();
let last = list let last = list
.item(n.checked_sub(1)?) .item(n.checked_sub(1)?)
.and_downcast::<glib::BoxedAnyObject>()?; .and_downcast::<glib::BoxedAnyObject>()?;
let last = last.borrow::<models::Message>(); let last = last.borrow::<models::ReceivedMessage>();
Some(last.clone()) Some(last.clone())
} }
fn update_unread_count(&self) { fn update_unread_count(&self) {
@ -275,7 +275,7 @@ impl Subscription {
Ok(()) Ok(())
} }
pub async fn publish_msg(&self, mut msg: models::Message) -> anyhow::Result<()> { pub async fn publish_msg(&self, mut msg: models::OutgoingMessage) -> anyhow::Result<()> {
let imp = self.imp(); let imp = self.imp();
let json = { let json = {
msg.topic = self.topic(); msg.topic = self.topic();

View File

@ -166,7 +166,7 @@ impl AddSubscriptionDialog {
obj.set_content_width(480); obj.set_content_width(480);
obj.set_child(Some(&toolbar_view)); obj.set_child(Some(&toolbar_view));
} }
pub fn subscription(&self) -> Result<models::Subscription, Vec<ntfy_daemon::Error>> { pub fn subscription(&self) -> Result<models::Subscription, ntfy_daemon::Error> {
let w = { self.imp().widgets.borrow().clone() }; let w = { self.imp().widgets.borrow().clone() };
let mut sub = models::Subscription::builder(w.topic_entry.text().to_string()); let mut sub = models::Subscription::builder(w.topic_entry.text().to_string());
if w.server_expander.enables_expansion() { if w.server_expander.enables_expansion() {
@ -183,7 +183,7 @@ impl AddSubscriptionDialog {
w.topic_entry.remove_css_class("error"); w.topic_entry.remove_css_class("error");
w.sub_btn.set_sensitive(true); w.sub_btn.set_sensitive(true);
if let Err(errs) = sub { if let Err(ntfy_daemon::Error::InvalidSubscription(errs)) = sub {
w.sub_btn.set_sensitive(false); w.sub_btn.set_sensitive(false);
for e in errs { for e in errs {
match e { match e {

View File

@ -34,12 +34,12 @@ glib::wrapper! {
} }
impl MessageRow { impl MessageRow {
pub fn new(msg: models::Message) -> Self { pub fn new(msg: models::ReceivedMessage) -> Self {
let this: Self = glib::Object::new(); let this: Self = glib::Object::new();
this.build_ui(msg); this.build_ui(msg);
this this
} }
fn build_ui(&self, msg: models::Message) { fn build_ui(&self, msg: models::ReceivedMessage) {
self.set_margin_top(8); self.set_margin_top(8);
self.set_margin_bottom(8); self.set_margin_bottom(8);
self.set_margin_start(8); self.set_margin_start(8);

View File

@ -226,9 +226,9 @@ impl NotifyWindow {
entry.error_boundary().spawn(async move { entry.error_boundary().spawn(async move {
this.selected_subscription() this.selected_subscription()
.unwrap() .unwrap()
.publish_msg(models::Message { .publish_msg(models::OutgoingMessage {
message: Some(entry.text().as_str().to_string()), message: Some(entry.text().as_str().to_string()),
..models::Message::default() ..models::OutgoingMessage::default()
}) })
.await?; .await?;
Ok(()) Ok(())
@ -266,13 +266,7 @@ impl NotifyWindow {
fn add_subscription(&self, sub: models::Subscription) { fn add_subscription(&self, sub: models::Subscription) {
let this = self.clone(); let this = self.clone();
self.error_boundary().spawn(async move { self.error_boundary().spawn(async move {
let sub = this let sub = this.notifier().subscribe(&sub.server, &sub.topic).await?;
.notifier()
.subscribe(&sub.server, &sub.topic)
.await
.map_err(|err| {
anyhow::anyhow!(err.into_iter().map(|x| x.to_string()).collect::<String>())
})?;
let imp = this.imp(); let imp = this.imp();
// Subscription::new will use the pipelined client to retrieve info about the subscription // Subscription::new will use the pipelined client to retrieve info about the subscription
@ -371,7 +365,7 @@ impl NotifyWindow {
imp.message_list imp.message_list
.bind_model(Some(&sub.imp().messages), move |obj| { .bind_model(Some(&sub.imp().messages), move |obj| {
let b = obj.downcast_ref::<glib::BoxedAnyObject>().unwrap(); let b = obj.downcast_ref::<glib::BoxedAnyObject>().unwrap();
let msg = b.borrow::<models::Message>(); let msg = b.borrow::<models::ReceivedMessage>();
MessageRow::new(msg.clone()).upcast() MessageRow::new(msg.clone()).upcast()
}); });