From b3245c191a9ac65bfd67a34b328dca2d78933096 Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Wed, 29 Jan 2025 18:22:16 +0100 Subject: [PATCH] WIP: Restructure app --- Cargo.lock | 19 +++- Cargo.toml | 2 +- aardvark-app/Cargo.toml | 9 +- aardvark-app/src/application.rs | 169 ++++++----------------------- aardvark-app/src/document.rs | 65 +++++++++++ aardvark-app/src/main.rs | 1 - aardvark-app/src/textbuffer.rs | 122 +++++++++++++-------- aardvark-app/src/window.rs | 13 +-- aardvark-doc/Cargo.toml | 12 +++ aardvark-doc/src/document.rs | 186 ++++++++++++++++++++++++++++++++ aardvark-doc/src/lib.rs | 2 + aardvark-doc/src/service.rs | 72 +++++++++++++ 12 files changed, 469 insertions(+), 203 deletions(-) create mode 100644 aardvark-doc/Cargo.toml create mode 100644 aardvark-doc/src/document.rs create mode 100644 aardvark-doc/src/lib.rs create mode 100644 aardvark-doc/src/service.rs diff --git a/Cargo.lock b/Cargo.lock index 8f03be8..1a68416 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,19 +6,28 @@ version = 4 name = "aardvark" version = "0.1.0" dependencies = [ + "aardvark-doc", "aardvark-node", - "anyhow", - "automerge", "gettext-rs", "gtk4", "libadwaita", - "serde", - "serde_json", "sourceview5", - "tokio", + "tracing", "tracing-subscriber", ] +[[package]] +name = "aardvark-doc" +version = "0.1.0" +dependencies = [ + "aardvark-node", + "anyhow", + "automerge", + "glib", + "tokio", + "tracing", +] + [[package]] name = "aardvark-node" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 57f5326..52f5539 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["aardvark-app", "aardvark-node"] +members = ["aardvark-app", "aardvark-doc", "aardvark-node"] diff --git a/aardvark-app/Cargo.toml b/aardvark-app/Cargo.toml index 550f1e6..9aef4ef 100644 --- a/aardvark-app/Cargo.toml +++ b/aardvark-app/Cargo.toml @@ -4,18 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] +aardvark-doc = { path = "../aardvark-doc" } aardvark-node = { path = "../aardvark-node" } -anyhow = "1.0.94" -automerge = "0.5.12" gettext-rs = { version = "0.7", features = ["gettext-system"] } gtk = { version = "0.9", package = "gtk4", features = ["gnome_47"] } -serde = { version = "1.0.215", features = ["derive"] } -serde_json = "1.0.128" -tokio = { version = "1.42.0", features = ["full"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } sourceview = { package = "sourceview5", version = "0.9" } +tracing = "0.1" [dependencies.adw] package = "libadwaita" version = "0.7" -features = ["v1_6"] +features = ["v1_6"] \ No newline at end of file diff --git a/aardvark-app/src/application.rs b/aardvark-app/src/application.rs index a36f658..ebbb83c 100644 --- a/aardvark-app/src/application.rs +++ b/aardvark-app/src/application.rs @@ -18,35 +18,23 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -use std::cell::{OnceCell, RefCell}; - -use aardvark_node::network; +use aardvark_doc::service::Service; use adw::prelude::*; use adw::subclass::prelude::*; use gettextrs::gettext; -use gtk::{gio, glib}; -use tokio::sync::{mpsc, oneshot}; -use automerge::PatchAction; +use gtk::{gio, glib, glib::Properties}; +use crate::AardvarkWindow; use crate::config::VERSION; -use crate::document::Document; -use crate::glib::closure_local; -use crate::{AardvarkTextBuffer, AardvarkWindow}; mod imp { use super::*; - #[derive(Debug)] + #[derive(Properties, Default)] + #[properties(wrapper_type = super::AardvarkApplication)] pub struct AardvarkApplication { - pub window: OnceCell, - pub document: Document, - pub tx: mpsc::Sender>, - pub rx: RefCell>>>, - #[allow(dead_code)] - backend_shutdown: oneshot::Sender<()>, - } - - impl AardvarkApplication { + #[property(get)] + pub service: Service, } #[glib::object_subclass] @@ -54,21 +42,9 @@ mod imp { const NAME: &'static str = "AardvarkApplication"; type Type = super::AardvarkApplication; type ParentType = adw::Application; - - fn new() -> Self { - let document = Document::default(); - let (backend_shutdown, tx, rx) = network::run().expect("running p2p backend"); - - AardvarkApplication { - document, - backend_shutdown, - tx, - rx: RefCell::new(Some(rx)), - window: OnceCell::new(), - } - } } + #[glib::derived_properties] impl ObjectImpl for AardvarkApplication { fn constructed(&self) { self.parent_constructed(); @@ -79,16 +55,19 @@ mod imp { } impl ApplicationImpl for AardvarkApplication { - // We connect to the activate callback to create a window when the application has been - // launched. Additionally, this callback notifies us when the user tries to launch a - // "second instance" of the application. When they try to do that, we'll just present any - // existing window. - fn activate(&self) { - let application = self.obj(); - let window = application.get_window(); + fn startup(&self) { + self.service.startup(); + self.parent_startup(); + } - // Ask the window manager/compositor to present the window - window.clone().upcast::().present(); + fn shutdown(&self) { + self.service.shutdown(); + self.parent_shutdown(); + } + + fn activate(&self) { + let window = AardvarkWindow::new(self.obj().as_ref()); + window.present(); } } @@ -117,7 +96,16 @@ impl AardvarkApplication { let about_action = gio::ActionEntry::builder("about") .activate(move |app: &Self, _, _| app.show_about()) .build(); - self.add_action_entries([quit_action, about_action]); + let new_window_action = gio::ActionEntry::builder("new-window") + .activate(move |app: &Self, _, _| app.new_window()) + .build(); + self.add_action_entries([quit_action, about_action, new_window_action]); + } + + fn new_window(&self) { + println!("New window"); + let window = AardvarkWindow::new(self); + window.present(); } fn show_about(&self) { @@ -135,101 +123,4 @@ impl AardvarkApplication { about.present(Some(&window)); } - - pub fn get_window(&self) -> &AardvarkWindow { - // Get the current window or create one if necessary - self.imp().window.get_or_init(|| { - let window = AardvarkWindow::new(self); - - { - let application = self.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); - } - }); - } - - { - let application = self.clone(); - - window.get_text_buffer().connect_closure( - "text-change", - false, - closure_local!(|_buffer: AardvarkTextBuffer, - position: i32, - del: i32, - text: &str| { - application.update_text(position, del, text); - }), - ); - } - - window - }) - } - - fn ingest_message(&self, message: Vec) { - let document = &self.imp().document; - let window = self.imp().window.get().unwrap(); - let buffer = window.get_text_buffer(); - - // 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!" - )); - 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(), - ); - } - PatchAction::DeleteSeq { index, length } => { - buffer.splice(*index as i32, *length as i32, ""); - } - _ => (), - } - } - - // 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!")); - // if the state diverged, use the CRDT as the source of truth - buffer.set_text(&document.text()); - } - - dbg!(document.text()); - } - - fn update_text(&self, position: i32, del: i32, text: &str) { - self.imp() - .document - .update(position, del, text) - .expect("update automerge document after text update"); - - let bytes = self.imp().document.save_incremental(); - let tx = self.imp().tx.clone(); - glib::spawn_future_local(async move { - tx.send(bytes) - .await - .expect("sending message to networking backend"); - }); - } } diff --git a/aardvark-app/src/document.rs b/aardvark-app/src/document.rs index ac46f96..c5d0aa0 100644 --- a/aardvark-app/src/document.rs +++ b/aardvark-app/src/document.rs @@ -4,6 +4,8 @@ use std::fmt; use anyhow::Result; use automerge::transaction::Transactable; use automerge::{AutoCommit, AutoSerde, ObjId, ObjType, Patch, ReadDoc}; +use tokio::sync::{mpsc, oneshot}; +use automerge::PatchAction; /// Hard-coded automerge document schema in bytes representation for "Aardvark". /// @@ -38,6 +40,26 @@ impl Document { Self { doc: RefCell::new(doc), } + + self.tx.set(tx).unwrap(); + + glib::spawn_future_local(clone!(#[weak] window, async move { + while let Some(bytes) = rx.recv().await { + window.ingest_message(bytes); + } + })); + + buffer.connect_text_changed(|_buffer: AardvarkTextBuffer, + position: i32, + del: i32, + text: &str| { + let bytes = document.save_incremental(); + glib::spawn_future_local(async move { + tx.send(bytes) + .await + .expect("sending message to networking backend"); + }); + }); } pub fn from_bytes(bytes: &[u8]) -> Self { @@ -93,6 +115,49 @@ impl Document { let mut doc = self.doc.borrow_mut(); doc.save_incremental() } + + + fn ingest_message(&self, message: Vec) { + let document = self.imp().document; + let buffer = self.imp().text_view.buffer(); + + // 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}" + ); + self.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(), + ); + } + PatchAction::DeleteSeq { index, length } => { + buffer.splice(*index as i32, *length as i32, ""); + } + _ => (), + } + } + + // Sanity check that the text buffer and CRDT are in the same state + if buffer.full_text() != document.text() { + self.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()); + } + + dbg!(document.text()); + } } impl Default for Document { diff --git a/aardvark-app/src/main.rs b/aardvark-app/src/main.rs index 1afa626..ecf870d 100644 --- a/aardvark-app/src/main.rs +++ b/aardvark-app/src/main.rs @@ -21,7 +21,6 @@ mod application; mod config; mod components; -mod document; mod textbuffer; mod window; diff --git a/aardvark-app/src/textbuffer.rs b/aardvark-app/src/textbuffer.rs index 24af79a..6a44602 100644 --- a/aardvark-app/src/textbuffer.rs +++ b/aardvark-app/src/textbuffer.rs @@ -18,22 +18,39 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -use glib::subclass::Signal; -use gtk::glib; +use gtk::{glib, glib::clone}; use gtk::prelude::*; use gtk::subclass::prelude::*; -use std::cell::Cell; -use std::sync::OnceLock; +use std::cell::{OnceCell, Cell, RefCell}; use sourceview::*; use sourceview::subclass::prelude::*; use sourceview::prelude::BufferExt; +use aardvark_doc::document::Document; +use tracing::{info, error}; mod imp { use super::*; - #[derive(Debug, Default)] + #[derive(Debug, Default, glib::Properties)] + #[properties(wrapper_type = super::AardvarkTextBuffer)] pub struct AardvarkTextBuffer { - pub inhibit_emit_text_change: Cell, + pub inhibit_text_change: Cell, + pub document_handlers: OnceCell, + #[property(get, set = Self::set_document)] + pub document: RefCell>, + } + + impl AardvarkTextBuffer { + fn set_document(&self, document: Option<&Document>) { + if let Some(document) = document.as_ref() { + self.obj().set_inhibit_text_change(true); + self.obj().set_text(&document.text()); + self.obj().set_inhibit_text_change(false); + } + + self.document_handlers.get().unwrap().set_target(document); + self.document.replace(document.cloned()); + } } #[glib::object_subclass] @@ -43,17 +60,8 @@ mod imp { type ParentType = sourceview::Buffer; } + #[glib::derived_properties] impl ObjectImpl for AardvarkTextBuffer { - fn signals() -> &'static [Signal] { - static SIGNALS: OnceLock> = OnceLock::new(); - SIGNALS.get_or_init(|| { - vec![Signal::builder("text-change") - .param_types([i32::static_type(), i32::static_type(), str::static_type()]) - .build()] - }) - - } - fn constructed(&self) { let manager = adw::StyleManager::default(); let buffer = self.obj(); @@ -74,33 +82,73 @@ mod imp { buffer.set_style_scheme(style_scheme().as_ref()); } )); + + // We could use a signal group to block hanlders + let document_handlers = glib::SignalGroup::with_type(Document::static_type()); + document_handlers.connect_local("text-inserted", false, clone!(#[weak] buffer, #[upgrade_or] None, move |values| { + let pos: i32 = values.get(1).unwrap().get().unwrap(); + let text: &str = values.get(2).unwrap().get().unwrap(); + + let mut pos_iter = buffer.iter_at_offset(pos); + buffer.set_inhibit_text_change(true); + buffer.insert(&mut pos_iter, text); + buffer.set_inhibit_text_change(false); + + None + })); + + document_handlers.connect_local("range-deleted", false, clone!(#[weak] buffer, #[upgrade_or] None, move |values| { + let start: i32 = values.get(1).unwrap().get().unwrap(); + let end: i32 = values.get(2).unwrap().get().unwrap(); + let mut start = buffer.iter_at_offset(start); + let mut end = buffer.iter_at_offset(end); + buffer.set_inhibit_text_change(true); + buffer.delete(&mut start, &mut end); + buffer.set_inhibit_text_change(false); + + None + })); + + self.document_handlers.set(document_handlers).unwrap(); } } impl TextBufferImpl for AardvarkTextBuffer { fn insert_text(&self, iter: &mut gtk::TextIter, new_text: &str) { let offset = iter.offset(); - println!("inserting new text {} at pos {}", new_text, offset); - if !self.inhibit_emit_text_change.get() { - self.obj() - .emit_by_name::<()>("text-change", &[&offset, &0, &new_text]); + info!("inserting new text {} at pos {}", new_text, offset); + + if !self.inhibit_text_change.get() { + if let Some(document) = self.document.borrow().as_ref() { + self.document_handlers.get().unwrap().block(); + if let Err(error) = document.insert_text(offset, new_text) { + error!("Failed to submit changes to the document: {error}"); + } + self.document_handlers.get().unwrap().unblock(); + } } + self.parent_insert_text(iter, new_text); } fn delete_range(&self, start: &mut gtk::TextIter, end: &mut gtk::TextIter) { let offset_start = start.offset(); let offset_end = end.offset(); - println!( + info!( "deleting range at start {} end {}", offset_start, offset_end ); - if !self.inhibit_emit_text_change.get() { - self.obj().emit_by_name::<()>( - "text-change", - &[&offset_start, &(offset_end - offset_start), &""], - ); + + if !self.inhibit_text_change.get() { + if let Some(document) = self.document.borrow().as_ref() { + self.document_handlers.get().unwrap().block(); + if let Err(error) = document.delete_range(offset_start, offset_end) { + error!("Failed to submit changes to the document: {error}") + } + self.document_handlers.get().unwrap().unblock(); + } } + self.parent_delete_range(start, end); } } @@ -118,26 +166,10 @@ impl AardvarkTextBuffer { glib::Object::builder().build() } - fn set_inhibit_emit_text_change(&self, inhibit_emit_text_change: bool) { + fn set_inhibit_text_change(&self, inhibit_text_change: bool) { self.imp() - .inhibit_emit_text_change - .set(inhibit_emit_text_change); - } - - pub fn splice(&self, pos: i32, del: i32, text: &str) { - if del != 0 { - let mut begin = self.iter_at_offset(pos); - let mut end = self.iter_at_offset(pos + del); - self.set_inhibit_emit_text_change(true); - self.delete(&mut begin, &mut end); - self.set_inhibit_emit_text_change(false); - return; - } - - let mut pos_iter = self.iter_at_offset(pos); - self.set_inhibit_emit_text_change(true); - self.insert(&mut pos_iter, text); - self.set_inhibit_emit_text_change(false); + .inhibit_text_change + .set(inhibit_text_change); } pub fn full_text(&self) -> String { diff --git a/aardvark-app/src/window.rs b/aardvark-app/src/window.rs index 80e9279..bc8ee27 100644 --- a/aardvark-app/src/window.rs +++ b/aardvark-app/src/window.rs @@ -25,8 +25,9 @@ use adw::subclass::prelude::*; use gtk::prelude::*; use gtk::{gio, glib, gdk}; use sourceview::*; +use aardvark_doc::document::Document; -use crate::{AardvarkTextBuffer, components::ZoomLevelSelector}; +use crate::{AardvarkTextBuffer, AardvarkApplication, components::ZoomLevelSelector}; const BASE_TEXT_FONT_SIZE: f64 = 24.0; @@ -159,6 +160,11 @@ mod imp { self.open_document_button.connect_clicked(move |_| { dialog.present(Some(&window)); }); + + let service = gio::Application::default().and_downcast::().unwrap().service(); + //TODO wait for the document to be ready before displaying the buffer + // TODO: The user needs to provide a document id + buffer.set_document(Document::new(&service, "some id")); } } @@ -195,11 +201,6 @@ impl AardvarkWindow { .build() } - pub fn get_text_buffer(&self) -> AardvarkTextBuffer { - let window = self.imp(); - window.text_view.buffer().downcast().unwrap() - } - pub fn add_toast(&self, toast: adw::Toast) { self.imp().toast_overlay.add_toast(toast); } diff --git a/aardvark-doc/Cargo.toml b/aardvark-doc/Cargo.toml new file mode 100644 index 0000000..44a4928 --- /dev/null +++ b/aardvark-doc/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "aardvark-doc" +version = "0.1.0" +edition = "2021" + +[dependencies] +aardvark-node = { path = "../aardvark-node" } +anyhow = "1.0.94" +automerge = "0.5.12" +glib = "0.20" +tokio = { version = "1.42.0", features = ["full"] } +tracing = "0.1" \ No newline at end of file diff --git a/aardvark-doc/src/document.rs b/aardvark-doc/src/document.rs new file mode 100644 index 0000000..6d48512 --- /dev/null +++ b/aardvark-doc/src/document.rs @@ -0,0 +1,186 @@ +use anyhow::Result; +use glib::prelude::*; +use glib::subclass::Signal; +use glib::subclass::prelude::*; +use glib::{Properties, clone}; +use std::cell::{Cell, OnceCell, RefCell}; +use std::sync::OnceLock; + +use crate::service::Service; + +use automerge::PatchAction; +use automerge::transaction::Transactable; +use automerge::{AutoCommit, ObjId, ReadDoc}; +use tokio::sync::mpsc; +use tracing::error; + +/// Hard-coded automerge document schema in bytes representation for "Aardvark". +/// +/// Creating a local document based on this schema allows peers to independently do so as they'll +/// all have the same schema and object ids in the end. Otherwise peers wouldn't be able to merge +/// their changes into each other's documents as the id's wouldn't match. +/// +/// Read more here: +/// +const DOCUMENT_SCHEMA: [u8; 119] = [ + 133, 111, 74, 131, 14, 200, 8, 95, 0, 109, 1, 16, 163, 64, 79, 49, 42, 30, 77, 109, 146, 45, + 91, 5, 214, 2, 217, 205, 1, 252, 203, 208, 39, 6, 89, 188, 223, 101, 41, 50, 160, 144, 47, 147, + 187, 74, 77, 252, 185, 64, 18, 211, 205, 23, 118, 97, 221, 216, 176, 1, 239, 6, 1, 2, 3, 2, 19, + 2, 35, 2, 64, 2, 86, 2, 7, 21, 5, 33, 2, 35, 2, 52, 1, 66, 2, 86, 2, 128, 1, 2, 127, 0, 127, 1, + 127, 1, 127, 0, 127, 0, 127, 7, 127, 3, 100, 111, 99, 127, 0, 127, 1, 1, 127, 4, 127, 0, 127, + 0, 0, +]; + +/// Identifier in automerge document path where we store the text. +const DOCUMENT_OBJ_ID: &str = "doc"; + +mod imp { + use super::*; + + #[derive(Properties, Default)] + #[properties(wrapper_type = super::Document)] + pub struct Document { + crdt_doc: RefCell>, + #[property(get, construct_only)] + id: OnceCell, + #[property(get, set)] + ready: Cell, + #[property(get, construct_only)] + service: OnceCell, + tx: OnceCell>>, + } + + impl Document { + fn text_object(&self) -> ObjId { + let doc_borrow = self.crdt_doc.borrow(); + let doc = doc_borrow.as_ref().expect("crdt_doc to be set"); + let (_value, obj_id) = doc + .get(automerge::ROOT, DOCUMENT_OBJ_ID) + .unwrap_or_default() + .expect("text object at root"); + obj_id + } + + pub fn text(&self) -> String { + let text_obj = self.text_object(); + let doc_borrow = self.crdt_doc.borrow(); + let doc = doc_borrow.as_ref().expect("crdt_doc to be set"); + doc.text(&text_obj) + .expect("text to be given in automerge document") + } + + pub fn splice_text(&self, index: i32, delete_len: i32, chunk: &str) -> Result<()> { + let text_obj = self.text_object(); + let mut doc_borrow = self.crdt_doc.borrow_mut(); + let doc = doc_borrow.as_mut().expect("crdt_doc to be set"); + doc.splice_text(&text_obj, index as usize, delete_len as isize, chunk)?; + // Move the diff pointer forward to current position + doc.update_diff_cursor(); + + let bytes = doc.save_incremental(); + let tx = self.tx.get().unwrap().clone(); + glib::spawn_future(async move { + tx.send(bytes) + .await + .expect("sending message to networking backend"); + }); + + Ok(()) + } + + fn ingest_message(&self, message: Vec) { + let mut doc_borrow = self.crdt_doc.borrow_mut(); + let doc = doc_borrow.as_mut().expect("crdt_doc to be set"); + // Apply remote changes to our local text CRDT + if let Err(err) = doc.load_incremental(&message) { + error!("failed applying text change from remote peer to automerge document: {err}"); + return; + } + + // Get latest changes and apply them to our local text buffer + for patch in doc.diff_incremental() { + match &patch.action { + PatchAction::SpliceText { index, value, .. } => { + self.obj().emit_by_name::<()>("text-inserted", &[ + &(*index as i32), + &value.make_string().as_str(), + ]); + } + PatchAction::DeleteSeq { index, length } => { + self.obj().emit_by_name::<()>("range-deleted", &[ + &(*index as i32), + &(*length as i32), + ]); + } + _ => (), + } + } + } + } + + #[glib::derived_properties] + impl ObjectImpl for Document { + fn signals() -> &'static [Signal] { + static SIGNALS: OnceLock> = OnceLock::new(); + SIGNALS.get_or_init(|| { + vec![ + Signal::builder("text-inserted") + .param_types([glib::types::Type::I32, glib::types::Type::STRING]) + .build(), + Signal::builder("range-deleted") + .param_types([glib::types::Type::I32, glib::types::Type::I32]) + .build(), + ] + }) + } + + fn constructed(&self) { + let doc = + AutoCommit::load(&DOCUMENT_SCHEMA).expect("load automerge document from bytes"); + self.crdt_doc.replace(Some(doc)); + let (tx, mut rx) = self.service.get().unwrap().document("some id"); + + self.tx.set(tx).unwrap(); + + glib::spawn_future_local(clone!( + #[weak(rename_to = this)] + self, + async move { + while let Some(bytes) = rx.recv().await { + this.ingest_message(bytes); + } + } + )); + } + } + + #[glib::object_subclass] + impl ObjectSubclass for Document { + const NAME: &'static str = "Document"; + type Type = super::Document; + } +} + +glib::wrapper! { + pub struct Document(ObjectSubclass); +} +impl Document { + pub fn new(service: &Service, id: &str) -> Self { + glib::Object::builder() + .property("service", service) + .property("id", id) + .build() + } + + pub fn insert_text(&self, index: i32, chunk: &str) -> Result<()> { + self.imp().splice_text(index, 0, chunk) + } + + pub fn delete_range(&self, index: i32, len: i32) -> Result<()> { + self.imp().splice_text(index, len, "") + } + + pub fn text(&self) -> String { + self.imp().text() + } +} diff --git a/aardvark-doc/src/lib.rs b/aardvark-doc/src/lib.rs new file mode 100644 index 0000000..139b17f --- /dev/null +++ b/aardvark-doc/src/lib.rs @@ -0,0 +1,2 @@ +pub mod document; +pub mod service; diff --git a/aardvark-doc/src/service.rs b/aardvark-doc/src/service.rs new file mode 100644 index 0000000..286b3c5 --- /dev/null +++ b/aardvark-doc/src/service.rs @@ -0,0 +1,72 @@ +use glib::prelude::*; +use glib::subclass::prelude::*; +use glib::Properties; +use std::cell::{RefCell, Cell}; +use tokio::sync::{mpsc, oneshot}; +use tracing::error; + +use aardvark_node::network; + +mod imp { + use super::*; + + #[derive(Properties, Default)] + #[properties(wrapper_type = super::Service)] + pub struct Service { + #[property(get, set)] + ready: Cell, + pub backend_shutdown: RefCell>>, + pub backend_shutdown2: RefCell>>, + } + + #[glib::derived_properties] + impl ObjectImpl for Service { + } + + #[glib::object_subclass] + impl ObjectSubclass for Service { + const NAME: &'static str = "Service"; + type Type = super::Service; + } +} + +glib::wrapper! { + pub struct Service(ObjectSubclass); +} + +impl Service { + pub fn new() -> Self { + glib::Object::new() + } + + pub fn startup(&self) { + } + + pub fn shutdown(&self) { + if let Some(tx) = self.imp().backend_shutdown.take() { + if tx.send(()).is_err() { + error!("Failed to shutdown the network service"); + } + } + } + + pub(crate) fn document(&self, id: &str) -> ( + mpsc::Sender>, + mpsc::Receiver>, + ) { + // TODO: actually use the ID + let _ = id; + let (backend_shutdown, tx, rx) = network::run().expect("running p2p backend"); + + let prev = self.imp().backend_shutdown.replace(Some(backend_shutdown)); + self.imp().backend_shutdown2.replace(prev); + + (tx, rx) + } +} + +impl Default for Service { + fn default() -> Self { + Service::new() + } +}