diff --git a/Cargo.lock b/Cargo.lock
index 938ce6e6..57847376 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,12 +8,14 @@ version = "0.1.0"
dependencies = [
"aardvark-doc",
"aardvark-node",
- "ashpd",
+ "ashpd 0.9.2",
"futures-util",
"gettext-rs",
"gtk4",
"libadwaita",
+ "oo7",
"sourceview5",
+ "thiserror 2.0.12",
"tracing",
"tracing-subscriber",
]
@@ -40,6 +42,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
+ "chrono",
"ciborium",
"p2panda-core",
"p2panda-discovery",
@@ -48,6 +51,7 @@ dependencies = [
"p2panda-stream",
"p2panda-sync",
"serde",
+ "sqlx",
"tokio",
"tokio-stream",
"tracing",
@@ -177,7 +181,26 @@ dependencies = [
"serde_repr",
"tracing",
"url",
- "zbus",
+ "zbus 4.4.0",
+]
+
+[[package]]
+name = "ashpd"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
+dependencies = [
+ "async-fs",
+ "async-net",
+ "enumflags2",
+ "futures-channel",
+ "futures-util",
+ "rand 0.9.0",
+ "serde",
+ "serde_repr",
+ "tracing",
+ "url",
+ "zbus 5.5.0",
]
[[package]]
@@ -373,6 +396,15 @@ dependencies = [
"syn 2.0.100",
]
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
@@ -468,6 +500,9 @@ name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+dependencies = [
+ "serde",
+]
[[package]]
name = "bitmaps"
@@ -625,8 +660,10 @@ checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
+ "js-sys",
"num-traits",
"serde",
+ "wasm-bindgen",
"windows-link",
]
@@ -789,6 +826,15 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+dependencies = [
+ "crossbeam-utils",
+]
+
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@@ -922,6 +968,7 @@ checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"der_derive",
+ "pem-rfc7468",
"zeroize",
]
@@ -1010,6 +1057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
+ "const-oid",
"crypto-common",
"subtle",
]
@@ -1045,6 +1093,12 @@ dependencies = [
"litrs",
]
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
[[package]]
name = "ed25519"
version = "2.2.3"
@@ -1076,6 +1130,9 @@ name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+dependencies = [
+ "serde",
+]
[[package]]
name = "embedded-io"
@@ -1189,6 +1246,17 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "event-listener"
version = "5.4.0"
@@ -1268,6 +1336,21 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -1347,6 +1430,17 @@ dependencies = [
"futures-util",
]
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
[[package]]
name = "futures-io"
version = "0.3.31"
@@ -1836,6 +1930,15 @@ dependencies = [
"foldhash",
]
+[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.2",
+]
+
[[package]]
name = "heapless"
version = "0.7.17"
@@ -1957,6 +2060,15 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
[[package]]
name = "hmac"
version = "0.12.1"
@@ -1982,6 +2094,15 @@ version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb"
+[[package]]
+name = "home"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "hostname"
version = "0.4.0"
@@ -2691,6 +2812,9 @@ name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
[[package]]
name = "leb128"
@@ -2735,6 +2859,23 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
+[[package]]
+name = "libm"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+
+[[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -2982,6 +3123,16 @@ dependencies = [
"regex-automata 0.1.10",
]
+[[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
[[package]]
name = "md5"
version = "0.7.0"
@@ -3336,6 +3487,24 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+dependencies = [
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand 0.8.5",
+ "serde",
+ "smallvec",
+ "zeroize",
+]
+
[[package]]
name = "num-complex"
version = "0.4.6"
@@ -3389,6 +3558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
+ "libm",
]
[[package]]
@@ -3465,18 +3635,83 @@ version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
+[[package]]
+name = "oo7"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb23d3ec3527d65a83be1c1795cb883c52cfa57147d42acc797127df56fc489"
+dependencies = [
+ "ashpd 0.11.0",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "blocking",
+ "endi",
+ "futures-lite",
+ "futures-util",
+ "getrandom 0.3.1",
+ "num",
+ "num-bigint-dig",
+ "openssl",
+ "rand 0.9.0",
+ "serde",
+ "tracing",
+ "zbus 5.5.0",
+ "zbus_macros 5.5.0",
+ "zeroize",
+ "zvariant 5.4.0",
+]
+
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+[[package]]
+name = "openssl"
+version = "0.10.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+[[package]]
+name = "openssl-sys"
+version = "0.9.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
[[package]]
name = "ordered-stream"
version = "0.2.0"
@@ -3496,7 +3731,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "p2panda-core"
version = "0.3.0"
-source = "git+https://github.com/p2panda/p2panda?rev=f3a016324b69beac45cf20a792fe6890cb1a21e3#f3a016324b69beac45cf20a792fe6890cb1a21e3"
+source = "git+https://github.com/jsparber/p2panda.git?rev=dd9088a33d30014e9a11cbb3600278cfa2a2d889#dd9088a33d30014e9a11cbb3600278cfa2a2d889"
dependencies = [
"blake3",
"ciborium",
@@ -3511,7 +3746,7 @@ dependencies = [
[[package]]
name = "p2panda-discovery"
version = "0.3.0"
-source = "git+https://github.com/p2panda/p2panda?rev=f3a016324b69beac45cf20a792fe6890cb1a21e3#f3a016324b69beac45cf20a792fe6890cb1a21e3"
+source = "git+https://github.com/jsparber/p2panda.git?rev=dd9088a33d30014e9a11cbb3600278cfa2a2d889#dd9088a33d30014e9a11cbb3600278cfa2a2d889"
dependencies = [
"anyhow",
"base32",
@@ -3531,7 +3766,7 @@ dependencies = [
[[package]]
name = "p2panda-net"
version = "0.3.0"
-source = "git+https://github.com/p2panda/p2panda?rev=f3a016324b69beac45cf20a792fe6890cb1a21e3#f3a016324b69beac45cf20a792fe6890cb1a21e3"
+source = "git+https://github.com/jsparber/p2panda.git?rev=dd9088a33d30014e9a11cbb3600278cfa2a2d889#dd9088a33d30014e9a11cbb3600278cfa2a2d889"
dependencies = [
"anyhow",
"async-trait",
@@ -3558,9 +3793,12 @@ dependencies = [
[[package]]
name = "p2panda-store"
version = "0.3.0"
-source = "git+https://github.com/p2panda/p2panda?rev=f3a016324b69beac45cf20a792fe6890cb1a21e3#f3a016324b69beac45cf20a792fe6890cb1a21e3"
+source = "git+https://github.com/jsparber/p2panda.git?rev=dd9088a33d30014e9a11cbb3600278cfa2a2d889#dd9088a33d30014e9a11cbb3600278cfa2a2d889"
dependencies = [
+ "ciborium",
+ "hex",
"p2panda-core",
+ "sqlx",
"thiserror 2.0.12",
"trait-variant",
]
@@ -3568,7 +3806,7 @@ dependencies = [
[[package]]
name = "p2panda-stream"
version = "0.3.0"
-source = "git+https://github.com/p2panda/p2panda?rev=f3a016324b69beac45cf20a792fe6890cb1a21e3#f3a016324b69beac45cf20a792fe6890cb1a21e3"
+source = "git+https://github.com/jsparber/p2panda.git?rev=dd9088a33d30014e9a11cbb3600278cfa2a2d889#dd9088a33d30014e9a11cbb3600278cfa2a2d889"
dependencies = [
"ciborium",
"futures-channel",
@@ -3583,7 +3821,7 @@ dependencies = [
[[package]]
name = "p2panda-sync"
version = "0.3.0"
-source = "git+https://github.com/p2panda/p2panda?rev=f3a016324b69beac45cf20a792fe6890cb1a21e3#f3a016324b69beac45cf20a792fe6890cb1a21e3"
+source = "git+https://github.com/jsparber/p2panda.git?rev=dd9088a33d30014e9a11cbb3600278cfa2a2d889#dd9088a33d30014e9a11cbb3600278cfa2a2d889"
dependencies = [
"async-trait",
"futures",
@@ -3664,6 +3902,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -3782,6 +4029,17 @@ dependencies = [
"z32",
]
+[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
[[package]]
name = "pkcs8"
version = "0.10.2"
@@ -4295,6 +4553,26 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "rsa"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "rtnetlink"
version = "0.13.1"
@@ -4731,6 +5009,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
+ "digest",
"rand_core 0.6.4",
]
@@ -4835,6 +5114,199 @@ dependencies = [
"der",
]
+[[package]]
+name = "sqlx"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3"
+dependencies = [
+ "base64",
+ "bytes",
+ "chrono",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown 0.15.2",
+ "hashlink",
+ "indexmap",
+ "log",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7"
+dependencies = [
+ "dotenvy",
+ "either",
+ "heck 0.5.0",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx-core",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+ "syn 2.0.100",
+ "tempfile",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-mysql"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7"
+dependencies = [
+ "atoi",
+ "base64",
+ "bitflags 2.9.0",
+ "byteorder",
+ "bytes",
+ "chrono",
+ "crc",
+ "digest",
+ "dotenvy",
+ "either",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "generic-array",
+ "hex",
+ "hkdf",
+ "hmac",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rand 0.8.5",
+ "rsa",
+ "serde",
+ "sha1",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.12",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6"
+dependencies = [
+ "atoi",
+ "base64",
+ "bitflags 2.9.0",
+ "byteorder",
+ "chrono",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.12",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-sqlite"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc"
+dependencies = [
+ "atoi",
+ "chrono",
+ "flume",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "serde",
+ "serde_urlencoded",
+ "sqlx-core",
+ "thiserror 2.0.12",
+ "tracing",
+ "url",
+]
+
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -4847,6 +5319,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -5460,6 +5943,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
[[package]]
name = "unicode-ident"
version = "1.0.18"
@@ -5475,6 +5964,12 @@ dependencies = [
"tinyvec",
]
+[[package]]
+name = "unicode-properties"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
+
[[package]]
name = "unicode-xid"
version = "0.2.6"
@@ -5557,6 +6052,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
[[package]]
name = "version-compare"
version = "0.2.0"
@@ -5603,6 +6104,12 @@ dependencies = [
"wit-bindgen-rt",
]
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
@@ -5725,6 +6232,16 @@ dependencies = [
"rustls-pki-types",
]
+[[package]]
+name = "whoami"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
+dependencies = [
+ "redox_syscall",
+ "wasite",
+]
+
[[package]]
name = "widestring"
version = "1.1.0"
@@ -6393,9 +6910,45 @@ dependencies = [
"uds_windows",
"windows-sys 0.52.0",
"xdg-home",
- "zbus_macros",
- "zbus_names",
- "zvariant",
+ "zbus_macros 4.4.0",
+ "zbus_names 3.0.0",
+ "zvariant 4.2.0",
+]
+
+[[package]]
+name = "zbus"
+version = "5.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-lite",
+ "hex",
+ "nix 0.29.0",
+ "ordered-stream",
+ "serde",
+ "serde_repr",
+ "static_assertions",
+ "tracing",
+ "uds_windows",
+ "windows-sys 0.59.0",
+ "winnow",
+ "xdg-home",
+ "zbus_macros 5.5.0",
+ "zbus_names 4.2.0",
+ "zvariant 5.4.0",
]
[[package]]
@@ -6408,7 +6961,22 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
- "zvariant_utils",
+ "zvariant_utils 2.1.0",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "zbus_names 4.2.0",
+ "zvariant 5.4.0",
+ "zvariant_utils 3.2.0",
]
[[package]]
@@ -6419,7 +6987,19 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
dependencies = [
"serde",
"static_assertions",
- "zvariant",
+ "zvariant 4.2.0",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "winnow",
+ "zvariant 5.4.0",
]
[[package]]
@@ -6488,6 +7068,20 @@ name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
[[package]]
name = "zerovec"
@@ -6522,7 +7116,23 @@ dependencies = [
"serde",
"static_assertions",
"url",
- "zvariant_derive",
+ "zvariant_derive 4.2.0",
+]
+
+[[package]]
+name = "zvariant"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2df9ee044893fcffbdc25de30546edef3e32341466811ca18421e3cd6c5a3ac"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "static_assertions",
+ "url",
+ "winnow",
+ "zvariant_derive 5.4.0",
+ "zvariant_utils 3.2.0",
]
[[package]]
@@ -6535,7 +7145,20 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
- "zvariant_utils",
+ "zvariant_utils 2.1.0",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "zvariant_utils 3.2.0",
]
[[package]]
@@ -6548,3 +7171,17 @@ dependencies = [
"quote",
"syn 2.0.100",
]
+
+[[package]]
+name = "zvariant_utils"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "static_assertions",
+ "syn 2.0.100",
+ "winnow",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 52f55394..3afcfa8d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,11 @@
[workspace]
resolver = "2"
members = ["aardvark-app", "aardvark-doc", "aardvark-node"]
+
+[patch."https://github.com/p2panda/p2panda"]
+p2panda-core = { git = "https://github.com/jsparber/p2panda.git", rev = "dd9088a33d30014e9a11cbb3600278cfa2a2d889" }
+p2panda-discovery = { git = "https://github.com/jsparber/p2panda.git", rev = "dd9088a33d30014e9a11cbb3600278cfa2a2d889", features = ["mdns"] }
+p2panda-net = { git = "https://github.com/jsparber/p2panda.git", rev = "dd9088a33d30014e9a11cbb3600278cfa2a2d889" }
+p2panda-store = { git = "https://github.com/jsparber/p2panda.git", rev = "dd9088a33d30014e9a11cbb3600278cfa2a2d889", features = ["sqlite"] }
+p2panda-stream = { git = "https://github.com/jsparber/p2panda.git", rev = "dd9088a33d30014e9a11cbb3600278cfa2a2d889" }
+p2panda-sync = { git = "https://github.com/jsparber/p2panda.git", rev = "dd9088a33d30014e9a11cbb3600278cfa2a2d889", features = ["log-sync"] }
\ No newline at end of file
diff --git a/aardvark-app/Cargo.toml b/aardvark-app/Cargo.toml
index 9cdfe104..9d787829 100644
--- a/aardvark-app/Cargo.toml
+++ b/aardvark-app/Cargo.toml
@@ -17,9 +17,15 @@ sourceview = { package = "sourceview5", version = "0.9" }
tracing = "0.1"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
ashpd = { version = "0.9", default-features = false, features = ["tracing", "async-std"] }
+thiserror = { version = "2.0" }
futures-util = "0.3"
+oo7 = { version = "0.4", default-features = false, features = [
+ "openssl_crypto",
+ "async-std",
+ "tracing",
+] }
[dependencies.adw]
package = "libadwaita"
version = "0.7"
-features = ["v1_6"]
+features = ["v1_6"]
\ No newline at end of file
diff --git a/aardvark-app/data/resources/icons/scalable/actions/down-smaller-symbolic.svg b/aardvark-app/data/resources/icons/scalable/actions/down-smaller-symbolic.svg
new file mode 100644
index 00000000..0d073be9
--- /dev/null
+++ b/aardvark-app/data/resources/icons/scalable/actions/down-smaller-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/aardvark-app/data/resources/icons/scalable/actions/join-document-symbolic.svg b/aardvark-app/data/resources/icons/scalable/actions/join-document-symbolic.svg
new file mode 100644
index 00000000..975067b2
--- /dev/null
+++ b/aardvark-app/data/resources/icons/scalable/actions/join-document-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/aardvark-app/data/resources/resources.gresource.xml b/aardvark-app/data/resources/resources.gresource.xml
index 2b7e3e0d..2ffd3ac5 100644
--- a/aardvark-app/data/resources/resources.gresource.xml
+++ b/aardvark-app/data/resources/resources.gresource.xml
@@ -1,5 +1,7 @@
+ icons/scalable/actions/down-smaller-symbolic.svg
+ icons/scalable/actions/join-document-symbolic.svg
diff --git a/aardvark-app/src/application.rs b/aardvark-app/src/application.rs
index 59f243af..890cb527 100644
--- a/aardvark-app/src/application.rs
+++ b/aardvark-app/src/application.rs
@@ -23,9 +23,12 @@ use adw::prelude::*;
use adw::subclass::prelude::*;
use gettextrs::gettext;
use gtk::{gio, glib, glib::Properties};
+use std::{cell::OnceCell, fs};
+use tracing::error;
use crate::AardvarkWindow;
use crate::config;
+use crate::secret;
use crate::system_settings::SystemSettings;
mod imp {
@@ -35,7 +38,7 @@ mod imp {
#[properties(wrapper_type = super::AardvarkApplication)]
pub struct AardvarkApplication {
#[property(get)]
- pub service: Service,
+ pub service: OnceCell,
#[property(get)]
pub system_settings: SystemSettings,
}
@@ -55,17 +58,36 @@ mod imp {
obj.setup_gactions();
obj.set_accels_for_action("app.quit", &["q"]);
obj.set_accels_for_action("app.new-window", &["n"]);
+
+ // FIXME: Don't block on loading the identity
+ glib::MainContext::new().block_on(async move {
+ let private_key = secret::get_or_create_identity()
+ .await
+ .expect("Unable to get or create identity");
+
+ let mut data_path = glib::user_data_dir();
+ data_path.push("Aardvark");
+ data_path.push(private_key.public_key().to_string());
+ if let Err(error) = fs::create_dir_all(&data_path) {
+ error!("Failed to create data directory: {error}");
+ }
+ let data_dir = gio::File::for_path(data_path);
+
+ self.service
+ .set(Service::new(&private_key, &data_dir))
+ .unwrap();
+ });
}
}
impl ApplicationImpl for AardvarkApplication {
fn startup(&self) {
- self.service.startup();
+ self.obj().service().startup();
self.parent_startup();
}
fn shutdown(&self) {
- self.service.shutdown();
+ self.obj().service().shutdown();
self.parent_shutdown();
}
@@ -116,7 +138,7 @@ impl AardvarkApplication {
}
fn new_window(&self) {
- let window = AardvarkWindow::new(self, &self.imp().service);
+ let window = AardvarkWindow::new(self, &self.service());
window.present();
}
diff --git a/aardvark-app/src/main.rs b/aardvark-app/src/main.rs
index 1d82a03a..7b56ba1d 100644
--- a/aardvark-app/src/main.rs
+++ b/aardvark-app/src/main.rs
@@ -23,6 +23,8 @@ mod components;
mod config;
mod connection_popover;
mod open_dialog;
+mod open_popover;
+mod secret;
mod system_settings;
mod textbuffer;
mod window;
@@ -37,9 +39,12 @@ use tracing_subscriber::prelude::*;
use self::application::AardvarkApplication;
use self::config::*;
use self::connection_popover::ConnectionPopover;
+use self::open_popover::OpenPopover;
use self::textbuffer::AardvarkTextBuffer;
use self::window::AardvarkWindow;
+pub use self::config::APP_ID;
+
fn main() -> glib::ExitCode {
setup_logging();
diff --git a/aardvark-app/src/open_popover/mod.rs b/aardvark-app/src/open_popover/mod.rs
new file mode 100644
index 00000000..7804884f
--- /dev/null
+++ b/aardvark-app/src/open_popover/mod.rs
@@ -0,0 +1,361 @@
+/* Copyright 2025 The Aardvark Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+use adw::subclass::prelude::*;
+use gettextrs::gettext;
+use gtk::prelude::*;
+use gtk::{glib, glib::clone, glib::closure_local};
+
+use crate::system_settings::ClockFormat;
+use crate::{AardvarkApplication, AardvarkWindow, open_dialog::OpenDialog};
+use aardvark_doc::{document::Document, documents::Documents};
+
+mod imp {
+ use super::*;
+ use adw::prelude::AdwDialogExt;
+ use glib::subclass::Signal;
+ use std::sync::LazyLock;
+
+ #[derive(Debug, Default, glib::Properties, gtk::CompositeTemplate)]
+ #[properties(wrapper_type = super::OpenPopover)]
+ #[template(resource = "/org/p2panda/aardvark/open_popover/open_popover.ui")]
+ pub struct OpenPopover {
+ #[template_child]
+ search_entry: TemplateChild,
+ #[template_child]
+ listbox: TemplateChild,
+ #[template_child]
+ stack: TemplateChild,
+ #[template_child]
+ no_results_page: TemplateChild,
+ #[template_child]
+ document_list_page: TemplateChild,
+ #[template_child]
+ open_document_button: TemplateChild,
+ #[property(get = Self::model, set = Self::set_model, type = Option)]
+ model: gtk::FilterListModel,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for OpenPopover {
+ const NAME: &'static str = "AardvarkOpenPopover";
+ type Type = super::OpenPopover;
+ type ParentType = gtk::Popover;
+
+ fn class_init(klass: &mut Self::Class) {
+ klass.bind_template();
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ #[glib::derived_properties]
+ impl ObjectImpl for OpenPopover {
+ fn signals() -> &'static [Signal] {
+ static SIGNALS: LazyLock> = LazyLock::new(|| {
+ vec![
+ // The user has activated a document in the document list.
+ Signal::builder("document-activated")
+ .param_types([Document::static_type()])
+ .build(),
+ ]
+ });
+ SIGNALS.as_ref()
+ }
+
+ fn constructed(&self) {
+ self.parent_constructed();
+
+ // TODO: We should also match the document id with a more complex filter
+ let filter = gtk::StringFilter::builder()
+ .expression(gtk::PropertyExpression::new(
+ Document::static_type(),
+ gtk::Expression::NONE,
+ "name",
+ ))
+ .ignore_case(true)
+ .match_mode(gtk::StringFilterMatchMode::Substring)
+ .build();
+ self.model.set_filter(Some(&filter));
+
+ self.search_entry
+ .connect_search_changed(move |search_entry| {
+ filter.set_search(Some(&search_entry.text()));
+ });
+
+ self.model.connect_items_changed(clone!(
+ #[weak(rename_to = this)]
+ self,
+ move |model, _, _, _| {
+ if model.n_items() > 0 {
+ this.stack.set_visible_child(&*this.document_list_page);
+ } else {
+ this.stack.set_visible_child(&*this.no_results_page);
+ }
+ }
+ ));
+
+ self.listbox.connect_row_activated(clone!(
+ #[weak(rename_to = this)]
+ self,
+ move |_, row| {
+ let document: Document = this
+ .model
+ .item(row.index() as u32)
+ .unwrap()
+ .downcast()
+ .unwrap();
+ this.obj()
+ .emit_by_name::<()>("document-activated", &[&document]);
+ this.search_entry.set_text("");
+ this.obj().popdown();
+ }
+ ));
+
+ self.listbox.bind_model(Some(&self.model), |document| {
+ let document = document.downcast_ref::().unwrap();
+ let row = adw::ActionRow::builder()
+ .selectable(false)
+ .activatable(true)
+ .build();
+
+ document
+ .bind_property("name", &row, "title")
+ .sync_create()
+ .transform_to(|_, title: Option| {
+ if let Some(title) = title {
+ Some(title)
+ } else {
+ Some(gettext("Empty document"))
+ }
+ })
+ .build();
+
+ document
+ .bind_property("last-accessed", &row, "subtitle")
+ .sync_create()
+ .transform_to(|binding, last_accessed: Option| {
+ let document: Document = binding.source().unwrap().downcast().unwrap();
+ if let Some(last_accessed) = last_accessed {
+ Some(format_last_accessed(&last_accessed))
+ } else if document.subscribed() {
+ Some(gettext("Currently open"))
+ } else {
+ Some(gettext("Never accessed"))
+ }
+ })
+ .build();
+
+ row.upcast()
+ });
+
+ self.open_document_button.connect_clicked(clone!(
+ #[weak(rename_to = this)]
+ self,
+ move |_| {
+ let dialog = OpenDialog::new();
+ let window = this
+ .obj()
+ .root()
+ .and_then(|w| w.downcast::().ok())
+ .expect("Toplevel window needs to be a AardvarkWindow");
+
+ this.obj().popdown();
+ dialog.present(Some(&window));
+
+ let service = window.service();
+ dialog.connect_open(clone!(
+ #[weak]
+ this,
+ #[weak]
+ service,
+ move |_, document_id| {
+ let document = service
+ .documents()
+ .by_id(document_id)
+ .unwrap_or_else(|| Document::new(&service, Some(document_id)));
+
+ this.obj()
+ .emit_by_name::<()>("document-activated", &[&document]);
+ }
+ ));
+ }
+ ));
+ }
+ }
+
+ impl OpenPopover {
+ fn model(&self) -> Option {
+ if let Some(model) = self.model.model() {
+ model.downcast().ok()
+ } else {
+ None
+ }
+ }
+
+ fn set_model(&self, model: Option<&Documents>) {
+ self.model.set_model(model);
+ }
+ }
+
+ impl WidgetImpl for OpenPopover {}
+ impl PopoverImpl for OpenPopover {}
+}
+
+glib::wrapper! {
+ pub struct OpenPopover(ObjectSubclass)
+ @extends gtk::Widget, gtk::Popover;
+}
+
+impl OpenPopover {
+ pub fn new>(model: &P) -> Self {
+ glib::Object::builder().property("model", model).build()
+ }
+
+ /// Connect to the signal emitted when a user clicks a document in the document list.
+ pub fn connect_document_activated(
+ &self,
+ f: F,
+ ) -> glib::SignalHandlerId {
+ self.connect_closure(
+ "document-activated",
+ true,
+ closure_local!(move |obj: Self, document: Document| {
+ f(&obj, &document);
+ }),
+ )
+ }
+}
+
+// This was copied from Fractal
+// See: https://gitlab.gnome.org/World/fractal/-/blob/main/src/session/model/user_sessions_list/user_session.rs#L258
+fn format_last_accessed(datetime: &glib::DateTime) -> String {
+ let clock_format = AardvarkApplication::default()
+ .system_settings()
+ .clock_format();
+ let use_24 = clock_format == ClockFormat::TwentyFourHours;
+
+ // This was ported from Nautilus and simplified for our use case.
+ // See: https://gitlab.gnome.org/GNOME/nautilus/-/blob/1c5bd3614a35cfbb49de087bc10381cdef5a218f/src/nautilus-file.c#L5001
+ let now = glib::DateTime::now_local().unwrap();
+ let format;
+ let days_ago = {
+ let today_midnight =
+ glib::DateTime::from_local(now.year(), now.month(), now.day_of_month(), 0, 0, 0f64)
+ .expect("constructing GDateTime works");
+
+ let date = glib::DateTime::from_local(
+ datetime.year(),
+ datetime.month(),
+ datetime.day_of_month(),
+ 0,
+ 0,
+ 0f64,
+ )
+ .expect("constructing GDateTime works");
+
+ today_midnight.difference(&date).as_days()
+ };
+
+ // Show only the time if date is on today
+ if days_ago == 0 {
+ if use_24 {
+ // Translators: Time in 24h format, i.e. "23:04".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ format = gettext("Last accessed at %H:%M");
+ } else {
+ // Translators: Time in 12h format, i.e. "11:04 PM".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ format = gettext("Last accessed at %I:%M %p");
+ }
+ }
+ // Show the word "Yesterday" and time if date is on yesterday
+ else if days_ago == 1 {
+ if use_24 {
+ // Translators: this a time in 24h format, i.e. "Last seen yesterday at 23:04".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed yesterday at %H:%M");
+ } else {
+ // Translators: this is a time in 12h format, i.e. "Last seen Yesterday at 11:04
+ // PM".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed yesterday at %I:%M %p");
+ }
+ }
+ // Show a week day and time if date is in the last week
+ else if days_ago > 1 && days_ago < 7 {
+ if use_24 {
+ // Translators: this is the name of the week day followed by a time in 24h
+ // format, i.e. "Last seen Monday at 23:04".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed %A at %H:%M");
+ } else {
+ // Translators: this is the week day name followed by a time in 12h format, i.e.
+ // "Last seen Monday at 11:04 PM".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed %A at %I:%M %p");
+ }
+ } else if datetime.year() == now.year() {
+ if use_24 {
+ // Translators: this is the month and day and the time in 24h format, i.e. "Last
+ // seen February 3 at 23:04".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed %B %-e at %H:%M");
+ } else {
+ // Translators: this is the month and day and the time in 12h format, i.e. "Last
+ // seen February 3 at 11:04 PM".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed %B %-e at %I:%M %p");
+ }
+ } else if use_24 {
+ // Translators: this is the full date and the time in 24h format, i.e. "Last
+ // seen February 3 2015 at 23:04".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed %B %-e %Y at %H:%M");
+ } else {
+ // Translators: this is the full date and the time in 12h format, i.e. "Last
+ // seen February 3 2015 at 11:04 PM".
+ // Do not change the time format as it will follow the system settings.
+ // See `man strftime` or the documentation of g_date_time_format for the available specifiers:
+ // xgettext:no-c-format
+ format = gettext("Last accessed %B %-e %Y at %I:%M %p");
+ }
+
+ datetime
+ .format(&format)
+ .expect("formatting GDateTime works")
+ .into()
+}
diff --git a/aardvark-app/src/open_popover/open_popover.ui b/aardvark-app/src/open_popover/open_popover.ui
new file mode 100644
index 00000000..7af58451
--- /dev/null
+++ b/aardvark-app/src/open_popover/open_popover.ui
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aardvark-app/src/secret.rs b/aardvark-app/src/secret.rs
new file mode 100644
index 00000000..f81e03d4
--- /dev/null
+++ b/aardvark-app/src/secret.rs
@@ -0,0 +1,78 @@
+/* Copyright 2025 The Aardvark Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+use std::collections::HashMap;
+use thiserror::Error;
+use tracing::info;
+
+use crate::APP_ID;
+use aardvark_doc::identity::{IdentityError, PrivateKey};
+
+const XDG_SCHEMA: &'static str = "xdg:schema";
+
+fn attributes() -> HashMap<&'static str, String> {
+ HashMap::from([(XDG_SCHEMA, APP_ID.to_owned())])
+}
+
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("Secret Service error: {0}")]
+ Service(oo7::Error),
+ #[error("Format error: {0}")]
+ Format(IdentityError),
+}
+
+impl From for Error {
+ fn from(value: IdentityError) -> Self {
+ Error::Format(value)
+ }
+}
+
+impl From for Error {
+ fn from(value: oo7::Error) -> Self {
+ Error::Service(value)
+ }
+}
+
+pub async fn get_or_create_identity() -> Result {
+ let keyring = oo7::Keyring::new().await?;
+
+ keyring.unlock().await?;
+
+ let private_key: PrivateKey =
+ if let Some(item) = keyring.search_items(&attributes()).await?.get(0) {
+ item.unlock().await?;
+ let private_key = PrivateKey::try_from(item.secret().await?.as_bytes())?;
+ info!("Found existing identity: {}", private_key.public_key());
+
+ private_key
+ } else {
+ let private_key = PrivateKey::new();
+ keyring
+ .create_item("Aardvark", &attributes(), private_key.as_bytes(), true)
+ .await?;
+
+ info!(
+ "No existing identity found. Create new identity: {}",
+ private_key.public_key()
+ );
+ private_key
+ };
+
+ Ok(private_key)
+}
diff --git a/aardvark-app/src/style.css b/aardvark-app/src/style.css
index 9a9d3ed0..e891fbab 100644
--- a/aardvark-app/src/style.css
+++ b/aardvark-app/src/style.css
@@ -42,3 +42,20 @@
font-weight: 700;
font-size: 9px;
}
+.open-popover contents {
+ padding: 0px;
+ min-width: 300px;
+}
+
+.open-popover .search {
+ margin: 6px;
+}
+
+.open-popover row {
+ border-radius: 6px;
+ margin: 6px;
+}
+
+.open-popover .open-document {
+ margin: 12px;
+}
diff --git a/aardvark-app/src/textbuffer.rs b/aardvark-app/src/textbuffer.rs
index 33782d3e..97c0e5c8 100644
--- a/aardvark-app/src/textbuffer.rs
+++ b/aardvark-app/src/textbuffer.rs
@@ -37,20 +37,23 @@ mod imp {
pub struct AardvarkTextBuffer {
pub inhibit_text_change: Cell,
pub document_handlers: OnceCell,
- #[property(get, set = Self::set_document)]
+ #[property(get, set = Self::set_document, nullable)]
pub document: RefCell