From 13bf17bdb4b123f5c4cfcf5336d851749b552d09 Mon Sep 17 00:00:00 2001 From: Aurelia Date: Thu, 21 Nov 2024 15:45:27 +0100 Subject: [PATCH] [WIP] Expose last updater. --- bindings/generator/api/workspace.py | 3 + .../crates/client/src/workspace/merge.rs | 4 + .../workspace/transactions/inbound_sync.rs | 2 + .../src/workspace/transactions/read_folder.rs | 1 + .../src/workspace/transactions/stat_entry.rs | 6 ++ .../tests/unit/workspace/inbound_sync_file.rs | 4 + .../client/tests/unit/workspace/merge_file.rs | 5 + .../tests/unit/workspace/merge_folder.rs | 12 ++- .../tests/unit/workspace/read_folder.rs | 12 +++ .../client/tests/unit/workspace/stat_entry.rs | 28 ++++-- .../tests/unit/operations/create_folder.rs | 1 + .../tests/unit/operations/open_file.rs | 1 + .../platform_storage/tests/unit/workspace.rs | 1 + .../crates/testbed/src/template/crc_hash.rs | 4 + .../local_manifest/local_file_manifest.json5 | 4 + .../local_folder_manifest.json5 | 4 + .../crates/types/src/local_manifest/file.rs | 5 + .../crates/types/src/local_manifest/folder.rs | 9 ++ .../types/tests/unit/local_file_manifest.rs | 93 +++++-------------- .../types/tests/unit/local_folder_manifest.rs | 17 ++++ 20 files changed, 139 insertions(+), 77 deletions(-) diff --git a/bindings/generator/api/workspace.py b/bindings/generator/api/workspace.py index 18f1cff1e46..b387e65396e 100644 --- a/bindings/generator/api/workspace.py +++ b/bindings/generator/api/workspace.py @@ -5,6 +5,7 @@ from .common import ( U64, DateTime, + DeviceID, EntryName, ErrorVariant, FsPath, @@ -316,6 +317,7 @@ class File: is_placeholder: bool need_sync: bool size: SizeInt + last_updater: DeviceID class Folder: confinement_point: Optional[VlobID] @@ -326,6 +328,7 @@ class Folder: base_version: VersionInt is_placeholder: bool need_sync: bool + last_update: DeviceID async def workspace_stat_entry( diff --git a/libparsec/crates/client/src/workspace/merge.rs b/libparsec/crates/client/src/workspace/merge.rs index 4db24a8c552..3e69b054362 100644 --- a/libparsec/crates/client/src/workspace/merge.rs +++ b/libparsec/crates/client/src/workspace/merge.rs @@ -174,6 +174,7 @@ pub(super) fn merge_local_file_manifest( need_sync: _, // Ignore `updated`: we don't merge data that change on each sync updated: _, + last_updater: _, parent: local_parent, size: local_size, blocksize: local_blocksize, @@ -275,6 +276,7 @@ pub(super) fn merge_local_folder_manifest( local_confinement_points: _, // Ignored, we don't merge data that change on each sync updated: _, + last_updater: _, } = local; // 0) Sanity checks, caller is responsible to handle them properly ! @@ -370,12 +372,14 @@ pub(super) fn merge_local_folder_manifest( updated: merged_updated, children: merged_children, speculative: merged_speculative, + last_updater: merged_last_updater, } = &mut merge_in_progress; *merged_speculative = false; *merged_parent = local.parent; *merged_need_sync = local.need_sync; *merged_updated = local.updated; + *merged_last_updater = local.last_updater; local.children.clone_into(merged_children); return MergeLocalFolderManifestOutcome::Merged(Arc::new(merge_in_progress)); diff --git a/libparsec/crates/client/src/workspace/transactions/inbound_sync.rs b/libparsec/crates/client/src/workspace/transactions/inbound_sync.rs index cf5bcc2db1d..5cdfcab4a88 100644 --- a/libparsec/crates/client/src/workspace/transactions/inbound_sync.rs +++ b/libparsec/crates/client/src/workspace/transactions/inbound_sync.rs @@ -454,6 +454,7 @@ async fn handle_conflict_and_update_store( size, blocksize, blocks, + last_updater, } = child_manifest.as_ref(); let mut conflicting = @@ -462,6 +463,7 @@ async fn handle_conflict_and_update_store( conflicting.updated = *updated; conflicting.size = *size; conflicting.blocksize = *blocksize; + conflicting.last_updater = *last_updater; blocks.clone_into(&mut conflicting.blocks); (child_manifest.base.id, Arc::new(conflicting)) diff --git a/libparsec/crates/client/src/workspace/transactions/read_folder.rs b/libparsec/crates/client/src/workspace/transactions/read_folder.rs index 736929e9e46..b239bb8fced 100644 --- a/libparsec/crates/client/src/workspace/transactions/read_folder.rs +++ b/libparsec/crates/client/src/workspace/transactions/read_folder.rs @@ -105,6 +105,7 @@ impl FolderReader { base_version: self.manifest.base.version, is_placeholder: self.manifest.base.version == 0, need_sync: self.manifest.need_sync, + last_updater: self.manifest.last_updater, } } diff --git a/libparsec/crates/client/src/workspace/transactions/stat_entry.rs b/libparsec/crates/client/src/workspace/transactions/stat_entry.rs index 3509e5e4ae2..02dcd85ae9e 100644 --- a/libparsec/crates/client/src/workspace/transactions/stat_entry.rs +++ b/libparsec/crates/client/src/workspace/transactions/stat_entry.rs @@ -26,6 +26,7 @@ pub enum EntryStat { is_placeholder: bool, need_sync: bool, size: SizeInt, + last_updater: DeviceID, }, // Here Folder can also be the root of the workspace (i.e. WorkspaceManifest) Folder { @@ -40,6 +41,7 @@ pub enum EntryStat { base_version: VersionInt, is_placeholder: bool, need_sync: bool, + last_updater: DeviceID, }, } @@ -145,6 +147,7 @@ pub(crate) async fn stat_entry_by_id( base_version: manifest.base.version, is_placeholder: manifest.base.version == 0, need_sync: manifest.need_sync, + last_updater: manifest.last_updater, }, ArcLocalChildManifest::File(manifest) => { // If the file may be currently opened with un-flushed modifications. @@ -174,6 +177,7 @@ pub(crate) async fn stat_entry_by_id( is_placeholder: manifest.base.version == 0, need_sync: manifest.need_sync, size: manifest.size, + last_updater: manifest.last_updater, } } }; @@ -216,6 +220,7 @@ pub(crate) async fn stat_entry( base_version: manifest.base.version, is_placeholder: manifest.base.version == 0, need_sync: manifest.need_sync, + last_updater: manifest.last_updater, }, ArcLocalChildManifest::File(manifest) => { // If the file may be currently opened with un-flushed modifications. @@ -245,6 +250,7 @@ pub(crate) async fn stat_entry( is_placeholder: manifest.base.version == 0, need_sync: manifest.need_sync, size: manifest.size, + last_updater: manifest.last_updater, } } }; diff --git a/libparsec/crates/client/tests/unit/workspace/inbound_sync_file.rs b/libparsec/crates/client/tests/unit/workspace/inbound_sync_file.rs index 06ec6698a95..523ccc8b6c1 100644 --- a/libparsec/crates/client/tests/unit/workspace/inbound_sync_file.rs +++ b/libparsec/crates/client/tests/unit/workspace/inbound_sync_file.rs @@ -357,10 +357,12 @@ async fn non_placeholder( size, blocksize, blocks, + last_updater, } = conflicted_manifest.as_ref(); p_assert_eq!(*parent, before_sync_bar_txt_manifest.parent); p_assert_eq!(*need_sync, before_sync_bar_txt_manifest.need_sync); p_assert_eq!(*updated, before_sync_bar_txt_manifest.updated); + p_assert_eq!(*last_updater, before_sync_bar_txt_manifest.last_updater); p_assert_eq!(*size, before_sync_bar_txt_manifest.size); p_assert_eq!(*blocksize, before_sync_bar_txt_manifest.blocksize); p_assert_eq!(*blocks, before_sync_bar_txt_manifest.blocks); @@ -414,10 +416,12 @@ async fn non_placeholder( size, blocksize, blocks, + last_updater, } = conflicted_manifest.as_ref(); p_assert_eq!(*parent, before_sync_bar_txt_manifest.parent); p_assert_eq!(*need_sync, before_sync_bar_txt_manifest.need_sync); p_assert_eq!(*updated, before_sync_bar_txt_manifest.updated); + p_assert_eq!(*last_updater, before_sync_bar_txt_manifest.last_updater); p_assert_eq!(*size, before_sync_bar_txt_manifest.size); p_assert_eq!(*blocksize, before_sync_bar_txt_manifest.blocksize); p_assert_eq!(*blocks, before_sync_bar_txt_manifest.blocks); diff --git a/libparsec/crates/client/tests/unit/workspace/merge_file.rs b/libparsec/crates/client/tests/unit/workspace/merge_file.rs index 5981c331620..0e079401171 100644 --- a/libparsec/crates/client/tests/unit/workspace/merge_file.rs +++ b/libparsec/crates/client/tests/unit/workspace/merge_file.rs @@ -51,6 +51,7 @@ async fn no_remote_change( raw_size: NonZeroU64::new(10).unwrap(), access: Some(remote.blocks[0].clone()), }]], + last_updater: remote.author, }; match kind { "same_version" => (), @@ -132,6 +133,7 @@ async fn remote_only_change( size: remote.size, blocksize: remote.blocksize, blocks: vec![], + last_updater: remote.author, }; remote.version = 2; @@ -157,6 +159,7 @@ async fn remote_only_change( size: 0, blocksize: Blocksize::try_from(512 * 1024).unwrap(), blocks: vec![], + last_updater: "bob@dev1".parse().unwrap(), }; match kind { @@ -314,6 +317,7 @@ async fn local_and_remote_changes( raw_size: NonZeroU64::new(10).unwrap(), access: Some(remote.blocks[0].clone()), }]], + last_updater: remote.author, }; remote.version = 2; @@ -335,6 +339,7 @@ async fn local_and_remote_changes( raw_size: NonZeroU64::new(10).unwrap(), access: Some(remote.blocks[0].clone()), }]], + last_updater: remote.author, }; let expected = match kind { diff --git a/libparsec/crates/client/tests/unit/workspace/merge_folder.rs b/libparsec/crates/client/tests/unit/workspace/merge_folder.rs index 389f6e85c96..9152e751eef 100644 --- a/libparsec/crates/client/tests/unit/workspace/merge_folder.rs +++ b/libparsec/crates/client/tests/unit/workspace/merge_folder.rs @@ -45,6 +45,7 @@ async fn no_remote_change( local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: remote.author, }; match kind { "same_version" => (), @@ -129,6 +130,7 @@ async fn no_remote_change_but_local_uses_outdated_prevent_sync_pattern( local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: remote.author, }; match kind { @@ -237,6 +239,7 @@ async fn remote_only_change( local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: remote.author, }; remote.version = 2; @@ -261,6 +264,7 @@ async fn remote_only_change( local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: "bob@dev1".parse().unwrap(), }; let prevent_sync_pattern = Regex::from_glob_pattern("*.tmp").unwrap(); @@ -632,6 +636,7 @@ async fn local_and_remote_changes( local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: remote.author, }; remote.version = 2; @@ -656,6 +661,7 @@ async fn local_and_remote_changes( local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: "bob@dev1".parse().unwrap(), }; let prevent_sync_pattern = Regex::from_glob_pattern("*.tmp").unwrap(); @@ -1424,6 +1430,7 @@ async fn local_and_remote_changes( expected.parent = local.base.id; expected.base.parent = local.base.id; + let local_child_id = VlobID::from_hex("a1d7229d7e44418a8a4e4fd821003fd3").unwrap(); let remote_child_id = VlobID::from_hex("9a20331879744a149f55bc3ba16e8225").unwrap(); @@ -1443,6 +1450,7 @@ async fn local_and_remote_changes( // So in the end we must do a merge between local and remote changes, // which leads to a conflict ! remote.author = local_author; + expected.last_updater = local_author; expected.base.author = local_author; expected @@ -2237,8 +2245,8 @@ async fn local_and_remote_changes( local.need_sync = false; // ...the remote hasn't anything important to merge, but this should // refresh the confinement in local with the new prevent sync pattern. - remote.author = local_author; - + remote.author = local_author; + expected.last_updater = local_author; // Given the remote is from ourself, the merge considers we already know // about it and hence acknowledges it and preserve the local children. // However the new prevent sync pattern means the local child is now diff --git a/libparsec/crates/client/tests/unit/workspace/read_folder.rs b/libparsec/crates/client/tests/unit/workspace/read_folder.rs index 3d832adb608..5ada2bf3a65 100644 --- a/libparsec/crates/client/tests/unit/workspace/read_folder.rs +++ b/libparsec/crates/client/tests/unit/workspace/read_folder.rs @@ -51,6 +51,7 @@ async fn ok_with_local_cache(#[values(true, false)] target_is_root: bool, env: & is_placeholder: false, need_sync: false, size: 11, + last_updater: alice.device_id, }, ), ( @@ -64,6 +65,7 @@ async fn ok_with_local_cache(#[values(true, false)] target_is_root: bool, env: & base_version: 1, is_placeholder: false, need_sync: false, + last_updater: alice.device_id, }, ), ]; @@ -84,6 +86,7 @@ async fn ok_with_local_cache(#[values(true, false)] target_is_root: bool, env: & is_placeholder: false, need_sync: false, size: 0, + last_updater: alice.device_id, }, ), ( @@ -97,6 +100,7 @@ async fn ok_with_local_cache(#[values(true, false)] target_is_root: bool, env: & base_version: 1, is_placeholder: false, need_sync: false, + last_updater: alice.device_id, }, ), ]; @@ -200,6 +204,7 @@ async fn ok_no_local_cache(#[values(true, false)] target_is_root: bool, env: &Te is_placeholder: false, need_sync: false, size: 11, + last_updater: alice.device_id, }, ), ( @@ -213,6 +218,7 @@ async fn ok_no_local_cache(#[values(true, false)] target_is_root: bool, env: &Te base_version: 1, is_placeholder: false, need_sync: false, + last_updater: alice.device_id, }, ), ]; @@ -233,6 +239,7 @@ async fn ok_no_local_cache(#[values(true, false)] target_is_root: bool, env: &Te is_placeholder: false, need_sync: false, size: 0, + last_updater: alice.device_id, }, ), ( @@ -246,6 +253,7 @@ async fn ok_no_local_cache(#[values(true, false)] target_is_root: bool, env: &Te base_version: 1, is_placeholder: false, need_sync: false, + last_updater: alice.device_id, }, ), ]; @@ -435,6 +443,7 @@ async fn read_folder_with_confined_entries( is_placeholder: false, need_sync: false, size: 0, + last_updater: alice.device_id, }, ), ( @@ -448,6 +457,7 @@ async fn read_folder_with_confined_entries( base_version: 1, is_placeholder: false, need_sync: false, + last_updater: alice.device_id, }, ), ]; @@ -572,6 +582,7 @@ async fn read_folder_containing_under_modification_file( is_placeholder: false, need_sync: true, size: expected_size, + last_updater: alice.device_id, }, ), ( @@ -585,6 +596,7 @@ async fn read_folder_containing_under_modification_file( base_version: 1, is_placeholder: false, need_sync: false, + last_updater: alice.device_id, }, ), ]; diff --git a/libparsec/crates/client/tests/unit/workspace/stat_entry.rs b/libparsec/crates/client/tests/unit/workspace/stat_entry.rs index 05a9847f160..187e30dec43 100644 --- a/libparsec/crates/client/tests/unit/workspace/stat_entry.rs +++ b/libparsec/crates/client/tests/unit/workspace/stat_entry.rs @@ -47,7 +47,7 @@ async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) updated, base_version, is_placeholder, - need_sync, + need_sync,last_updater } if { p_assert_eq!(confinement_point, None); @@ -58,6 +58,8 @@ async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) p_assert_eq!(base_version, 1); p_assert_eq!(is_placeholder, false); p_assert_eq!(need_sync, false); + + p_assert_eq!(last_updater, alice.device_id); true } ); @@ -75,7 +77,7 @@ async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) updated, base_version, is_placeholder, - need_sync, + need_sync,last_updater } if { p_assert_eq!(confinement_point, None); @@ -86,6 +88,8 @@ async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) p_assert_eq!(base_version, 1); p_assert_eq!(is_placeholder, false); p_assert_eq!(need_sync, false); + p_assert_eq!(last_updater, alice.device_id); + true } ); @@ -104,7 +108,7 @@ async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) base_version, is_placeholder, need_sync, - size, + size,last_updater } if { p_assert_eq!(confinement_point, None); @@ -116,6 +120,8 @@ async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) p_assert_eq!(is_placeholder, false); p_assert_eq!(need_sync, false); p_assert_eq!(size, 11); // Contains "hello world" + p_assert_eq!(last_updater, alice.device_id); + true } ); @@ -206,7 +212,7 @@ async fn stat_entry_by_id( updated, base_version, is_placeholder, - need_sync, + need_sync,last_updater } if { p_assert_eq!(confinement_point, expected_confinement_point); @@ -217,6 +223,8 @@ async fn stat_entry_by_id( p_assert_eq!(base_version, 1); p_assert_eq!(is_placeholder, false); p_assert_eq!(need_sync, false); + p_assert_eq!(last_updater, alice.device_id); + true } ); @@ -235,7 +243,7 @@ async fn stat_entry_by_id( updated, base_version, is_placeholder, - need_sync, + need_sync, last_updater } if { p_assert_eq!(confinement_point, expected_confinement_point); @@ -246,6 +254,8 @@ async fn stat_entry_by_id( p_assert_eq!(base_version, 1); p_assert_eq!(is_placeholder, false); p_assert_eq!(need_sync, false); + p_assert_eq!(last_updater, alice.device_id); + true } ); @@ -264,7 +274,7 @@ async fn stat_entry_by_id( base_version, is_placeholder, need_sync, - size, + size,last_updater } if { p_assert_eq!(confinement_point, expected_confinement_point); @@ -276,6 +286,8 @@ async fn stat_entry_by_id( p_assert_eq!(is_placeholder, false); p_assert_eq!(need_sync, false); p_assert_eq!(size, 11); // Contains "hello world" + p_assert_eq!(last_updater, alice.device_id); + true } ); @@ -319,6 +331,7 @@ async fn stat_entry_on_speculative_workspace(env: &TestbedEnv) { base_version, is_placeholder, need_sync, + last_updater, } if { p_assert_eq!(confinement_point, None); p_assert_eq!(id, wksp1_id); @@ -328,6 +341,8 @@ async fn stat_entry_on_speculative_workspace(env: &TestbedEnv) { p_assert_eq!(base_version, 0); p_assert_eq!(is_placeholder, true); p_assert_eq!(need_sync, true); + p_assert_eq!(last_updater, alice.device_id); + true } ); @@ -606,6 +621,7 @@ async fn stat_entry_on_under_modification_file( is_placeholder: false, need_sync: true, size: expected_size, + last_updater: alice.device_id, }; p_assert_eq!(stat, expected_stat); diff --git a/libparsec/crates/platform_mountpoint/tests/unit/operations/create_folder.rs b/libparsec/crates/platform_mountpoint/tests/unit/operations/create_folder.rs index 1d26183bd69..e928322cb5f 100644 --- a/libparsec/crates/platform_mountpoint/tests/unit/operations/create_folder.rs +++ b/libparsec/crates/platform_mountpoint/tests/unit/operations/create_folder.rs @@ -100,6 +100,7 @@ async fn parent_doesnt_exist(tmp_path: TmpPath, env: &TestbedEnv) { base_version: 0, is_placeholder: false, need_sync: false, + last_updater: "alice@dev1".parse().unwrap(), })) } else { // Fallback to real lookup diff --git a/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs b/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs index ed8424af83e..0a1e9cb75ff 100644 --- a/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs +++ b/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs @@ -151,6 +151,7 @@ async fn no_create_and_not_found(tmp_path: TmpPath, env: &TestbedEnv) { is_placeholder: false, need_sync: false, size: 0, + last_updater: "alice@dev1".parse().unwrap(), })) } else { // Fallback to real lookup diff --git a/libparsec/crates/platform_storage/tests/unit/workspace.rs b/libparsec/crates/platform_storage/tests/unit/workspace.rs index 67cee1354a7..ca7a3b767cb 100644 --- a/libparsec/crates/platform_storage/tests/unit/workspace.rs +++ b/libparsec/crates/platform_storage/tests/unit/workspace.rs @@ -1039,6 +1039,7 @@ async fn non_speculative_init(env: &TestbedEnv) { local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: alice.device_id, }; p_assert_eq!(workspace_manifest, expected); } diff --git a/libparsec/crates/testbed/src/template/crc_hash.rs b/libparsec/crates/testbed/src/template/crc_hash.rs index 98a11d73f74..29886347903 100644 --- a/libparsec/crates/testbed/src/template/crc_hash.rs +++ b/libparsec/crates/testbed/src/template/crc_hash.rs @@ -407,6 +407,7 @@ impl CrcHash for LocalFolderManifest { local_confinement_points, remote_confinement_points, speculative, + last_updater, } = self; base.crc_hash(hasher); @@ -417,6 +418,7 @@ impl CrcHash for LocalFolderManifest { local_confinement_points.crc_hash(hasher); remote_confinement_points.crc_hash(hasher); speculative.crc_hash(hasher); + last_updater.crc_hash(hasher); } } @@ -501,6 +503,7 @@ impl CrcHash for LocalFileManifest { size, blocksize, blocks, + last_updater, } = self; base.crc_hash(hasher); @@ -510,6 +513,7 @@ impl CrcHash for LocalFileManifest { size.crc_hash(hasher); blocksize.crc_hash(hasher); blocks.crc_hash(hasher); + last_updater.crc_hash(hasher); } } diff --git a/libparsec/crates/types/schema/local_manifest/local_file_manifest.json5 b/libparsec/crates/types/schema/local_manifest/local_file_manifest.json5 index 9995f2ff6d6..5c476491792 100644 --- a/libparsec/crates/types/schema/local_manifest/local_file_manifest.json5 +++ b/libparsec/crates/types/schema/local_manifest/local_file_manifest.json5 @@ -19,6 +19,10 @@ "name": "updated", "type": "DateTime" }, + { + "name": "last_updater", + "type": "DeviceID" + }, { "name": "size", "type": "Size" diff --git a/libparsec/crates/types/schema/local_manifest/local_folder_manifest.json5 b/libparsec/crates/types/schema/local_manifest/local_folder_manifest.json5 index d2a1feb6b19..1ed33ff742d 100644 --- a/libparsec/crates/types/schema/local_manifest/local_folder_manifest.json5 +++ b/libparsec/crates/types/schema/local_manifest/local_folder_manifest.json5 @@ -19,6 +19,10 @@ "name": "updated", "type": "DateTime" }, + { + "name": "last_updater", + "type": "DeviceID" + }, { "name": "children", "type": "Map" diff --git a/libparsec/crates/types/src/local_manifest/file.rs b/libparsec/crates/types/src/local_manifest/file.rs index c34859e5025..856126e9af7 100644 --- a/libparsec/crates/types/src/local_manifest/file.rs +++ b/libparsec/crates/types/src/local_manifest/file.rs @@ -29,6 +29,7 @@ pub struct LocalFileManifest { pub parent: VlobID, pub need_sync: bool, pub updated: DateTime, + pub last_updater: DeviceID, pub size: u64, pub blocksize: Blocksize, /// This field is named after the `FileManifest::blocks` field, however it @@ -60,6 +61,7 @@ impl TryFrom for LocalFileManifest { size: data.size, blocksize: data.blocksize.try_into()?, blocks: data.blocks, + last_updater: data.last_updater, }) } } @@ -75,6 +77,7 @@ impl From for LocalFileManifestData { size: obj.size, blocksize: obj.blocksize.into(), blocks: obj.blocks, + last_updater: obj.last_updater, } } } @@ -102,6 +105,7 @@ impl LocalFileManifest { blocksize: DEFAULT_BLOCK_SIZE, size: 0, blocks: vec![], + last_updater: author, } } @@ -218,6 +222,7 @@ impl LocalFileManifest { size: remote.size, blocksize: remote.blocksize, blocks, + last_updater: remote.author, base: remote, }; diff --git a/libparsec/crates/types/src/local_manifest/folder.rs b/libparsec/crates/types/src/local_manifest/folder.rs index 8454db8db06..a389f24d454 100644 --- a/libparsec/crates/types/src/local_manifest/folder.rs +++ b/libparsec/crates/types/src/local_manifest/folder.rs @@ -25,6 +25,7 @@ pub struct LocalFolderManifest { pub parent: VlobID, pub need_sync: bool, pub updated: DateTime, + pub last_updater: DeviceID, pub children: HashMap, // Confined entries are entries that are meant to stay locally and not be added // to the uploaded remote manifest when synchronizing. The criteria for being @@ -62,6 +63,7 @@ impl_transparent_data_format_conversion!( local_confinement_points, remote_confinement_points, speculative, + last_updater ); impl_local_manifest_dump!(LocalFolderManifest); @@ -87,6 +89,7 @@ impl LocalFolderManifest { local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: author, } } @@ -156,6 +159,7 @@ impl LocalFolderManifest { local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative, + last_updater: author, } } @@ -270,6 +274,7 @@ pub struct UnconfinedLocalFolderManifest { pub updated: DateTime, pub children: HashMap, pub speculative: bool, + pub last_updater: DeviceID, } impl UnconfinedLocalFolderManifest { @@ -280,6 +285,7 @@ impl UnconfinedLocalFolderManifest { updated: remote.updated, children: remote.children.clone(), speculative: false, + last_updater: remote.author, base: remote, } } @@ -327,6 +333,7 @@ impl UnconfinedLocalFolderManifest { local_confinement_points: HashSet::new(), remote_confinement_points, speculative: false, + last_updater: remote.author, base: remote, } } @@ -390,6 +397,7 @@ impl UnconfinedLocalFolderManifest { local_confinement_points, remote_confinement_points, speculative: self.speculative, + last_updater: self.last_updater, }; // Check whether there are existing local confinement entries to restore @@ -463,6 +471,7 @@ impl UnconfinedLocalFolderManifest { updated: local_manifest.updated, children: new_children, speculative: local_manifest.speculative, + last_updater: local_manifest.last_updater, } } } diff --git a/libparsec/crates/types/tests/unit/local_file_manifest.rs b/libparsec/crates/types/tests/unit/local_file_manifest.rs index 5b71f71225f..84acb1d8814 100644 --- a/libparsec/crates/types/tests/unit/local_file_manifest.rs +++ b/libparsec/crates/types/tests/unit/local_file_manifest.rs @@ -12,81 +12,35 @@ use libparsec_tests_lite::prelude::*; #[rstest] fn serde_local_file_manifest_ok(alice: &Device) { - // Generated from Parsec 3.0.0-b.12+dev + // Generated from Parsec 3.1.1-a.0+dev // Content: // type: 'local_file_manifest' - // base: { - // type: 'file_manifest', - // author: ext(2, 0xde10a11cec0010000000000000000000), - // timestamp: ext(1, 1638618643208821) i.e. 2021-12-04T12:50:43.208821Z, - // id: ext(2, 0x87c6b5fd3b454c94bab51d6af1c6930b), - // parent: ext(2, 0x07748fbf67a646428427865fd730bf3e), - // version: 42, - // created: ext(1, 1638618643208821) i.e. 2021-12-04T12:50:43.208821Z, - // updated: ext(1, 1638618643208821) i.e. 2021-12-04T12:50:43.208821Z, - // size: 700, - // blocksize: 512, - // blocks: [ - // { - // id: ext(2, 0xb82954f1138b4d719b7f5bd78915d20f), - // offset: 0, - // size: 512, - // digest: 0x076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560, - // }, - // { - // id: ext(2, 0xd7e3af6a03e1414db0f4682901e9aa4b), - // offset: 512, - // size: 188, - // digest: 0xe37ce3b00a1f15b3de62029972345420b76313a885c6ccc6e3b5547857b3ecc6, - // }, - // ], - // } + // base: { type: 'file_manifest', author: ext(2, 0xde10a11cec0010000000000000000000), timestamp: ext(1, 1638618643208821) i.e. 2021-12-04T12:50:43.208821Z, id: ext(2, 0x87c6b5fd3b454c94bab51d6af1c6930b), parent: ext(2, 0x07748fbf67a646428427865fd730bf3e), version: 42, created: ext(1, 1638618643208821) i.e. 2021-12-04T12:50:43.208821Z, updated: ext(1, 1638618643208821) i.e. 2021-12-04T12:50:43.208821Z, size: 700, blocksize: 512, blocks: [ { id: ext(2, 0xb82954f1138b4d719b7f5bd78915d20f), offset: 0, size: 512, digest: 0x076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560, }, { id: ext(2, 0xd7e3af6a03e1414db0f4682901e9aa4b), offset: 512, size: 188, digest: 0xe37ce3b00a1f15b3de62029972345420b76313a885c6ccc6e3b5547857b3ecc6, }, ], } // parent: ext(2, 0x40c8fe8cd69742479f418f1a6d54ea7a) // need_sync: True // updated: ext(1, 1638618643208821) i.e. 2021-12-04T12:50:43.208821Z + // last_updater: ext(2, 0xde10a11cec0010000000000000000000) // size: 500 // blocksize: 512 - // blocks: [ - // [ - // { - // id: ext(2, 0xad67b6b5b9ad4653bf8e2b405bb6115f), - // start: 0, - // stop: 250, - // raw_offset: 0, - // raw_size: 512, - // access: { id: ext(2, 0xb82954f1138b4d719b7f5bd78915d20f), - // offset: 0, - // size: 512, - // digest: 0x076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560, - // }, - // }, - // { - // id: ext(2, 0x2f99258022a94555b3109e81d34bdf97), - // start: 250, - // stop: 500, - // raw_offset: 250, - // raw_size: 250, - // access: None, - // }, - // ], - // ] - let data = &hex!( - "9c71be661347b29f96b31b32976c4bce3997dbc1f6033999dc2ffd14c73c5c2e81db1f" - "59735067b4c7e1afc74bfa35b5395b2371e30c3b3945265ea2df504ecc82e0cfb2395e" - "c93cf04e92516e3c543b612dd8e42a2466fdb98505f78baeba6d5dad78ee97f25e5513" - "bff5bf471822e5210adb9caa4e01acdbd8b7e959d678d1815a437e33c57c431c433375" - "ea491c086d095beca33d74d8ba5b9ee70cd1e91259e431b486eb6b9fe8644e5d323542" - "f157ca168ba2809e368a1299a6f24b0e58d5704e9d5838312c0b41d012c79d7da356cb" - "e1ffc4b0eab584dcf48600fd0a40ef622c96fab9983040db9b17e4e14cf1a8709d1990" - "91624259e21e78e813f34b999a15fbc5defc68caac902a4497eec7a6acee6b1904cba2" - "4b845aab519deabdaa57f8b353e43ada8c32dcc1f86c2c31e64f8db4f1ed79cf0e752a" - "605833a2069f28e37d1a9a32ac97bbfae1b4740ee93097d4a5fb2cec89b1e84eca3380" - "2e04bc5a53af9b44a3ac921166aa474d6baf9b81dc9538833f25e77263553cbbc0cd24" - "86e0a19e70fb7effb13640e4e9e0f09c3cf568ef74f74f7ead10c8a61e566f841d9362" - "1ae29b363411bac33da28aa120d942ab96a12d92399e6a593e39a37056271e8355eba2" - "8e384f90787165215c4e5ebef6492f4af1aa7a6bdb78cc40e061a1f78e6f3e96db4cad" - "48f5291e3c23fd929876f1a5d064" - )[..]; + // blocks: [ [ { id: ext(2, 0xad67b6b5b9ad4653bf8e2b405bb6115f), start: 0, stop: 250, raw_offset: 0, raw_size: 512, access: { id: ext(2, 0xb82954f1138b4d719b7f5bd78915d20f), offset: 0, size: 512, digest: 0x076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560, }, }, { id: ext(2, 0x2f99258022a94555b3109e81d34bdf97), start: 250, stop: 500, raw_offset: 250, raw_size: 250, access: None, }, ], ] + let data: &[u8] = hex!( + "d4019b91cf39325e6ea219b896dab78722ffb0aa3370ca88ba496a25a8ed07e50a2fdc" + "ce369d45d56e57f633032d720cfd9c732237dacc4917de61ab0b3e17d12d8196696aa3" + "73861ab8f4ccf260ca0c2af034e055f83d09dade1ef8e2fe6f12bd8dbae4a92c4f35bc" + "9f603191006af0abdd11ad4e124e29489276420969aac1c1ca7b217ceb325ecb110058" + "b2db52a13feae884079e9ae27aa9c2e07d4cc83d5cb397c4c7e1b93e55ec402252ea34" + "57b3f19019c10d3987fa3f7f34a198b5b12d4e46086af07a99c84d01a48d3379bea81b" + "5ff4105313f0275d62076ec4639ebb87cff7816d4876a109be5a8ab3e968d0e8b7f6e7" + "14e52d5e311863b9b25997421ba375b08675af917106737ffc4f3bad0b260278f7a1ce" + "39575a9369dfaca0ef2179648597be8db6f9b7f548dc5e5f1c73e012dd89cfcd526fd8" + "5f58c941c3102f60d961df2cff36a8a1352e28ce4df1eba2f19c61f1f3e2bfff3f4334" + "bbfbfe98d6ea51f896e847752ad657ba8af0ec1a1c9493a40bd0b8fe1e76f1dc0cbd8c" + "a6d39072ee545272d2187cbf78491cd1dfae2c992ccfd8367062ed52118ab2b52f1eb3" + "a508532a5d294ecac029a5adeda9ce25e01df93499401107114fdd849d8556e07aa2a3" + "fcc6f027697be807ea0e7be4ce131340a40d090fac94c032ee5cbc2172778a0356c854" + "53f6a35521caa10a1eacde3e9e156e5e757ea16f5377fc0069fa5b5428" + ) + .as_ref(); let now = "2021-12-04T11:50:43.208821Z".parse().unwrap(); let key = SecretKey::from(hex!( "b1b52e16c1b46ab133c8bf576e82d26c887f1e9deae1af80043a258c36fcabf3" @@ -152,9 +106,10 @@ fn serde_local_file_manifest_ok(alice: &Device) { blocksize: Blocksize::try_from(512).unwrap(), need_sync: true, size: 500, + last_updater: alice.device_id, }; - let manifest = LocalChildManifest::decrypt_and_load(data, &key).unwrap(); + let manifest = LocalChildManifest::decrypt_and_load(data, &key).unwrap(); p_assert_eq!(manifest, LocalChildManifest::File(expected.clone())); // Also test serialization round trip diff --git a/libparsec/crates/types/tests/unit/local_folder_manifest.rs b/libparsec/crates/types/tests/unit/local_folder_manifest.rs index d72709150c3..e3a0eeddaea 100644 --- a/libparsec/crates/types/tests/unit/local_folder_manifest.rs +++ b/libparsec/crates/types/tests/unit/local_folder_manifest.rs @@ -75,6 +75,7 @@ const PREVENT_SYNC_PATTERN_TMP: &str = r"\.tmp$"; remote_confinement_points: HashSet::from([VlobID::from_hex("b82954f1138b4d719b7f5bd78915d20f").unwrap()]), need_sync: true, speculative: false, + last_updater: alice.device_id } ) }))] @@ -130,6 +131,7 @@ const PREVENT_SYNC_PATTERN_TMP: &str = r"\.tmp$"; remote_confinement_points: HashSet::new(), need_sync: true, speculative: true, + last_updater:alice.device_id, } ) }))] @@ -181,6 +183,7 @@ fn new(timestamp: DateTime) { local_confinement_points, remote_confinement_points, speculative, + last_updater, } = lfm; p_assert_ne!(base_id, expected_parent); @@ -199,6 +202,7 @@ fn new(timestamp: DateTime) { p_assert_eq!(local_confinement_points.len(), 0); p_assert_eq!(remote_confinement_points.len(), 0); assert!(!speculative); + p_assert_eq!(last_updater, expected_author) } #[rstest] @@ -233,6 +237,7 @@ fn new_root(#[values(true, false)] speculative: bool, timestamp: DateTime) { local_confinement_points, remote_confinement_points, speculative, + last_updater, } = lfm; p_assert_eq!(base_id, expected_realm); @@ -251,6 +256,7 @@ fn new_root(#[values(true, false)] speculative: bool, timestamp: DateTime) { p_assert_eq!(local_confinement_points.len(), 0); p_assert_eq!(remote_confinement_points.len(), 0); p_assert_eq!(speculative, expected_speculative); + p_assert_eq!(last_updater, expected_author) } #[rstest] @@ -470,6 +476,7 @@ fn from_remote_with_restored_local_confinement_points( // ignores it and rebuilds it from the remote manifest only. remote_confinement_points: HashSet::new(), speculative: false, + last_updater: fm.author, }; let lfm = LocalFolderManifest::from_remote_with_restored_local_confinement_points( @@ -722,6 +729,7 @@ fn evolve_children_and_mark_updated( children, remote_confinement_points: HashSet::new(), speculative: false, + last_updater: fm.author, }; // Actual method tested lfm.evolve_children_and_mark_updated(data, &prevent_sync_pattern, t2); @@ -792,6 +800,7 @@ fn apply_prevent_sync_pattern_nothing_confined() { local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: fm.author, }; // New prevent sync pattern doesn't match any entry, so nothing should change @@ -872,6 +881,7 @@ fn apply_prevent_sync_pattern_stability_with_confined() { ) .unwrap()]), speculative: false, + last_updater: fm.author, }; // We re-apply the same `.tmp` prevent sync pattern, so nothing should change @@ -920,6 +930,7 @@ fn apply_prevent_sync_pattern_with_non_confined_local_children_matching_future_p local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: fm.author, }; // Now we change the prevent sync pattern to something that matches some of the local children @@ -1004,6 +1015,7 @@ fn apply_prevent_sync_pattern_with_non_confined_remote_children_matching_future_ local_confinement_points: HashSet::new(), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: fm.author, }; // Now we change the prevent sync pattern to something that matches some of the remote children @@ -1067,6 +1079,7 @@ fn apply_prevent_sync_pattern_with_confined_local_children_turning_non_confined( .unwrap()]), remote_confinement_points: HashSet::new(), speculative: false, + last_updater: fm.author, }; // The new prevent sync pattern doesn't match any entry, hence `fileA.tmp` is @@ -1142,6 +1155,7 @@ fn apply_prevent_sync_pattern_with_local_changes_and_confined_remote_children_tu ) .unwrap()]), speculative: false, + last_updater: fm.author, }; // The new prevent sync pattern doesn't match any entry, hence `file3.tmp` is @@ -1213,6 +1227,7 @@ fn apply_prevent_sync_pattern_with_only_confined_remote_children_turning_non_con ) .unwrap()]), speculative: false, + last_updater: fm.author, }; // The new prevent sync pattern doesn't match any entry, hence `file3.tmp` is @@ -1302,6 +1317,7 @@ fn apply_prevent_sync_pattern_with_broader_prevent_sync_pattern() { ) .unwrap()]), speculative: false, + last_updater: fm.author, }; // `.+` is a superset of the previous `.tmp` pattern, all entries should @@ -1430,6 +1446,7 @@ fn apply_prevent_sync_pattern_on_renamed_entry( local_confinement_points, remote_confinement_points, speculative: false, + last_updater: fm.author, }; // Now apply the new prevent sync pattern...