Live-sync authorized file roots
This commit is contained in:
parent
1caaedda90
commit
bab13cc0a1
5 changed files with 128 additions and 7 deletions
|
|
@ -917,6 +917,7 @@ async fn cas_root_sync_from_peer(
|
|||
}
|
||||
let store = Store::open(&node.paths.metadata_db())?;
|
||||
let mut tree_bytes_imported = false;
|
||||
let mut imported_high_water = None;
|
||||
let root = root
|
||||
.map(|remote_root| {
|
||||
let stored_name = remote_file_root_name(&node_id, &remote_root.name);
|
||||
|
|
@ -960,9 +961,18 @@ async fn cas_root_sync_from_peer(
|
|||
updated_at_ms: remote_root.updated_at_ms,
|
||||
};
|
||||
store.upsert_file_root(&stored)?;
|
||||
imported_high_water = Some(remote_root.updated_at_ms);
|
||||
Ok(file_root_from_stored(&stored))
|
||||
})
|
||||
.transpose()?;
|
||||
if let Some(high_water_ms) = imported_high_water {
|
||||
store_live_sync_cursor(
|
||||
&store,
|
||||
peer_node,
|
||||
&format!("cas-tree:{response_name}"),
|
||||
high_water_ms,
|
||||
)?;
|
||||
}
|
||||
Ok(ControlResponse::CasRootSynced {
|
||||
peer_node_id: node_id,
|
||||
peer_agent_id: agent_id,
|
||||
|
|
@ -1844,6 +1854,18 @@ fn sync_watermarks_for_peer(
|
|||
}
|
||||
}
|
||||
}
|
||||
for root in store.list_file_roots()? {
|
||||
if root.path.starts_with("remote:") {
|
||||
continue;
|
||||
}
|
||||
let resource = format!("resource:cas-tree:{}", root.name);
|
||||
if can_sync_resource(store, &peer, &resource, "cas.fetch")? {
|
||||
watermarks.push(SyncWatermark {
|
||||
stream: format!("cas-tree:{}", root.name),
|
||||
high_water: root.updated_at_ms,
|
||||
});
|
||||
}
|
||||
}
|
||||
watermarks.sort_by(|left, right| left.stream.cmp(&right.stream));
|
||||
Ok(watermarks)
|
||||
}
|
||||
|
|
@ -2176,6 +2198,22 @@ async fn run_live_sync_once(node: &LocalNode) -> Result<(), NodeError> {
|
|||
tracing::debug!(peer = %peer.peer_id, %error, "SSH revocation live sync failed");
|
||||
}
|
||||
}
|
||||
if let Some(remote_watermarks) = remote_watermarks.as_ref() {
|
||||
for stream in remote_watermarks
|
||||
.keys()
|
||||
.filter(|stream| stream.starts_with("cas-tree:"))
|
||||
{
|
||||
if should_live_sync_stream(&store, &peer.peer_id, stream, Some(remote_watermarks))?
|
||||
{
|
||||
let name = stream.trim_start_matches("cas-tree:");
|
||||
if let Err(error) =
|
||||
cas_root_sync_from_peer(node, &peer.peer_id, name, None).await
|
||||
{
|
||||
tracing::debug!(peer = %peer.peer_id, root = %name, %error, "file-root live sync failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for kv in &kv_stores {
|
||||
let stream = format!("kv:{}", kv.name);
|
||||
if should_live_sync_stream(&store, &peer.peer_id, &stream, remote_watermarks.as_ref())?
|
||||
|
|
@ -5358,9 +5396,32 @@ mod tests {
|
|||
path: db_path.display().to_string(),
|
||||
})
|
||||
.expect("insert db");
|
||||
store
|
||||
.upsert_file_root(&StoredFileRoot {
|
||||
root_id: "file-root:shared".to_owned(),
|
||||
resource_id: "resource:cas-tree:shared".to_owned(),
|
||||
name: "shared".to_owned(),
|
||||
path: dir.path().join("shared").display().to_string(),
|
||||
latest_tree_hash: Some("abc".to_owned()),
|
||||
latest_tree_json: None,
|
||||
updated_at_ms: 123,
|
||||
})
|
||||
.expect("insert file root");
|
||||
store
|
||||
.upsert_file_root(&StoredFileRoot {
|
||||
root_id: "file-root:remote:node:right:shared".to_owned(),
|
||||
resource_id: "resource:cas-tree:shared".to_owned(),
|
||||
name: "remote-node-right-shared".to_owned(),
|
||||
path: "remote:node:right:shared".to_owned(),
|
||||
latest_tree_hash: Some("def".to_owned()),
|
||||
latest_tree_json: None,
|
||||
updated_at_ms: 456,
|
||||
})
|
||||
.expect("insert imported remote file root");
|
||||
|
||||
grant_test_capability(&store, "node:left", "resource:kv:prefs", "kv.read");
|
||||
grant_test_capability(&store, "node:left", "resource:db:notes", "db.sync");
|
||||
grant_test_capability(&store, "node:left", "resource:cas-tree:shared", "cas.fetch");
|
||||
|
||||
let watermarks = sync_watermarks_for_peer(&store, "node:left").expect("watermarks");
|
||||
|
||||
|
|
@ -5372,6 +5433,15 @@ mod tests {
|
|||
stream: "db:notes".to_owned(),
|
||||
high_water: 7,
|
||||
}));
|
||||
assert!(watermarks.contains(&SyncWatermark {
|
||||
stream: "cas-tree:shared".to_owned(),
|
||||
high_water: 123,
|
||||
}));
|
||||
assert!(
|
||||
!watermarks
|
||||
.iter()
|
||||
.any(|watermark| watermark.stream == "cas-tree:remote-node-right-shared")
|
||||
);
|
||||
assert!(
|
||||
!watermarks
|
||||
.iter()
|
||||
|
|
@ -5475,7 +5545,7 @@ mod tests {
|
|||
&right,
|
||||
ControlRequest::CasRootAdd {
|
||||
name: "shared".to_owned(),
|
||||
path: right_file_root,
|
||||
path: right_file_root.clone(),
|
||||
},
|
||||
)
|
||||
.expect("right file root add");
|
||||
|
|
@ -6588,6 +6658,19 @@ mod tests {
|
|||
},
|
||||
)
|
||||
.expect("right live document set");
|
||||
std::fs::write(right_file_root.join("live.txt"), b"live file root sync")
|
||||
.expect("write live file");
|
||||
let second_tree_hash = match handle_request(
|
||||
&right,
|
||||
ControlRequest::CasRootScan {
|
||||
name: "shared".to_owned(),
|
||||
},
|
||||
)
|
||||
.expect("right live file root scan")
|
||||
{
|
||||
ControlResponse::CasRootScanned { scan } => scan.tree.hash,
|
||||
other => panic!("unexpected right live file root scan response: {other:?}"),
|
||||
};
|
||||
insert_mock_crsqlite_change(&right_db_path, 8, "live");
|
||||
|
||||
run_live_sync_once(&left)
|
||||
|
|
@ -6622,6 +6705,34 @@ mod tests {
|
|||
.map(|document| document.state_json),
|
||||
Some(r#"{"title":"live"}"#.to_owned())
|
||||
);
|
||||
let live_remote_root = left_store
|
||||
.list_file_roots()
|
||||
.expect("list live-synced file roots")
|
||||
.into_iter()
|
||||
.find(|root| root.path == format!("remote:{}:shared", right.node_id))
|
||||
.expect("live-synced remote root");
|
||||
assert_eq!(
|
||||
live_remote_root.latest_tree_hash.as_deref(),
|
||||
Some(second_tree_hash.as_str())
|
||||
);
|
||||
assert!(
|
||||
LocalCas::new(left.paths.cas_dir())
|
||||
.has(&second_tree_hash)
|
||||
.expect("left has live-synced tree object")
|
||||
);
|
||||
let file_root_cursor = left_store
|
||||
.get_module_state(&live_sync_cursor_key(
|
||||
right_card.node_id.as_str(),
|
||||
"cas-tree:shared",
|
||||
))
|
||||
.expect("get live-synced file root cursor")
|
||||
.expect("file root cursor exists");
|
||||
assert_eq!(
|
||||
serde_json::from_str::<LiveSyncCursor>(&file_root_cursor.state_json)
|
||||
.expect("parse file root cursor")
|
||||
.cursor_ms,
|
||||
live_remote_root.updated_at_ms
|
||||
);
|
||||
let db_cursor = left_store
|
||||
.get_module_state(&live_sync_cursor_key(
|
||||
right_card.node_id.as_str(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue