From 7cbbaefee8af9562f7b3fd6e98d67da62bc0a481 Mon Sep 17 00:00:00 2001 From: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Thu, 23 Nov 2023 23:54:45 +0300 Subject: [PATCH] [ENG-1413] Full disk access perms (#1791) * fda wip * clippy * add tauri invoke fns for FDA * fda wip * clippy * add tauri invoke fns for FDA * wip * fda wip * clippy * add tauri invoke fns for FDA * wip * wip * wip fda * remove imports * hopefully improve FDA * execute only on macos * ts * ts * Update Platform.tsx * Update AddLocationButton.tsx * remove console log * fix: fda and add unit tests * temp commit for Jake * add fda state and keybind handling (so the frontend is kept up to date) * update FDA * update imports * testing purposes * Jakes work * fix fda checks * work in progress (but not working) * remove dead files * attempt #2 * !!!temporarily enable devtools in prod * remove alert * show FDA screen but don't require it * add an FDA button to general client settings * Update AddLocationButton.tsx * remove dead code * unused dep * old errors * remove import * dead code * dead code + typesafety * eslint * remove fda dialog references * remove mp4 vid * hopefully fix onboarding for non-macos OSes * shorter nav --------- Co-authored-by: jake <77554505+brxken128@users.noreply.github.com> --- Cargo.lock | Bin 237729 -> 237495 bytes apps/desktop/src-tauri/Cargo.toml | 27 ++++--- apps/desktop/src-tauri/src/main.rs | 10 ++- apps/desktop/src/App.tsx | 5 +- apps/desktop/src/commands.ts | 4 + apps/web/src/App.tsx | 5 +- core/src/p2p/operations/mod.rs | 1 - crates/fda/Cargo.toml | 2 - crates/fda/README.md | 4 +- crates/fda/src/error.rs | 5 -- crates/fda/src/lib.rs | 74 ++---------------- .../$libraryId/settings/client/general.tsx | 16 +++- .../library/locations/AddLocationButton.tsx | 26 +++--- interface/app/index.tsx | 55 +++++++------ interface/app/onboarding/Progress.tsx | 4 +- interface/app/onboarding/full-disk.tsx | 63 +++++++++++++++ interface/app/onboarding/index.tsx | 42 ++++------ interface/app/onboarding/locations.tsx | 13 +++ interface/app/onboarding/new-library.tsx | 10 ++- interface/util/Platform.tsx | 1 + packages/assets/scripts/generate.mjs | 2 +- packages/assets/videos/fda.mp4 | Bin 0 -> 2126401 bytes packages/assets/videos/index.ts | 5 ++ 23 files changed, 209 insertions(+), 165 deletions(-) create mode 100644 interface/app/onboarding/full-disk.tsx create mode 100644 packages/assets/videos/fda.mp4 create mode 100644 packages/assets/videos/index.ts diff --git a/Cargo.lock b/Cargo.lock index 438804c8d3858507a273ca4220100b6f20e63c16..205756182759005df2ccece8775eeef583ff1730 100644 GIT binary patch delta 53 zcmV-50LuTN!49{-4zSmsgSnu$xu5|v-vef3VYlRe0zCqkjrIZwm%Zr%8kg{b0tAO! Lf&#Z&f&)e#!3r0V delta 126 zcmdnKpKswoz75ysPyRYD(cZ))*~B!_GR43+#mvmy%*@m%DLKhB)!fJ=**w+K!XhQr zFflnX#mG3(+|V$|)X300G08GHDHVi_jEp94ye`(fdj9s+^BLpsZx^j+GG$^($t)_K dKGB&`cslhELi{m diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 3b10068ee..c6e3b535e 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -10,24 +10,25 @@ edition = { workspace = true } [dependencies] tauri = { version = "1.5.2", features = [ - "dialog-all", - "linux-protocol-headers", - "macos-private-api", - "os-all", - "path-all", - "protocol-all", - "shell-all", - "updater", - "window-all", - "native-tls-vendored", + "macos-private-api", + "path-all", + "protocol-all", + "os-all", + "shell-all", + "dialog-all", + "linux-protocol-headers", + "updater", + "window-all", + "native-tls-vendored", ] } rspc = { workspace = true, features = ["tauri"] } sd-core = { path = "../../../core", features = [ - "ffmpeg", - "location-watcher", - "heif", + "ffmpeg", + "location-watcher", + "heif", ] } +sd-fda = { path = "../../../crates/fda" } tokio = { workspace = true, features = ["sync"] } tracing = { workspace = true } serde = "1.0.190" diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index a65bd6df8..b39d83a59 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -7,6 +7,7 @@ use std::{fs, path::PathBuf, sync::Arc, time::Duration}; use sd_core::{Node, NodeError}; +use sd_fda::DiskAccess; use tauri::{ api::path, ipc::RemoteDomainAccessScope, window::PlatformWebview, AppHandle, Manager, WindowEvent, @@ -27,10 +28,16 @@ mod updater; #[specta::specta] async fn app_ready(app_handle: AppHandle) { let window = app_handle.get_window("main").unwrap(); - window.show().unwrap(); } +#[tauri::command(async)] +#[specta::specta] +// If this erorrs, we don't have FDA and we need to re-prompt for it +async fn request_fda_macos() { + DiskAccess::request_fda().expect("Unable to request full disk access"); +} + #[tauri::command(async)] #[specta::specta] async fn set_menu_bar_item_state(_window: tauri::Window, _id: String, _enabled: bool) { @@ -302,6 +309,7 @@ async fn main() -> tauri::Result<()> { refresh_menu_bar, reload_webview, set_menu_bar_item_state, + request_fda_macos, file::open_file_paths, file::open_ephemeral_files, file::get_file_path_open_with_apps, diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index bf88f6e04..add9d7116 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -17,6 +17,8 @@ import { getSpacedropState } from '@sd/interface/hooks/useSpacedropState'; import '@sd/ui/style/style.scss'; +import { useOperatingSystem } from '@sd/interface/hooks'; + import * as commands from './commands'; import { platform } from './platform'; import { queryClient } from './query'; @@ -79,9 +81,10 @@ export default function App() { const TAB_CREATE_DELAY = 150; function AppInner() { + const os = useOperatingSystem(); function createTab() { const history = createMemoryHistory(); - const router = createMemoryRouterWithHistory({ routes, history }); + const router = createMemoryRouterWithHistory({ routes: routes(os), history }); const dispose = router.subscribe((event) => { setTabs((routers) => { diff --git a/apps/desktop/src/commands.ts b/apps/desktop/src/commands.ts index 9a5279985..69b428610 100644 --- a/apps/desktop/src/commands.ts +++ b/apps/desktop/src/commands.ts @@ -34,6 +34,10 @@ export function setMenuBarItemState(id: string, enabled: boolean) { return invoke()("set_menu_bar_item_state", { id,enabled }) } +export function requestFdaMacos() { + return invoke()("request_fda_macos") +} + export function openFilePaths(library: string, ids: number[]) { return invoke()("open_file_paths", { library,ids }) } diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index b1e7c6bc1..de10f027d 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'; import { createBrowserRouter } from 'react-router-dom'; import { RspcProvider } from '@sd/client'; import { Platform, PlatformProvider, routes, SpacedriveInterface } from '@sd/interface'; -import { useShowControls } from '@sd/interface/hooks'; +import { useOperatingSystem, useShowControls } from '@sd/interface/hooks'; import demoData from './demoData.json'; import ScreenshotWrapper from './ScreenshotWrapper'; @@ -76,8 +76,9 @@ const queryClient = new QueryClient({ }); function App() { + const os = useOperatingSystem(); const [router, setRouter] = useState(() => { - const router = createBrowserRouter(routes); + const router = createBrowserRouter(routes(os)); router.subscribe((event) => { setRouter((router) => { diff --git a/core/src/p2p/operations/mod.rs b/core/src/p2p/operations/mod.rs index 0bfe54690..2083c64f8 100644 --- a/core/src/p2p/operations/mod.rs +++ b/core/src/p2p/operations/mod.rs @@ -2,6 +2,5 @@ pub mod ping; pub mod request_file; pub mod spacedrop; -pub use ping::ping; pub use request_file::request_file; pub use spacedrop::spacedrop; diff --git a/crates/fda/Cargo.toml b/crates/fda/Cargo.toml index d0188fd0f..2c0f82581 100644 --- a/crates/fda/Cargo.toml +++ b/crates/fda/Cargo.toml @@ -7,6 +7,4 @@ repository = { workspace = true } edition = { workspace = true } [dependencies] -dirs = "5.0.1" -tokio = { workspace = true, features = ["rt-multi-thread", "fs", "macros"] } thiserror = "1.0.50" diff --git a/crates/fda/README.md b/crates/fda/README.md index 084028de4..e3d2273cb 100644 --- a/crates/fda/README.md +++ b/crates/fda/README.md @@ -1,7 +1,5 @@ # Spacedrive FDA Handling -## Platforms - -### MacOS +## MacOS On MacOS, we are able to open the "Full disk access" settings prompt to instruct the user to allow Spacedrive full disk access, which should alleviate all permissions issues. diff --git a/crates/fda/src/error.rs b/crates/fda/src/error.rs index ca0986462..f2319ad6c 100644 --- a/crates/fda/src/error.rs +++ b/crates/fda/src/error.rs @@ -1,10 +1,5 @@ -use std::path::PathBuf; - #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("unable to access path: {0}")] - PermissionDenied(PathBuf), - #[cfg(target_os = "macos")] #[error("there was an error while prompting for full disk access")] FDAPromptError, diff --git a/crates/fda/src/lib.rs b/crates/fda/src/lib.rs index 5149d8527..d1a0dfd77 100644 --- a/crates/fda/src/lib.rs +++ b/crates/fda/src/lib.rs @@ -24,43 +24,20 @@ #![forbid(unsafe_code, deprecated_in_future)] #![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] -use std::{io::ErrorKind, path::PathBuf}; - -use dirs::{ - audio_dir, cache_dir, config_dir, config_local_dir, data_dir, data_local_dir, desktop_dir, - document_dir, download_dir, executable_dir, home_dir, picture_dir, preference_dir, public_dir, - runtime_dir, state_dir, template_dir, video_dir, -}; - pub mod error; use error::Result; -pub struct FullDiskAccess(Vec); +pub struct DiskAccess; -impl FullDiskAccess { - async fn can_access_path(path: PathBuf) -> bool { - match tokio::fs::read_dir(path).await { - Ok(_) => true, - Err(e) => !matches!(e.kind(), ErrorKind::PermissionDenied), - } - } - - pub async fn has_fda() -> bool { - let dirs = Self::default(); - for dir in dirs.0 { - if !Self::can_access_path(dir).await { - return false; - } - } - true - } - - #[allow(clippy::missing_const_for_fn)] +impl DiskAccess { + /// This function is a no-op on non-MacOS systems. + /// + /// Once ran, it will open the "Full Disk Access" prompt. pub fn request_fda() -> Result<()> { #[cfg(target_os = "macos")] { - use error::Error; + use crate::error::Error; use std::process::Command; Command::new("open") @@ -73,49 +50,14 @@ impl FullDiskAccess { } } -impl Default for FullDiskAccess { - fn default() -> Self { - Self( - [ - audio_dir(), - cache_dir(), - config_dir(), - config_local_dir(), - data_dir(), - data_local_dir(), - desktop_dir(), - document_dir(), - download_dir(), - executable_dir(), - home_dir(), - picture_dir(), - preference_dir(), - public_dir(), - runtime_dir(), - state_dir(), - template_dir(), - video_dir(), - ] - .into_iter() - .flatten() - .collect(), - ) - } -} - #[cfg(test)] mod tests { - use super::FullDiskAccess; + use super::DiskAccess; #[test] #[cfg_attr(miri, ignore = "Miri can't run this test")] #[ignore = "CI can't run this due to lack of a GUI"] fn macos_open_full_disk_prompt() { - FullDiskAccess::request_fda().unwrap(); - } - - #[tokio::test] - async fn has_fda() { - FullDiskAccess::has_fda().await; + DiskAccess::request_fda().unwrap(); } } diff --git a/interface/app/$libraryId/settings/client/general.tsx b/interface/app/$libraryId/settings/client/general.tsx index 928fcb744..1a174a545 100644 --- a/interface/app/$libraryId/settings/client/general.tsx +++ b/interface/app/$libraryId/settings/client/general.tsx @@ -11,7 +11,7 @@ import { } from '@sd/client'; import { Button, Card, Input, Select, SelectOption, Slider, Switch, tw, z } from '@sd/ui'; import { Icon } from '~/components'; -import { useDebouncedFormWatch } from '~/hooks'; +import { useDebouncedFormWatch, useOperatingSystem } from '~/hooks'; import { usePlatform } from '~/util/Platform'; import { Heading } from '../Layout'; @@ -29,6 +29,8 @@ export const Component = () => { const debugState = useDebugState(); const editNode = useBridgeMutation('nodes.edit'); const connectedPeers = useConnectedPeers(); + const os = useOperatingSystem(); + const { requestFdaMacos } = usePlatform(); const updateThumbnailerPreferences = useBridgeMutation('nodes.updateThumbnailerPreferences'); const form = useZodForm({ @@ -178,6 +180,18 @@ export const Component = () => { + {os === 'macOS' && ( + + + + )} + { const platform = usePlatform(); const libraryId = useLibraryContext().library.uuid; - const navigate = useNavigate(); const transition = { type: 'keyframes', @@ -41,28 +39,30 @@ export const AddLocationButton = ({ path, className, onClick, ...props }: AddLoc setIsOverflowing(text.scrollWidth > overflow.clientWidth); }, [overflowRef, textRef]); + const locationDialogHandler = async () => { + if (!path) { + path = (await openDirectoryPickerDialog(platform)) ?? undefined; + } + // Remember `path` will be `undefined` on web cause the user has to provide it in the modal + if (path !== '') + dialogManager.create((dp) => ( + + )); + }; + return ( <> + + + + ) : ( +
+
+ )} +
+ + {showVideo && ( + + )} +
+ + ); +}; diff --git a/interface/app/onboarding/index.tsx b/interface/app/onboarding/index.tsx index f2b1da876..280f3359e 100644 --- a/interface/app/onboarding/index.tsx +++ b/interface/app/onboarding/index.tsx @@ -1,9 +1,11 @@ import { Navigate, RouteObject } from 'react-router'; import { getOnboardingStore } from '@sd/client'; +import { OperatingSystem } from '~/util/Platform'; import Alpha from './alpha'; import { useOnboardingContext } from './context'; import CreatingLibrary from './creating-library'; +import { FullDisk } from './full-disk'; import Locations from './locations'; import NewLibrary from './new-library'; import Privacy from './privacy'; @@ -18,30 +20,16 @@ const Index = () => { return ; }; -export default [ - { - index: true, - element: - }, - { path: 'alpha', element: }, - // { - // element: , - // path: 'login' - // }, - { - element: , - path: 'new-library' - }, - { - element: , - path: 'locations' - }, - { - element: , - path: 'privacy' - }, - { - element: , - path: 'creating-library' - } -] satisfies RouteObject[]; +const onboardingRoutes = (os: OperatingSystem) => { + return [ + { index: true, element: }, + { path: 'alpha', element: }, + { path: 'new-library', element: }, + ...(os === 'macOS' ? [{ element: , path: 'full-disk' }] : []), + { path: 'locations', element: }, + { path: 'privacy', element: }, + { path: 'creating-library', element: } + ] satisfies RouteObject[]; +}; + +export default onboardingRoutes; diff --git a/interface/app/onboarding/locations.tsx b/interface/app/onboarding/locations.tsx index c9b2236fc..9ac45ac9b 100644 --- a/interface/app/onboarding/locations.tsx +++ b/interface/app/onboarding/locations.tsx @@ -90,6 +90,19 @@ export default function OnboardingLocations() { className="flex flex-col items-center" > +
+ + + +
Add Locations Enhance your Spacedrive experience by adding your favorite locations to your diff --git a/interface/app/onboarding/new-library.tsx b/interface/app/onboarding/new-library.tsx index fd6b1bbd1..0a2a5479b 100644 --- a/interface/app/onboarding/new-library.tsx +++ b/interface/app/onboarding/new-library.tsx @@ -2,12 +2,14 @@ import { useState } from 'react'; import { useNavigate } from 'react-router'; import { Button, Form, InputField } from '@sd/ui'; import { Icon } from '~/components'; +import { useOperatingSystem } from '~/hooks'; import { OnboardingContainer, OnboardingDescription, OnboardingTitle } from './components'; import { useOnboardingContext } from './context'; export default function OnboardingNewLibrary() { const navigate = useNavigate(); + const os = useOperatingSystem(); const form = useOnboardingContext().forms.useForm('new-library'); const [importMode, setImportMode] = useState(false); @@ -20,11 +22,11 @@ export default function OnboardingNewLibrary() {
{ - navigate('../locations', { replace: true }); + navigate(`../${os === 'macOS' ? 'full-disk' : 'locations'}`, { replace: true }); })} > - + Create a Library Libraries are a secure, on-device database. Your files remain where they are, @@ -32,7 +34,7 @@ export default function OnboardingNewLibrary() { {importMode ? ( -
+
@@ -51,7 +53,7 @@ export default function OnboardingNewLibrary() { placeholder={'e.g. "James\' Library"'} />
-
+