Skip to content

Commit 64a669b

Browse files
committed
fix: seed newly registered file provider domain
1 parent f884cbd commit 64a669b

4 files changed

Lines changed: 156 additions & 45 deletions

File tree

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

Lines changed: 111 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8800,15 +8800,15 @@ fn activate_virtual_projection_mount(
88008800
{
88018801
wait_for_virtual_projection_mount_point_children(state_root, mount)?;
88028802
}
8803-
register_virtual_projection(state_root, mount)?;
8803+
let macos_domain_registration = register_virtual_projection(state_root, mount)?;
88048804
prefetch_virtual_projection_root(state_root, mount)?;
88058805
if wait_for_entities
88068806
&& !virtual_projection_waits_for_mount_point_children_before_registration(&mount.projection)
88078807
{
88088808
wait_for_mount_entities(state_root, &mount.mount_id)?;
88098809
}
88108810
ensure_virtual_projection_runtime(state_root, mount)?;
8811-
refresh_virtual_projection_mount_activation(mount);
8811+
refresh_virtual_projection_mount_activation(mount, macos_domain_registration);
88128812
recover_virtual_projection_mount_root_if_needed(state_root, mount)?;
88138813
desktop_log(
88148814
"info",
@@ -8885,7 +8885,7 @@ fn recover_macos_file_provider_mount_root_if_needed(
88858885
);
88868886
prefetch_virtual_projection_root(state_root, mount)?;
88878887
ensure_virtual_projection_runtime(state_root, mount)?;
8888-
refresh_virtual_projection_mount_activation(mount);
8888+
refresh_virtual_projection_mount_activation(mount, MacosFileProviderDomainRegistration::Reused);
88898889

88908890
wait_for_macos_file_provider_mount_root_recovery(
88918891
&root,
@@ -9786,6 +9786,12 @@ enum VirtualProjectionRefreshAction {
97869786
ReimportThenSignal(String),
97879787
}
97889788

9789+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9790+
enum MacosFileProviderDomainRegistration {
9791+
Created,
9792+
Reused,
9793+
}
9794+
97899795
impl VirtualProjectionRefreshAction {
97909796
fn identifier(&self) -> &str {
97919797
match self {
@@ -9800,8 +9806,11 @@ fn signal_virtual_projection_refresh(mount: &MountConfig) {
98009806
}
98019807
}
98029808

9803-
fn refresh_virtual_projection_mount_activation(mount: &MountConfig) {
9804-
for action in virtual_projection_mount_activation_refresh_actions(mount) {
9809+
fn refresh_virtual_projection_mount_activation(
9810+
mount: &MountConfig,
9811+
domain_registration: MacosFileProviderDomainRegistration,
9812+
) {
9813+
for action in virtual_projection_mount_activation_refresh_actions(mount, domain_registration) {
98059814
run_virtual_projection_refresh_action(mount, &action);
98069815
}
98079816
}
@@ -9839,12 +9848,19 @@ fn virtual_projection_refresh_actions(mount: &MountConfig) -> Vec<VirtualProject
98399848

98409849
fn virtual_projection_mount_activation_refresh_actions(
98419850
mount: &MountConfig,
9851+
domain_registration: MacosFileProviderDomainRegistration,
98429852
) -> Vec<VirtualProjectionRefreshAction> {
98439853
if mount.projection == ProjectionMode::MacosFileProvider {
9844-
return vec![
9854+
let mut actions = vec![
98459855
VirtualProjectionRefreshAction::Signal(ROOT_CONTAINER_IDENTIFIER.to_string()),
98469856
VirtualProjectionRefreshAction::ReimportThenSignal(mount_point_identifier(mount)),
98479857
];
9858+
if domain_registration == MacosFileProviderDomainRegistration::Created {
9859+
actions.push(VirtualProjectionRefreshAction::Signal(
9860+
"working-set".to_string(),
9861+
));
9862+
}
9863+
return actions;
98489864
}
98499865
virtual_projection_refresh_actions(mount)
98509866
}
@@ -9934,35 +9950,62 @@ fn refresh_macos_virtual_projection(
99349950
Ok(())
99359951
}
99369952

9937-
fn register_virtual_projection(state_root: &Path, mount: &MountConfig) -> Result<(), String> {
9953+
fn register_virtual_projection(
9954+
state_root: &Path,
9955+
mount: &MountConfig,
9956+
) -> Result<MacosFileProviderDomainRegistration, String> {
99389957
match mount.projection {
99399958
ProjectionMode::MacosFileProvider => {
99409959
register_macos_virtual_projection(&mount.mount_id.0, &mount.root.display().to_string())
99419960
}
9942-
ProjectionMode::LinuxFuse => register_linux_virtual_projection(state_root, mount),
9943-
ProjectionMode::PlainFiles => Ok(()),
9944-
ProjectionMode::WindowsCloudFiles => register_windows_virtual_projection(state_root, mount),
9961+
ProjectionMode::LinuxFuse => register_linux_virtual_projection(state_root, mount)
9962+
.map(|_| MacosFileProviderDomainRegistration::Reused),
9963+
ProjectionMode::PlainFiles => Ok(MacosFileProviderDomainRegistration::Reused),
9964+
ProjectionMode::WindowsCloudFiles => register_windows_virtual_projection(state_root, mount)
9965+
.map(|_| MacosFileProviderDomainRegistration::Reused),
99459966
}
99469967
}
99479968

99489969
#[cfg(target_os = "macos")]
9949-
fn register_macos_virtual_projection(_mount_id: &str, _root: &str) -> Result<(), String> {
9950-
register_macos_file_provider_domain(
9970+
fn register_macos_virtual_projection(
9971+
_mount_id: &str,
9972+
_root: &str,
9973+
) -> Result<MacosFileProviderDomainRegistration, String> {
9974+
let report = register_macos_file_provider_domain(
99519975
localityd::file_provider::MACOS_FILE_PROVIDER_DOMAIN_ID,
99529976
localityd::file_provider::MACOS_FILE_PROVIDER_DISPLAY_NAME,
99539977
)
9954-
.map(|_| ())
99559978
.map_err(|error| {
99569979
format!(
99579980
"Could not register macOS File Provider: {}",
99589981
error.message()
99599982
)
9960-
})
9983+
})?;
9984+
Ok(macos_file_provider_domain_registration(
9985+
&report.helper_report,
9986+
))
99619987
}
99629988

99639989
#[cfg(not(target_os = "macos"))]
9964-
fn register_macos_virtual_projection(_mount_id: &str, _root: &str) -> Result<(), String> {
9965-
Ok(())
9990+
fn register_macos_virtual_projection(
9991+
_mount_id: &str,
9992+
_root: &str,
9993+
) -> Result<MacosFileProviderDomainRegistration, String> {
9994+
Ok(MacosFileProviderDomainRegistration::Reused)
9995+
}
9996+
9997+
fn macos_file_provider_domain_registration(
9998+
helper_report: &serde_json::Value,
9999+
) -> MacosFileProviderDomainRegistration {
10000+
if helper_report
10001+
.get("registrationCreated")
10002+
.and_then(serde_json::Value::as_bool)
10003+
== Some(true)
10004+
{
10005+
MacosFileProviderDomainRegistration::Created
10006+
} else {
10007+
MacosFileProviderDomainRegistration::Reused
10008+
}
996610009
}
996710010

996810011
#[cfg(target_os = "linux")]
@@ -12027,14 +12070,14 @@ mod tests {
1202712070
use super::{
1202812071
ActionReport, DESKTOP_ACTIVITY_LIMIT, DESKTOP_INSTALL_MARKER_VERSION,
1202912072
LIVE_MODE_REMOTE_FAST_FORWARD_LEASE, LIVE_MODE_RUNNER_ACTIVE_INTERVAL,
12030-
LIVE_MODE_RUNNER_PERIODIC_RECHECK, MonitorScreenBounds, PendingChange, ScreenBounds,
12031-
TerminalCliLinkState, TrayVisualState, VirtualProjectionRefreshAction,
12032-
acknowledge_install_state_at, activity_timestamp, clear_mount_cached_projection,
12033-
clear_state_root_contents, clear_visible_projection_paths, conflict_preview,
12034-
connection_metadata_changed, current_daemon_build_id, current_desktop_build_id,
12035-
diff_report_message, exact_located_entity_record, exact_notion_entry_matches,
12036-
failed_push_summary, has_unresolved_conflict_markers, hydration_after_editor_write,
12037-
inspect_install_state, install_terminal_cli_link_at,
12073+
LIVE_MODE_RUNNER_PERIODIC_RECHECK, MacosFileProviderDomainRegistration,
12074+
MonitorScreenBounds, PendingChange, ScreenBounds, TerminalCliLinkState, TrayVisualState,
12075+
VirtualProjectionRefreshAction, acknowledge_install_state_at, activity_timestamp,
12076+
clear_mount_cached_projection, clear_state_root_contents, clear_visible_projection_paths,
12077+
conflict_preview, connection_metadata_changed, current_daemon_build_id,
12078+
current_desktop_build_id, diff_report_message, exact_located_entity_record,
12079+
exact_notion_entry_matches, failed_push_summary, has_unresolved_conflict_markers,
12080+
hydration_after_editor_write, inspect_install_state, install_terminal_cli_link_at,
1203812081
install_terminal_cli_link_in_path_dirs, is_notion_access_lost_message,
1203912082
is_unsupported_schema_version_message, live_mode_claim_remote_fast_forward_key,
1204012083
live_mode_enabled_mount, live_mode_local_reconcile_targets_for_mount_at,
@@ -12044,7 +12087,7 @@ mod tests {
1204412087
live_mode_should_reconcile_local_target_for_key, live_mode_target,
1204512088
live_mode_tick_from_snapshot, live_mode_wake_generation, load_desktop_activity,
1204612089
macos_app_bundle_for_exe, macos_file_provider_child_item_count,
12047-
macos_file_provider_mount_root_health_error,
12090+
macos_file_provider_domain_registration, macos_file_provider_mount_root_health_error,
1204812091
macos_file_provider_mount_root_inspection_recovery_reason,
1204912092
macos_file_provider_mount_root_is_missing, macos_file_provider_mount_root_recovery_reason,
1205012093
mark_mount_live_mode_syncing, mount_has_pending_local_changes,
@@ -14998,7 +15041,7 @@ mod tests {
1499815041
}
1499915042

1500015043
#[test]
15001-
fn macos_file_provider_refresh_is_scoped_to_the_new_mount() {
15044+
fn macos_file_provider_reused_domain_refresh_is_scoped_to_the_new_mount() {
1500215045
let mount = MountConfig::new(
1500315046
MountId::new("google-calendar-main"),
1500415047
"google-calendar",
@@ -15007,7 +15050,10 @@ mod tests {
1500715050
.projection(ProjectionMode::MacosFileProvider);
1500815051

1500915052
assert_eq!(
15010-
virtual_projection_mount_activation_refresh_actions(&mount),
15053+
virtual_projection_mount_activation_refresh_actions(
15054+
&mount,
15055+
MacosFileProviderDomainRegistration::Reused,
15056+
),
1501115057
vec![
1501215058
VirtualProjectionRefreshAction::Signal("root".to_string()),
1501315059
VirtualProjectionRefreshAction::ReimportThenSignal(
@@ -15017,6 +15063,44 @@ mod tests {
1501715063
);
1501815064
}
1501915065

15066+
#[test]
15067+
fn macos_file_provider_new_domain_seeds_the_working_set_once() {
15068+
let mount = MountConfig::new(
15069+
MountId::new("notion-main"),
15070+
"notion",
15071+
"/tmp/Locality/notion",
15072+
)
15073+
.projection(ProjectionMode::MacosFileProvider);
15074+
15075+
assert_eq!(
15076+
virtual_projection_mount_activation_refresh_actions(
15077+
&mount,
15078+
MacosFileProviderDomainRegistration::Created,
15079+
),
15080+
vec![
15081+
VirtualProjectionRefreshAction::Signal("root".to_string()),
15082+
VirtualProjectionRefreshAction::ReimportThenSignal("mount:notion-main".to_string(),),
15083+
VirtualProjectionRefreshAction::Signal("working-set".to_string()),
15084+
]
15085+
);
15086+
}
15087+
15088+
#[test]
15089+
fn macos_file_provider_registration_report_defaults_to_reused() {
15090+
assert_eq!(
15091+
macos_file_provider_domain_registration(&serde_json::json!({
15092+
"registrationCreated": true,
15093+
})),
15094+
MacosFileProviderDomainRegistration::Created
15095+
);
15096+
assert_eq!(
15097+
macos_file_provider_domain_registration(&serde_json::json!({
15098+
"message": "already registered loc",
15099+
})),
15100+
MacosFileProviderDomainRegistration::Reused
15101+
);
15102+
}
15103+
1502015104
#[test]
1502115105
fn macos_file_provider_visible_refresh_never_reimports_the_shared_root() {
1502215106
let mount = MountConfig::new(

docs/desktop-app.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,17 @@ Later source mounts reuse the existing shared File Provider domain. Adding a
213213
top-level source folder such as `google-calendar-main` signals the domain root
214214
enumerator so macOS can discover that one child, but must not reimport the
215215
shared root, replace its registration, or disturb sibling mounts such as
216-
`notion`. Registration is idempotent when the shared domain already exists;
217-
automatic setup treats that registration as authoritative rather than removing
218-
and recreating it to repair metadata. Reimport and readiness repair stay scoped
219-
to the new mount-point identifier. Mount-point appearance gets an initial
220-
30-second wait and one 30-second scoped refresh window; if it is still
221-
unavailable, Locality reports a recoverable preparation warning without
222-
resetting the domain. Whole-domain unregister or reset is reserved for explicit
223-
repair flows, never automatic source activation.
216+
`notion`. A newly created domain also receives one 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. Later source activation
219+
does not repeat the working-set signal. Registration is idempotent when the
220+
shared domain already exists; automatic setup treats that registration as
221+
authoritative rather than removing and recreating it to repair metadata.
222+
Reimport and readiness repair stay scoped to the new mount-point identifier.
223+
Mount-point appearance gets an initial 30-second wait and one 30-second scoped
224+
refresh window; if it is still unavailable, Locality reports a recoverable
225+
preparation warning without resetting the domain. Whole-domain unregister or
226+
reset is reserved for explicit repair flows, never automatic source activation.
224227

225228
The final ready screen must not appear until File Provider approval, the
226229
CloudStorage root, and the mount root are all verified successfully.

platform/macos/LocalityFileProvider/README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +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. Because
73-
macOS creates that source folder asynchronously, Locality briefly waits for the
74-
new mount point before inspecting it. If the folder remains absent or File
75-
Provider reports an unhealthy replica, Locality resets and re-registers the
76-
shared domain, refreshes it from durable mount state, and waits for the source
77-
folder to become healthy. Reconnecting an existing source retries this
78-
activation path instead of only reloading daemon mounts.
72+
Mount activation signals the shared domain root after adding a source. A newly
73+
registered domain also receives one working-set signal to seed macOS's initially
74+
empty File Provider database. Later source mounts do not repeat that global
75+
signal: their reimport and readiness repair stay scoped to the new mount-point
76+
identifier. Because macOS creates a source folder asynchronously, Locality waits
77+
for it before inspecting it and retries the scoped refresh once. Automatic
78+
activation never resets or re-registers the shared domain. Reconnecting an
79+
existing source retries this activation path instead of only reloading daemon
80+
mounts.
7981

8082
Delete support still returns unsupported. Creates and renames are represented as
8183
daemon virtual mutations and stay pending until the normal review and push flow

platform/macos/LocalityFileProvider/Sources/LocalityFileProviderCtl/main.swift

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ private enum Command {
9494
domain: DomainReport(existing),
9595
domains: nil,
9696
url: nil,
97-
message: "already registered \(mountId)"
97+
message: "already registered \(mountId)",
98+
registrationCreated: false
9899
)
99100
}
100101

@@ -120,7 +121,8 @@ private enum Command {
120121
domain: DomainReport(existing),
121122
domains: nil,
122123
url: nil,
123-
message: "already registered \(mountId)"
124+
message: "already registered \(mountId)",
125+
registrationCreated: false
124126
)
125127
}
126128
return FileProviderCtlReport(
@@ -129,7 +131,8 @@ private enum Command {
129131
domain: DomainReport(domain),
130132
domains: nil,
131133
url: nil,
132-
message: "registered \(mountId)"
134+
message: "registered \(mountId)",
135+
registrationCreated: true
133136
)
134137
case .open(let mountId):
135138
guard let domain = try getDomains().first(where: { $0.identifier.rawValue == mountId }) else {
@@ -283,6 +286,25 @@ private struct FileProviderCtlReport: Encodable {
283286
let domains: [DomainReport]?
284287
let url: String?
285288
let message: String
289+
let registrationCreated: Bool?
290+
291+
init(
292+
ok: Bool,
293+
action: String,
294+
domain: DomainReport?,
295+
domains: [DomainReport]?,
296+
url: String?,
297+
message: String,
298+
registrationCreated: Bool? = nil
299+
) {
300+
self.ok = ok
301+
self.action = action
302+
self.domain = domain
303+
self.domains = domains
304+
self.url = url
305+
self.message = message
306+
self.registrationCreated = registrationCreated
307+
}
286308
}
287309

288310
private struct DomainReport: Encodable {

0 commit comments

Comments
 (0)