use std::io::{BufRead, Read}; use jmbl::{ ops::{ObjID, OpKind, OpWithTarget}, ReadableJMBL, OpOutput, JMBL, Value, }; use litl::Val; use std::collections::HashMap; use thousands::Separable; use ti64::MsSinceEpoch; pub fn load_automerge_paper_from_json( round_to_seconds: bool, ) -> (ReadableJMBL, ObjID, Vec) { let file = std::fs::File::open("./tests/automerge-paper.json.zip").unwrap(); let mut archive = zip::ZipArchive::new(file).unwrap(); let json_file = archive.by_index(0).unwrap(); let changes = std::io::BufReader::new(json_file) .lines() .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); let mut doc = JMBL::new_from_root(|view| view.create_list::([]), OpOutput::new_dummy()).readable; let list_id = doc.get_root().obj_id().unwrap(); let mut op_output_for_actor = HashMap::new(); let mut op_id_for_automerge_key = HashMap::new(); let mut n_inserts = 0; let mut n_deletes = 0; let op_stream = changes.filter_map(|change_value: serde_json::Value| { let change = change_value.as_object().unwrap(); let actor = change.get("actor").unwrap().as_str().unwrap(); let mut timestamp_ms = change.get("time").unwrap().as_u64().unwrap(); if round_to_seconds { timestamp_ms = (timestamp_ms / 1000) * 1000; } let mut op_output = op_output_for_actor .remove(actor) .unwrap_or_else(OpOutput::new_dummy); let change_ops = change.get("ops").unwrap().as_array().unwrap(); let first_op = change_ops.first().unwrap().as_object().unwrap(); let is_insert = first_op .get("insert") .unwrap_or(&serde_json::Value::Bool(false)) .as_bool() .unwrap(); let is_del = !is_insert && first_op.get("action").unwrap().as_str().unwrap() == "del"; if is_insert || is_del { let prev_key = first_op.get("key").unwrap().as_str().unwrap(); let prev = if prev_key == "_head" { list_id.init_op } else { *op_id_for_automerge_key .get(prev_key) .unwrap_or_else(|| panic!("Expected op id for automerge id {}", prev_key)) }; let op = if is_insert { n_inserts += 1; op_output.write_op_at_time( list_id, MsSinceEpoch(timestamp_ms as i64), prev, OpKind::ListInsertAfter, Some(Val::str(first_op.get("value").unwrap().as_str().unwrap())), ) } else { n_deletes += 1; op_output.write_op_at_time( list_id, MsSinceEpoch(timestamp_ms as i64), prev, OpKind::Undo, None, ) }; let automerge_key = format!( "{}@{}", change.get("startOp").unwrap().as_u64().unwrap(), actor ); op_id_for_automerge_key.insert(automerge_key, op.id); op_output_for_actor.insert(actor.to_owned(), op_output); doc = doc.with_ops_applied(&[OpWithTarget { target_obj_id: list_id, op: op.clone(), }]); // info!("{} = {:?}", automerge_key, op); Some(op) } else { None } }); let op_stream_with_target = op_stream .map(|op| OpWithTarget { target_obj_id: list_id, op, }) .collect(); println!( "Loaded from JSON: {} inserts, {} deletes, {} ops", n_inserts.separate_with_underscores(), n_deletes.separate_with_underscores(), (n_inserts + n_deletes).separate_with_underscores() ); (doc, list_id, op_stream_with_target) } pub fn load_automerge_paper_result() -> String { let mut file = std::fs::File::open("./tests/automerge-paper.tex").unwrap(); let mut string = String::new(); file.read_to_string(&mut string).unwrap(); string }