Skip to content

Commit 3a4acc2

Browse files
authored
Merge pull request #27 from ranfdev/nullables
Rewrite of ntfy-daemon. Adds basic tests with Nullables and removes any trace of capnp
2 parents 3716b22 + 33a4708 commit 3a4acc2

29 files changed

+3058
-1991
lines changed

Cargo.lock

Lines changed: 1060 additions & 740 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ tracing-subscriber = "0.3"
2323
adw = { version = "0.7", package = "libadwaita", features = ["v1_6"] }
2424
serde = { version = "1.0", features = ["derive"] }
2525
serde_json = "1.0"
26-
capnp = "0.18.0"
27-
capnp-rpc = "0.18.0"
2826
anyhow = "1.0.71"
2927
chrono = "0.4.26"
3028
rand = "0.8.5"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ https://ntfy.sh client application to receive everyday's notifications.
1717
## Architecture
1818

1919
The code is split between the GUI and the underlying ntfy-daemon.
20+
![](./architecture.svg)
2021

2122
## How to run
2223
Use gnome-builder to clone and run the project. Note: after clicking the "run"

architecture.svg

Lines changed: 12 additions & 0 deletions
Loading

build-aux/com.ranfdev.Notify.Devel.json

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,6 @@
3737
]
3838
},
3939
"modules": [
40-
{
41-
"name": "capnp",
42-
"buildsystem": "cmake",
43-
"sources": [
44-
{
45-
"type": "archive",
46-
"url": "https://capnproto.org/capnproto-c++-0.10.4.tar.gz",
47-
"sha256": "981e7ef6dbe3ac745907e55a78870fbb491c5d23abd4ebc04e20ec235af4458c"
48-
}
49-
]
50-
},
5140
{
5241
"name": "blueprint-compiler",
5342
"buildsystem": "meson",
@@ -56,7 +45,8 @@
5645
{
5746
"type": "git",
5847
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
59-
"tag": "v0.14.0"
48+
"tag": "v0.14.0",
49+
"commit": "8e10fcf8692108b9d4ab78f41086c5d7773ef864"
6050
}
6151
]
6252
},

ntfy-daemon/Cargo.toml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,23 @@ edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

8-
9-
[build-dependencies]
10-
capnpc = "0.17.2"
11-
128
[dependencies]
139
serde = { version = "1.0", features = ["derive"] }
1410
serde_json = "1.0"
15-
capnp = "0.18.0"
16-
capnp-rpc = "0.18.0"
1711
futures = "0.3.0"
1812
tokio = { version = "1.0.0", features = ["net", "rt", "macros", "parking_lot"]}
1913
tokio-util = { version = "0.7.4", features = ["compat", "io"] }
2014
clap = { version = "4.3.11", features = ["derive"] }
2115
anyhow = "1.0.71"
22-
tokio-stream = { version = "0.1.14", features = ["io-util", "time"] }
16+
tokio-stream = { version = "0.1.14", features = ["io-util", "time", "sync"] }
2317
rusqlite = "0.29.0"
2418
rand = "0.8.5"
25-
reqwest = { version = "0.11.18", features = ["stream", "rustls-tls-native-roots"]}
26-
url = "2.4.0"
27-
generational-arena = "0.2.9"
19+
reqwest = { version = "0.12.9", features = ["stream", "rustls-tls-native-roots"]}
20+
url = { version = "2.4.0", features = ["serde"] }
2821
tracing = "0.1.37"
2922
thiserror = "1.0.49"
3023
regex = "1.9.6"
3124
oo7 = "0.2.1"
25+
async-trait = "0.1.83"
26+
http = "1.1.0"
27+
async-channel = "2.3.1"

ntfy-daemon/README.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

ntfy-daemon/build.rs

Lines changed: 0 additions & 6 deletions
This file was deleted.

ntfy-daemon/src/actor_utils.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
macro_rules! send_command {
2+
($self:expr, $command:expr) => {{
3+
let (resp_tx, resp_rx) = oneshot::channel();
4+
use anyhow::Context;
5+
$self
6+
.command_tx
7+
.send($command(resp_tx))
8+
.await
9+
.context("Actor mailbox error")?;
10+
resp_rx.await.context("Actor response error")?
11+
}};
12+
}
13+
14+
pub(crate) use send_command;

ntfy-daemon/src/credentials.rs

Lines changed: 159 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,196 @@
11
use std::cell::RefCell;
22
use std::collections::HashMap;
33
use std::rc::Rc;
4+
use std::sync::{Arc, RwLock};
5+
6+
use async_trait::async_trait;
7+
8+
#[derive(Clone)]
9+
pub struct KeyringItem {
10+
attributes: HashMap<String, String>,
11+
// we could zero-out this region of memory
12+
secret: Vec<u8>,
13+
}
14+
15+
impl KeyringItem {
16+
async fn attributes(&self) -> HashMap<String, String> {
17+
self.attributes.clone()
18+
}
19+
async fn secret(&self) -> &[u8] {
20+
&self.secret[..]
21+
}
22+
}
23+
24+
#[async_trait]
25+
trait LightKeyring {
26+
async fn search_items(
27+
&self,
28+
attributes: HashMap<&str, &str>,
29+
) -> anyhow::Result<Vec<KeyringItem>>;
30+
async fn create_item(
31+
&self,
32+
label: &str,
33+
attributes: HashMap<&str, &str>,
34+
secret: &str,
35+
replace: bool,
36+
) -> anyhow::Result<()>;
37+
async fn delete(&self, attributes: HashMap<&str, &str>) -> anyhow::Result<()>;
38+
}
39+
40+
struct RealKeyring {
41+
keyring: oo7::Keyring,
42+
}
43+
44+
#[async_trait]
45+
impl LightKeyring for RealKeyring {
46+
async fn search_items(
47+
&self,
48+
attributes: HashMap<&str, &str>,
49+
) -> anyhow::Result<Vec<KeyringItem>> {
50+
let items = self.keyring.search_items(attributes).await?;
51+
52+
let mut out_items = vec![];
53+
for item in items {
54+
out_items.push(KeyringItem {
55+
attributes: item.attributes().await?,
56+
secret: item.secret().await?.to_vec(),
57+
});
58+
}
59+
Ok(out_items)
60+
}
61+
62+
async fn create_item(
63+
&self,
64+
label: &str,
65+
attributes: HashMap<&str, &str>,
66+
secret: &str,
67+
replace: bool,
68+
) -> anyhow::Result<()> {
69+
self.keyring
70+
.create_item(label, attributes, secret, replace)
71+
.await?;
72+
Ok(())
73+
}
74+
75+
async fn delete(&self, attributes: HashMap<&str, &str>) -> anyhow::Result<()> {
76+
self.keyring.delete(attributes).await?;
77+
Ok(())
78+
}
79+
}
80+
81+
struct NullableKeyring {
82+
search_response: Vec<KeyringItem>,
83+
}
84+
85+
impl NullableKeyring {
86+
pub fn new(search_response: Vec<KeyringItem>) -> Self {
87+
Self { search_response }
88+
}
89+
}
90+
91+
#[async_trait]
92+
impl LightKeyring for NullableKeyring {
93+
async fn search_items(
94+
&self,
95+
_attributes: HashMap<&str, &str>,
96+
) -> anyhow::Result<Vec<KeyringItem>> {
97+
Ok(self.search_response.clone())
98+
}
99+
100+
async fn create_item(
101+
&self,
102+
_label: &str,
103+
_attributes: HashMap<&str, &str>,
104+
_secret: &str,
105+
_replace: bool,
106+
) -> anyhow::Result<()> {
107+
Ok(())
108+
}
109+
110+
async fn delete(&self, _attributes: HashMap<&str, &str>) -> anyhow::Result<()> {
111+
Ok(())
112+
}
113+
}
114+
impl NullableKeyring {
115+
pub fn with_credentials(credentials: Vec<Credential>) -> Self {
116+
let mut search_response = vec![];
117+
118+
for cred in credentials {
119+
let attributes = HashMap::from([
120+
("type".to_string(), "password".to_string()),
121+
("username".to_string(), cred.username.clone()),
122+
("server".to_string(), cred.password.clone()),
123+
]);
124+
search_response.push(KeyringItem {
125+
attributes,
126+
secret: cred.password.into_bytes(),
127+
});
128+
}
129+
130+
Self { search_response }
131+
}
132+
}
4133

5134
#[derive(Debug, Clone)]
6135
pub struct Credential {
7136
pub username: String,
8137
pub password: String,
9138
}
10139

11-
#[derive(Debug, Clone)]
140+
#[derive(Clone)]
12141
pub struct Credentials {
13-
keyring: Rc<oo7::Keyring>,
14-
creds: Rc<RefCell<HashMap<String, Credential>>>,
142+
keyring: Arc<dyn LightKeyring + Send + Sync>,
143+
creds: Arc<RwLock<HashMap<String, Credential>>>,
15144
}
16145

17146
impl Credentials {
18147
pub async fn new() -> anyhow::Result<Self> {
19148
let mut this = Self {
20-
keyring: Rc::new(
21-
oo7::Keyring::new()
149+
keyring: Arc::new(RealKeyring {
150+
keyring: oo7::Keyring::new()
22151
.await
23152
.expect("Failed to start Secret Service"),
24-
),
153+
}),
154+
creds: Default::default(),
155+
};
156+
this.load().await?;
157+
Ok(this)
158+
}
159+
pub async fn new_nullable(credentials: Vec<Credential>) -> anyhow::Result<Self> {
160+
let mut this = Self {
161+
keyring: Arc::new(NullableKeyring::with_credentials(credentials)),
25162
creds: Default::default(),
26163
};
27164
this.load().await?;
28165
Ok(this)
29166
}
30167
pub async fn load(&mut self) -> anyhow::Result<()> {
31168
let attrs = HashMap::from([("type", "password")]);
32-
let values = self
33-
.keyring
34-
.search_items(attrs)
35-
.await
36-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
169+
let values = self.keyring.search_items(attrs).await?;
37170

38-
self.creds.borrow_mut().clear();
171+
let mut lock = self.creds.write().unwrap();
172+
lock.clear();
39173
for item in values {
40-
let attrs = item
41-
.attributes()
42-
.await
43-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
44-
self.creds.borrow_mut().insert(
174+
let attrs = item.attributes().await;
175+
lock.insert(
45176
attrs["server"].to_string(),
46177
Credential {
47178
username: attrs["username"].to_string(),
48-
password: std::str::from_utf8(&item.secret().await?)?.to_string(),
179+
password: std::str::from_utf8(&item.secret().await)?.to_string(),
49180
},
50181
);
51182
}
52183
Ok(())
53184
}
54185
pub fn get(&self, server: &str) -> Option<Credential> {
55-
self.creds.borrow().get(server).cloned()
186+
self.creds.read().unwrap().get(server).cloned()
56187
}
57188
pub fn list_all(&self) -> HashMap<String, Credential> {
58-
self.creds.borrow().clone()
189+
self.creds.read().unwrap().clone()
59190
}
60191
pub async fn insert(&self, server: &str, username: &str, password: &str) -> anyhow::Result<()> {
61192
{
62-
if let Some(cred) = self.creds.borrow().get(server) {
193+
if let Some(cred) = self.creds.read().unwrap().get(server) {
63194
if cred.username != username {
64195
anyhow::bail!("You can add only one account per server");
65196
}
@@ -72,10 +203,9 @@ impl Credentials {
72203
]);
73204
self.keyring
74205
.create_item("Password", attrs, password, true)
75-
.await
76-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
206+
.await?;
77207

78-
self.creds.borrow_mut().insert(
208+
self.creds.write().unwrap().insert(
79209
server.to_string(),
80210
Credential {
81211
username: username.to_string(),
@@ -87,7 +217,8 @@ impl Credentials {
87217
pub async fn delete(&self, server: &str) -> anyhow::Result<()> {
88218
let creds = {
89219
self.creds
90-
.borrow()
220+
.read()
221+
.unwrap()
91222
.get(server)
92223
.ok_or(anyhow::anyhow!("server creds not found"))?
93224
.clone()
@@ -97,12 +228,10 @@ impl Credentials {
97228
("username", &creds.username),
98229
("server", server),
99230
]);
100-
self.keyring
101-
.delete(attrs)
102-
.await
103-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
231+
self.keyring.delete(attrs).await?;
104232
self.creds
105-
.borrow_mut()
233+
.write()
234+
.unwrap()
106235
.remove(server)
107236
.ok_or(anyhow::anyhow!("server creds not found"))?;
108237
Ok(())

0 commit comments

Comments
 (0)