diff options
Diffstat (limited to 'src/remake/kind.rs')
| -rw-r--r-- | src/remake/kind.rs | 52 |
1 files changed, 50 insertions, 2 deletions
diff --git a/src/remake/kind.rs b/src/remake/kind.rs index b36db4c..42bdd88 100644 --- a/src/remake/kind.rs +++ b/src/remake/kind.rs @@ -17,6 +17,9 @@ //! Protocol data under `_continuum` is not a kind's business: the generic part carries //! it forward and replays it unchanged. +use std::path::Path; + +use anyhow::{Context, anyhow, bail}; use automerge::AutoCommit; use base64::engine::{Engine, general_purpose::STANDARD as BASE64}; use serde::{Deserialize, Serialize}; @@ -24,6 +27,7 @@ use serde_json::Value as JsonValue; use super::snapshot::{META_ROOT, canonical}; use crate::document::store::{self, StoreError}; +use crate::files::{FileKind, Files}; /// Name of the kind used when a request doesn't name one. pub(crate) const DEFAULT_KIND: &str = "compact"; @@ -74,8 +78,8 @@ pub(crate) trait RemakeKind: Send + Sync + 'static { /// Whether the coordinator fixes the base when it commits instead of when it /// proposes (no `baseHeads` then). Right for a kind whose content is the old - /// document at the base: the later the base, the fewer writes need carrying over, - /// and a write made after it is lost if its author restarts before switching. + /// document at the base: the later the base, the fewer writes every switching node + /// replays as fresh values (whole lists and texts, concurrently on every node). fn base_at_commit(&self) -> bool { false } @@ -91,6 +95,50 @@ pub(crate) fn by_name(name: &str) -> Option<&'static dyn RemakeKind> { } } +/// Builds a new swarm's genesis content into `target` (`--genesis --remake SPEC`), the +/// way a remake of that kind builds a new epoch from an empty one: +/// * `import:PATH`: an Automerge document, e.g. a backup (its content, without history +/// and protocol data); +/// * `replace:PATH`: a JSON document, the store root; +/// * `compact`: nothing (an empty document compacts to itself). +pub(crate) fn remake_genesis( + spec: &str, + files: &Files, + target: &mut AutoCommit, +) -> anyhow::Result<()> { + let (name, path) = match spec.split_once(':') { + Some((name, path)) => (name, Some(Path::new(path))), + None => (spec, None), + }; + let kind = by_name(name).ok_or_else(|| anyhow!("unknown remake kind {name:?}"))?; + let request = match (kind.name(), path) { + ("compact", None) => return Ok(()), + ("compact", Some(_)) => bail!("compact takes no file"), + (_, None) => bail!("{name} needs a file: {name}:PATH"), + (name, Some(path)) => { + let bytes = files + .read(FileKind::Remake, path) + .with_context(|| format!("Reading {}", path.display()))? + .with_context(|| format!("{} doesn't exist", path.display()))?; + match name { + "import" => serde_json::json!({ "document": BASE64.encode(&bytes) }), + "replace" => { + let document: JsonValue = serde_json::from_slice(&bytes) + .with_context(|| format!("{} is not JSON", path.display()))?; + serde_json::json!({ "document": document }) + } + other => bail!("{other} can't make a genesis document"), + } + } + }; + let prepared = kind + .propose(&request) + .map_err(|reason| anyhow!("{name}: {reason}"))?; + kind.build(&AutoCommit::new(), prepared.content.as_deref(), target)?; + target.commit(); + Ok(()) +} + /// Paths owned by the protocol rather than by any kind. pub(crate) fn is_reserved(path: &[String]) -> bool { path.first().is_some_and(|root| root == META_ROOT) |
