From baf5a3ba67e5ca1c457f47d1ab2bd485b05a65ca Mon Sep 17 00:00:00 2001 From: Arnab Chakraborty <11457760+Rocky43007@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:14:50 -0500 Subject: [PATCH] Working Location-Watcher system for iOS app (#2073) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Working External Storage Locked Locations We can now add Downloads (A location locked by MANGE_EXTERNAL_STORAGE) on Android. * Navigate to added page Adding location now navigates to the added location explorer. * Way simpler solution Found a way simpler solution, that doesn't require query calls. * Clean up Remove unused import calls. * Attempt to get Location Watcher working Well, Location watcher doesn't want to run. But I wanted to push the `xcrun` command to the README. Also, locations now actually show files, but break on displaying recursive folders (folder content in a folder). * Quick clean up + Working full rescan locations.fullRescan works now on mobile with 0 issues. It can recognize orphan paths and when new files are added. But, Location-Watcher for some reason doesn't want to run automatically when the file system change occurs. * Working iOS Watcher Location Watcher now works for iOS when files are created. However, it can't understand the file delete event yet. * Functional watcher for iOS The watcher system is now fully functional on iOS, with a few bugs to patch regarding the delete function freezing the system up. * Update ios.rs * See Logs on Android Finally figured out how to see the logs from Android. * Update CONTRIBUTING.md * Updated to Stable & Working * Working Android Build + Explorer working again * wip * wip * Squashed commit of the following: commit a7bc3b908dc7c36a0d4f747f992cc0906aa2d9ab Merge: 201913a1 898a81da Author: Arnab Chakraborty <11457760+Rocky43007@users.noreply.github.com> Date: Tue Feb 6 16:18:42 2024 -0500 Merge branch 'main' of https://github.com/Rocky43007/spacedrive commit 898a81dae4a47e0e47d9414aa25b440fd811531a Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Tue Feb 6 19:30:53 2024 +0300 [ENG-1594] Change online to connected (#2060) : This is a combination of 3 commits. Change online to connected remove offline json commit 581cbf6a276d6de5165c4132ea0e79415efe02ee Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Tue Feb 6 17:49:16 2024 +0300 [MOB-54] UI Fixes (#2059) * UI fixes - rive animation - SD version in settings - and more * twStyle commit c178b91fb0f3c5382fa6e6788ef9a46c5f68de33 Author: Jesse Rodrigo <39565615+JSSRDRG@users.noreply.github.com> Date: Tue Feb 6 11:41:05 2024 +0100 Dutch locale (#2054) * nl locale * add nl entry * improve some wording commit 18ab7cc1a431d640ccfc682bf2478eeaa09350d7 Author: Brendan Allan Date: Tue Feb 6 17:13:47 2024 +0800 [ENG-1548] use in-memory instances when sending messages to cloud (#2057) * use in-memory instances when sending messages to cloud * comments commit 8dd7e3586e9829c5b3f9e00dac28c0d41f74a4db Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Tue Feb 6 06:03:33 2024 +0300 [MOB-47] Location screen and header updates (#2056) * Location screen and header updates * use tw sizing * remove un-necessary prop * Nit: change name commit a7bffc4a4efce6b21e5785f031b1674094c06e1c Author: Arnab Chakraborty <11457760+Rocky43007@users.noreply.github.com> Date: Mon Feb 5 07:06:47 2024 -0500 Update to Expo 50 and Fix to Rive Crashing (#2049) * Update Mobile App to Expo SDK 50 + Fix to Rive Crashing * Added `metro-react-native-babel-transformer` to fix CI commit 72c51d56491163cb94e73540d7a1c8f709c3e5fb Author: Utku <74243531+utkubakir@users.noreply.github.com> Date: Sun Feb 4 23:52:26 2024 +0300 Fix Chinese language (#2050) * fix chinese * remove console.log * Squashed commit of the following: commit ec55d9f182daf13784297edfb3dbb2d6e6ba43b9 Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Wed Feb 7 20:25:04 2024 +0300 [MOB-55] Video animation for onboarding on mobile and desktop (#2065) * video animation for onboarding on mobile and desktop run assets gen cleanup declare mp4 type * update metro config to transform video files from sd assets * test ci without native video exclude * casing? * remove to add back again due to github * add videos back * versions * no need to transform --------- Co-authored-by: Utku Bakir <74243531+utkubakir@users.noreply.github.com> commit cd97c73cf48744804b3adad096590c6e2c3e5014 Author: Utku <74243531+utkubakir@users.noreply.github.com> Date: Wed Feb 7 16:47:55 2024 +0300 More translations (#2051) * translations * more translation keys * all the translations * Added Cleaning Script * Prep for PR * Clean up + `cargo fmt` * Update mod.rs * Squashed commit of the following: commit d51773caaf85b5e4498d3d356c1feeea11e5619b Author: Utku <74243531+utkubakir@users.noreply.github.com> Date: Fri Feb 9 18:42:42 2024 +0300 Update readme & contributing guide & language stuff (#2071) * updates * keep common errors * fix selector being empty for english * sort by label * update contributing * update ndk and docs * Update CONTRIBUTING.md commit 76de9d5fa49784d2692e38750c5d885fdcb5b546 Author: Brendan Allan Date: Fri Feb 9 21:20:51 2024 +0800 sync support for labels (#2070) * more sync support for file paths + saved searches * sync support for labels * update sync prisma generator to support more than tags * workey * don't do illegal db migration * use name as label id in explorer commit d3236ae735ecc8e97ff2637dccfe51c0d6157139 Author: Brendan Allan Date: Fri Feb 9 16:17:04 2024 +0800 More sync support for file paths + saved searches (#2067) more sync support for file paths + saved searches * Update build-rust.sh Fix script so it doesn't build debug apps always now. * Add Tests for iOS * Update android.rs PR 1812 was closed, therefore changing the message to mention the branch instead. * Change `--debug` to `--release` Oops * Remove debug cargo crate for `notify-rs` * Spelling Fix on `android.rs` * Squashed commit of the following: commit 8c3462a27a00a57b07fa2d8ca444a56ec0039af6 Author: Ericson "Fogo" Soares Date: Mon Feb 26 16:45:58 2024 -0300 [ENG-1513] Better integration between Jobs and processing Actors (#1974) * First draft on new task system * Removing save to disk from task system * Bunch of concurrency issues * Solving Future impl issue when pausing tasks * Fix cancel and abort * Bunch of fixes on pause, suspend, resume, cancel and abort Also better error handling on task completion for the user * New capabilities to return an output on a task * Introducing a simple way to linear backoff on failed steal * Sample actor where tasks can dispatch more tasks * Rustfmt * Steal test to make sure * Stale deps cleanup * Removing unused utils * Initial lib docs * Docs ok * Memory cleanup on idle --------- Co-authored-by: Vítor Vasconcellos commit 9da5fed74cea1267210273238440751d75d8cd95 Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Mon Feb 26 18:53:37 2024 +0300 [ENG-1625] Spacedrop UI correct hover condition & spacing (#2127) * improve spacedrop ui with correct hover & spacing * remove commit ed037df4c19e995b7490a062070edb9aa1df322e Author: Matteo Galiazzo <50683509+gekoxyz@users.noreply.github.com> Date: Mon Feb 26 16:20:41 2024 +0100 added it locale, added it entry (#2066) * added it locale, added it entry * Apply suggestions from code review Co-authored-by: Matteo Martellini * add missing keys and a readme about the script --------- Co-authored-by: Utku <74243531+utkubakir@users.noreply.github.com> Co-authored-by: Matteo Martellini commit 7cae4cb78b4a858706165a5c38a8f584d9e4775c Author: jake <77554505+brxken128@users.noreply.github.com> Date: Mon Feb 26 15:17:28 2024 +0000 Clean-up MacOS window closing behaviour code (#2124) * fix: delete dead/unused file * refactor: add the window event handler with the rest of them * refactor: formatting commit 4ef515d9746093184cf880dc84dc94d52db91fd0 Author: Oscar Beaumont Date: Mon Feb 26 15:23:48 2024 +0800 rspc over P2P (#2112) * wip: rspc over p2p * wip * rspc over P2P * Cleanup + error handling * slight cleanup * Using Hyper for HTTP streaming + websockets commit 3eb8cfe4afd3e90be06e3d170ad29b644812baab Author: Utku <74243531+utkubakir@users.noreply.github.com> Date: Fri Feb 23 11:26:58 2024 -0500 Fix android thumbs (#2121) * replace react-native-fs with an active fork * time sink * fix commit ca10749a9cbe701248a67c43a6199e450d511665 Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Fri Feb 23 00:28:35 2024 +0300 Mob: cleanup warning (#2122) Update Categories.tsx commit 0e3b4d84e5f68adc09cefc125f1aa90b7bd1f9ca Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Thu Feb 22 16:08:41 2024 +0300 MOB: Settings paddings (#2120) padding tweaks commit e92d96ff054313bd3aa6c55a48197dbaaf624bed Author: nikec <43032218+niikeec@users.noreply.github.com> Date: Thu Feb 22 13:53:02 2024 +0100 [ENG-1619] Add spacedrop to the context menu (#2119) add spacedrop to context menu commit edfbdeb9d7b62cff4fa29c5d3056c1603986f367 Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Thu Feb 22 15:47:08 2024 +0300 [ENG-1618] Spacedrop UI (#2118) * spacedrop ui update * i18n and description * glitch: remove ! * already in progress i18n * more i18n commit b4edf5b21061ebbe84084548632f9813b162751d Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Thu Feb 22 15:20:31 2024 +0300 [MOB-62] Spacing & padding tweaks (#2117) Spacing & padding tweaks commit f4e68497ed92ced9b409b1f7ca033cc54583d26a Author: Vítor Vasconcellos Date: Thu Feb 22 03:15:36 2024 -0300 Fix core test and CI breaking (#2116) Fix core test passing inverted arguments to sync_db_entry macro commit f2ce41b9b4355a31cefac3af472b5dc03a4b361e Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Wed Feb 21 17:19:40 2024 +0300 Mob: better visually width fitting for categories (#2114) * Visually width fitting for categories * remove padding commit a0b7cf21fc1a005ae10d5bdcf85cd23c2e41d09e Author: Vítor Vasconcellos Date: Wed Feb 21 11:18:15 2024 -0300 Fix mobile CI + Some small CI improvements (#2113) Fix mobile CI - Use rust envvars in all workflows - Use rust envvars and mold when build sd-server docker commit badae3c75af0a042fd0a4307f8e0771b339aed47 Author: Utku <74243531+utkubakir@users.noreply.github.com> Date: Wed Feb 21 08:26:05 2024 -0500 [MOB-37, MOB-38, MOB-39] Preview for PDF, Text and Media files (#2098) * version & microphonePermission text & eslint * remove polyfill as hermes supports intl now * why do we have solid on mobile? * cleanup * add solid back =_= * pnpm lock * we hate relative paths here * android config * open file logic * visual tweaks --------- Co-authored-by: ameer2468 <33054370+ameer2468@users.noreply.github.com> commit bfd6e813c0e481e852684796e3b24aa37ca06575 Author: Brendan Allan Date: Wed Feb 21 22:42:10 2024 +1100 media data sync (#2102) * basic sync operation backfill * media data sync * sync entry helpers * fix sync generator * nicer * re-add key_id commit c1e98dcb0dbc1e4c46df73d894d7a3bd2c1f3d11 Author: Vítor Vasconcellos Date: Wed Feb 21 06:27:40 2024 -0300 Update github actions due to nodejs 16 deprecation (#2107) * Update github actions due to nodejs 16 deprecation - Replace archived actions-rs/clippy-check with maintained fork actions-rs-plus/clippy-check - Replace redhat-actions/push-to-registry with updated fork Eusebiotrigo/push-to-registry - Point redhat-actions/buildah-build and softprops/action-gh-release to current master to fix nodejs deprecation * Build the correct ios core rust arch for CI runs * Build ios app for the same arch as the host in Mobile CI * Some changes to try and make cache-factory faster and avoid failing so much * Add trigger to run cache-factory on pull requests when there are changes to itself * Attempt to fix sed usage on macOS * Don't treat warning as errors * Fix windows * Fix windows 2 * Use target ad cache key for rust to differentiate between macOS x86_64 and arm64 * Use faster/better linkers to compile for macOS, Linux and Windows * Fix missing shell in action * Fix typo * Fix missing shell in action 2 * Fix mold download - Replace bsdtar with plain tar * Fix permission denied when extracting mold * Remove zld * Don't restore cache for rustfmt - Remove target symlink to C:/ in an attempt to speed-up windows CI * Fix typo * Restore target symlink on windows - Removing it didn't make CI faster * Run Mobile on macos-14 commit a7d836a922dbef986c53921e1b6140597ff381e0 Author: Oscar Beaumont Date: Wed Feb 21 16:13:40 2024 +0800 Fix P2P not working for libraries (#2031) * P2P Debug route * Remove legacy peer to peer pairing process * Fix error typo * Sync instances with cloud * Upgrade deps + extended instance data * Create instance with extended metadata * Auto sync instances * Actually `.await` * bruh * sync library info * this isn't gonna work * only sleep cloud receiver when no more messages (#1985) * [ENG-1567] Fix renaming (#1986) fix rename * only sleep cloud receiver when no more messages * use in memory instances during cloud receive (#1995) * use in memory instances during cloud receive * is_empty --------- Co-authored-by: nikec <43032218+niikeec@users.noreply.github.com> * fix type error * wip * make mdns mdns better * rebuild state * Add hooks + listeners + discovered state * Split into crates * wip fixing core + wip merging Spacetime into `sd-p2p2` * `SmartLockGuard` + `Listener` * Make `sd-core` compile * Reenable all operation receivers * Fix all broken code within `sd-core` * minor fixes found in review * Bring in `libp2p` + restructure `sd-p2p` for the gazillion-th time * whoops * Compile no matter the (runtime) cost * fixing merge issues * wip * a * b * C * Handle port betterer * c * Migrate node config * a * no crash on startup * wip * a * jdfhskjfsg * a * fix discovery * a bunch of fixes * getting Spacedrop working * I don't get why it no worky * debug example * a * wip * wip * removing logging from stream impl * wip: shit is fucked * Redo quic integration + Spacedrop working * Fix shutdown - deadlocks + shutdown peers * Add Prisma migrations * Fix shutdown * a * fix * cleanup * The lord clippy hath spoken * disable P2P settings for now --------- Co-authored-by: Brendan Allan Co-authored-by: nikec <43032218+niikeec@users.noreply.github.com> commit 146838d19e48d73ff20d8fcb99249b1b01427bb4 Author: Julian Braha Date: Tue Feb 20 20:06:05 2024 +0000 Improve error handling by using decode::Error instead of io::Result (#2078) Use decode::Error instead of io::Result commit 145cd20805abc34ee68f7d25048c9bb1705d9479 Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Tue Feb 20 22:59:11 2024 +0300 [MOB-59] Empty UI for locations and tags screen (#2110) Empty UI for locations and tags screen commit 704d03a329b28500c1a0a372e86488beae9c3f44 Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Tue Feb 20 22:47:14 2024 +0300 [MOB-60] locations & tags query invalidation on updates (#2111) Trigger UI updates on location adding/delete and tags lint name commit 45b9e357448a203919d1745d23b60fcd0314149e Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Tue Feb 20 13:05:53 2024 +0300 mousedown fix (#2108) quick mistake fix commit 25f9f03010f2531506020de1f672887c4b8f6a3c Author: Brendan Allan Date: Tue Feb 20 20:22:34 2024 +1100 Basic sync operation backfill (#2101) * basic sync operation backfill * no changes commit ce0203ff74a3ceb81211998d4d070dfa95307669 Author: Arnab Chakraborty <11457760+Rocky43007@users.noreply.github.com> Date: Tue Feb 20 01:33:52 2024 -0500 New Android Build Script (#2096) * New Android Build Script * Clean up + Works for CI now * Simplify android build.sh - Fix /var/home/vitor fallback for Linux systems - Run a single cargo ndk for all targets (not parallel build, but a bit faster) - Fix android target s/x86/x86_64/ - Format setup.sh - Minor improvements to rust mobile targets installation step in setup.sh * Add notice to CONTRIBUTING that only Java <= 17 is supported for building android - Make prettier ignore some mobile build artifacts * When in CI, Fix build android core for host architecture --------- Co-authored-by: Vítor Vasconcellos commit d280895d28666ec6068c31e530a3a787e226bf0f Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Mon Feb 19 23:45:29 2024 +0300 [ENG-1615] bg intro video fixed (#2104) * video intro bg * test hsl * test video bg * run tests * comment * mob intro * git glitch * git * webm type commit 9742dc7ab45a0c0f232fa338da13cbaac6217efa Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Mon Feb 19 19:12:11 2024 +0300 [MOB-58] Settings routes new design & more (#2103) * wip: redesigning settings pages * Edit location redesign & more * right actions * cleanup commit 94008de7f39d682a27839329c5d0e1ee24f39654 Author: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Mon Feb 19 18:23:20 2024 +0300 [ENG-1612] Fix mouse nav forwards and backwards (#2105) Fix mouse nav forwards and backwards * Clean up commented code * Clean up + Remove `LocationOnboarding.tsx` --- .vscode/settings.json | 6 +- Cargo.lock | Bin 264175 -> 264182 bytes apps/desktop/src-tauri/Cargo.toml | 1 - apps/mobile/scripts/cleanTarget.sh | 36 ++ apps/mobile/src/navigation/TabNavigator.tsx | 130 +++--- apps/mobile/src/navigation/index.tsx | 3 +- apps/mobile/src/screens/Location.tsx | 4 +- .../src/screens/settings/info/About.tsx | 9 +- apps/server/Cargo.toml | 1 - core/Cargo.toml | 8 +- core/src/api/locations.rs | 1 - core/src/lib.rs | 2 + core/src/location/manager/mod.rs | 91 ++-- core/src/location/manager/watcher/android.rs | 271 ++++++++++++ core/src/location/manager/watcher/ios.rs | 394 ++++++++++++++++++ core/src/location/manager/watcher/mod.rs | 42 +- core/src/location/mod.rs | 2 - core/src/object/media/mod.rs | 1 - 18 files changed, 847 insertions(+), 155 deletions(-) create mode 100755 apps/mobile/scripts/cleanTarget.sh create mode 100644 core/src/location/manager/watcher/android.rs create mode 100644 core/src/location/manager/watcher/ios.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 1fcc13707..4180530b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -84,5 +84,9 @@ ".eslintrc.js": ".eslintcache", "package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, pnpm-workspace.yaml, .pnp.cjs, .pnp.loader.mjs", "tsconfig.json": "tsconfig.*.json" - } + }, + "rust-analyzer.linkedProjects": [], + "rust-analyzer.cargo.extraEnv": {}, + "rust-analyzer.check.targets": null, + "rust-analyzer.showUnlinkedFileNotification": false } diff --git a/Cargo.lock b/Cargo.lock index 61c38518875d25d60536925fbd4497d97f440426..05f22ca60368ea126c351ee842eb7eb480ff2bbe 100644 GIT binary patch delta 253 zcmaFgFYv8jpkWJRM*8ISiNW@1DJh0#7AcnIW|jsi21yplCWb~SW=Uqrh6cuF21e%Q zX(lP=si`KGrpcx$1}UZ~rfI1L7N#baM#e^_rpc2X6i`h)iaJpj& zlWJC8eo1CprEXENK7^&0o>^jFlv-w+Y;0*{nVMo~Xp)wcWRR3>ZenVlXqjkYX<=k$ zU}9!yYH5~aV33@sOsvA`2ep~RrWbHC2~F?&%xF7Z{xdVj^wXaim8T!L#wgp){e=;T MnYMF(VSXYE0JaNIp8x;= delta 196 zcmWNJF>V4e5CCQP!0C`snpF9K?cF`wMZzDTrLp(CT$MUyIwYisNLO51{t$@=@B@AT zqD?43spDd%oB7*T*KPIwt-4iz{+iFk>n602*%0RBsR=}ESW6PgMI^weqtTc-7~7%? z4h*vK+QcNo?p00qRi_RcMg8e_@#1Wd2D0HuL=izUWQi!ntzfCREZXQPAy|txy5wvl mK)~K(AAcVD&s{xy+|=`Mys4*Q`KBJ%^<5Q4`}#h - ), - label: 'Overview', - labelStyle: tw`text-[10px] font-semibold`, - testID: 'overview-tab' - }, - { - name: 'NetworkStack', - component: NetworkStack, - icon: ( - - ), - label: 'Network', - labelStyle: tw`text-[10px] font-semibold`, - testID: 'network-tab' - }, - { - name: 'BrowseStack', - component: BrowseStack, - icon: ( - - ), - label: 'Browse', - labelStyle: tw`text-[10px] font-semibold`, - testID: 'browse-tab' - }, - { - name: 'SettingsStack', - component: SettingsStack, - icon: ( - - ), - label: 'Settings', - labelStyle: tw`text-[10px] font-semibold`, - testID: 'settings-tab' - } - ]; + { + name: 'OverviewStack', + component: OverviewStack, + icon: ( + + ), + label: 'Overview', + labelStyle: tw`text-[10px] font-semibold`, + testID: 'overview-tab' + }, + { + name: 'NetworkStack', + component: NetworkStack, + icon: ( + + ), + label: 'Network', + labelStyle: tw`text-[10px] font-semibold`, + testID: 'network-tab' + }, + { + name: 'BrowseStack', + component: BrowseStack, + icon: ( + + ), + label: 'Browse', + labelStyle: tw`text-[10px] font-semibold`, + testID: 'browse-tab' + }, + { + name: 'SettingsStack', + component: SettingsStack, + icon: ( + + ), + label: 'Settings', + labelStyle: tw`text-[10px] font-semibold`, + testID: 'settings-tab' + } + ]; return ( (); - // This is the main navigator we nest everything under. export default function RootNavigator() { return ( @@ -38,6 +37,6 @@ export type RootStackScreenProps = Stac declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace ReactNavigation { - interface RootParamList extends RootStackParamList {} + interface RootParamList extends RootStackParamList { } } } diff --git a/apps/mobile/src/screens/Location.tsx b/apps/mobile/src/screens/Location.tsx index e5687ac2d..18e1d108b 100644 --- a/apps/mobile/src/screens/Location.tsx +++ b/apps/mobile/src/screens/Location.tsx @@ -25,7 +25,7 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP take: 100 } ]); - + const pathsItemsReferences = useMemo(() => paths.data?.items ?? [], [paths.data]); useNodes(paths.data?.nodes); const pathsItems = useCache(pathsItemsReferences); @@ -52,5 +52,5 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP getExplorerStore().path = path ?? ''; }, [id, path]); - return ; + return } diff --git a/apps/mobile/src/screens/settings/info/About.tsx b/apps/mobile/src/screens/settings/info/About.tsx index 4bf9dd403..bdb7d14a1 100644 --- a/apps/mobile/src/screens/settings/info/About.tsx +++ b/apps/mobile/src/screens/settings/info/About.tsx @@ -22,11 +22,10 @@ const AboutScreen = () => { Spacedrive{' '} - {`for ${ - Platform.OS === 'android' - ? Platform.OS[0]?.toUpperCase() + Platform.OS.slice(1) - : Platform.OS[0] + Platform.OS.slice(1).toUpperCase() - }`} + {`for ${Platform.OS === 'android' + ? Platform.OS[0]?.toUpperCase() + Platform.OS.slice(1) + : Platform.OS[0] + Platform.OS.slice(1).toUpperCase() + }`} The file manager from the future. diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml index a3b1b541c..836defb71 100644 --- a/apps/server/Cargo.toml +++ b/apps/server/Cargo.toml @@ -13,7 +13,6 @@ ai-models = ["sd-core/ai"] [dependencies] sd-core = { path = "../../core", features = [ "ffmpeg", - "location-watcher", "heif", ] } diff --git a/core/Cargo.toml b/core/Cargo.toml index 8b16e2b92..e3b2717f0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,7 +14,6 @@ default = [] mobile = [] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg. ffmpeg = ["dep:sd-ffmpeg"] -location-watcher = ["dep:notify"] heif = ["sd-images/heif"] ai = ["dep:sd-ai"] @@ -48,7 +47,6 @@ sd-sync = { path = "../crates/sync" } sd-utils = { path = "../crates/utils" } sd-cloud-api = { version = "0.1.0", path = "../crates/cloud-api" } - # Workspace dependencies async-channel = { workspace = true } async-trait = { workspace = true } @@ -112,9 +110,9 @@ http-range = "0.1.5" int-enum = "0.5.0" itertools = "0.12.0" mini-moka = "0.10.2" -notify = { version = "=5.2.0", default-features = false, features = [ - "macos_fsevent", -], optional = true } +notify = { git="https://github.com/notify-rs/notify.git", rev="c3929ed114fbb0bc7457a9a498260461596b00ca", default-features = false, features = [ + "macos_fsevent", +] } rmpv = "^1.0.1" serde-hashkey = "0.4.5" serde_repr = "0.1" diff --git a/core/src/api/locations.rs b/core/src/api/locations.rs index 11aa8f2cf..b0418c3c5 100644 --- a/core/src/api/locations.rs +++ b/core/src/api/locations.rs @@ -366,7 +366,6 @@ pub(crate) fn mount() -> AlphaRouter { pub location_id: location::id::Type, pub reidentify_objects: bool, } - R.with2(library()).mutation( |(node, library), FullRescanArgs { diff --git a/core/src/lib.rs b/core/src/lib.rs index 53ebdd3ca..fd6c89a3d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -206,6 +206,8 @@ impl Node { "info" }; + // let level = "debug"; // Exists for now to debug the location manager + std::env::set_var( "RUST_LOG", format!("info,sd_core={level},sd_p2p=debug,sd_core::location::manager=info,sd_ai={level}"), diff --git a/core/src/location/manager/mod.rs b/core/src/location/manager/mod.rs index dfbda6199..466750dd4 100644 --- a/core/src/location/manager/mod.rs +++ b/core/src/location/manager/mod.rs @@ -20,16 +20,13 @@ use tokio::sync::{ broadcast::{self, Receiver}, oneshot, RwLock, }; -use tracing::error; +use tracing::{debug, error}; -#[cfg(feature = "location-watcher")] use tokio::sync::mpsc; use uuid::Uuid; -#[cfg(feature = "location-watcher")] mod watcher; -#[cfg(feature = "location-watcher")] mod helpers; #[derive(Clone, Copy, Debug)] @@ -67,22 +64,18 @@ pub struct WatcherManagementMessage { #[derive(Error, Debug)] pub enum LocationManagerError { - #[cfg(feature = "location-watcher")] #[error("Unable to send location management message to location manager actor: (error: {0})")] ActorSendLocationError(#[from] mpsc::error::SendError), - #[cfg(feature = "location-watcher")] #[error("Unable to send path to be ignored by watcher actor: (error: {0})")] ActorIgnorePathError(#[from] mpsc::error::SendError), - #[cfg(feature = "location-watcher")] #[error("Unable to watcher management message to watcher manager actor: (error: {0})")] ActorIgnorePathMessageError(#[from] mpsc::error::SendError), #[error("Unable to receive actor response: (error: {0})")] ActorResponseError(#[from] oneshot::error::RecvError), - #[cfg(feature = "location-watcher")] #[error("Watcher error: (error: {0})")] WatcherError(#[from] notify::Error), @@ -119,11 +112,10 @@ type OnlineLocations = BTreeSet>; #[must_use = "'LocationManagerActor::start' must be used to start the actor"] pub struct LocationManagerActor { - #[cfg(feature = "location-watcher")] location_management_rx: mpsc::Receiver, - #[cfg(feature = "location-watcher")] + watcher_management_rx: mpsc::Receiver, - #[cfg(feature = "location-watcher")] + stop_rx: oneshot::Receiver<()>, } @@ -178,25 +170,21 @@ impl LocationManagerActor { } }); - #[cfg(feature = "location-watcher")] tokio::spawn(Locations::run_locations_checker( self.location_management_rx, self.watcher_management_rx, self.stop_rx, node, )); - - #[cfg(not(feature = "location-watcher"))] - tracing::warn!("Location watcher is disabled, locations will not be checked"); } } pub struct Locations { online_locations: RwLock, pub online_tx: broadcast::Sender, - #[cfg(feature = "location-watcher")] + location_management_tx: mpsc::Sender, - #[cfg(feature = "location-watcher")] + watcher_management_tx: mpsc::Sender, stop_tx: Option>, } @@ -205,11 +193,11 @@ impl Locations { pub fn new() -> (Self, LocationManagerActor) { let online_tx = broadcast::channel(16).0; - #[cfg(feature = "location-watcher")] { let (location_management_tx, location_management_rx) = mpsc::channel(128); let (watcher_management_tx, watcher_management_rx) = mpsc::channel(128); let (stop_tx, stop_rx) = oneshot::channel(); + debug!("Starting location manager actor"); ( Self { @@ -226,19 +214,6 @@ impl Locations { }, ) } - - #[cfg(not(feature = "location-watcher"))] - { - tracing::warn!("Location watcher is disabled, locations will not be checked"); - ( - Self { - online_tx, - online_locations: Default::default(), - stop_tx: None, - }, - LocationManagerActor {}, - ) - } } #[inline] @@ -249,9 +224,9 @@ impl Locations { library: Arc, action: ManagementMessageAction, ) -> Result<(), LocationManagerError> { - #[cfg(feature = "location-watcher")] { let (tx, rx) = oneshot::channel(); + debug!("Sending location management message to location manager actor: {action:?}"); self.location_management_tx .send(LocationManagementMessage { @@ -264,9 +239,6 @@ impl Locations { rx.await? } - - #[cfg(not(feature = "location-watcher"))] - Ok(()) } #[inline] @@ -277,10 +249,11 @@ impl Locations { library: Arc, action: WatcherManagementMessageAction, ) -> Result<(), LocationManagerError> { - #[cfg(feature = "location-watcher")] { let (tx, rx) = oneshot::channel(); + debug!("Sending watcher management message to location manager actor: {action:?}"); + self.watcher_management_tx .send(WatcherManagementMessage { location_id, @@ -292,9 +265,6 @@ impl Locations { rx.await? } - - #[cfg(not(feature = "location-watcher"))] - Ok(()) } pub async fn add( @@ -377,7 +347,6 @@ impl Locations { }) } - #[cfg(feature = "location-watcher")] async fn run_locations_checker( mut location_management_rx: mpsc::Receiver, mut watcher_management_rx: mpsc::Receiver, @@ -388,7 +357,7 @@ impl Locations { use futures::stream::{FuturesUnordered, StreamExt}; use tokio::select; - use tracing::{info, warn}; + use tracing::warn; use helpers::{ check_online, drop_location, get_location, handle_ignore_path_request, @@ -430,6 +399,8 @@ impl Locations { (location_id, library.id), watcher ); + debug!("Location {location_id} is online, watching it"); + // info!("Locations watched: {:#?}", locations_watched); } else { locations_unwatched.insert( (location_id, library.id), @@ -578,7 +549,7 @@ impl Locations { } _ = &mut stop_rx => { - info!("Stopping location manager"); + debug!("Stopping location manager"); break; } } @@ -642,14 +613,12 @@ pub struct StopWatcherGuard<'m> { impl Drop for StopWatcherGuard<'_> { fn drop(&mut self) { - if cfg!(feature = "location-watcher") { - // FIXME: change this Drop to async drop in the future - if let Err(e) = block_on(self.manager.reinit_watcher( - self.location_id, - self.library.take().expect("library should be set"), - )) { - error!("Failed to reinit watcher on stop watcher guard drop: {e}"); - } + // FIXME: change this Drop to async drop in the future + if let Err(e) = block_on(self.manager.reinit_watcher( + self.location_id, + self.library.take().expect("library should be set"), + )) { + error!("Failed to reinit watcher on stop watcher guard drop: {e}"); } } } @@ -664,18 +633,16 @@ pub struct IgnoreEventsForPathGuard<'m> { impl Drop for IgnoreEventsForPathGuard<'_> { fn drop(&mut self) { - if cfg!(feature = "location-watcher") { - // FIXME: change this Drop to async drop in the future - if let Err(e) = block_on(self.manager.watcher_management_message( - self.location_id, - self.library.take().expect("library should be set"), - WatcherManagementMessageAction::IgnoreEventsForPath { - path: self.path.take().expect("path should be set"), - ignore: false, - }, - )) { - error!("Failed to un-ignore path on watcher guard drop: {e}"); - } + // FIXME: change this Drop to async drop in the future + if let Err(e) = block_on(self.manager.watcher_management_message( + self.location_id, + self.library.take().expect("library should be set"), + WatcherManagementMessageAction::IgnoreEventsForPath { + path: self.path.take().expect("path should be set"), + ignore: false, + }, + )) { + error!("Failed to un-ignore path on watcher guard drop: {e}"); } } } diff --git a/core/src/location/manager/watcher/android.rs b/core/src/location/manager/watcher/android.rs new file mode 100644 index 000000000..a72612a21 --- /dev/null +++ b/core/src/location/manager/watcher/android.rs @@ -0,0 +1,271 @@ +//! Android file system watcher implementation. +//! TODO: Still being worked on by @Rocky43007 on Branch Rocky43007:location-watcher-test-3 +//! DO NOT TOUCH FOR NOW + +use crate::{invalidate_query, library::Library, location::manager::LocationManagerError, Node}; + +use sd_prisma::prisma::location; +use sd_utils::error::FileIOError; + +use std::{ + collections::{BTreeMap, HashMap}, + path::{Path, PathBuf}, + sync::Arc, +}; + +use async_trait::async_trait; +use notify::{ + event::{CreateKind, DataChange, ModifyKind, RenameMode}, + Event, +}; +use tokio::{fs, time::Instant}; +use tracing::{error, info, trace}; + +use super::{ + utils::{create_dir, recalculate_directories_size, remove, rename, update_file}, + EventHandler, HUNDRED_MILLIS, ONE_SECOND, +}; + +#[derive(Debug)] +pub(super) struct AndroidEventHandler<'lib> { + location_id: location::id::Type, + library: &'lib Arc, + node: &'lib Arc, + last_events_eviction_check: Instant, + rename_from: HashMap, + recently_renamed_from: BTreeMap, + files_to_update: HashMap, + reincident_to_update_files: HashMap, + to_recalculate_size: HashMap, + path_and_instant_buffer: Vec<(PathBuf, Instant)>, +} + +#[async_trait] +impl<'lib> EventHandler<'lib> for AndroidEventHandler<'lib> { + fn new( + location_id: location::id::Type, + library: &'lib Arc, + node: &'lib Arc, + ) -> Self { + Self { + location_id, + library, + node, + last_events_eviction_check: Instant::now(), + rename_from: HashMap::new(), + recently_renamed_from: BTreeMap::new(), + files_to_update: HashMap::new(), + reincident_to_update_files: HashMap::new(), + to_recalculate_size: HashMap::new(), + path_and_instant_buffer: Vec::new(), + } + } + + async fn handle_event(&mut self, event: Event) -> Result<(), LocationManagerError> { + info!("Received Android event: {:#?}", event); + + // let Event { + // kind, mut paths, .. + // } = event; + + // match kind { + // EventKind::Create(CreateKind::File) + // | EventKind::Modify(ModifyKind::Data(DataChange::Any)) => { + // // When we receive a create, modify data or metadata events of the abore kinds + // // we just mark the file to be updated in a near future + // // each consecutive event of these kinds that we receive for the same file + // // we just store the path again in the map below, with a new instant + // // that effectively resets the timer for the file to be updated + // let path = paths.remove(0); + // if self.files_to_update.contains_key(&path) { + // if let Some(old_instant) = + // self.files_to_update.insert(path.clone(), Instant::now()) + // { + // self.reincident_to_update_files + // .entry(path) + // .or_insert(old_instant); + // } + // } else { + // self.files_to_update.insert(path, Instant::now()); + // } + // } + + // EventKind::Create(CreateKind::Folder) => { + // let path = &paths[0]; + + // // Don't need to dispatch a recalculate directory event as `create_dir` dispatches + // // a `scan_location_sub_path` function, which recalculates the size already + + // create_dir( + // self.location_id, + // path, + // &fs::metadata(path) + // .await + // .map_err(|e| FileIOError::from((path, e)))?, + // self.node, + // self.library, + // ) + // .await?; + // } + // EventKind::Modify(ModifyKind::Name(RenameMode::From)) => { + // // Just in case we can't garantee that we receive the Rename From event before the + // // Rename Both event. Just a safeguard + // if self.recently_renamed_from.remove(&paths[0]).is_none() { + // self.rename_from.insert(paths.remove(0), Instant::now()); + // } + // } + + // EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => { + // let from_path = &paths[0]; + // let to_path = &paths[1]; + + // self.rename_from.remove(from_path); + // rename( + // self.location_id, + // to_path, + // from_path, + // fs::metadata(to_path) + // .await + // .map_err(|e| FileIOError::from((to_path, e)))?, + // self.library, + // ) + // .await?; + // self.recently_renamed_from + // .insert(paths.swap_remove(0), Instant::now()); + // } + // EventKind::Remove(_) => { + // let path = paths.remove(0); + // if let Some(parent) = path.parent() { + // if parent != Path::new("") { + // self.to_recalculate_size + // .insert(parent.to_path_buf(), Instant::now()); + // } + // } + + // remove(self.location_id, &path, self.library).await?; + // } + // other_event_kind => { + // trace!("Other Linux event that we don't handle for now: {other_event_kind:#?}"); + // } + // } + + Ok(()) + } + + async fn tick(&mut self) { + if self.last_events_eviction_check.elapsed() > HUNDRED_MILLIS { + if let Err(e) = self.handle_to_update_eviction().await { + error!("Error while handling recently created or update files eviction: {e:#?}"); + } + + if let Err(e) = self.handle_rename_from_eviction().await { + error!("Failed to remove file_path: {e:#?}"); + } + + self.recently_renamed_from + .retain(|_, instant| instant.elapsed() < HUNDRED_MILLIS); + + if !self.to_recalculate_size.is_empty() { + if let Err(e) = recalculate_directories_size( + &mut self.to_recalculate_size, + &mut self.path_and_instant_buffer, + self.location_id, + self.library, + ) + .await + { + error!("Failed to recalculate directories size: {e:#?}"); + } + } + + self.last_events_eviction_check = Instant::now(); + } + } +} + +impl AndroidEventHandler<'_> { + async fn handle_to_update_eviction(&mut self) -> Result<(), LocationManagerError> { + self.path_and_instant_buffer.clear(); + let mut should_invalidate = false; + + for (path, created_at) in self.files_to_update.drain() { + if created_at.elapsed() < HUNDRED_MILLIS * 5 { + self.path_and_instant_buffer.push((path, created_at)); + } else { + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + self.reincident_to_update_files.remove(&path); + update_file(self.location_id, &path, self.node, self.library).await?; + should_invalidate = true; + } + } + + self.files_to_update + .extend(self.path_and_instant_buffer.drain(..)); + + self.path_and_instant_buffer.clear(); + + // We have to check if we have any reincident files to update and update them after a bigger + // timeout, this way we keep track of files being update frequently enough to bypass our + // eviction check above + for (path, created_at) in self.reincident_to_update_files.drain() { + if created_at.elapsed() < ONE_SECOND * 10 { + self.path_and_instant_buffer.push((path, created_at)); + } else { + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + self.files_to_update.remove(&path); + update_file(self.location_id, &path, self.node, self.library).await?; + should_invalidate = true; + } + } + + if should_invalidate { + invalidate_query!(self.library, "search.paths"); + } + + self.reincident_to_update_files + .extend(self.path_and_instant_buffer.drain(..)); + + Ok(()) + } + + async fn handle_rename_from_eviction(&mut self) -> Result<(), LocationManagerError> { + self.path_and_instant_buffer.clear(); + let mut should_invalidate = false; + + for (path, instant) in self.rename_from.drain() { + if instant.elapsed() > HUNDRED_MILLIS { + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + remove(self.location_id, &path, self.library).await?; + should_invalidate = true; + trace!("Removed file_path due timeout: {}", path.display()); + } else { + self.path_and_instant_buffer.push((path, instant)); + } + } + + if should_invalidate { + invalidate_query!(self.library, "search.paths"); + } + + for (path, instant) in self.path_and_instant_buffer.drain(..) { + self.rename_from.insert(path, instant); + } + + Ok(()) + } +} diff --git a/core/src/location/manager/watcher/ios.rs b/core/src/location/manager/watcher/ios.rs new file mode 100644 index 000000000..a29925b96 --- /dev/null +++ b/core/src/location/manager/watcher/ios.rs @@ -0,0 +1,394 @@ +//! iOS file system watcher implementation. + +use crate::{invalidate_query, library::Library, location::manager::LocationManagerError, Node}; + +use sd_file_path_helper::{check_file_path_exists, get_inode, FilePathError, IsolatedFilePathData}; +use sd_prisma::prisma::location; +use sd_utils::error::FileIOError; + +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, +}; + +use async_trait::async_trait; +use notify::{ + event::{CreateKind, DataChange, MetadataKind, ModifyKind, RenameMode}, + Event, EventKind, +}; +use tokio::{fs, io, time::Instant}; +use tracing::{debug, error, trace, warn}; + +use super::{ + utils::{ + create_dir, create_file, extract_inode_from_path, extract_location_path, + recalculate_directories_size, remove, rename, update_file, + }, + EventHandler, INode, InstantAndPath, HUNDRED_MILLIS, ONE_SECOND, +}; + +#[derive(Debug)] +pub(super) struct IosEventHandler<'lib> { + location_id: location::id::Type, + library: &'lib Arc, + node: &'lib Arc, + files_to_update: HashMap, + reincident_to_update_files: HashMap, + last_events_eviction_check: Instant, + latest_created_dir: Option, + old_paths_map: HashMap, + new_paths_map: HashMap, + paths_map_buffer: Vec<(INode, InstantAndPath)>, + to_recalculate_size: HashMap, + path_and_instant_buffer: Vec<(PathBuf, Instant)>, +} + +#[async_trait] +impl<'lib> EventHandler<'lib> for IosEventHandler<'lib> { + fn new( + location_id: location::id::Type, + library: &'lib Arc, + node: &'lib Arc, + ) -> Self + where + Self: Sized, + { + Self { + location_id, + library, + node, + files_to_update: HashMap::new(), + reincident_to_update_files: HashMap::new(), + last_events_eviction_check: Instant::now(), + latest_created_dir: None, + old_paths_map: HashMap::new(), + new_paths_map: HashMap::new(), + paths_map_buffer: Vec::new(), + to_recalculate_size: HashMap::new(), + path_and_instant_buffer: Vec::new(), + } + } + + async fn handle_event(&mut self, event: Event) -> Result<(), LocationManagerError> { + let Event { + kind, mut paths, .. + } = event; + + match kind { + EventKind::Create(CreateKind::Folder) => { + let path = &paths[0]; + + create_dir( + self.location_id, + path, + &fs::metadata(path) + .await + .map_err(|e| FileIOError::from((path, e)))?, + self.node, + self.library, + ) + .await?; + self.latest_created_dir = Some(paths.remove(0)); + } + + EventKind::Create(CreateKind::File) + | EventKind::Modify(ModifyKind::Data(DataChange::Content)) + | EventKind::Modify(ModifyKind::Metadata( + MetadataKind::WriteTime | MetadataKind::Extended, + )) => { + // When we receive a create, modify data or metadata events of the abore kinds + // we just mark the file to be updated in a near future + // each consecutive event of these kinds that we receive for the same file + // we just store the path again in the map below, with a new instant + // that effectively resets the timer for the file to be updated <- Copied from macos.rs + let path = paths.remove(0); + if self.files_to_update.contains_key(&path) { + if let Some(old_instant) = + self.files_to_update.insert(path.clone(), Instant::now()) + { + self.reincident_to_update_files + .entry(path) + .or_insert(old_instant); + } + } else { + self.files_to_update.insert(path, Instant::now()); + } + } + EventKind::Modify(ModifyKind::Name(RenameMode::Any)) => { + self.handle_single_rename_event(paths.remove(0)).await?; + } + + // For some reason, iOS doesn't have a Delete Event, so the vent type comes up as this. + // Delete Event + EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) => { + debug!("File has been deleted: {:#?}", paths); + let path = paths.remove(0); + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + remove(self.location_id, &path, self.library).await?; //FIXME: Find out why this freezes the watcher + } + other_event_kind => { + trace!("Other iOS event that we don't handle for now: {other_event_kind:#?}"); + } + } + + Ok(()) + } + + async fn tick(&mut self) { + if self.last_events_eviction_check.elapsed() > HUNDRED_MILLIS { + if let Err(e) = self.handle_to_update_eviction().await { + error!("Error while handling recently created or update files eviction: {e:#?}"); + } + + // Cleaning out recently renamed files that are older than 100 milliseconds + if let Err(e) = self.handle_rename_create_eviction().await { + error!("Failed to create file_path on iOS : {e:#?}"); + } + + if let Err(e) = self.handle_rename_remove_eviction().await { + error!("Failed to remove file_path: {e:#?}"); + } + + if !self.to_recalculate_size.is_empty() { + if let Err(e) = recalculate_directories_size( + &mut self.to_recalculate_size, + &mut self.path_and_instant_buffer, + self.location_id, + self.library, + ) + .await + { + error!("Failed to recalculate directories size: {e:#?}"); + } + } + + self.last_events_eviction_check = Instant::now(); + } + } +} + +impl IosEventHandler<'_> { + async fn handle_to_update_eviction(&mut self) -> Result<(), LocationManagerError> { + self.path_and_instant_buffer.clear(); + let mut should_invalidate = false; + + for (path, created_at) in self.files_to_update.drain() { + if created_at.elapsed() < HUNDRED_MILLIS * 5 { + self.path_and_instant_buffer.push((path, created_at)); + } else { + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + self.reincident_to_update_files.remove(&path); + update_file(self.location_id, &path, self.node, self.library).await?; + should_invalidate = true; + } + } + + self.files_to_update + .extend(self.path_and_instant_buffer.drain(..)); + + self.path_and_instant_buffer.clear(); + + // We have to check if we have any reincident files to update and update them after a bigger + // timeout, this way we keep track of files being update frequently enough to bypass our + // eviction check above + for (path, created_at) in self.reincident_to_update_files.drain() { + if created_at.elapsed() < ONE_SECOND * 10 { + self.path_and_instant_buffer.push((path, created_at)); + } else { + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + self.files_to_update.remove(&path); + update_file(self.location_id, &path, self.node, self.library).await?; + should_invalidate = true; + } + } + + if should_invalidate { + invalidate_query!(self.library, "search.paths"); + } + + self.reincident_to_update_files + .extend(self.path_and_instant_buffer.drain(..)); + + Ok(()) + } + + async fn handle_rename_create_eviction(&mut self) -> Result<(), LocationManagerError> { + // Just to make sure that our buffer is clean + self.paths_map_buffer.clear(); + let mut should_invalidate = false; + + for (inode, (instant, path)) in self.new_paths_map.drain() { + if instant.elapsed() > HUNDRED_MILLIS { + if !self.files_to_update.contains_key(&path) { + let metadata = fs::metadata(&path) + .await + .map_err(|e| FileIOError::from((&path, e)))?; + + if metadata.is_dir() { + // Don't need to dispatch a recalculate directory event as `create_dir` dispatches + // a `scan_location_sub_path` function, which recalculates the size already + create_dir(self.location_id, &path, &metadata, self.node, self.library) + .await?; + } else { + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + create_file(self.location_id, &path, &metadata, self.node, self.library) + .await?; + } + + trace!("Created file_path due timeout: {}", path.display()); + should_invalidate = true; + } + } else { + self.paths_map_buffer.push((inode, (instant, path))); + } + } + + if should_invalidate { + invalidate_query!(self.library, "search.paths"); + } + + self.new_paths_map.extend(self.paths_map_buffer.drain(..)); + + Ok(()) + } + + async fn handle_rename_remove_eviction(&mut self) -> Result<(), LocationManagerError> { + // Just to make sure that our buffer is clean + self.paths_map_buffer.clear(); + let mut should_invalidate = false; + + for (inode, (instant, path)) in self.old_paths_map.drain() { + if instant.elapsed() > HUNDRED_MILLIS { + if let Some(parent) = path.parent() { + if parent != Path::new("") { + self.to_recalculate_size + .insert(parent.to_path_buf(), Instant::now()); + } + } + remove(self.location_id, &path, self.library).await?; + trace!("Removed file_path due timeout: {}", path.display()); + should_invalidate = true; + } else { + self.paths_map_buffer.push((inode, (instant, path))); + } + } + + if should_invalidate { + invalidate_query!(self.library, "search.paths"); + } + + self.old_paths_map.extend(self.paths_map_buffer.drain(..)); + + Ok(()) + } + + async fn handle_single_rename_event( + &mut self, + path: PathBuf, // this is used internally only once, so we can use just PathBuf + ) -> Result<(), LocationManagerError> { + match fs::metadata(&path).await { + Ok(meta) => { + // File or directory exists, so this can be a "new path" to an actual rename/move or a creation + trace!("Path exists: {}", path.display()); + + let inode = get_inode(&meta); + let location_path = extract_location_path(self.location_id, self.library).await?; + + if !check_file_path_exists::( + &IsolatedFilePathData::new( + self.location_id, + &location_path, + &path, + meta.is_dir(), + )?, + &self.library.db, + ) + .await? + { + if let Some((_, old_path)) = self.old_paths_map.remove(&inode) { + trace!( + "Got a match new -> old: {} -> {}", + path.display(), + old_path.display() + ); + + // We found a new path for this old path, so we can rename it + rename(self.location_id, &path, &old_path, meta, self.library).await?; + } else { + trace!("No match for new path yet: {}", path.display()); + self.new_paths_map.insert(inode, (Instant::now(), path)); + } + } else { + warn!( + "Received rename event for a file that already exists in the database: {}", + path.display() + ); + } + } + Err(e) if e.kind() == io::ErrorKind::NotFound => { + // File or directory does not exist in the filesystem, if it exists in the database, + // then we try pairing it with the old path from our map + + trace!("Path doesn't exists: {}", path.display()); + + let inode = + match extract_inode_from_path(self.location_id, &path, self.library).await { + Ok(inode) => inode, + Err(LocationManagerError::FilePath(FilePathError::NotFound(_))) => { + // temporary file, we can ignore it + return Ok(()); + } + Err(e) => return Err(e), + }; + + if let Some((_, new_path)) = self.new_paths_map.remove(&inode) { + trace!( + "Got a match old -> new: {} -> {}", + path.display(), + new_path.display() + ); + + // We found a new path for this old path, so we can rename it + rename( + self.location_id, + &new_path, + &path, + fs::metadata(&new_path) + .await + .map_err(|e| FileIOError::from((&new_path, e)))?, + self.library, + ) + .await?; + } else { + trace!("No match for old path yet: {}", path.display()); + // We didn't find a new path for this old path, so we store ir for later + self.old_paths_map.insert(inode, (Instant::now(), path)); + } + } + Err(e) => return Err(FileIOError::from((path, e)).into()), + } + + Ok(()) + } +} diff --git a/core/src/location/manager/watcher/mod.rs b/core/src/location/manager/watcher/mod.rs index 59810f216..d6d70b77f 100644 --- a/core/src/location/manager/watcher/mod.rs +++ b/core/src/location/manager/watcher/mod.rs @@ -24,6 +24,8 @@ use uuid::Uuid; use super::LocationManagerError; +mod android; +mod ios; mod linux; mod macos; mod windows; @@ -41,6 +43,12 @@ type Handler<'lib> = macos::MacOsEventHandler<'lib>; #[cfg(target_os = "windows")] type Handler<'lib> = windows::WindowsEventHandler<'lib>; +#[cfg(target_os = "android")] +type Handler<'lib> = android::AndroidEventHandler<'lib>; + +#[cfg(target_os = "ios")] +type Handler<'lib> = ios::IosEventHandler<'lib>; + pub(super) type IgnorePath = (PathBuf, bool); type INode = u64; @@ -142,12 +150,12 @@ impl LocationWatcher { let mut handler_interval = interval_at(Instant::now() + HUNDRED_MILLIS, HUNDRED_MILLIS); // In case of doubt check: https://docs.rs/tokio/latest/tokio/time/enum.MissedTickBehavior.html handler_interval.set_missed_tick_behavior(MissedTickBehavior::Delay); - loop { select! { Some(event) = events_rx.recv() => { match event { Ok(event) => { + debug!("[Debug - handle_watch_events] Received event: {:#?}", event); if let Err(e) = Self::handle_single_event( location_id, location_pub_id, @@ -197,6 +205,7 @@ impl LocationWatcher { _library: &'lib Library, ignore_paths: &HashSet, ) -> Result<(), LocationManagerError> { + debug!("Event: {:#?}", event); if !check_event(&event, ignore_paths) { return Ok(()); } @@ -215,6 +224,8 @@ impl LocationWatcher { return Ok(()); } + // debug!("Handling event: {:#?}", event); + event_handler.handle_event(event).await } @@ -232,6 +243,7 @@ impl LocationWatcher { pub(super) fn watch(&mut self) { let path = &self.path; + debug!("Start watching location: (path: {path})"); if let Err(e) = self .watcher @@ -368,7 +380,7 @@ mod tests { use tracing::{debug, error}; // use tracing_test::traced_test; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] use notify::event::DataChange; #[cfg(target_os = "linux")] @@ -447,7 +459,7 @@ mod tests { #[cfg(target_os = "windows")] expect_event(events_rx, &file_path, EventKind::Modify(ModifyKind::Any)).await; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] expect_event( events_rx, &file_path, @@ -487,7 +499,7 @@ mod tests { #[cfg(target_os = "windows")] expect_event(events_rx, &dir_path, EventKind::Create(CreateKind::Any)).await; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] expect_event(events_rx, &dir_path, EventKind::Create(CreateKind::Folder)).await; #[cfg(target_os = "linux")] @@ -528,7 +540,7 @@ mod tests { #[cfg(target_os = "windows")] expect_event(events_rx, &file_path, EventKind::Modify(ModifyKind::Any)).await; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] expect_event( events_rx, &file_path, @@ -577,7 +589,7 @@ mod tests { ) .await; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] expect_event( events_rx, &file_path, @@ -628,7 +640,7 @@ mod tests { ) .await; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] expect_event( events_rx, &dir_path, @@ -676,6 +688,14 @@ mod tests { #[cfg(target_os = "linux")] expect_event(events_rx, &file_path, EventKind::Remove(RemoveKind::File)).await; + #[cfg(target_os = "ios")] + expect_event( + events_rx, + &file_path, + EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)), + ) + .await; + debug!("Unwatching root directory: {}", root_dir.path().display()); if let Err(e) = watcher.unwatch(root_dir.path()) { error!("Failed to unwatch root directory: {e:#?}"); @@ -723,6 +743,14 @@ mod tests { #[cfg(target_os = "linux")] expect_event(events_rx, &dir_path, EventKind::Remove(RemoveKind::Folder)).await; + #[cfg(target_os = "ios")] + expect_event( + events_rx, + &file_path, + EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)), + ) + .await; + debug!("Unwatching root directory: {}", root_dir.path().display()); if let Err(e) = watcher.unwatch(root_dir.path()) { error!("Failed to unwatch root directory: {e:#?}"); diff --git a/core/src/location/mod.rs b/core/src/location/mod.rs index 92246cf4f..9defd643f 100644 --- a/core/src/location/mod.rs +++ b/core/src/location/mod.rs @@ -21,7 +21,6 @@ use sd_utils::{ uuid_to_bytes, }; -#[cfg(feature = "location-watcher")] use sd_file_path_helper::IsolatedFilePathDataParts; use std::{ @@ -1037,7 +1036,6 @@ pub async fn get_location_path_from_location_id( }) } -#[cfg(feature = "location-watcher")] pub async fn create_file_path( crate::location::Library { db, sync, .. }: &crate::location::Library, IsolatedFilePathDataParts { diff --git a/core/src/object/media/mod.rs b/core/src/object/media/mod.rs index 09833ce1c..bfce2b4d0 100644 --- a/core/src/object/media/mod.rs +++ b/core/src/object/media/mod.rs @@ -28,7 +28,6 @@ pub fn media_data_image_to_query( }) } -#[cfg(feature = "location-watcher")] pub fn media_data_image_to_query_params( mdi: ImageMetadata, ) -> (Vec<(&'static str, serde_json::Value)>, Vec) {