Skip to content

Commit e1ab03f

Browse files
authored
feat: create doc from snapshot (#136)
1 parent c9cf106 commit e1ab03f

File tree

5 files changed

+57
-21
lines changed

5 files changed

+57
-21
lines changed

crates/loro-internal/src/encoding/encode_enhanced.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ pub fn decode_oplog_v2(oplog: &mut OpLog, input: &[u8]) -> Result<(), LoroError>
677677
// convert change into inner format
678678
let mut ops = RleVec::new();
679679
for op in change.ops {
680-
let mut lamport = change.lamport;
680+
let lamport = change.lamport;
681681
let content = op.content;
682682
let op = converter.convert_single_op(
683683
&op.container,
@@ -686,7 +686,6 @@ pub fn decode_oplog_v2(oplog: &mut OpLog, input: &[u8]) -> Result<(), LoroError>
686686
lamport,
687687
content,
688688
);
689-
lamport += op.atom_len() as Lamport;
690689
ops.push(op);
691690
}
692691

crates/loro-internal/src/loro.rs

+34-17
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ impl LoroDoc {
8383
doc
8484
}
8585

86+
pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
87+
let doc = Self::new();
88+
let (input, mode) = parse_encode_header(bytes)?;
89+
match mode {
90+
EncodeMode::Snapshot => {
91+
decode_app_snapshot(&doc, input, true)?;
92+
Ok(doc)
93+
}
94+
_ => Err(LoroError::DecodeError(
95+
"Invalid encode mode".to_string().into(),
96+
)),
97+
}
98+
}
99+
86100
/// Is the document empty? (no ops)
87101
#[inline(always)]
88102
pub fn can_reset_with_snapshot(&self) -> bool {
@@ -340,21 +354,7 @@ impl LoroDoc {
340354
bytes: &[u8],
341355
origin: string_cache::Atom<string_cache::EmptyStaticAtomSet>,
342356
) -> Result<(), LoroError> {
343-
if bytes.len() <= 6 {
344-
return Err(LoroError::DecodeError("Invalid bytes".into()));
345-
}
346-
347-
let (magic_bytes, input) = bytes.split_at(4);
348-
let magic_bytes: [u8; 4] = magic_bytes.try_into().unwrap();
349-
if magic_bytes != MAGIC_BYTES {
350-
return Err(LoroError::DecodeError("Invalid header bytes".into()));
351-
}
352-
let (version, input) = input.split_at(1);
353-
if version != [ENCODE_SCHEMA_VERSION] {
354-
return Err(LoroError::DecodeError("Invalid version".into()));
355-
}
356-
357-
let mode: EncodeMode = input[0].try_into()?;
357+
let (input, mode) = parse_encode_header(bytes)?;
358358
match mode {
359359
EncodeMode::Updates | EncodeMode::RleUpdates | EncodeMode::CompressedRleUpdates => {
360360
// TODO: need to throw error if state is in transaction
@@ -386,10 +386,10 @@ impl LoroDoc {
386386
}
387387
EncodeMode::Snapshot => {
388388
if self.can_reset_with_snapshot() {
389-
decode_app_snapshot(self, &input[1..], !self.detached)?;
389+
decode_app_snapshot(self, input, !self.detached)?;
390390
} else {
391391
let app = LoroDoc::new();
392-
decode_app_snapshot(&app, &input[1..], false)?;
392+
decode_app_snapshot(&app, input, false)?;
393393
let oplog = self.oplog.lock().unwrap();
394394
// TODO: PERF: the ser and de can be optimized out
395395
let updates = app.export_from(oplog.vv());
@@ -629,6 +629,23 @@ impl LoroDoc {
629629
}
630630
}
631631

632+
fn parse_encode_header(bytes: &[u8]) -> Result<(&[u8], EncodeMode), LoroError> {
633+
if bytes.len() <= 6 {
634+
return Err(LoroError::DecodeError("Invalid import data".into()));
635+
}
636+
let (magic_bytes, input) = bytes.split_at(4);
637+
let magic_bytes: [u8; 4] = magic_bytes.try_into().unwrap();
638+
if magic_bytes != MAGIC_BYTES {
639+
return Err(LoroError::DecodeError("Invalid header bytes".into()));
640+
}
641+
let (version, input) = input.split_at(1);
642+
if version != [ENCODE_SCHEMA_VERSION] {
643+
return Err(LoroError::DecodeError("Invalid version".into()));
644+
}
645+
let mode: EncodeMode = input[0].try_into()?;
646+
Ok((&input[1..], mode))
647+
}
648+
632649
impl Default for LoroDoc {
633650
fn default() -> Self {
634651
Self::new()

crates/loro-internal/src/oplog/pending_changes.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ impl OpLog {
208208
pub(super) fn to_local_op(change: Change<RemoteOp>, converter: &mut OpConverter) -> Change {
209209
let mut ops = RleVec::new();
210210
for op in change.ops {
211-
let mut lamport = change.lamport;
211+
let lamport = change.lamport;
212212
let content = op.content;
213213
let op = converter.convert_single_op(
214214
&op.container,
@@ -217,7 +217,6 @@ pub(super) fn to_local_op(change: Change<RemoteOp>, converter: &mut OpConverter)
217217
lamport,
218218
content,
219219
);
220-
lamport += op.atom_len() as Lamport;
221220
ops.push(op);
222221
}
223222
Change {

crates/loro-internal/tests/test.rs

+15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ fn import_after_init_handlers() {
4343
a.import(&b.export_snapshot()).unwrap();
4444
a.commit();
4545
}
46+
fn test_from_snapshot() {
47+
let a = LoroDoc::new_auto_commit();
48+
a.get_text("text").insert_(0, "0").unwrap();
49+
let snapshot = a.export_snapshot();
50+
let c = LoroDoc::from_snapshot(&snapshot).unwrap();
51+
assert_eq!(a.get_deep_value(), c.get_deep_value());
52+
assert_eq!(a.oplog_frontiers(), c.oplog_frontiers());
53+
assert_eq!(a.state_frontiers(), c.state_frontiers());
54+
let updates = a.export_from(&Default::default());
55+
let d = match LoroDoc::from_snapshot(&updates) {
56+
Ok(_) => panic!(),
57+
Err(e) => e,
58+
};
59+
assert!(matches!(d, loro_common::LoroError::DecodeError(..)));
60+
}
4661

4762
#[test]
4863
fn test_pending() {

crates/loro-wasm/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ impl Loro {
146146
Self(doc)
147147
}
148148

149+
#[wasm_bindgen(js_name = "fromSnapshot")]
150+
pub fn from_snapshot(snapshot: &[u8]) -> JsResult<Loro> {
151+
let doc = LoroDoc::from_snapshot(snapshot)?;
152+
Ok(Loro(doc))
153+
}
154+
149155
pub fn attach(&mut self) {
150156
self.0.attach();
151157
}

0 commit comments

Comments
 (0)