diff options
| author | tv <tv@krebsco.de> | 2026-11-01 22:22:41 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-01 22:22:41 +0100 |
| commit | 656cd21ce2625a8a2931b5b03d1d84227dd7e3e8 (patch) | |
| tree | 8441c5ea268ffde428e39b869bf95552167d03c5 /src/document/replica.rs | |
| parent | d2c05c17bc58cdbedfb0a702b2858c52aff2eedf (diff) | |
record node ID and API user for every write
Diffstat (limited to 'src/document/replica.rs')
| -rw-r--r-- | src/document/replica.rs | 54 |
1 files changed, 44 insertions, 10 deletions
diff --git a/src/document/replica.rs b/src/document/replica.rs index df2fafd..3717865 100644 --- a/src/document/replica.rs +++ b/src/document/replica.rs @@ -175,14 +175,16 @@ impl Replica { } /// The writes `f` makes, as one change stamped `at` if it succeeds (receivers tell - /// by the stamp how long the change took to reach them, `swarm::stats`), dropped if - /// not. Doesn't refresh the heads: the caller does, under the same lock. + /// by the stamp how long the change took to reach them, `swarm::stats`), by + /// `writer`, dropped if not. Doesn't refresh the heads: the caller does, under the + /// same lock. pub(crate) fn transact<T, E>( &mut self, at: crate::clock::UnixMillis, + writer: &Writer, f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E>, ) -> Result<T, E> { - transact(&mut self.doc, at, f) + transact(&mut self.doc, at, writer, f) } } @@ -231,7 +233,11 @@ mod tests { let path = vec!["a".to_owned()]; let mut replica = Replica::new(Epoch::GENESIS, Document::new()); - let refused: Result<(), &str> = replica.transact(UnixMillis(1), |doc| { + let writer = Writer { + node: "node", + user: None, + }; + let refused: Result<(), &str> = replica.transact(UnixMillis(1), &writer, |doc| { store::write_path(doc, &path, &json!(1)).unwrap(); Err("refused") }); @@ -242,7 +248,7 @@ mod tests { // The same writer commits afterwards, and that saves too. replica - .transact(UnixMillis(2), |doc| { + .transact(UnixMillis(2), &writer, |doc| { store::write_path(doc, &path, &json!(2)) }) .unwrap(); @@ -261,20 +267,33 @@ mod tests { } } -/// The writes `f` makes to `doc`, as one change stamped `at` if it succeeds, dropped if -/// not ([`Replica::transact`], and writes into the document before a rejoin). +/// Who makes a change, as it records: the node, as the change's author (its node ID), +/// and the user it wrote for through the API, if any, as its message. Every node holds +/// every change, so who wrote what is known swarm-wide. +pub(crate) struct Writer<'a> { + pub(crate) node: &'a str, + pub(crate) user: Option<&'a str>, +} + +/// The writes `f` makes to `doc`, as one change stamped `at` by `writer` if it succeeds, +/// dropped if not ([`Replica::transact`], and writes into the document before a rejoin). pub(crate) fn transact<T, E>( doc: &mut Document, at: crate::clock::UnixMillis, + writer: &Writer, f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E>, ) -> Result<T, E> { + authored(doc, writer.node); let mut transaction = doc.transaction(); let out = f(&mut transaction); match &out { Ok(_) => { - transaction.commit_with( - automerge::transaction::CommitOptions::default().with_time(at.as_u64() as i64), - ); + let mut options = + automerge::transaction::CommitOptions::default().with_time(at.as_u64() as i64); + if let Some(user) = writer.user { + options.set_message(user); + } + transaction.commit_with(options); } Err(_) => { transaction.rollback(); @@ -282,3 +301,18 @@ pub(crate) fn transact<T, E>( } out } + +/// Makes `node` the author of `doc`'s changes from now on. Setting an author gives the +/// document a new actor; this node keeps its own (`with_actor`), which it tracks +/// (`RemakeState::own_actors`, the document file), as only it writes with it. +fn authored(doc: &mut Document, node: &str) { + if doc + .get_author() + .is_some_and(|author| author.as_bytes() == node.as_bytes()) + { + return; + } + let actor = doc.get_actor().clone(); + doc.set_author(Some(automerge::Author::from(node.as_bytes().to_vec()))); + doc.set_actor(actor); +} |
