Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discover and subscribe to documents based on short code #17

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 28 additions & 21 deletions aardvark-app/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@

use std::cell::{OnceCell, RefCell};

use aardvark_node::network;
use aardvark_node::network::{self, FromApp, ToApp};
use adw::prelude::*;
use adw::subclass::prelude::*;
use automerge::PatchAction;
use gettextrs::gettext;
use gtk::{gio, glib};
use tokio::sync::{mpsc, oneshot};
use automerge::PatchAction;

use crate::config::VERSION;
use crate::document::Document;
Expand All @@ -40,14 +40,13 @@ mod imp {
pub struct AardvarkApplication {
pub window: OnceCell<AardvarkWindow>,
pub document: Document,
pub tx: mpsc::Sender<Vec<u8>>,
pub rx: RefCell<Option<mpsc::Receiver<Vec<u8>>>>,
pub tx: mpsc::Sender<FromApp>,
pub rx: RefCell<Option<mpsc::Receiver<ToApp>>>,
#[allow(dead_code)]
backend_shutdown: oneshot::Sender<()>,
}

impl AardvarkApplication {
}
impl AardvarkApplication {}

#[glib::object_subclass]
impl ObjectSubclass for AardvarkApplication {
Expand Down Expand Up @@ -143,15 +142,29 @@ impl AardvarkApplication {

{
let application = self.clone();

let tx = self.imp().tx.clone();

let mut rx = self
.imp()
.rx
.take()
.expect("rx should be given at this point");

glib::spawn_future_local(async move {
while let Some(bytes) = rx.recv().await {
application.ingest_message(bytes);
tx.send(FromApp::CreateDocument)
.await
.expect("app tx channel is open");

while let Some(message) = rx.recv().await {
match message {
ToApp::SubscriptionSuccess(text_document) => {
// @TODO: get the short code for this document into the share UI
Copy link
Member Author

@sandreae sandreae Dec 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to load a new document into the UI here. This event occurs when the app starts, and also after a preceding request from the frontend to subscribe to a document based on it's short code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach (maybe nicer) for this messaging channel would be to still only have one message variant, but always include the document id as well as the message bytes. Then when a change in document id is detected we interpret this as a change of document.

Doesn't change anything about how the frontend would react, just how this change is communicated to the front end.

// component.
println!("new document: {}", text_document.hash())
}
ToApp::MessageReceived(bytes) => application.ingest_message(bytes),
}
}
});
}
Expand Down Expand Up @@ -182,24 +195,16 @@ impl AardvarkApplication {

// Apply remote changes to our local text CRDT
if let Err(err) = document.load_incremental(&message) {
eprintln!(
"failed applying text change from remote peer to automerge document: {err}"
);
window.add_toast(adw::Toast::new(
"The network provided bad data!"
));
eprintln!("failed applying text change from remote peer to automerge document: {err}");
window.add_toast(adw::Toast::new("The network provided bad data!"));
return;
}

// Get latest changes and apply them to our local text buffer
for patch in document.diff_incremental() {
match &patch.action {
PatchAction::SpliceText { index, value, .. } => {
buffer.splice(
*index as i32,
0,
value.make_string().as_str(),
);
buffer.splice(*index as i32, 0, value.make_string().as_str());
}
PatchAction::DeleteSeq { index, length } => {
buffer.splice(*index as i32, *length as i32, "");
Expand All @@ -210,7 +215,9 @@ impl AardvarkApplication {

// Sanity check that the text buffer and CRDT are in the same state
if buffer.full_text() != document.text() {
window.add_toast(adw::Toast::new("The CRDT and the text view have different states!"));
window.add_toast(adw::Toast::new(
"The CRDT and the text view have different states!",
));
// if the state diverged, use the CRDT as the source of truth
buffer.set_text(&document.text());
}
Expand All @@ -227,7 +234,7 @@ impl AardvarkApplication {
let bytes = self.imp().document.save_incremental();
let tx = self.imp().tx.clone();
glib::spawn_future_local(async move {
tx.send(bytes)
tx.send(FromApp::HandleMessage(bytes))
.await
.expect("sending message to networking backend");
});
Expand Down
2 changes: 2 additions & 0 deletions aardvark-app/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ mod imp {
let window = self.obj().clone();
let dialog = self.open_document_dialog.clone();
self.open_document_button.connect_clicked(move |_| {
// @TODO: Send `FromApp::SubscribeToDocument` message to network containing the
// share code and wait for the document to be joined.
dialog.present(Some(&window));
});
}
Expand Down
21 changes: 14 additions & 7 deletions aardvark-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ edition = "2021"
anyhow = "1.0.94"
async-trait = "0.1.83"
ciborium = "0.2.2"
futures-io = "0.3.31"
futures-sink = "0.3.31"
futures-util = "0.3.31"
iroh-gossip = "0.25.0"
p2panda-core = "0.1.0"
p2panda-discovery = { version = "0.1.0", features = ["mdns"] }
p2panda-net = "0.1.0"
p2panda-store = "0.1.0"
p2panda-stream = "0.1.0"
p2panda-sync = { version = "0.1.0", features = ["log-sync"] }
p2panda-core = { git = "https://github.com/p2panda/p2panda", rev = "a174565f25c40603cfa3eb48fcde97918f23dbc8" }
p2panda-discovery = { git = "https://github.com/p2panda/p2panda", rev = "a174565f25c40603cfa3eb48fcde97918f23dbc8", features = [
"mdns",
] }
p2panda-net = { git = "https://github.com/p2panda/p2panda", rev = "a174565f25c40603cfa3eb48fcde97918f23dbc8" }
p2panda-store = { git = "https://github.com/p2panda/p2panda", rev = "a174565f25c40603cfa3eb48fcde97918f23dbc8" }
p2panda-stream = { git = "https://github.com/p2panda/p2panda", rev = "a174565f25c40603cfa3eb48fcde97918f23dbc8" }
p2panda-sync = { git = "https://github.com/p2panda/p2panda", rev = "a174565f25c40603cfa3eb48fcde97918f23dbc8", features = [
"log-sync",
] }
serde = { version = "1.0.215", features = ["derive"] }
tokio = { version = "1.42.0", features = ["full"] }
tokio = { version = "1.42.0", features = ["rt"] }
tokio-stream = "0.1.17"
64 changes: 64 additions & 0 deletions aardvark-node/src/document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::collections::HashMap;
use std::sync::{Arc, RwLock, RwLockWriteGuard};

use async_trait::async_trait;
use p2panda_core::PublicKey;
use p2panda_sync::log_sync::TopicLogMap;

use crate::operation::LogId;
use crate::topics::{AardvarkTopics, TextDocument};

pub type ShortCode = [char; 6];

#[derive(Clone, Debug)]
pub struct TextDocumentStore {
inner: Arc<RwLock<TextDocumentStoreInner>>,
}

impl Default for TextDocumentStore {
fn default() -> Self {
Self {
inner: Arc::new(RwLock::new(TextDocumentStoreInner {
authors: HashMap::new(),
})),
}
}
}

impl TextDocumentStore {
pub fn write(&self) -> RwLockWriteGuard<TextDocumentStoreInner> {
self.inner.write().expect("acquire write lock")
}
}

#[derive(Clone, Debug)]
pub struct TextDocumentStoreInner {
pub authors: HashMap<PublicKey, Vec<TextDocument>>,
}

#[async_trait]
impl TopicLogMap<AardvarkTopics, LogId> for TextDocumentStore {
async fn get(&self, topic: &AardvarkTopics) -> Option<HashMap<PublicKey, Vec<LogId>>> {
let text_document = match topic {
// When discovering documents we don't want any sync sessions to occur, this is a
// little hack to make sure that is the case, as if both peers resolve a topic to
// "None" then the sync session will naturally end.
AardvarkTopics::DiscoveryCode(_) => return None,
AardvarkTopics::TextDocument(text_document) => text_document,
};

let authors = &self.inner.read().unwrap().authors;
let mut result = HashMap::<PublicKey, Vec<LogId>>::new();

for (public_key, documents) in authors {
if documents.contains(text_document) {
result
.entry(*public_key)
.and_modify(|logs| logs.push(text_document.hash()))
.or_insert(vec![text_document.hash()]);
}
}

Some(result)
}
}
4 changes: 3 additions & 1 deletion aardvark-node/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub mod document;
pub mod network;
pub mod operation;
pub mod operation;
pub mod topics;
Loading
Loading