Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ prepare-macos-file-provider: ## Stage the macOS File Provider extension for Taur
install-macos-file-provider: ## Install/register the local macOS File Provider development bundle.
platform/macos/LocalityFileProvider/scripts/install-dev-bundle.sh

.PHONY: install-macos-prompt-test-app
install-macos-prompt-test-app: build-tauri ## Build, reset, install, register, and launch a fresh macOS File Provider prompt test app.
scripts/install-macos-prompt-test-app.sh $(PROMPT_TEST_APP_ARGS)

.PHONY: prepare-desktop-dev-sidecars
prepare-desktop-dev-sidecars: ## Build debug desktop sidecars used by Tauri dev.
$(DESKTOP_NPM) run dev:prepare
Expand Down Expand Up @@ -209,6 +213,10 @@ test-linux-publish-config: ## Validate Linux package publish configuration.
test-macos-publish-config: ## Validate macOS package publish configuration.
tests/macos_publish_config.sh

.PHONY: test-macos-prompt-test-app-installer
test-macos-prompt-test-app-installer: ## Validate the macOS prompt test app installer dry-run plan.
bash scripts/install-macos-prompt-test-app.test.sh

.PHONY: test-windows-publish-config
test-windows-publish-config: ## Validate Windows package publish configuration.
tests/windows_publish_config.sh
Expand Down
276 changes: 273 additions & 3 deletions apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ struct WorkspaceMountOnboardingReport {
launch_strategy: String,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileProviderEnablementReport {
state: String,
message: String,
path: Option<String>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum MacosWorkspaceMountOnboardingState {
Created,
Expand All @@ -369,7 +377,7 @@ impl MacosWorkspaceMountOnboardingState {
fn as_str(self) -> &'static str {
match self {
Self::Created => "created",
Self::ApprovalRequired => "approval_required",
Self::ApprovalRequired => "needs_finder_enable",
Self::WaitingForCloudStorageRoot => "waiting_for_cloudstorage_root",
Self::Failed => "failed",
}
Expand Down Expand Up @@ -1929,6 +1937,33 @@ async fn run_workspace_mount_onboarding(
report
}

#[tauri::command]
async fn file_provider_enablement_status() -> FileProviderEnablementReport {
tauri::async_runtime::spawn_blocking(macos_file_provider_enablement_status_blocking)
.await
.unwrap_or_else(|error| FileProviderEnablementReport {
state: "unavailable".to_string(),
message: format!("File Provider status worker failed: {error}"),
path: None,
})
}

#[tauri::command]
async fn reveal_file_provider_enablement() -> ActionReport {
tauri::async_runtime::spawn_blocking(reveal_file_provider_enablement_blocking)
.await
.map_or_else(
|error| ActionReport {
ok: false,
message: format!("Finder worker failed: {error}"),
},
|result| match result {
Ok(message) => ActionReport { ok: true, message },
Err(message) => ActionReport { ok: false, message },
},
)
}

async fn create_desktop_mount_command(
app: AppHandle,
request: CreateDesktopMountRequest,
Expand Down Expand Up @@ -10101,7 +10136,7 @@ fn workspace_mount_onboarding_curated_message(
) -> Option<&'static str> {
match state {
MacosWorkspaceMountOnboardingState::ApprovalRequired => {
Some("Enable Locality in Finder, then return here and click Check again.")
Some("In Finder, click Enable for Locality. Locality will continue automatically.")
}
MacosWorkspaceMountOnboardingState::WaitingForCloudStorageRoot => {
Some("Locality is still waiting for the CloudStorage folder to appear.")
Expand Down Expand Up @@ -10193,6 +10228,142 @@ fn macos_workspace_mount_domain_user_enabled() -> Result<bool, String> {
.unwrap_or(false))
}

fn classify_macos_file_provider_enablement(
user_enabled: Option<bool>,
fallback_path: Option<PathBuf>,
resolved_root: Result<Option<PathBuf>, String>,
) -> FileProviderEnablementReport {
let path = |value: Option<PathBuf>| value.map(|path| path.display().to_string());
match user_enabled {
None => FileProviderEnablementReport {
state: "not_registered".to_string(),
message: "Locality is preparing the Finder location.".to_string(),
path: path(fallback_path),
},
Some(false) => FileProviderEnablementReport {
state: "needs_finder_enable".to_string(),
message: "In Finder, click Enable for Locality.".to_string(),
path: path(fallback_path),
},
Some(true) => match resolved_root {
Ok(Some(root)) => FileProviderEnablementReport {
state: "ready".to_string(),
message: "Locality is enabled in Finder.".to_string(),
path: path(Some(root)),
},
Ok(None) => FileProviderEnablementReport {
state: "waiting_for_root".to_string(),
message: "Finishing the Locality folder setup.".to_string(),
path: path(fallback_path),
},
Err(message) => FileProviderEnablementReport {
state: "unavailable".to_string(),
message,
path: path(fallback_path),
},
},
}
}

fn macos_file_provider_domain_status(
report: &serde_json::Value,
) -> (Option<bool>, Option<PathBuf>) {
let domain = report
.get("domains")
.and_then(serde_json::Value::as_array)
.and_then(|domains| {
domains.iter().find(|domain| {
domain.get("identifier").and_then(serde_json::Value::as_str)
== Some(localityd::file_provider::MACOS_FILE_PROVIDER_DOMAIN_ID)
})
});
let user_enabled = domain
.and_then(|domain| domain.get("userEnabled"))
.and_then(serde_json::Value::as_bool);
let path = domain
.and_then(|domain| domain.get("url"))
.and_then(serde_json::Value::as_str)
.filter(|url| !url.is_empty())
.map(PathBuf::from);
(user_enabled, path)
}

#[cfg(target_os = "macos")]
fn macos_file_provider_enablement_status_blocking() -> FileProviderEnablementReport {
let provider_roots = macos_file_provider_cloud_storage_roots();
let report = match run_macos_file_provider_helper("list", Vec::new()) {
Ok(report) => report,
Err(error) => {
return FileProviderEnablementReport {
state: "unavailable".to_string(),
message: error.message(),
path: provider_roots
.first()
.map(|path| path.display().to_string()),
};
}
};
let (user_enabled, reported_path) = macos_file_provider_domain_status(&report.helper_report);
let fallback_path = reported_path.or_else(|| provider_roots.first().cloned());

let resolved_root = if user_enabled == Some(true) {
match macos_file_provider_domain_url(
localityd::file_provider::MACOS_FILE_PROVIDER_DOMAIN_ID,
) {
Ok(root) => Ok(Some(root)),
Err(error) if recoverable_macos_file_provider_activation_error(&error.message()) => {
Ok(None)
}
Err(error) => Err(error.message()),
}
} else {
Ok(None)
};
classify_macos_file_provider_enablement(user_enabled, fallback_path, resolved_root)
}

#[cfg(not(target_os = "macos"))]
fn macos_file_provider_enablement_status_blocking() -> FileProviderEnablementReport {
FileProviderEnablementReport {
state: "unavailable".to_string(),
message: "File Provider enablement is only available on macOS.".to_string(),
path: None,
}
}

fn macos_file_provider_reveal_path(
domain_path: Option<&Path>,
candidates: &[PathBuf],
) -> Option<PathBuf> {
domain_path
.into_iter()
.chain(candidates.iter().map(PathBuf::as_path))
.find_map(|candidate| {
candidate
.ancestors()
.find(|ancestor| ancestor.is_dir())
.map(Path::to_path_buf)
})
}

#[cfg(target_os = "macos")]
fn reveal_file_provider_enablement_blocking() -> Result<String, String> {
let report = macos_file_provider_enablement_status_blocking();
let domain_path = report.path.as_deref().map(Path::new);
let path =
macos_file_provider_reveal_path(domain_path, &macos_file_provider_cloud_storage_roots())
.ok_or_else(|| {
"Could not find the Locality location or its CloudStorage parent.".to_string()
})?;
open_in_file_manager(&path)?;
Ok(format!("Opened {} in Finder.", path.display()))
}

#[cfg(not(target_os = "macos"))]
fn reveal_file_provider_enablement_blocking() -> Result<String, String> {
Err("File Provider enablement is only available on macOS.".to_string())
}

#[cfg(not(target_os = "macos"))]
fn macos_workspace_mount_domain_user_enabled() -> Result<bool, String> {
Ok(false)
Expand Down Expand Up @@ -13642,6 +13813,100 @@ mod tests {
));
}

#[test]
fn file_provider_enablement_report_distinguishes_registration_and_approval() {
let missing = super::classify_macos_file_provider_enablement(None, None, Ok(None));
assert_eq!(missing.state, "not_registered");

let disabled = super::classify_macos_file_provider_enablement(
Some(false),
Some(PathBuf::from("/Users/test/Library/CloudStorage/Locality")),
Ok(None),
);
assert_eq!(disabled.state, "needs_finder_enable");
assert_eq!(
disabled.path.as_deref(),
Some("/Users/test/Library/CloudStorage/Locality")
);
}

#[test]
fn file_provider_domain_status_reads_helper_url_while_disabled() {
let helper_report = serde_json::json!({
"domains": [{
"identifier": "loc",
"userEnabled": false,
"url": "/Users/test/Library/CloudStorage/LocalityPromptTest"
}]
});

let (user_enabled, path) = super::macos_file_provider_domain_status(&helper_report);

assert_eq!(user_enabled, Some(false));
assert_eq!(
path,
Some(PathBuf::from(
"/Users/test/Library/CloudStorage/LocalityPromptTest"
))
);
}

#[test]
fn file_provider_enablement_report_waits_for_enabled_root() {
let report = super::classify_macos_file_provider_enablement(
Some(true),
Some(PathBuf::from("/Users/test/Library/CloudStorage/Locality")),
Ok(None),
);

assert_eq!(report.state, "waiting_for_root");
assert!(report.message.contains("Finishing"));
}

#[test]
fn file_provider_enablement_report_is_ready_only_with_resolved_root() {
let root = PathBuf::from("/Users/test/Library/CloudStorage/Locality");
let report = super::classify_macos_file_provider_enablement(
Some(true),
Some(root.clone()),
Ok(Some(root.clone())),
);

assert_eq!(report.state, "ready");
assert_eq!(report.path.as_deref(), root.to_str());
}

#[test]
fn file_provider_enablement_report_exposes_unavailable_errors() {
let report = super::classify_macos_file_provider_enablement(
Some(true),
None,
Err("File Provider helper unavailable".to_string()),
);

assert_eq!(report.state, "unavailable");
assert_eq!(report.message, "File Provider helper unavailable");
}

#[test]
fn file_provider_reveal_path_prefers_domain_then_existing_parent() {
let temp = TestTempDir::new("file-provider-reveal");
let cloud_storage = temp.path().join("Library/CloudStorage");
fs::create_dir_all(&cloud_storage).expect("create CloudStorage parent");
let domain = cloud_storage.join("Locality");

assert_eq!(
super::macos_file_provider_reveal_path(Some(&domain), &[domain.clone()]),
Some(cloud_storage)
);

fs::create_dir_all(&domain).expect("create domain root");
assert_eq!(
super::macos_file_provider_reveal_path(Some(&domain), &[domain.clone()]),
Some(domain)
);
}

#[test]
fn macos_file_provider_approval_surface_path_uses_first_existing_candidate() {
let temp = TestTempDir::new("approval-surface");
Expand Down Expand Up @@ -13676,6 +13941,9 @@ mod tests {
),
expected
);
if let Some(state) = expected {
assert_eq!(state.as_str(), "needs_finder_enable");
}
}

#[test]
Expand Down Expand Up @@ -13716,7 +13984,7 @@ mod tests {
super::workspace_mount_onboarding_curated_message(
super::MacosWorkspaceMountOnboardingState::ApprovalRequired
),
Some("Enable Locality in Finder, then return here and click Check again.")
Some("In Finder, click Enable for Locality. Locality will continue automatically.")
);
assert_eq!(
super::workspace_mount_onboarding_curated_message(
Expand Down Expand Up @@ -17068,6 +17336,8 @@ fn main() {
disconnect_source,
export_source_backup,
run_workspace_mount_onboarding,
file_provider_enablement_status,
reveal_file_provider_enablement,
install_agent_guidance,
locate_notion_page,
search_notion_pages,
Expand Down
Loading
Loading