Skip to content

Commit ea5fca6

Browse files
committed
fix: add file provider mounts through delta working set
1 parent 32a2ab8 commit ea5fca6

6 files changed

Lines changed: 337 additions & 236 deletions

File tree

apps/desktop/src-tauri/src/main.rs

Lines changed: 27 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -8800,27 +8800,15 @@ fn activate_virtual_projection_mount(
88008800
{
88018801
wait_for_virtual_projection_mount_point_children(state_root, mount)?;
88028802
}
8803-
let macos_domain_registration = register_virtual_projection(state_root, mount)?;
8804-
let seed_macos_working_set =
8805-
virtual_projection_mount_activation_seeds_working_set(mount, macos_domain_registration);
8806-
if seed_macos_working_set {
8807-
desktop_log(
8808-
"info",
8809-
"file_provider.working_set_seed",
8810-
format!(
8811-
"seeding macOS File Provider working set for mount `{}` because the shared domain is newly registered or has no visible source folders",
8812-
mount.mount_id.0
8813-
),
8814-
);
8815-
}
8803+
register_virtual_projection(state_root, mount)?;
88168804
prefetch_virtual_projection_root(state_root, mount)?;
88178805
if wait_for_entities
88188806
&& !virtual_projection_waits_for_mount_point_children_before_registration(&mount.projection)
88198807
{
88208808
wait_for_mount_entities(state_root, &mount.mount_id)?;
88218809
}
88228810
ensure_virtual_projection_runtime(state_root, mount)?;
8823-
refresh_virtual_projection_mount_activation(mount, seed_macos_working_set);
8811+
refresh_virtual_projection_mount_activation(mount);
88248812
recover_virtual_projection_mount_root_if_needed(state_root, mount)?;
88258813
desktop_log(
88268814
"info",
@@ -8897,11 +8885,7 @@ fn recover_macos_file_provider_mount_root_if_needed(
88978885
);
88988886
prefetch_virtual_projection_root(state_root, mount)?;
88998887
ensure_virtual_projection_runtime(state_root, mount)?;
8900-
let seed_macos_working_set = virtual_projection_mount_activation_seeds_working_set(
8901-
mount,
8902-
MacosFileProviderDomainRegistration::Reused,
8903-
);
8904-
refresh_virtual_projection_mount_activation(mount, seed_macos_working_set);
8888+
refresh_virtual_projection_mount_activation(mount);
89058889

89068890
wait_for_macos_file_provider_mount_root_recovery(
89078891
&root,
@@ -9802,12 +9786,6 @@ enum VirtualProjectionRefreshAction {
98029786
ReimportThenSignal(String),
98039787
}
98049788

9805-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9806-
enum MacosFileProviderDomainRegistration {
9807-
Created,
9808-
Reused,
9809-
}
9810-
98119789
impl VirtualProjectionRefreshAction {
98129790
fn identifier(&self) -> &str {
98139791
match self {
@@ -9822,9 +9800,8 @@ fn signal_virtual_projection_refresh(mount: &MountConfig) {
98229800
}
98239801
}
98249802

9825-
fn refresh_virtual_projection_mount_activation(mount: &MountConfig, seed_macos_working_set: bool) {
9826-
for action in virtual_projection_mount_activation_refresh_actions(mount, seed_macos_working_set)
9827-
{
9803+
fn refresh_virtual_projection_mount_activation(mount: &MountConfig) {
9804+
for action in virtual_projection_mount_activation_refresh_actions(mount) {
98289805
run_virtual_projection_refresh_action(mount, &action);
98299806
}
98309807
}
@@ -9862,55 +9839,17 @@ fn virtual_projection_refresh_actions(mount: &MountConfig) -> Vec<VirtualProject
98629839

98639840
fn virtual_projection_mount_activation_refresh_actions(
98649841
mount: &MountConfig,
9865-
seed_macos_working_set: bool,
98669842
) -> Vec<VirtualProjectionRefreshAction> {
98679843
if mount.projection == ProjectionMode::MacosFileProvider {
9868-
let mut actions = vec![
9844+
return vec![
98699845
VirtualProjectionRefreshAction::Signal(ROOT_CONTAINER_IDENTIFIER.to_string()),
98709846
VirtualProjectionRefreshAction::ReimportThenSignal(mount_point_identifier(mount)),
9847+
VirtualProjectionRefreshAction::Signal("working-set".to_string()),
98719848
];
9872-
if seed_macos_working_set {
9873-
actions.push(VirtualProjectionRefreshAction::Signal(
9874-
"working-set".to_string(),
9875-
));
9876-
}
9877-
return actions;
98789849
}
98799850
virtual_projection_refresh_actions(mount)
98809851
}
98819852

9882-
fn virtual_projection_mount_activation_seeds_working_set(
9883-
mount: &MountConfig,
9884-
domain_registration: MacosFileProviderDomainRegistration,
9885-
) -> bool {
9886-
if mount.projection != ProjectionMode::MacosFileProvider {
9887-
return false;
9888-
}
9889-
if domain_registration == MacosFileProviderDomainRegistration::Created {
9890-
return true;
9891-
}
9892-
9893-
mount_access_root(mount)
9894-
.parent()
9895-
.is_some_and(macos_file_provider_domain_root_is_visibly_empty)
9896-
}
9897-
9898-
fn macos_file_provider_domain_root_is_visibly_empty(root: &Path) -> bool {
9899-
let entries = match fs::read_dir(root) {
9900-
Ok(entries) => entries,
9901-
Err(error) => return error.kind() == io::ErrorKind::NotFound,
9902-
};
9903-
for entry in entries {
9904-
let Ok(entry) = entry else {
9905-
return false;
9906-
};
9907-
if !entry.file_name().to_string_lossy().starts_with('.') {
9908-
return false;
9909-
}
9910-
}
9911-
true
9912-
}
9913-
99149853
fn refresh_virtual_projection_container(
99159854
mount: &MountConfig,
99169855
action: &VirtualProjectionRefreshAction,
@@ -9996,62 +9935,35 @@ fn refresh_macos_virtual_projection(
99969935
Ok(())
99979936
}
99989937

9999-
fn register_virtual_projection(
10000-
state_root: &Path,
10001-
mount: &MountConfig,
10002-
) -> Result<MacosFileProviderDomainRegistration, String> {
9938+
fn register_virtual_projection(state_root: &Path, mount: &MountConfig) -> Result<(), String> {
100039939
match mount.projection {
100049940
ProjectionMode::MacosFileProvider => {
100059941
register_macos_virtual_projection(&mount.mount_id.0, &mount.root.display().to_string())
100069942
}
10007-
ProjectionMode::LinuxFuse => register_linux_virtual_projection(state_root, mount)
10008-
.map(|_| MacosFileProviderDomainRegistration::Reused),
10009-
ProjectionMode::PlainFiles => Ok(MacosFileProviderDomainRegistration::Reused),
10010-
ProjectionMode::WindowsCloudFiles => register_windows_virtual_projection(state_root, mount)
10011-
.map(|_| MacosFileProviderDomainRegistration::Reused),
9943+
ProjectionMode::LinuxFuse => register_linux_virtual_projection(state_root, mount),
9944+
ProjectionMode::PlainFiles => Ok(()),
9945+
ProjectionMode::WindowsCloudFiles => register_windows_virtual_projection(state_root, mount),
100129946
}
100139947
}
100149948

100159949
#[cfg(target_os = "macos")]
10016-
fn register_macos_virtual_projection(
10017-
_mount_id: &str,
10018-
_root: &str,
10019-
) -> Result<MacosFileProviderDomainRegistration, String> {
10020-
let report = register_macos_file_provider_domain(
9950+
fn register_macos_virtual_projection(_mount_id: &str, _root: &str) -> Result<(), String> {
9951+
register_macos_file_provider_domain(
100219952
localityd::file_provider::MACOS_FILE_PROVIDER_DOMAIN_ID,
100229953
localityd::file_provider::MACOS_FILE_PROVIDER_DISPLAY_NAME,
100239954
)
9955+
.map(|_| ())
100249956
.map_err(|error| {
100259957
format!(
100269958
"Could not register macOS File Provider: {}",
100279959
error.message()
100289960
)
10029-
})?;
10030-
Ok(macos_file_provider_domain_registration(
10031-
&report.helper_report,
10032-
))
9961+
})
100339962
}
100349963

100359964
#[cfg(not(target_os = "macos"))]
10036-
fn register_macos_virtual_projection(
10037-
_mount_id: &str,
10038-
_root: &str,
10039-
) -> Result<MacosFileProviderDomainRegistration, String> {
10040-
Ok(MacosFileProviderDomainRegistration::Reused)
10041-
}
10042-
10043-
fn macos_file_provider_domain_registration(
10044-
helper_report: &serde_json::Value,
10045-
) -> MacosFileProviderDomainRegistration {
10046-
if helper_report
10047-
.get("registrationCreated")
10048-
.and_then(serde_json::Value::as_bool)
10049-
== Some(true)
10050-
{
10051-
MacosFileProviderDomainRegistration::Created
10052-
} else {
10053-
MacosFileProviderDomainRegistration::Reused
10054-
}
9965+
fn register_macos_virtual_projection(_mount_id: &str, _root: &str) -> Result<(), String> {
9966+
Ok(())
100559967
}
100569968

100579969
#[cfg(target_os = "linux")]
@@ -12116,14 +12028,14 @@ mod tests {
1211612028
use super::{
1211712029
ActionReport, DESKTOP_ACTIVITY_LIMIT, DESKTOP_INSTALL_MARKER_VERSION,
1211812030
LIVE_MODE_REMOTE_FAST_FORWARD_LEASE, LIVE_MODE_RUNNER_ACTIVE_INTERVAL,
12119-
LIVE_MODE_RUNNER_PERIODIC_RECHECK, MacosFileProviderDomainRegistration,
12120-
MonitorScreenBounds, PendingChange, ScreenBounds, TerminalCliLinkState, TrayVisualState,
12121-
VirtualProjectionRefreshAction, acknowledge_install_state_at, activity_timestamp,
12122-
clear_mount_cached_projection, clear_state_root_contents, clear_visible_projection_paths,
12123-
conflict_preview, connection_metadata_changed, current_daemon_build_id,
12124-
current_desktop_build_id, diff_report_message, exact_located_entity_record,
12125-
exact_notion_entry_matches, failed_push_summary, has_unresolved_conflict_markers,
12126-
hydration_after_editor_write, inspect_install_state, install_terminal_cli_link_at,
12031+
LIVE_MODE_RUNNER_PERIODIC_RECHECK, MonitorScreenBounds, PendingChange, ScreenBounds,
12032+
TerminalCliLinkState, TrayVisualState, VirtualProjectionRefreshAction,
12033+
acknowledge_install_state_at, activity_timestamp, clear_mount_cached_projection,
12034+
clear_state_root_contents, clear_visible_projection_paths, conflict_preview,
12035+
connection_metadata_changed, current_daemon_build_id, current_desktop_build_id,
12036+
diff_report_message, exact_located_entity_record, exact_notion_entry_matches,
12037+
failed_push_summary, has_unresolved_conflict_markers, hydration_after_editor_write,
12038+
inspect_install_state, install_terminal_cli_link_at,
1212712039
install_terminal_cli_link_in_path_dirs, is_notion_access_lost_message,
1212812040
is_unsupported_schema_version_message, live_mode_claim_remote_fast_forward_key,
1212912041
live_mode_enabled_mount, live_mode_local_reconcile_targets_for_mount_at,
@@ -12133,7 +12045,6 @@ mod tests {
1213312045
live_mode_should_reconcile_local_target_for_key, live_mode_target,
1213412046
live_mode_tick_from_snapshot, live_mode_wake_generation, load_desktop_activity,
1213512047
macos_app_bundle_for_exe, macos_file_provider_child_item_count,
12136-
macos_file_provider_domain_registration, macos_file_provider_domain_root_is_visibly_empty,
1213712048
macos_file_provider_mount_root_health_error,
1213812049
macos_file_provider_mount_root_inspection_recovery_reason,
1213912050
macos_file_provider_mount_root_is_missing, macos_file_provider_mount_root_recovery_reason,
@@ -15088,7 +14999,7 @@ mod tests {
1508814999
}
1508915000

1509015001
#[test]
15091-
fn macos_file_provider_reused_domain_refresh_is_scoped_to_the_new_mount() {
15002+
fn macos_file_provider_mount_activation_uses_delta_aware_working_set() {
1509215003
let mount = MountConfig::new(
1509315004
MountId::new("google-calendar-main"),
1509415005
"google-calendar",
@@ -15097,77 +15008,17 @@ mod tests {
1509715008
.projection(ProjectionMode::MacosFileProvider);
1509815009

1509915010
assert_eq!(
15100-
virtual_projection_mount_activation_refresh_actions(&mount, false),
15011+
virtual_projection_mount_activation_refresh_actions(&mount),
1510115012
vec![
1510215013
VirtualProjectionRefreshAction::Signal("root".to_string()),
1510315014
VirtualProjectionRefreshAction::ReimportThenSignal(
1510415015
"mount:google-calendar-main".to_string(),
1510515016
),
15106-
]
15107-
);
15108-
}
15109-
15110-
#[test]
15111-
fn macos_file_provider_new_domain_seeds_the_working_set() {
15112-
let mount = MountConfig::new(
15113-
MountId::new("notion-main"),
15114-
"notion",
15115-
"/tmp/Locality/notion",
15116-
)
15117-
.projection(ProjectionMode::MacosFileProvider);
15118-
15119-
assert_eq!(
15120-
virtual_projection_mount_activation_refresh_actions(&mount, true),
15121-
vec![
15122-
VirtualProjectionRefreshAction::Signal("root".to_string()),
15123-
VirtualProjectionRefreshAction::ReimportThenSignal("mount:notion-main".to_string(),),
1512415017
VirtualProjectionRefreshAction::Signal("working-set".to_string()),
1512515018
]
1512615019
);
1512715020
}
1512815021

15129-
#[test]
15130-
fn macos_file_provider_retained_empty_domain_requires_working_set_seed() {
15131-
let temp = TestTempDir::new("desktop-file-provider-retained-empty-domain");
15132-
let domain_root = temp.path().join("Locality");
15133-
fs::create_dir_all(domain_root.join(".Trash")).expect("create File Provider trash");
15134-
fs::write(domain_root.join(".DS_Store"), "metadata").expect("create hidden metadata");
15135-
15136-
assert!(macos_file_provider_domain_root_is_visibly_empty(
15137-
&domain_root
15138-
));
15139-
15140-
fs::create_dir(domain_root.join("notion")).expect("create visible Notion mount");
15141-
assert!(!macos_file_provider_domain_root_is_visibly_empty(
15142-
&domain_root
15143-
));
15144-
}
15145-
15146-
#[test]
15147-
fn macos_file_provider_missing_domain_root_requires_working_set_seed() {
15148-
let temp = TestTempDir::new("desktop-file-provider-missing-domain-root");
15149-
15150-
assert!(macos_file_provider_domain_root_is_visibly_empty(
15151-
&temp.path().join("Locality")
15152-
));
15153-
}
15154-
15155-
#[test]
15156-
fn macos_file_provider_registration_report_defaults_to_reused() {
15157-
assert_eq!(
15158-
macos_file_provider_domain_registration(&serde_json::json!({
15159-
"registrationCreated": true,
15160-
})),
15161-
MacosFileProviderDomainRegistration::Created
15162-
);
15163-
assert_eq!(
15164-
macos_file_provider_domain_registration(&serde_json::json!({
15165-
"message": "already registered loc",
15166-
})),
15167-
MacosFileProviderDomainRegistration::Reused
15168-
);
15169-
}
15170-
1517115022
#[test]
1517215023
fn macos_file_provider_visible_refresh_never_reimports_the_shared_root() {
1517315024
let mount = MountConfig::new(

docs/desktop-app.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -210,21 +210,19 @@ encountered during later source setup. Slow approval remains a guided waiting
210210
state; missing helpers or extensions remain explicit setup failures.
211211

212212
Later source mounts reuse the existing shared File Provider domain. Adding a
213-
top-level source folder such as `google-calendar-main` signals the domain root
214-
enumerator so macOS can discover that one child, but must not reimport the
215-
shared root, replace its registration, or disturb sibling mounts such as
216-
`notion`. A newly created domain also receives a non-destructive working-set
217-
signal after its first source is ready; macOS requires that initial enumeration
218-
to seed the otherwise-empty File Provider database. macOS can retain a domain
219-
registration while discarding all of its visible source folders, so activation
220-
also seeds a reused domain when its root contains no visible entries other than
221-
system-owned hidden items such as `.Trash`. The scoped retry repeats that seed
222-
only while the root remains empty. Later source activation does not signal the
223-
working set when any sibling source folder is visible. Registration is
224-
idempotent when the shared domain already exists; automatic setup treats that
225-
registration as authoritative rather than removing and recreating it to repair
226-
metadata.
227-
Reimport and readiness repair stay scoped to the new mount-point identifier.
213+
top-level source folder such as `google-calendar-main` signals the working-set
214+
enumerator because macOS can drop root-container signals when no root enumerator
215+
is active. Compact working-set sync anchors reference rebuildable item-version
216+
snapshots in the File Provider app-group cache, so change enumeration reports
217+
only new, changed, or deleted items while the anchor stays below macOS's
218+
500-byte limit. A missing or incompatible cached snapshot expires the anchor
219+
and lets macOS perform a clean enumeration. Adding Calendar therefore inserts
220+
the Calendar mount and its children without re-reporting or reimporting an
221+
unchanged `notion` subtree. The shared root is never reimported or
222+
re-registered. Registration is idempotent when the domain already exists;
223+
automatic setup treats that registration as authoritative rather than removing
224+
and recreating it to repair metadata. Reimport and readiness repair stay scoped
225+
to the new mount-point identifier.
228226
Mount-point appearance gets an initial 30-second wait and one 30-second scoped
229227
refresh window; if it is still unavailable, Locality reports a recoverable
230228
preparation warning without resetting the domain. Whole-domain unregister or

platform/macos/LocalityFileProvider/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,15 @@ in Finder. Opening the raw mount root is not enough to test lazy enumeration:
6969
Finder must enter the File Provider domain so directory listings call
7070
`file_provider_children` on `localityd`.
7171

72-
Mount activation signals the shared domain root after adding a source. A newly
73-
registered domain also receives a working-set signal to seed macOS's initially
74-
empty File Provider database. Because macOS may retain the registration while
75-
discarding its visible source folders, Locality applies the same seed to a reused
76-
domain whose root has only hidden system items such as `.Trash`. The scoped
77-
retry repeats the seed only while that root remains empty. When a sibling source
78-
folder is visible, later source mounts do not send the global signal: their
79-
reimport and readiness repair stay scoped to the new mount-point identifier.
72+
Mount activation signals the working-set enumerator after adding a source
73+
because macOS can ignore a root-container signal when no root enumerator is
74+
active. Compact working-set sync anchors reference rebuildable item-version
75+
snapshots in the File Provider app-group cache; subsequent change enumerations
76+
report only new, changed, or deleted items while anchors stay within macOS's
77+
500-byte limit. A missing or incompatible snapshot expires its anchor and falls
78+
back to a clean enumeration. Adding a source can therefore insert its mount
79+
point and immediate children without updating an unchanged sibling subtree.
80+
Reimport and readiness repair stay scoped to the new mount-point identifier.
8081
Because macOS creates a source folder asynchronously, Locality waits for it
8182
before inspecting it and retries the scoped refresh once. Automatic activation
8283
never resets or re-registers the shared domain. Reconnecting an existing source

0 commit comments

Comments
 (0)