├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Package.swift
├── README.md
├── TOFileSystemObserver.podspec
├── TOFileSystemObserver
├── Categories
│ ├── NSFileManager+TOFileSystemDirectoryEnumerator.h
│ ├── NSFileManager+TOFileSystemDirectoryEnumerator.m
│ ├── NSIndexPath+AppKitAdditions.h
│ ├── NSIndexPath+AppKitAdditions.m
│ ├── NSURL+TOFileSystemAttributes.h
│ ├── NSURL+TOFileSystemAttributes.m
│ ├── NSURL+TOFileSystemUUID.h
│ └── NSURL+TOFileSystemUUID.m
├── Entities
│ ├── Changes
│ │ ├── TOFileSystemChanges+Private.h
│ │ ├── TOFileSystemChanges.h
│ │ ├── TOFileSystemChanges.m
│ │ ├── TOFileSystemItemListChanges+Private.h
│ │ ├── TOFileSystemItemListChanges.h
│ │ └── TOFileSystemItemListChanges.m
│ ├── Collections
│ │ ├── TOFileSystemItemMapTable.h
│ │ ├── TOFileSystemItemMapTable.m
│ │ ├── TOFileSystemItemURLDictionary.h
│ │ └── TOFileSystemItemURLDictionary.m
│ ├── FilePaths
│ │ ├── TOFileSystemPath.h
│ │ └── TOFileSystemPath.m
│ ├── Items
│ │ ├── TOFileSystemItem+Private.h
│ │ ├── TOFileSystemItem.h
│ │ ├── TOFileSystemItem.m
│ │ ├── TOFileSystemItemList+Private.h
│ │ ├── TOFileSystemItemList.h
│ │ └── TOFileSystemItemList.m
│ └── Notifications
│ │ ├── TOFileSystemNotificationToken+Private.h
│ │ ├── TOFileSystemNotificationToken.h
│ │ └── TOFileSystemNotificationToken.m
├── Info.plist
├── Scanning
│ ├── TOFileSystemPresenter.h
│ ├── TOFileSystemPresenter.m
│ ├── TOFileSystemScanOperation.h
│ └── TOFileSystemScanOperation.m
├── TOFileSystemObserver.h
├── TOFileSystemObserver.m
├── Utilities
│ ├── TOFileSystemObserver+AppKit.h
│ ├── TOFileSystemObserver+UIKit.h
│ └── TOFileSystemObserverConstants.h
└── include
│ ├── NSFileManager+TOFileSystemDirectoryEnumerator.h
│ ├── NSIndexPath+AppKitAdditions.h
│ ├── NSURL+TOFileSystemAttributes.h
│ ├── NSURL+TOFileSystemUUID.h
│ ├── TOFileSystemChanges+Private.h
│ ├── TOFileSystemChanges.h
│ ├── TOFileSystemItem+Private.h
│ ├── TOFileSystemItem.h
│ ├── TOFileSystemItemList+Private.h
│ ├── TOFileSystemItemList.h
│ ├── TOFileSystemItemListChanges+Private.h
│ ├── TOFileSystemItemListChanges.h
│ ├── TOFileSystemItemMapTable.h
│ ├── TOFileSystemItemURLDictionary.h
│ ├── TOFileSystemNotificationToken+Private.h
│ ├── TOFileSystemNotificationToken.h
│ ├── TOFileSystemObserver+AppKit.h
│ ├── TOFileSystemObserver+UIKit.h
│ ├── TOFileSystemObserver.h
│ ├── TOFileSystemObserverConstants.h
│ ├── TOFileSystemPath.h
│ ├── TOFileSystemPresenter.h
│ └── TOFileSystemScanOperation.h
├── TOFileSystemObserverExample.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── TOFileSystemObserverExample.xcscheme
│ ├── TOFileSystemObserverMacExample.xcscheme
│ └── TOFileSystemObserverTests.xcscheme
├── TOFileSystemObserverExample
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
├── TOAppDelegate.h
├── TOAppDelegate.m
├── TOImages.h
├── TOImages.m
├── TOViewController.h
├── TOViewController.m
└── main.m
├── TOFileSystemObserverMacExample
├── ARAppDelegate.h
├── ARAppDelegate.m
├── ARImages.h
├── ARImages.m
├── ARViewController.h
├── ARViewController.m
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── Info.plist
├── TOFileSystemObserverMacExample.entitlements
└── main.m
├── TOFileSystemObserverTests
├── Categories
│ ├── TOFileSystemEnumeratorTests.m
│ ├── TOFileSystemFileAttributesTests.m
│ └── TOFileSystemUUIDTests.m
├── Entities
│ ├── TOFileSystemChangesTests.m
│ ├── TOFileSystemItemDictionaryTests.m
│ ├── TOFileSystemItemListChangesTests.m
│ ├── TOFileSystemItemMapTableTests.m
│ └── TOFileSystemItemURLDictionaryTests.m
└── Info.plist
└── screenshot.jpg
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: timoliver
2 | custom: https://tim.dev/paypal
3 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: macos-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Run all of the library unit tests.
13 | run: '(curl -s -L https://tim.dev/install_ios_oss_ci | bash -s arg1 arg2) && bundle exec fastlane test'
14 | env:
15 | TEST_SCHEME: "TOFileSystemObserverTests"
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | # CocoaPods
32 | #
33 | # We recommend against adding the Pods directory to your .gitignore. However
34 | # you should judge for yourself, the pros and cons are mentioned at:
35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
36 | #
37 | Pods/
38 |
39 | # Carthage
40 | #
41 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
42 | # Carthage/Checkouts
43 |
44 | Carthage/Build
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
52 |
53 | fastlane/report.xml
54 | fastlane/Preview.html
55 | fastlane/screenshots/**/*.png
56 | fastlane/test_output
57 |
58 | # Code Injection
59 | #
60 | # After new code Injection tools there's a generated folder /iOSInjectionProject
61 | # https://github.com/johnno1962/injectionforxcode
62 |
63 | iOSInjectionProject/
64 | .DS_Store
65 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | x.y.z Release Notes (yyyy-MM-dd)
2 | =============================================================
3 |
4 | 0.0.4 Release Notes (2022-01-23)
5 | =============================================================
6 |
7 | ### Enhancements
8 |
9 | * Replaced dispatch barriers in `TOFileSystemItem` with threading locks to minimize GCD queue creation.
10 | * General internal code/commenting cleanup and refinement.
11 |
12 | ### Fixed
13 |
14 | * An incorrect casting breaking the visible number of items inside of a directory.
15 | * A crash that can occur if querying for the number of files in a deleted folder.
16 | * A linking issue when importing the library via SPM on macOS.
17 |
18 | 0.0.3 Release Notes (2020-02-24)
19 | =============================================================
20 |
21 | ### Added
22 |
23 | * Public, thread-safe API access for accessing the UUID string of an observed item,
24 | and/or its parent directory.
25 |
26 | ### Fixed
27 |
28 | * A thread coordinating issue where file UUIDs might not have
29 | been set yet upon first access.
30 | * An issue where proper UUIDs were not being generated before
31 | adding items as children to list objects.
32 | * An issue where deleting an item in Files.app would make the
33 | `.Trash` folder become treated like an official item.
34 |
35 | 0.0.2 Release Notes (2020-02-11)
36 | =============================================================
37 |
38 | ### Enhancements
39 |
40 | * Exposed full system scans as a property on `TOFileSystemChanges` in order to let
41 | observing objects defer work until the scan is complete.
42 |
43 | ### Fixed
44 |
45 | * A bug where files moved below the sub-directory level limit weren't treated as deleted.
46 | * A bug where specifying a sub-directory limit would result in an infinite loop.
47 | * A bug where renaming/moving a file wouldn't be properly updated in the main graph.
48 | * A bug where items marked as 'skipped' weren't being handled as such.
49 | * A bug where files created during the initial scan would be permanently left in the 'copying' state.
50 | * A bug where files of the incorrect sub-directory level limit were still being accessed.
51 |
52 | 0.0.1 Release Notes (2020-01-30)
53 | =============================================================
54 |
55 | * Initial Release! 🎉
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2020 Tim Oliver
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "TOFileSystemObserver",
7 | platforms: [
8 | .macOS(.v10_12),
9 | .iOS(.v8)
10 | ],
11 | products: [
12 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
13 | .library(
14 | name: "TOFileSystemObserver",
15 | type: .dynamic,
16 | targets: ["TOFileSystemObserver"]),
17 | ],
18 | dependencies: [ ],
19 | targets: [
20 | .target(
21 | name: "TOFileSystemObserver",
22 | dependencies: [],
23 | path: "TOFileSystemObserver",
24 | cSettings: [.define("TARGET_OS_OSX", .when(platforms: [.macOS]))]
25 | ),
26 | ]
27 | )
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TOFileSystemObserver
2 |
3 |
4 |
5 | [](https://github.com/TimOliver/TOFileSystemObserver/actions?query=workflow%3ACI)
6 | [](http://cocoadocs.org/docsets/TOFileSystemObserver)
7 | [](http://cocoadocs.org/docsets/TOFileSystemObserver)
8 | [](https://raw.githubusercontent.com/TimOliver/TOFileSystemObserver/master/LICENSE)
9 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M4RKULAVKV7K8)
10 | [](http://twitch.tv/timXD)
11 |
12 | `TOFileSystemObserver` is a bullet-proof mechanism (hopefully) for detecting any user-initiated changes made to the contents of an iOS / iPadOS app's sandbox while the app is open.
13 |
14 | Since iOS 11, the Files app has given the option to allow apps to expose the contents of their Documents directories to the users, letting them manipulate the files, either while the app is closed, suspended, or even running side-by-side with iPad multitasking.
15 |
16 | For document-based apps that display a list of available files from the Documents library, this library aims to let you detect and respond to all file events that your app will need to adjust its UI and caches for.
17 |
18 | # Features
19 | * A single class that runs for the duration of the app session, and processes file events from the system.
20 | * Can detect file changes on *any* subdirectory level.
21 | * Can detect when the user imports a new file, and is able to detect when large files have finished copying.
22 | * Can detect when the user moves a file, including between directory levels.
23 | * Can detect when a user deletes a file, either from the Files app, or directly via iTunes.
24 | * Can detect when a user renames a file.
25 | * Can detect when a user duplicates the same file via the Files app.
26 | * Provides "live" objects representing the contents of directories, and files which will update in conjunction with the file system.
27 |
28 | # Example
29 |
30 | ```objc
31 | #import "TOFileSystemObserver.h"
32 |
33 | // Create a new instance (Targets the Documents directory by default)
34 | TOFileSystemObserver *observer = [[TOFileSystemObserver alloc] init];
35 |
36 | // Start observing the target directory.
37 | [observer start];
38 |
39 | // Register a notification token to receive events from the observer
40 | TOFileSystemNotificationToken *observerToken = [self.observer addNotificationBlock:
41 | ^(TOFileSystemObserver *observer,
42 | TOFileSystemObserverNotificationType type,
43 | TOFileSystemChanges *changes)
44 | {
45 | // At the start of the session, the observer will perform a full system scan.
46 | // This event will give observers a chance to set up before the scan.
47 | if (type == TOFileSystemObserverNotificationTypeWillBeginFullScan) {
48 | NSLog(@"Scan Will Start!");
49 | return;
50 | }
51 |
52 | // At the start of the session, the observer will perform a full system scan.
53 | // This event will give observers a chance to clean up after the scan.
54 | if (type == TOFileSystemObserverNotificationTypeDidCompleteFullScan) {
55 | NSLog(@"Scan Complete!");
56 | return;
57 | }
58 |
59 | NSLog(@"%@", changes);
60 | }];
61 | ```
62 |
63 | Please check the sample app for more examples on the features of this library.
64 |
65 | # Requirements
66 |
67 | `TOFileSystemObserver` will work with iOS 8.0 and above. While it's been written in Objective-C, it will also work with Swift (But the Swift interface may need some more work.)
68 |
69 | ## Manual Installation
70 |
71 | Copy the contents of the `TOFileSystemObserver` folder to your app project.
72 |
73 | ## CocoaPods
74 |
75 | ```
76 | pod 'TOFileSystemObserver'
77 | ```
78 |
79 | ## Carthage and SPM
80 |
81 | I only need CocoaPods for my current plans with this library, so Carthage and SPM are low priority for now. If you would like Carthage or SPM support, please submit a PR.
82 |
83 | # How Does it Work?
84 |
85 | Observing files on the file system consists of a variety of problems that each need to be solved to work.
86 |
87 | ### Receiving System Events for File Changes
88 |
89 | Historically, [Apple staff have recommended](https://forums.developer.apple.com/thread/90531) using `DispatchSource` for detecting file changes. However, since this doesn't support subdirectories, it wasn't suitable here. Instead, `TOFileSystemObserver` uses [`NSFilePresenter`](https://developer.apple.com/documentation/foundation/nsfilepresenter) a component of coordinated file access to detect when a file has changed.
90 |
91 | ### Tracking Files Uniquely on Disk
92 |
93 | Since it's very easy for file names to change, and there's no guarantee they'll be unique (eg, multiple `Chapter1.zip` files in different folders), it was necessary to assign each file an ID that the user cannot easily modify and would be unique.
94 |
95 | To that end, `TOFileSystemObserver` uses the [Extended File Attributes](https://nshipster.com/extended-file-attributes/) feature of APFS to attach a unique UUID string to each file it tracks. The observer then keeps an in-memory graph of every file's UUID and the URL of their last location, in order to determine when a file is moved or renamed.
96 |
97 | ### Determining When a File is Copying
98 |
99 | `NSFilePresenter` will trigger 2 times for a file being copied in: once at the start, and again at the end. Since most file imports need to happen only when the file has finished copying, a way to check that the file has finished copying was necessary. I sadly lost the original Stack Overflow post, but an extremely bright person discovered that when a file is still copying, its reported modification date will be equal to the current date. In this way, we can check if the file is still copying or not.
100 |
101 | # Credits
102 |
103 | `TOFileSystemObserver` was created by [Tim Oliver](http://twitter.com/TimOliverAU) as a component of [iComics](http://icomics.co).
104 |
105 | A huge thank you to [Jeffrey Bergier](https://twitter.com/jeffburg) whose [`JSBFileSystem`](https://github.com/jeffreybergier/JSBFilesystem) served as the base inspiration for this library, and for his help in letting me bounce ideas off him (such as eschewing having an on-disk store) during the start of this project.
106 |
107 | iOS device mockup art by [Pixeden](http://pixeden.com).
108 |
109 | # License
110 |
111 | `TOFileSystemObserver` is available under the MIT license. Please see the [LICENSE](LICENSE) file for more information. 
112 |
--------------------------------------------------------------------------------
/TOFileSystemObserver.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'TOFileSystemObserver'
3 | s.version = '0.0.4'
4 | s.license = { :type => 'MIT', :file => 'LICENSE' }
5 | s.summary = 'A bullet-proof mechanism for detecting any changes made to the contents of a folder in iOS & iPadOS.'
6 | s.homepage = 'https://github.com/TimOliver/TOFileSystemObserver'
7 | s.author = 'Tim Oliver'
8 | s.source = { :git => 'https://github.com/TimOliver/TOFileSystemObserver.git', :tag => s.version }
9 | s.platforms = { :ios => "8.0", :osx => "10.12" }
10 | s.source_files = 'TOFileSystemObserver/**/*.{h,m}'
11 | s.exclude_files = 'TOFileSystemObserver/include/**'
12 | s.osx.exclude_files = 'TOFileSystemObserver/Utilities/TOFileSystemObserver+UIKit.h'
13 | s.ios.exclude_files = ['TOFileSystemObserver/Utilities/TOFileSystemObserver+AppKit.h','TOFileSystemObserver/Categories/NSIndexPath+AppKitAdditions.{h,m}']
14 | s.requires_arc = true
15 | end
16 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSFileManager+TOFileSystemDirectoryEnumerator.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSFileManager+TOFileSystemDirectoryEnumerator.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | A convenience category that creates and pre-configures
29 | directory enumerators with the parameters we need.
30 | */
31 | @interface NSFileManager (TOFileSystemDirectoryEnumerator)
32 |
33 | /** Create a new directory enumerator with the attributes we're interested configured. */
34 | - (NSDirectoryEnumerator *)to_fileSystemEnumeratorForDirectoryAtURL:(NSURL *)url;
35 |
36 | @end
37 |
38 | NS_ASSUME_NONNULL_END
39 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSFileManager+TOFileSystemDirectoryEnumerator.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSFileManager+TOFileSystemDirectoryEnumerator.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "NSFileManager+TOFileSystemDirectoryEnumerator.h"
24 |
25 | @implementation NSFileManager (TOFileSystemDirectoryEnumerator)
26 |
27 | - (NSDirectoryEnumerator *)to_fileSystemEnumeratorForDirectoryAtURL:(NSURL *)url
28 | {
29 | // Set the keys for the properties we wish to capture
30 | NSArray *keys = @[NSURLIsDirectoryKey,
31 | NSURLFileSizeKey,
32 | NSURLCreationDateKey,
33 | NSURLContentModificationDateKey];
34 |
35 | // Set the flags for the enumerator
36 | NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsHiddenFiles |
37 | NSDirectoryEnumerationSkipsSubdirectoryDescendants;
38 |
39 | // Create the enumerator
40 | NSDirectoryEnumerator *urlEnumerator = [self enumeratorAtURL:url
41 | includingPropertiesForKeys:keys
42 | options:options
43 | errorHandler:nil];
44 | return urlEnumerator;
45 | }
46 |
47 | @end
48 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSIndexPath+AppKitAdditions.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSIndexPath+AppKitAdditions.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver, Anatoly Rosencrantz. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #if TARGET_OS_OSX
24 |
25 | #import
26 | #import
27 |
28 | @interface NSIndexPath(UIKitAdditions)
29 |
30 | - (NSInteger)row;
31 | + (NSIndexPath*)indexPathForRow:(NSInteger)row inSection:(NSInteger)section;
32 |
33 | @end
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSIndexPath+AppKitAdditions.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSIndexPath+AppKitAdditions.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver, Anatoly Rosencrantz. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #if TARGET_OS_OSX
24 |
25 | #import "NSIndexPath+AppKitAdditions.h"
26 |
27 | @implementation NSIndexPath(UIKitAdditions)
28 |
29 | - (NSInteger)row { return self.item; }
30 |
31 | + (NSIndexPath *)indexPathForRow:(NSInteger)row inSection:(NSInteger)section {
32 | return [self indexPathForItem:row inSection:section];
33 | }
34 |
35 | @end
36 |
37 | #endif
38 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSURL+TOFileSystemAttributes.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | A convenience wrapper for fetching specific attributes
29 | about the item on disk that this URL represents
30 | */
31 | @interface NSURL (TOFileSystemAttributes)
32 |
33 | /** Whether the file is currently being copied or not. */
34 | @property (nonatomic, readonly) BOOL to_isCopying;
35 |
36 | /** Whether the item is a directory or file. */
37 | @property (nonatomic, readonly) BOOL to_isDirectory;
38 |
39 | /** The file size of the item (0 for directories) */
40 | @property (nonatomic, readonly) long long to_size;
41 |
42 | /** The creation date of the item. */
43 | @property (nonatomic, readonly) NSDate *to_creationDate;
44 |
45 | /** The modification date of the item. */
46 | @property (nonatomic, readonly) NSDate *to_modificationDate;
47 |
48 | /** The number of sub-items in this directory. */
49 | @property (nonatomic, readonly) NSInteger to_numberOfSubItems;
50 |
51 | @end
52 |
53 | NS_ASSUME_NONNULL_END
54 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSURL+TOFileSystemAttributes.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "NSURL+TOFileSystemAttributes.h"
24 | #import "TOFileSystemObserverConstants.h"
25 | #include
26 |
27 | @implementation NSURL (TOFileSystemAttributes)
28 |
29 | - (BOOL)to_isCopying
30 | {
31 | // When files are still being copied, their
32 | // modification date is equal to the current device time.
33 | return [self.to_modificationDate timeIntervalSinceDate:[NSDate date]]
34 | > (-kTOFileSystemObserverCopyingTimeDelay - FLT_EPSILON);
35 | }
36 |
37 | - (BOOL)to_isDirectory
38 | {
39 | NSNumber *isDirectory;
40 | [self getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
41 | return isDirectory.boolValue;
42 | }
43 |
44 | - (long long)to_size
45 | {
46 | NSNumber *fileSize;
47 | [self getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil];
48 | return fileSize.longLongValue;
49 | }
50 |
51 | - (NSDate *)to_creationDate
52 | {
53 | NSDate *creationDate;
54 | [self getResourceValue:&creationDate forKey:NSURLCreationDateKey error:nil];
55 | return creationDate;
56 | }
57 |
58 | - (NSDate *)to_modificationDate
59 | {
60 | [self removeCachedResourceValueForKey:NSURLContentModificationDateKey];
61 | NSDate *modificationDate;
62 | [self getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:nil];
63 | return modificationDate;
64 | }
65 |
66 | - (NSInteger)to_numberOfSubItems
67 | {
68 | NSInteger numberOfItems = 0;
69 | DIR *directory;
70 | struct dirent *entry;
71 |
72 | // Do it using POSIX APIs to avoid needing to load in all of the file names
73 | const char *path = [self.path cStringUsingEncoding:NSUTF8StringEncoding];
74 | directory = opendir(path);
75 | if (directory == NULL) { return 0; }
76 | while ((entry = readdir(directory)) != NULL) {
77 | if (entry->d_name[0] == '.') { continue; }
78 | if (entry->d_type == DT_REG || entry->d_type == DT_DIR) {
79 | numberOfItems++;
80 | }
81 | }
82 | closedir(directory);
83 |
84 | return numberOfItems;
85 | }
86 |
87 | @end
88 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSURL+TOFileSystemUUID.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | A convenience category that wraps the ability
29 | to assign a specific extended attribute
30 | to the file, so we can uniquely track it.
31 |
32 | Thanks to NSHipster for highlighting this technique.
33 | https://nshipster.com/extended-file-attributes/
34 | */
35 | @interface NSURL (TOFileSystemUUID)
36 |
37 | /** Changes the prefix of the key name under which the UUID is saved.*/
38 | + (void)to_setKeyNamePrefix:(NSString *)prefix;
39 |
40 | /** Returns the unique UUID value assigned to this file. */
41 | - (nullable NSString *)to_fileSystemUUID;
42 |
43 | /** Sets a predetermined UUID to be the value of the file. */
44 | - (void)to_setFileSystemUUID:(NSString *)uuid;
45 |
46 | /** Regardless if one exists, generate and save a new UUID. */
47 | - (NSString *)to_generateFileSystemUUID;
48 |
49 | @end
50 |
51 | NS_ASSUME_NONNULL_END
52 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSURL+TOFileSystemUUID.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "NSURL+TOFileSystemUUID.h"
24 | #import
25 |
26 | static NSString *kTOFileSystemAttributeKey = @"dev.tim.fileSystemObserver.UUID";
27 |
28 | @implementation NSURL (TOFileSystemUUID)
29 |
30 | + (void)to_setKeyNamePrefix:(NSString *)prefix
31 | {
32 | kTOFileSystemAttributeKey = [NSString stringWithFormat:@"%@.fileSystemObserver.UUID", prefix];
33 | }
34 |
35 | - (NSString *)to_fileSystemUUID
36 | {
37 | const char *filePath = [self.path fileSystemRepresentation];
38 | const char *keyName = kTOFileSystemAttributeKey.UTF8String;
39 |
40 | // Allocate a buffer for the value (UUID values are always 36 characters)
41 | char value[36];
42 |
43 | // Fetch the value from disk
44 | getxattr(filePath, keyName, value, 36, 0, 0);
45 |
46 | // Convert to a string, and return if successful
47 | NSString *uuid = [[NSString alloc] initWithBytes:value length:36 encoding:NSUTF8StringEncoding];
48 | if (uuid.length == 0) {
49 | return nil;
50 | }
51 |
52 | // Verify to make sure the provided value is a valid UUID string
53 | NSString *uuidPattern = @"\\A[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}\\Z";
54 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:uuidPattern
55 | options:NSRegularExpressionCaseInsensitive
56 | error:nil];
57 | NSRange range = [regex rangeOfFirstMatchInString:uuid options:0 range:NSMakeRange(0, uuid.length)];
58 |
59 | // A valid regex was found.
60 | if (range.location == NSNotFound) {
61 | return nil;
62 | }
63 |
64 | return uuid;
65 | }
66 |
67 | - (void)to_setFileSystemUUID:(NSString *)uuid
68 | {
69 | if (uuid.length > 0 && uuid.length != 36) {
70 | @throw [NSException exceptionWithName:NSInternalInconsistencyException
71 | reason:@"UUID must be 36 characters long!"
72 | userInfo:nil];
73 | }
74 |
75 | // Determine the file path and destination key
76 | const char *filePath = [self.path fileSystemRepresentation];
77 | const char *keyName = kTOFileSystemAttributeKey.UTF8String;
78 |
79 | // Convert the string to a C byte string
80 | const char *uuidString = [uuid cStringUsingEncoding:NSUTF8StringEncoding];
81 |
82 | // Save it to this file
83 | setxattr(filePath, keyName, uuidString, strlen(uuidString), 0, 0);
84 | }
85 |
86 | - (NSString *)to_generateFileSystemUUID
87 | {
88 | NSString *uuid = [NSUUID UUID].UUIDString;
89 | [self to_setFileSystemUUID:uuid];
90 | return uuid;
91 | }
92 |
93 | @end
94 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Changes/TOFileSystemChanges+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemChanges.m
3 | //
4 | // Copyright 2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | #import "TOFileSystemChanges.h"
26 |
27 | NS_ASSUME_NONNULL_BEGIN
28 |
29 | @interface TOFileSystemChanges ()
30 |
31 | /** Create a new instance. */
32 | - (instancetype)initWithFileSystemObserver:(TOFileSystemObserver *)fileSystemObserver;
33 |
34 | /** Sets that these changes came from the initial full system scan */
35 | - (void)setIsFullScan;
36 |
37 | /** Add a new discovered item to the list. */
38 | - (void)addDiscoveredItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL;
39 |
40 | /** Add a new discovered item to the list. */
41 | - (void)addModifiedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL;
42 |
43 | /** Add a new discovered item to the list. */
44 | - (void)addDeletedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL;
45 |
46 | /** Add a new discovered item to the list. */
47 | - (void)addMovedItemWithUUID:(NSString *)uuid oldFileURL:(NSURL *)oldFileURL newFileURL:(NSURL *)newFileURL;
48 |
49 | @end
50 |
51 | NS_ASSUME_NONNULL_END
52 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Changes/TOFileSystemChanges.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemChanges.h
3 | //
4 | // Copyright 2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | @class TOFileSystemObserver;
26 |
27 | NS_ASSUME_NONNULL_BEGIN
28 |
29 | /**
30 | Whenever a file system observer object detects a change
31 | on disk, it will broadcast an event to any objects that
32 | have subscribed. The specific changes that were detected
33 | will be contained in an instance of this class.
34 | */
35 | NS_SWIFT_NAME(FileSystemChanges)
36 | @interface TOFileSystemChanges : NSObject
37 |
38 | /** The observer from where these changes were broadcasted. */
39 | @property (nonatomic, weak, readonly) TOFileSystemObserver *fileSystemObserver;
40 |
41 | /**
42 | Whether the observer is performing the full scan at the start of the session or
43 | an explict list of files mid-session. If this is true, you might prefer to
44 | wait to defer any heavy work to the notification that will be sent along when
45 | this particular scan is complete.
46 | */
47 | @property (nonatomic, assign, readonly) BOOL isFullScan;
48 |
49 | /**
50 | A dictionary of items that were discovered by the file system observer.
51 | These are either files that were already on disk and were just discovered for the
52 | first time in this session, or they are brand new files that were just added by the user.
53 |
54 | All files in your app will be discovered at least once during an app session. Use
55 | this method to compare the files against the current state of your cached information
56 | to see if it needs to be updated.
57 |
58 | The dictionary key is the unique UUID string assigned to the file on disk, and the value
59 | is the absolute file path URL to the file on disk.
60 | */
61 | @property (nonatomic, readonly, nullable) NSDictionary *discoveredItems;
62 |
63 | /**
64 | A dictionary of items that were noted to have had changed during this app session.
65 | Sorts of changes include the file-name changing, or if it was still being copied and
66 | had just completed.
67 |
68 | The dictionary key is the unique UUID string assigned to the file on disk, and the value
69 | is the absolute file path URL to the file on disk.
70 | */
71 | @property (nonatomic, readonly, nullable) NSDictionary *modifiedItems;
72 |
73 | /**
74 | A dictionary of items that were noted to have been deleted during this app session.
75 |
76 | The dictionary key is the unique UUID string assigned to the file on disk, and the value
77 | is the absolute file path URL to the file on disk.
78 | */
79 | @property (nonatomic, readonly, nullable) NSDictionary *deletedItems;
80 |
81 | /**
82 | A dictionary of items that were noted to have moved during this app session.
83 |
84 | The dictionary key is the unique UUID string assigned to the file on disk, and the value
85 | is a 2-element array where the first value is the previous URL and the second is the new URL.
86 | */
87 | @property (nonatomic, readonly, nullable) NSDictionary *movedItems;
88 |
89 | @end
90 |
91 | NS_ASSUME_NONNULL_END
92 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Changes/TOFileSystemChanges.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemChanges.m
3 | //
4 | // Copyright 2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemChanges.h"
24 |
25 | @interface TOFileSystemChanges ()
26 |
27 | @property (nonatomic, weak, readwrite) TOFileSystemObserver *fileSystemObserver;
28 | @property (nonatomic, strong, readwrite) NSMutableDictionary *discoveredItems;
29 | @property (nonatomic, strong, readwrite) NSMutableDictionary *modifiedItems;
30 | @property (nonatomic, strong, readwrite) NSMutableDictionary *deletedItems;
31 | @property (nonatomic, strong, readwrite) NSMutableDictionary *movedItems;
32 | @property (nonatomic, assign, readwrite) BOOL isFullScan;
33 | @end
34 |
35 | @implementation TOFileSystemChanges
36 |
37 | - (instancetype)initWithFileSystemObserver:(TOFileSystemObserver *)fileSystemObserver
38 | {
39 | if (self = [super init]) {
40 | _fileSystemObserver = fileSystemObserver;
41 | }
42 | return self;
43 | }
44 |
45 | - (void)addDiscoveredItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL
46 | {
47 | if (_discoveredItems == nil) {
48 | _discoveredItems = [NSMutableDictionary dictionary];
49 | }
50 | _discoveredItems[uuid] = fileURL;
51 | }
52 |
53 | - (void)addModifiedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL
54 | {
55 | if (_modifiedItems == nil) {
56 | _modifiedItems = [NSMutableDictionary dictionary];
57 | }
58 | _modifiedItems[uuid] = fileURL;
59 | }
60 |
61 | - (void)addDeletedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL
62 | {
63 | if (_deletedItems == nil) {
64 | _deletedItems = [NSMutableDictionary dictionary];
65 | }
66 | _deletedItems[uuid] = fileURL;
67 | }
68 |
69 | - (void)addMovedItemWithUUID:(NSString *)uuid
70 | oldFileURL:(NSURL *)oldFileURL
71 | newFileURL:(NSURL *)newFileURL
72 | {
73 | if (_movedItems == nil) {
74 | _movedItems = [NSMutableDictionary dictionary];
75 | }
76 | _movedItems[uuid] = @[oldFileURL, newFileURL];
77 | }
78 |
79 | - (void)setIsFullScan
80 | {
81 | self.isFullScan = YES;
82 | }
83 |
84 | - (NSString *)description
85 | {
86 | return [NSString stringWithFormat:@"Discovered items: %@\nModified items: %@\nDeleted Items: %@\nMoved items: %@\n",
87 | self.discoveredItems, self.modifiedItems, self.deletedItems, self.movedItems];
88 | }
89 |
90 | @end
91 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Changes/TOFileSystemItemListChanges+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemListChanges+Private.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "TOFileSystemItemListChanges.h"
25 |
26 | NS_ASSUME_NONNULL_BEGIN
27 |
28 | @interface TOFileSystemItemListChanges ()
29 |
30 | /** Add the index of an item to be deleted. */
31 | - (void)addDeletionIndex:(NSInteger)index;
32 |
33 | /** Add the index of an item to be inserted. */
34 | - (void)addInsertionIndex:(NSInteger)index;
35 |
36 | /** Add the index of an item to be modified. */
37 | - (void)addModificationIndex:(NSInteger)index;
38 |
39 | /** Add the source and dest index values for a row to be moved. */
40 | - (void)addMovementWithSourceIndex:(NSInteger)sourceIndex
41 | destinationIndex:(NSInteger)destinationIndex;
42 |
43 | @end
44 |
45 | NS_ASSUME_NONNULL_END
46 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Changes/TOFileSystemItemListChanges.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemListChanges.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | This class stores sets of indices denoting which objects,
29 | if any, in an item list were changed, and any subsequent user
30 | interfaces need to be refreshed.
31 |
32 | Whenever an item list detects a change has happened, it will
33 | trigger a notification block and provide an instance of this class.
34 | */
35 |
36 | NS_SWIFT_NAME(FileSystemListChanges)
37 | @interface TOFileSystemItemListChanges : NSObject
38 |
39 | /** State check to see if there are any pending movements. */
40 | @property (nonatomic, readonly) BOOL hasItemMovements;
41 |
42 | /** State check if it has cell updates that aren't movements. */
43 | @property (nonatomic, readonly) BOOL hasItemChanges;
44 |
45 | /** The indices of any objects that were deleted. */
46 | @property (nonatomic, readonly, nullable) NSArray *deletions;
47 |
48 | /** The indices of any objects that were added. */
49 | @property (nonatomic, readonly, nullable) NSArray *insertions;
50 |
51 | /** The indices of any objects that were modified. */
52 | @property (nonatomic, readonly, nullable) NSArray *modificatons;
53 |
54 | /** The indices of any objects that have been moved in the list. */
55 | @property (nonatomic, readonly, nullable) NSDictionary *movements;
56 |
57 | /** For table/collection view convenience, create an array of index paths
58 | for items that were deleted. */
59 | - (NSArray *)indexPathsForDeletionsInSection:(NSInteger)section;
60 |
61 | /** For table/collection view convenience, create an array of index paths
62 | for items that were inserted. */
63 | - (NSArray *)indexPathsForInsertionsInSection:(NSInteger)section;
64 |
65 | /** For table/collection view convenience, create an array of index paths
66 | for items that were modified. */
67 | - (NSArray *)indexPathsForModificationsInSection:(NSInteger)section;
68 |
69 | /** For table/collection view convenience, create an array of index paths that
70 | items about to be moved were originally in. */
71 | - (NSArray *)indexPathsForMovementSourcesInSection:(NSInteger)section;
72 |
73 | /** For table/collection view convenience, create an array of index paths that
74 | items about to be moved are now currently in, based off an array of source index */
75 | - (NSArray *)indexPathsForMovementDestinationsWithSourceIndexPaths:(NSArray *)sourceIndexPaths;
76 |
77 | @end
78 |
79 | NS_ASSUME_NONNULL_END
80 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Changes/TOFileSystemItemListChanges.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemListChanges.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemItemListChanges.h"
24 |
25 | #if TARGET_OS_OSX
26 | #import "NSIndexPath+AppKitAdditions.h"
27 | #else
28 | #import
29 | #endif
30 |
31 |
32 | @interface TOFileSystemItemListChanges ()
33 |
34 | /** The indices of any objects that were deleted. */
35 | @property (nonatomic, strong, readwrite) NSMutableArray *deletions;
36 |
37 | /** The indices of any objects that were added. */
38 | @property (nonatomic, strong, readwrite) NSMutableArray *insertions;
39 |
40 | /** The indices of any objects that were modified. */
41 | @property (nonatomic, strong, readwrite) NSMutableArray *modificatons;
42 |
43 | /** The indices of any objects that have been moved in the list. */
44 | @property (nonatomic, strong, readwrite) NSMutableDictionary *movements;
45 |
46 | @end
47 |
48 | @implementation TOFileSystemItemListChanges
49 |
50 | #pragma mark - Adding Index Values -
51 |
52 | - (void)addDeletionIndex:(NSInteger)index
53 | {
54 | if (self.deletions == nil) {
55 | self.deletions = [NSMutableArray array];
56 | }
57 |
58 | [(NSMutableArray *)self.deletions addObject:@(index)];
59 | }
60 |
61 | - (void)addInsertionIndex:(NSInteger)index
62 | {
63 | if (self.insertions == nil) {
64 | self.insertions = [NSMutableArray array];
65 | }
66 |
67 | [(NSMutableArray *)self.insertions addObject:@(index)];
68 | }
69 |
70 | - (void)addModificationIndex:(NSInteger)index
71 | {
72 | if (self.modificatons == nil) {
73 | self.modificatons = [NSMutableArray array];
74 | }
75 |
76 | [(NSMutableArray *)self.modificatons addObject:@(index)];
77 | }
78 |
79 | - (void)addMovementWithSourceIndex:(NSInteger)sourceIndex
80 | destinationIndex:(NSInteger)destinationIndex
81 | {
82 | if (self.movements == nil) {
83 | self.movements = [NSMutableDictionary dictionary];
84 | }
85 |
86 | NSMutableDictionary *dict = (NSMutableDictionary *)self.movements;
87 | dict[@(sourceIndex)] = @(destinationIndex);
88 | }
89 |
90 | #pragma mark - Table/Collection View Converters -
91 |
92 | - (NSArray *)indexPathsForCollection:(nullable NSArray *)collection
93 | inSection:(NSInteger)section
94 | {
95 | if (!collection) { return [NSArray array]; }
96 |
97 | NSMutableArray *array = [NSMutableArray array];
98 | for (NSNumber *number in collection) {
99 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:number.intValue inSection:section];
100 | [array addObject:indexPath];
101 | }
102 |
103 | return [NSArray arrayWithArray:array];
104 | }
105 |
106 | - (NSArray *)indexPathsForDeletionsInSection:(NSInteger)section
107 | {
108 | return [self indexPathsForCollection:self.deletions inSection:section];
109 | }
110 |
111 | - (NSArray *)indexPathsForInsertionsInSection:(NSInteger)section
112 | {
113 | return [self indexPathsForCollection:self.insertions inSection:section];
114 | }
115 |
116 | - (NSArray *)indexPathsForModificationsInSection:(NSInteger)section
117 | {
118 | return [self indexPathsForCollection:self.modificatons inSection:section];
119 | }
120 |
121 | - (NSArray *)indexPathsForMovementSourcesInSection:(NSInteger)section
122 | {
123 | return [self indexPathsForCollection:self.movements.allKeys inSection:section];
124 | }
125 |
126 | - (NSArray *)indexPathsForMovementDestinationsWithSourceIndexPaths:(NSArray *)sourceIndexPaths
127 | {
128 | if (self.movements == nil) { return [NSArray array]; }
129 |
130 | NSMutableArray *array = [NSMutableArray array];
131 | for (NSIndexPath *sourceIndexPath in sourceIndexPaths) {
132 | NSInteger row = self.movements[@(sourceIndexPath.row)].intValue;
133 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:sourceIndexPath.section];
134 | [array addObject:indexPath];
135 | }
136 |
137 | return [NSArray arrayWithArray:array];
138 | }
139 |
140 | - (BOOL)hasItemMovements
141 | {
142 | return self.movements != nil;
143 | }
144 |
145 | - (BOOL)hasItemChanges
146 | {
147 | return (self.deletions.count ||
148 | self.insertions.count ||
149 | self.modificatons.count);
150 | }
151 |
152 | @end
153 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemMapTable.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | A thread-safe wrapper for the NSMapTable objects
29 | used to store re-usable instances of item and list
30 | objects.
31 | */
32 | @interface TOFileSystemItemMapTable : NSObject
33 |
34 | @property (nonatomic, readonly) NSInteger count;
35 |
36 | - (void)setItem:(id)object forUUID:(NSString *)uuid;
37 | - (id)itemForUUID:(NSString *)uuid;
38 | - (void)removeItemForUUID:(NSString *)uuid;
39 |
40 | /** Implementations for allowing dictionary style literal syntax. */
41 | - (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key;
42 | - (nullable id)objectForKeyedSubscript:(NSString *)key;
43 |
44 | @end
45 |
46 | NS_ASSUME_NONNULL_END
47 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemMapTable.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemItemMapTable.h"
24 |
25 | @interface TOFileSystemItemMapTable ()
26 |
27 | /** The map table to hold the items */
28 | @property (nonatomic, strong) NSMapTable *mapTable;
29 |
30 | /** The dispatch queue for synchronzing reads */
31 | @property (nonatomic, strong) dispatch_queue_t dispatchQueue;
32 |
33 | @end
34 |
35 | @implementation TOFileSystemItemMapTable
36 |
37 | - (instancetype)init
38 | {
39 | if (self = [super init]) {
40 | _mapTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory
41 | valueOptions:NSPointerFunctionsWeakMemory];
42 | _dispatchQueue = dispatch_queue_create("TOFileSystemObserver.itemMapTable", DISPATCH_QUEUE_CONCURRENT);
43 | }
44 |
45 | return self;
46 | }
47 |
48 | - (NSInteger)count
49 | {
50 | __block NSInteger count = 0;
51 | dispatch_sync(self.dispatchQueue, ^{
52 | count = self.mapTable.count;
53 | });
54 |
55 | return count;
56 | }
57 |
58 | - (void)setItem:(id)object forUUID:(NSString *)uuid
59 | {
60 | dispatch_barrier_async(self.dispatchQueue, ^{
61 | [self.mapTable setObject:object forKey:uuid];
62 | });
63 | }
64 |
65 | - (id)itemForUUID:(NSString *)uuid
66 | {
67 | __block id item = nil;
68 | dispatch_sync(self.dispatchQueue, ^{
69 | @autoreleasepool {
70 | item = [self.mapTable objectForKey:uuid];
71 | }
72 | });
73 |
74 | return item;
75 | }
76 |
77 | - (void)removeItemForUUID:(NSString *)uuid
78 | {
79 | dispatch_barrier_async(self.dispatchQueue, ^{
80 | [self.mapTable removeObjectForKey:uuid];
81 | });
82 | }
83 |
84 | - (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key
85 | {
86 | [self setItem:object forUUID:key];
87 | }
88 |
89 | - (nullable id)objectForKeyedSubscript:(NSString *)key
90 | {
91 | return [self itemForUUID:key];
92 | }
93 |
94 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
95 | objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer
96 | count:(NSUInteger)len
97 | {
98 | return [_mapTable countByEnumeratingWithState:state
99 | objects:buffer
100 | count:len];
101 | }
102 |
103 | @end
104 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemDictionary.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | A thread-safe dictionary that stores the file paths
29 | to items using their on-disk UUID string as the key.
30 |
31 | This is used to store an in-memory graph of all of the files
32 | as they were at the start of the app session, so that any
33 | detected changes to the file system can be compared.
34 |
35 | The URLs are converted to and stored as relative URLs to
36 | save memory, and are converted back to absolute URLs when retrieved.
37 | */
38 | @interface TOFileSystemItemURLDictionary : NSObject
39 |
40 | /** The number of items currently in the dictionary. */
41 | @property (nonatomic, readonly) NSUInteger count;
42 |
43 | /** Create a new instance with the base URL that all items will be relatively saved against. */
44 | - (instancetype)initWithBaseURL:(NSURL *)baseURL;
45 |
46 | /** Adds an item URL to the dictionary. May be called from multiple threads. */
47 | - (void)setItemURL:(nullable NSURL *)itemURL forUUID:(nullable NSString *)uuid;
48 |
49 | /** Retrieves an item URL from the dictionary. May be called from multiple threads. */
50 | - (nullable NSURL *)itemURLForUUID:(nullable NSString *)uuid;
51 |
52 | /** Tries to retrieve a UUID value for a URL, if it exists */
53 | - (nullable NSString *)uuidForItemWithURL:(NSURL *)itemURL;
54 |
55 | /** Get all UUID keys. */
56 | - (nullable NSArray *)allUUIDs;
57 |
58 | /** Get all URL objects. */
59 | - (nullable NSArray *)allURLs;
60 |
61 | /** Delete an entry from the store. */
62 | - (void)removeItemURLForUUID:(NSString *)uuid;
63 |
64 | /** Remove all items. */
65 | - (void)removeAllItems;
66 |
67 | /** Implementations for allowing dictionary style literal syntax. */
68 | - (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key;
69 | - (nullable id)objectForKeyedSubscript:(NSString *)key;
70 |
71 | - (instancetype)init NS_UNAVAILABLE;
72 |
73 | @end
74 |
75 | NS_ASSUME_NONNULL_END
76 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemDictionary.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemItemURLDictionary.h"
24 |
25 | @interface TOFileSystemItemURLDictionary ()
26 |
27 | /** The base URL against which all other URLs are saved. */
28 | @property (nonatomic, strong) NSURL *baseURL;
29 |
30 | /** The dictionary that holds all of the item URLs */
31 | @property (nonatomic, strong) NSMutableDictionary *uuidItems;
32 |
33 | /** A reverse dictionary tha stores UUIDs for URLs */
34 | @property (nonatomic, strong) NSMutableDictionary *urlItems;
35 |
36 | /** The dispatch queue used to read and write safely to this dictionary. */
37 | @property (nonatomic, strong) dispatch_queue_t itemQueue;
38 |
39 | @end
40 |
41 | @implementation TOFileSystemItemURLDictionary
42 |
43 | #pragma mark - Class Creation -
44 |
45 | - (instancetype)initWithBaseURL:(NSURL *)baseURL
46 | {
47 | if (self = [super init]) {
48 | _baseURL = baseURL.URLByDeletingLastPathComponent.URLByStandardizingPath;
49 | _uuidItems = [NSMutableDictionary dictionary];
50 | _urlItems = [NSMutableDictionary dictionary];
51 | _itemQueue = dispatch_queue_create("TOFileSystemObserver.itemDictionaryQueue",
52 | DISPATCH_QUEUE_CONCURRENT);
53 | }
54 |
55 | return self;
56 | }
57 |
58 | - (NSUInteger)count
59 | {
60 | __block NSInteger count = 0;
61 | dispatch_sync(self.itemQueue, ^{
62 | count = self.uuidItems.count;
63 | });
64 |
65 | return count;
66 | }
67 |
68 | - (void)setItemURL:(nullable NSURL *)itemURL forUUID:(nullable NSString *)uuid
69 | {
70 | if (uuid.length == 0) { return; }
71 |
72 | // If the item is nil, remove it from the store
73 | if (itemURL == nil) {
74 | dispatch_barrier_async(self.itemQueue, ^{
75 | NSURL *url = self.uuidItems[uuid];
76 | [self.urlItems removeObjectForKey:url];
77 | [self.uuidItems removeObjectForKey:uuid];
78 | });
79 | return;
80 | }
81 |
82 | // Use dispatch barriers to block all reads when we mutate the dictionary
83 | dispatch_barrier_async(self.itemQueue, ^{
84 | // Purge the previously saved entries as they may be stale
85 | NSURL *savedURL = self.uuidItems[uuid];
86 | NSString *savedUUID = self.urlItems[savedURL];
87 | if (savedUUID) { [self.uuidItems removeObjectForKey:savedUUID]; }
88 | if (savedURL) { [self.urlItems removeObjectForKey:savedURL]; }
89 |
90 | // Remove the un-needed absolute path to save memory
91 | NSURL *url = [self relativeURLForURL:itemURL];
92 | self.uuidItems[uuid] = url;
93 | self.urlItems[url] = uuid;
94 | });
95 | }
96 |
97 | - (nullable NSURL *)itemURLForUUID:(NSString *)uuid
98 | {
99 | if (uuid.length == 0) { return nil; }
100 |
101 | // Use dispatch barriers to allow asynchronouse reading
102 | __block NSURL *itemURL = nil;
103 | dispatch_sync(self.itemQueue, ^{
104 | itemURL = self.uuidItems[uuid];
105 | });
106 | if (itemURL == nil) { return nil; }
107 |
108 | return [self.baseURL URLByAppendingPathComponent:itemURL.path].URLByStandardizingPath;
109 | }
110 |
111 | - (nullable NSString *)uuidForItemWithURL:(NSURL *)itemURL
112 | {
113 | // Convert the item URL to relative
114 | NSURL *url = [self relativeURLForURL:itemURL];
115 |
116 | // Look up the URL in the dictionary
117 | __block NSString *uuid = nil;
118 | dispatch_sync(self.itemQueue, ^{
119 | uuid = self.urlItems[url];
120 | });
121 |
122 | return uuid;
123 | }
124 |
125 | - (nullable NSArray *)allUUIDs
126 | {
127 | __block NSArray *uuids = nil;
128 | dispatch_sync(self.itemQueue, ^{
129 | uuids = self.uuidItems.allKeys;
130 | });
131 | return uuids;
132 | }
133 |
134 | - (nullable NSArray *)allURLs
135 | {
136 | // Loop through each item in the store, and restore its URL
137 | __block NSMutableArray *array = [NSMutableArray array];
138 | dispatch_sync(self.itemQueue, ^{
139 | for (NSString *uuid in self.uuidItems) {
140 | NSString *path = self.uuidItems[uuid].path;
141 | NSURL *url = [self.baseURL URLByAppendingPathComponent:path];
142 | [array addObject:url.URLByStandardizingPath];
143 | }
144 | });
145 |
146 | // If the array was empty, return nil
147 | if (array.count == 0) { return nil; }
148 |
149 | // Return an immutable version
150 | return [NSArray arrayWithArray:array];
151 | }
152 |
153 | - (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key
154 | {
155 | [self setItemURL:object forUUID:key];
156 | }
157 |
158 | - (void)removeItemURLForUUID:(NSString *)uuid
159 | {
160 | if (uuid == nil) { return; }
161 |
162 | dispatch_barrier_async(self.itemQueue, ^{
163 | NSURL *url = self.uuidItems[uuid];
164 | if (url == nil) { return; }
165 | [self.urlItems removeObjectForKey:url];
166 | [self.uuidItems removeObjectForKey:uuid];
167 | });
168 | }
169 |
170 | - (void)removeAllItems
171 | {
172 | dispatch_barrier_async(self.itemQueue, ^{
173 | [self.urlItems removeAllObjects];
174 | [self.uuidItems removeAllObjects];
175 | });
176 | }
177 |
178 | - (nullable id)objectForKeyedSubscript:(NSString *)key
179 | {
180 | return [self itemURLForUUID:key];
181 | }
182 |
183 | #pragma mark - URL Conversion -
184 |
185 | - (NSURL *)relativeURLForURL:(NSURL *)url
186 | {
187 | NSString *basePath = self.baseURL.path;
188 | NSString *itemPath = url.URLByStandardizingPath.path;
189 | NSString *relativePath = [itemPath stringByReplacingOccurrencesOfString:basePath withString:@""];
190 | return [NSURL fileURLWithPath:relativePath];
191 | }
192 |
193 | #pragma mark - Debugging -
194 |
195 | - (NSString *)description
196 | {
197 | NSString *descriptionString = @"";
198 | for (NSString *key in self.uuidItems.allKeys) {
199 | descriptionString = [descriptionString stringByAppendingFormat:@"%@ - %@\n", key, self.uuidItems[key]];
200 | }
201 |
202 | return descriptionString;
203 | }
204 |
205 | @end
206 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemPath.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | A static class to centralize all file path
29 | manipulation logic.
30 | */
31 | @interface TOFileSystemPath : NSObject
32 |
33 | /** The path to the application sandbox. */
34 | + (NSURL *)applicationSandboxURL;
35 |
36 | /** The path to the application documents directory. */
37 | + (NSURL *)documentsDirectoryURL;
38 |
39 | /** Takes an absolute URL, and strips off the sandbox portion, making it relative. */
40 | + (NSString *)relativePathWithPath:(NSURL *)fileURL;
41 |
42 | /** Takes a flat array of URLs, and organizes them into a
43 | dictionary where each key is the parent directory URL, and the value
44 | is an array of all items in that directory. */
45 | + (NSDictionary *)directoryDictionaryWithItemURLs:(NSArray *)itemURLs;
46 |
47 | @end
48 |
49 | NS_ASSUME_NONNULL_END
50 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemPath.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemPath.h"
24 |
25 | @implementation TOFileSystemPath
26 |
27 | + (NSURL *)applicationSandboxURL
28 | {
29 | return [NSURL fileURLWithPath:NSHomeDirectory()];
30 | }
31 |
32 | + (NSURL *)documentsDirectoryURL
33 | {
34 | NSFileManager *fileManager = [NSFileManager defaultManager];
35 | return [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject;
36 | }
37 |
38 | + (NSString *)relativePathWithPath:(NSURL *)fileURL
39 | {
40 | NSString *sandboxPath = [TOFileSystemPath applicationSandboxURL].path;
41 | NSString *path = fileURL.path;
42 |
43 | // Replace the sandbox portion with an empty string.
44 | path = [path stringByReplacingOccurrencesOfString:sandboxPath withString:@""];
45 |
46 | // Remove leading slashes
47 | if ([[path substringToIndex:1] isEqualToString:@"/"]) {
48 | path = [path substringFromIndex:1];
49 | }
50 |
51 | // Remove trailing slashes
52 | if ([[path substringFromIndex:path.length - 1] isEqualToString:@"/"]) {
53 | path = [path substringToIndex:path.length - 2];
54 | }
55 |
56 | return path;
57 | }
58 |
59 | + (NSDictionary *)directoryDictionaryWithItemURLs:(NSArray *)itemURLs
60 | {
61 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
62 |
63 | return dictionary;
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Items/TOFileSystemItem+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItem+Private.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "TOFileSystemItem.h"
25 |
26 | @class TOFileSystemObserver;
27 |
28 | NS_ASSUME_NONNULL_BEGIN
29 |
30 | /** Private interface for creating item objects */
31 | @interface TOFileSystemItem ()
32 |
33 | /** Creates a new instance of an item for the target item. */
34 | - (instancetype)initWithItemAtFileURL:(NSURL *)fileURL
35 | fileSystemObserver:(TOFileSystemObserver *)observer;
36 |
37 | /** Adds this item as a child of a list. */
38 | - (void)addToList:(TOFileSystemItemList *)list;
39 |
40 | /** Remove this item from a list. */
41 | - (void)removeFromList;
42 |
43 | /** Forces a refresh of the UUID (in cases where the file seems to have been duplicated) */
44 | - (void)regenerateUUID;
45 |
46 | /** Notify this object that it should re-fetch all its properties from disk.
47 | Returns true if there were changes. */
48 | - (BOOL)refreshWithURL:(nullable NSURL *)itemURL;
49 |
50 | @end
51 |
52 | NS_ASSUME_NONNULL_END
53 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Items/TOFileSystemItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItem.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemObserverConstants.h"
24 |
25 | @class TOFileSystemObserver;
26 | @class TOFileSystemItemList;
27 |
28 | NS_ASSUME_NONNULL_BEGIN
29 |
30 | /**
31 | An object that represents either a file
32 | or folder on disk.
33 | */
34 | NS_SWIFT_NAME(FileSystemItem)
35 | @interface TOFileSystemItem : NSObject
36 |
37 | /** The absolute URL path to this item. */
38 | @property (nonatomic, readonly) NSURL *fileURL;
39 |
40 | /** The type of the item (either a file or folder) */
41 | @property (nonatomic, readonly) TOFileSystemItemType type;
42 |
43 | /** The unique UUID that was assigned to the file by this library. */
44 | @property (nonatomic, readonly) NSString *uuid;
45 |
46 | /** The name on disk of the item. */
47 | @property (nonatomic, readonly) NSString *name;
48 |
49 | /** The size (in bytes) of this item. (0 for directories). */
50 | @property (nonatomic, readonly) long long size;
51 |
52 | /** The creation date of the item. */
53 | @property (nonatomic, readonly) NSDate *creationDate;
54 |
55 | /** The last modification date of the item. */
56 | @property (nonatomic, readonly) NSDate *modificationDate;
57 |
58 | /** If a directory, the number of files/subdirectories inside this item. */
59 | @property (nonatomic, readonly) NSInteger numberOfSubItems;
60 |
61 | /** Whether the item is still being copied into the app container. */
62 | @property (nonatomic, readonly) BOOL isCopying;
63 |
64 | /** Whether the item on disk represented by this object no longer exists. */
65 | @property (nonatomic, readonly) BOOL isDeleted;
66 |
67 | /** The file system observer backing this object. */
68 | @property (nonatomic, weak, readonly) TOFileSystemObserver *fileSystemObserver;
69 |
70 | /** Returns the list this item belongs to (if a list has separately been created.) */
71 | @property (nonatomic, weak, nullable, readonly) TOFileSystemItemList *list;
72 |
73 | @end
74 |
75 | NS_ASSUME_NONNULL_END
76 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Items/TOFileSystemItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItem.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemItem.h"
24 | #import "TOFileSystemItemList+Private.h"
25 | #import "TOFileSystemPath.h"
26 | #import "TOFileSystemObserver.h"
27 | #import "TOFileSystemPresenter.h"
28 | #import "NSURL+TOFileSystemAttributes.h"
29 | #import "NSURL+TOFileSystemUUID.h"
30 | #import "TOFileSystemObserverConstants.h"
31 |
32 | #import
33 | #import
34 |
35 | /** Private interface to expose the file presenter for coordinated writes. */
36 | @interface TOFileSystemObserver ()
37 | @property (nonatomic, readonly) TOFileSystemPresenter *fileSystemPresenter;
38 | @end
39 |
40 | @interface TOFileSystemItem ()
41 |
42 | /** The list that this item belongs to. */
43 | @property (nonatomic, weak, nullable, readwrite) TOFileSystemItemList *list;
44 |
45 | /** Internal writing overrides for public properties */
46 | @property (nonatomic, strong, readwrite) NSURL *fileURL;
47 | @property (nonatomic, assign, readwrite) TOFileSystemItemType type;
48 | @property (nonatomic, copy, readwrite) NSString *uuid;
49 | @property (nonatomic, copy, readwrite) NSString *name;
50 | @property (nonatomic, assign, readwrite) long long size;
51 | @property (nonatomic, strong, readwrite) NSDate *creationDate;
52 | @property (nonatomic, strong, readwrite) NSDate *modificationDate;
53 | @property (nonatomic, assign, readwrite) BOOL isCopying;
54 | @property (nonatomic, assign, readwrite) NSInteger numberOfSubItems;
55 |
56 | /** Thread safe locks */
57 | #pragma clang diagnostic push
58 | #pragma clang diagnostic ignored "-Weverything"
59 | @property (nonatomic, assign) os_unfair_lock unfairLock;
60 | @property (nonatomic, assign) pthread_mutex_t pthreadMutexLock;
61 | #pragma clang diagnostic pop
62 |
63 | @end
64 |
65 | @implementation TOFileSystemItem
66 |
67 | #pragma mark - Class Creation -
68 |
69 | - (instancetype)initWithItemAtFileURL:(NSURL *)fileURL
70 | fileSystemObserver:(TOFileSystemObserver *)observer
71 | {
72 | if (self = [super init]) {
73 | _fileURL = fileURL;
74 | _fileSystemObserver = observer;
75 |
76 | // Initialize the lock
77 | if (@available(iOS 10.0, *)) {
78 | self.unfairLock = OS_UNFAIR_LOCK_INIT;
79 | } else {
80 | pthread_mutex_init(&_pthreadMutexLock, NULL);
81 | }
82 |
83 | // If this item represents a deleted file, skip gathering the data
84 | if (!self.isDeleted) {
85 | [self performWithLock:^{
86 | [self configureUUIDForceRefresh:NO];
87 | [self refreshFromItemAtURL:fileURL];
88 | }];
89 | }
90 | }
91 |
92 | return self;
93 | }
94 |
95 | #pragma mark - Update Properties -
96 |
97 | - (void)configureUUIDForceRefresh:(BOOL)forceRefresh
98 | {
99 | TOFileSystemPresenter *presenter = self.fileSystemObserver.fileSystemPresenter;
100 | _uuid = [presenter uuidForItemAtURL:_fileURL];
101 | }
102 |
103 | - (BOOL)refreshFromItemAtURL:(NSURL *)url
104 | {
105 | BOOL hasChanges = NO;
106 |
107 | // Copy the new URL to this item
108 | if (url) {
109 | _fileURL = url;
110 | }
111 |
112 | // Copy the name of the item
113 | NSString *name = [_fileURL lastPathComponent];
114 | if (_name.length == 0 || ![name isEqualToString:_name]) {
115 | _name = name;
116 | hasChanges = YES;
117 | }
118 |
119 | // Check if it is a file or directory
120 | TOFileSystemItemType type = _fileURL.to_isDirectory ? TOFileSystemItemTypeDirectory :
121 | TOFileSystemItemTypeFile;
122 | if (type != _type) {
123 | _type = type;
124 | hasChanges = YES;
125 | }
126 |
127 | // Get its creation date
128 | NSDate *creationDate = _fileURL.to_creationDate;
129 | if (![_creationDate isEqualToDate:creationDate]) {
130 | _creationDate = creationDate;
131 | hasChanges = YES;
132 | }
133 |
134 | // Get its modification date
135 | NSDate *modificationDate = _fileURL.to_modificationDate;
136 | if (![_modificationDate isEqualToDate:modificationDate]) {
137 | _modificationDate = modificationDate;
138 | hasChanges = YES;
139 | }
140 |
141 | // If the type is a file
142 | if (_type == TOFileSystemItemTypeFile) {
143 | // Fetch the item file size
144 | long long fileSize = _fileURL.to_size;
145 | if (fileSize != _size) {
146 | _size = fileSize;
147 | hasChanges = YES;
148 | }
149 |
150 | // Check to see if it is copying
151 | BOOL isCopying = _fileURL.to_isCopying;
152 | if (isCopying != _isCopying) {
153 | _isCopying = isCopying;
154 | hasChanges = YES;
155 | }
156 | }
157 | else {
158 | // Else, it's a directory, count the number of items inside
159 | NSInteger numberOfChildItems = [_fileURL to_numberOfSubItems];
160 | if (_numberOfSubItems != numberOfChildItems) {
161 | _numberOfSubItems = numberOfChildItems;
162 | hasChanges = YES;
163 | }
164 | }
165 |
166 | return hasChanges;
167 | }
168 |
169 | - (BOOL)isDeleted
170 | {
171 | return ![[NSFileManager defaultManager] fileExistsAtPath:self.fileURL.path];
172 | }
173 |
174 | - (void)regenerateUUID
175 | {
176 | [self performWithLock:^{
177 | [self configureUUIDForceRefresh:YES];
178 | }];
179 | }
180 |
181 | #pragma mark - Lists -
182 |
183 | - (BOOL)refreshWithURL:(nullable NSURL *)itemURL
184 | {
185 | // Perform a re-fetch of all of the properties of the
186 | // item from disk, and re-populate all of the properties.
187 |
188 | // A lock needs to be used as this operation will ideally be done
189 | // in the background due to how heavy it could potentially be
190 | __block BOOL hasChanges = NO;
191 | [self performWithLock:^{
192 | hasChanges = [self refreshFromItemAtURL:itemURL];
193 | }];
194 |
195 | // If it was detected one or more of the properties were
196 | // different, if the item is a member of a list, inform
197 | // the list that the UI state of this item will need to be updated.
198 | if (hasChanges) {
199 | [NSOperationQueue.mainQueue addOperationWithBlock:^{
200 | if (self.list == nil) { return; }
201 | [self.list itemDidRefreshWithUUID:self.uuid];
202 | }];
203 | }
204 |
205 | return hasChanges;
206 | }
207 |
208 | - (void)addToList:(TOFileSystemItemList *)list
209 | {
210 | self.list = list;
211 | }
212 |
213 | - (void)removeFromList
214 | {
215 | self.list = nil;
216 | }
217 |
218 | #pragma mark - Equality -
219 |
220 | - (BOOL)isEqual:(id)object
221 | {
222 | if (self == object) { return YES; }
223 | if (![object isKindOfClass:TOFileSystemItem.class]) { return NO; }
224 |
225 | TOFileSystemItem *item = (TOFileSystemItem *)object;
226 | return [item.uuid isEqualToString:self.uuid];
227 | }
228 |
229 | - (NSUInteger)hash
230 | {
231 | return self.uuid.hash;
232 | }
233 |
234 | #pragma mark - Thread-Safe Accessors -
235 |
236 | // To ensure thread safety, fetch the value of an object
237 | // on the barrier queue
238 | - (id)fetchValueForObject:(NSString *)objectName
239 | {
240 | __block id objectValue = nil;
241 | [self performWithLock:^{
242 | objectValue = [self valueForKey:objectName];
243 | }];
244 |
245 | return objectValue;
246 | }
247 |
248 | - (long long)fetchValueForInteger:(NSString *)integerName
249 | {
250 | __block long long intValue = 0;
251 | [self performWithLock:^{
252 | intValue = [[self valueForKey:integerName] longLongValue];
253 | }];
254 |
255 | return intValue;
256 | }
257 |
258 | - (NSURL *)fileURL { return (NSURL *)[self fetchValueForObject:@"_fileURL"]; }
259 | - (NSString *)uuid { return (NSString *)[self fetchValueForObject:@"_uuid"]; }
260 | - (NSString *)name { return (NSString *)[self fetchValueForObject:@"_name"]; }
261 | - (long long)size { return (long long)[self fetchValueForInteger:@"_size"]; }
262 | - (NSDate *)creationDate { return (NSDate *)[self fetchValueForObject:@"_creationDate"]; }
263 | - (NSDate *)modificationDate { return (NSDate *)[self fetchValueForObject:@"_modificationDate"]; }
264 | - (BOOL)isCopying { return (BOOL)[self fetchValueForInteger:@"_isCopying"]; }
265 | - (NSInteger)numberOfSubItems { return (NSInteger)[self fetchValueForInteger:@"_numberOfSubItems"]; }
266 |
267 | #pragma mark - Thread Safe Access -
268 |
269 | - (void)performWithLock:(void (^)(void))block;
270 | {
271 | // Lock the current thread
272 | if (@available(iOS 10.0, *)) {
273 | os_unfair_lock_lock(&_unfairLock);
274 | } else {
275 | pthread_mutex_lock(&_pthreadMutexLock);
276 | }
277 |
278 | block();
279 |
280 | // Unlock the thread
281 | if (@available(iOS 10.0, *)) {
282 | os_unfair_lock_unlock(&_unfairLock);
283 | } else {
284 | pthread_mutex_unlock(&_pthreadMutexLock);
285 | }
286 | }
287 |
288 | #pragma mark - Debugging -
289 |
290 | - (NSString *)description
291 | {
292 | NSString *description = @"TOFileSystem Item - \n"
293 | @"Name: %@\n"
294 | @"UUID: %@\n"
295 | @"Type: %@\n"
296 | @"Size: %d\n"
297 | @"Created: %@\n"
298 | @"Modified: %@\n";
299 |
300 | return [NSString stringWithFormat:description,
301 | self.name,
302 | self.uuid,
303 | self.type != 0 ? @"Folder" : @"File",
304 | self.size,
305 | self.creationDate,
306 | self.modificationDate];
307 | }
308 |
309 | @end
310 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Items/TOFileSystemItemList+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemList+Private.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "TOFileSystemItemList.h"
25 | #import "TOFileSystemItem.h"
26 | #import "TOFileSystemItemListChanges.h"
27 |
28 | NS_ASSUME_NONNULL_BEGIN
29 |
30 | @class TOFileSystemObserver;
31 |
32 | /** Private interface for creating item objects */
33 | @interface TOFileSystemItemList ()
34 |
35 | /** Creates a new instance of an item for the target item. */
36 | - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
37 | fileSystemObserver:(TOFileSystemObserver *)observer;
38 |
39 | /** Add a new item to the list. */
40 | - (void)addItemWithUUID:(NSString *)uuid itemURL:(NSURL *)url;
41 |
42 | /** Triggered when an item's properties have changed. */
43 | - (void)itemDidRefreshWithUUID:(NSString *)uuid;
44 |
45 | /** Remove an object from the list (It was deleted or moved away). */
46 | - (void)removeItemWithUUID:(NSString *)uuid fileURL:(NSURL *)url;
47 |
48 | /** If the folder was moved, update it's own reference to its file path. */
49 | - (BOOL)refreshWithURL:(nullable NSURL *)directoryURL;
50 |
51 | /** Loop through every item on disk, and delete any items that are no longer there. */
52 | - (void)synchronizeWithDisk;
53 |
54 | @end
55 |
56 | NS_ASSUME_NONNULL_END
57 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemList.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "TOFileSystemObserverConstants.h"
25 |
26 | @class TOFileSystemObserver;
27 | @class TOFileSystemItem;
28 | @class TOFileSystemItemListChanges;
29 | @class TOFileSystemNotificationToken;
30 | @class TOFileSystemItemList;
31 |
32 | NS_ASSUME_NONNULL_BEGIN
33 |
34 | /**
35 | This class represents a list of files and
36 | directories located within the directroy that was
37 | specified.
38 |
39 | It is backed by an observer object, that will ensure
40 | that it is kept up-to-date with any changes that occur on
41 | the file system.
42 | */
43 | NS_SWIFT_NAME(FileSystemItemList)
44 | @interface TOFileSystemItemList : NSObject
45 |
46 | /** The unique UUID string saved in the attributes of this directory. */
47 | @property (nonatomic, readonly) NSString *uuid;
48 |
49 | /** The observer object backing this list object. */
50 | @property (nonatomic, weak, readonly) TOFileSystemObserver *fileSystemObserver;
51 |
52 | /** The type of ordering of the items. */
53 | @property (nonatomic, assign) TOFileSystemItemListOrder listOrder;
54 |
55 | /** Whether the list is ascending or descending. (Default is ascending). */
56 | @property (nonatomic, assign) BOOL isDescending;
57 |
58 | /** The number of items in this list. */
59 | @property (nonatomic, readonly) NSUInteger count;
60 |
61 | /** The absolute URL to this directory containing these items. */
62 | @property (nonatomic, readonly) NSURL *directoryURL;
63 |
64 | /**
65 | Registers a new notification block that will be
66 | triggered each time the data in the list changes.
67 |
68 | The returned notification token must be strongly retained
69 | by your code for the duration you wish to receive notifications.
70 | */
71 | - (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemItemListNotificationBlock)block;
72 |
73 | /** Retrieves the item at the requested index. */
74 | - (TOFileSystemItem *)objectAtIndex:(NSUInteger)index;
75 |
76 | /** Allows array-style lookup of items at specific indexes. */
77 | - (TOFileSystemItem *)objectAtIndexedSubscript:(NSUInteger)index;
78 |
79 | - (instancetype)init NS_UNAVAILABLE;
80 |
81 | @end
82 |
83 | NS_ASSUME_NONNULL_END
84 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemNotificationToken+Private.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "TOFileSystemNotificationToken.h"
25 |
26 | NS_ASSUME_NONNULL_BEGIN
27 |
28 | /** A protocol denoting that the object can serve notification tokens. */
29 | @protocol TOFileSystemNotifying
30 |
31 | @required
32 | /** Removes the notification from the observing object. */
33 | - (void)removeNotificationToken:(TOFileSystemNotificationToken *)token;
34 |
35 | @end
36 |
37 | @interface TOFileSystemNotificationToken ()
38 |
39 | /** The object for which this token was generated from. */
40 | @property (nonatomic, weak, readwrite) id observingObject;
41 |
42 | /** The block that will be triggered each time an event occurs. */
43 | @property (nonatomic, copy, readwrite) id notificationBlock;
44 |
45 | /** Create a new instance with the observer and the block */
46 | + (instancetype)tokenWithObservingObject:(id)observingObject
47 | block:(id)block;
48 |
49 | @end
50 |
51 | NS_ASSUME_NONNULL_END
52 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemNotificationToken.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | An opaque token object used to track registered notification blocks.
29 |
30 | It is your responsibility to maintain a strong reference to this
31 | object for the duration that you wish to receive events. It will
32 | remove itself from the observing object when deallocated.
33 | */
34 | NS_SWIFT_NAME(FileSystemNotificationToken)
35 | @interface TOFileSystemNotificationToken : NSObject
36 |
37 | /**
38 | Stops all notifications being made to the block, and removes it
39 | from the file system observer.
40 | */
41 | - (void)invalidate;
42 |
43 | - (instancetype)init NS_UNAVAILABLE;
44 |
45 | @end
46 |
47 | NS_ASSUME_NONNULL_END
48 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemNotificationToken.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemNotificationToken.h"
24 | #import "TOFileSystemNotificationToken+Private.h"
25 |
26 | @implementation TOFileSystemNotificationToken
27 |
28 | #pragma mark - Class Creation -
29 |
30 | + (instancetype)tokenWithObservingObject:(id)observingObject
31 | block:(id)block
32 | {
33 | TOFileSystemNotificationToken *token = [[TOFileSystemNotificationToken alloc] init];
34 | token.observingObject = observingObject;
35 | token.notificationBlock = block;
36 | return token;
37 | }
38 |
39 | - (void)dealloc
40 | {
41 | [self invalidate];
42 | }
43 |
44 | - (void)invalidate
45 | {
46 | [self.observingObject removeNotificationToken:self];
47 | }
48 |
49 | @end
50 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Scanning/TOFileSystemPresenter.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemPresenter.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | This file presenter object handles coordinating state
29 | and events with the file system. It uses `NSFileCoordinator`
30 | to receive events from the system, when files change,
31 | and also performs coordinated reads and writes for retrieving
32 | UUIDs from the files it manages.
33 | */
34 | @interface TOFileSystemPresenter : NSObject
35 |
36 | /** The directory that will be observed by this presenter object */
37 | @property (nonatomic, strong) NSURL *directoryURL;
38 |
39 | /** The presenter is actively listening for events. */
40 | @property (nonatomic, readonly) BOOL isRunning;
41 |
42 | /**
43 | Since multiple events can come through, a timer is used to
44 | coalesce batches of events and trigger an update periodically.
45 | (Default is 100 miliseconds)
46 | */
47 | @property (nonatomic, assign) NSTimeInterval timerInterval;
48 |
49 | /**
50 | A block that will be called with all of the collected events.
51 | It will be called on the same operation queue as managed by this class,
52 | so the logic contained should be thread-safe.
53 | */
54 | @property (nonatomic, copy) void (^itemsDidChangeHandler)(NSArray *itemURLs);
55 |
56 | /** Start listening for file events in the target directory. */
57 | - (void)start;
58 |
59 | /** Perform a synchronous coordinated read on a file. */
60 | - (void)performCoordinatedRead:(void (^)(void))block;
61 |
62 | /** Perform a synchronous write operation on a file */
63 | - (void)performCoordinatedWrite:(void (^)(void))block;
64 |
65 | /** Stop listening and cancel any pending timer events. */
66 | - (void)stop;
67 |
68 | /** Coordinates reading (and writing if need be) a UUID string for the supplied item */
69 | - (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL;
70 |
71 | @end
72 |
73 | NS_ASSUME_NONNULL_END
74 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Scanning/TOFileSystemPresenter.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemPresenter.m
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import "TOFileSystemPresenter.h"
24 | #import "NSURL+TOFileSystemUUID.h"
25 |
26 | @interface TOFileSystemPresenter ()
27 |
28 | /** The presenter is actively listening for events. */
29 | @property (nonatomic, assign, readwrite) BOOL isRunning;
30 |
31 | /** The operation queue that will receive all of the file events*/
32 | @property (nonatomic, strong) NSOperationQueue *eventsOperationQueue;
33 |
34 | /** The list of items currently detected. */
35 | @property (nonatomic, strong) NSMutableArray *items;
36 |
37 | /** A serial queue for managing access to the list (including the timer) */
38 | @property (nonatomic, strong) dispatch_queue_t itemListAccessQueue;
39 |
40 | /** Whether a timer has been set yet or not */
41 | @property (nonatomic, assign) BOOL isTiming;
42 |
43 | /** A concurrent queue used to coordinate writing UUIDs to files. */
44 | @property (nonatomic, readonly) dispatch_queue_t fileCoordinatorQueue;
45 |
46 | @end
47 |
48 | @implementation TOFileSystemPresenter
49 |
50 | #pragma mark - Class Lifecycle -
51 |
52 | - (instancetype)init
53 | {
54 | if (self = [super init]) {
55 | [self commonInit];
56 | }
57 |
58 | return self;
59 | }
60 |
61 | - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
62 | {
63 | if (self = [super init]) {
64 | _directoryURL = directoryURL;
65 | [self commonInit];
66 | }
67 |
68 | return self;
69 | }
70 |
71 | - (dispatch_queue_t)fileCoordinatorQueue
72 | {
73 | // In case we have multiple file observers, we must share this
74 | // coordinator amongst all of them in case two separate instances
75 | // try and write to the same file.
76 | static dispatch_queue_t _fileCoordinatorQueue = NULL;
77 | static dispatch_once_t onceToken;
78 | dispatch_once(&onceToken, ^{
79 | _fileCoordinatorQueue = dispatch_queue_create("TOFileSystemObserver.fileCoordinatorQueue",
80 | DISPATCH_QUEUE_CONCURRENT);
81 | });
82 | return _fileCoordinatorQueue;
83 | }
84 |
85 | - (void)commonInit
86 | {
87 | // Create the queue to receive events
88 | _eventsOperationQueue = [[NSOperationQueue alloc] init];
89 | _eventsOperationQueue.qualityOfService = NSQualityOfServiceBackground;
90 |
91 | // Create the array to hold the items detected
92 | _items = [NSMutableArray array];
93 |
94 | // Create the dispatch queue for the items
95 | _itemListAccessQueue = dispatch_queue_create("TOFileSystemObserver.itemListAccessQueue", DISPATCH_QUEUE_SERIAL);
96 |
97 | // Default time interval
98 | _timerInterval = 0.1f;
99 | }
100 |
101 | - (void)dealloc
102 | {
103 | [self stop];
104 | }
105 |
106 | #pragma mark - Timer Handling -
107 |
108 | - (void)beginTimer
109 | {
110 | // When the timer finishes, create a copy of the items,
111 | // and then flush what we currently have in the main item list
112 | id completionBlock = ^{
113 | if (!self.isRunning) { return; }
114 | self.isTiming = NO;
115 |
116 | @autoreleasepool {
117 | NSArray *items = [self.items copy];
118 | [self.items removeAllObjects];
119 | if (items.count == 0) { return; }
120 |
121 | if (self.itemsDidChangeHandler) {
122 | self.itemsDidChangeHandler(items);
123 | }
124 | }
125 | };
126 |
127 | id timerBlock = ^{
128 | // Cancel if timing has already been started
129 | if (self.isTiming) { return; }
130 | self.isTiming = YES;
131 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
132 | (int64_t)(self.timerInterval * NSEC_PER_SEC)),
133 | self.itemListAccessQueue,
134 | completionBlock);
135 | };
136 |
137 | dispatch_async(self.itemListAccessQueue, timerBlock);
138 | }
139 |
140 | #pragma mark - Item Handling -
141 |
142 | - (void)addItemToList:(NSURL *)itemURL
143 | {
144 | // Add the new item to the items list in a barrier queue access.
145 | dispatch_async(self.itemListAccessQueue, ^{
146 | [self.items addObject:itemURL];
147 | });
148 | }
149 |
150 | #pragma mark - Public Control -
151 |
152 | - (void)start
153 | {
154 | if (self.isRunning) { return; }
155 | [NSFileCoordinator addFilePresenter:self];
156 | self.isRunning = YES;
157 | }
158 |
159 | - (void)performCoordinatedRead:(void (^)(void))block
160 | {
161 | dispatch_sync(self.fileCoordinatorQueue, ^{
162 | @autoreleasepool {
163 | if (block) { block(); }
164 | }
165 | });
166 | }
167 |
168 | - (void)performCoordinatedWrite:(void (^)(void))block
169 | {
170 | dispatch_barrier_sync(self.fileCoordinatorQueue, ^{
171 | @autoreleasepool {
172 | if (block) { block(); }
173 | }
174 | });
175 | }
176 |
177 | - (void)stop
178 | {
179 | if (!self.isRunning) { return; }
180 | [NSFileCoordinator removeFilePresenter:self];
181 | self.isRunning = NO;
182 | }
183 |
184 | - (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL
185 | {
186 | __block NSString *uuid = nil;
187 |
188 | // If the file exists, but it's not in the store yet,
189 | // attempt to access it from disk
190 | [self performCoordinatedRead:^{
191 | uuid = [itemURL to_fileSystemUUID];
192 | }];
193 | if (uuid.length) { return uuid; }
194 |
195 | // If even that failed, then it's necessary to generate a new one
196 | [self performCoordinatedWrite:^{
197 | // Try again in case a previous operation already generated one
198 | uuid = [itemURL to_fileSystemUUID];
199 | if (uuid.length == 0) {
200 | uuid = [itemURL to_generateFileSystemUUID];
201 | }
202 | }];
203 |
204 | return uuid;
205 | }
206 |
207 | #pragma mark - NSFilePresenter Delegate Events -
208 |
209 | - (void)presentedSubitemDidChangeAtURL:(NSURL *)url
210 | {
211 | [self addItemToList:url];
212 | [self beginTimer];
213 | }
214 |
215 | - (NSURL *)presentedItemURL
216 | {
217 | return self.directoryURL;
218 | }
219 |
220 | - (NSOperationQueue *)presentedItemOperationQueue
221 | {
222 | return self.eventsOperationQueue;
223 | }
224 |
225 | @end
226 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Scanning/TOFileSystemScanOperation.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemScanOperation.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | @class TOFileSystemPresenter;
26 | @class TOFileSystemItemURLDictionary;
27 | @class TOFileSystemScanOperation;
28 |
29 | NS_ASSUME_NONNULL_BEGIN
30 |
31 | @protocol TOFileSystemScanOperationDelegate
32 |
33 | @required
34 |
35 | /**
36 | Called when a new item is discovered is discovered during a scan.
37 | This is called every time for full-system scans, but will then only be called on items not previously found before
38 | in subsequent scans.
39 | */
40 | - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation didDiscoverItemAtURL:(NSURL *)itemURL withUUID:(NSString *)uuid;
41 |
42 | /** Called when the properties of an object have been changed (eg, renamed etc) */
43 | - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation itemDidChangeAtURL:(NSURL *)itemURL withUUID:(NSString *)uuid;
44 |
45 | /** Called when the file has been moved to another part of the sandbox. */
46 | - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation itemWithUUID:(NSString *)uuid
47 | didMoveFromURL:(NSURL *)previousURL
48 | toURL:(NSURL *)url;
49 |
50 | /** Called when the file has been deleted. */
51 | - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation didDeleteItemAtURL:(NSURL *)itemURL withUUID:(NSString *)uuid;
52 |
53 | /** Called before a full directory scan has started to allow any delegates to prepare in advance. */
54 | - (void)scanOperationWillBeginFullScan:(TOFileSystemScanOperation *)scanOperation;
55 |
56 | /** Called when a full directory scan has been completed so we can do some final clean-up. */
57 | - (void)scanOperationDidCompleteFullScan:(TOFileSystemScanOperation *)scanOperation;
58 |
59 | @end
60 |
61 | /**
62 | An operation that will either scan all
63 | child items of a directory, or a list of items
64 | and update their snapshot if they have changed.
65 | */
66 | @interface TOFileSystemScanOperation : NSOperation
67 |
68 | /** Whether this a complete deep scan or not. */
69 | @property (nonatomic, readonly) BOOL isFullScan;
70 |
71 | /** A delegate object that will be called upon any detected change events. */
72 | @property (nonatomic, weak) id delegate;
73 |
74 | /** When scanning hierarchies, the numbers deep to scan (-1 is all of them) */
75 | @property (nonatomic, assign) NSInteger subDirectoryLevelLimit;
76 |
77 | /** Create a new instance that will scan all of the child items of the provided directory */
78 | - (instancetype)initForFullScanWithDirectoryAtURL:(NSURL *)directoryURL
79 | skippingItems:(NSArray *)skippedItems
80 | allItemsDictionary:(nonnull TOFileSystemItemURLDictionary *)allItems
81 | filePresenter:(TOFileSystemPresenter *)filePresenter;
82 |
83 | /** Create a new instance that will scan all of the files/folders provided. */
84 | - (instancetype)initForItemScanWithItemURLs:(NSArray *)itemURLs
85 | baseURL:(NSURL *)baseURL
86 | skippingItems:(NSArray *)skippedItems
87 | allItemsDictionary:(nonnull TOFileSystemItemURLDictionary *)allItems
88 | filePresenter:(TOFileSystemPresenter *)filePresenter;
89 |
90 | @end
91 |
92 | NS_ASSUME_NONNULL_END
93 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/TOFileSystemObserver.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemObserver.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | #import "TOFileSystemItemList.h"
26 | #import "TOFileSystemItem.h"
27 | #import "TOFileSystemNotificationToken.h"
28 | #import "TOFileSystemItemListChanges.h"
29 | #import "TOFileSystemChanges.h"
30 | #import "TOFileSystemObserverConstants.h"
31 |
32 | NS_ASSUME_NONNULL_BEGIN
33 |
34 | @class TOFileSystemObserver;
35 |
36 | NS_SWIFT_NAME(FileSystemObserver)
37 | @interface TOFileSystemObserver : NSObject
38 |
39 | /** Whether the observer is currently active and observing its target directory. */
40 | @property (nonatomic, readonly) BOOL isRunning;
41 |
42 | /**
43 | The absolute file path to the directory that is being observed.
44 | It may only be set while the observer isn't running. (Default is the Documents directory).
45 | */
46 | @property (nonatomic, strong) NSURL *directoryURL;
47 |
48 | /**
49 | Optionally, a list of relative file paths from `directoryURL` to directories that
50 | will not be monitored. By default, this includes the 'Inbox' directory in the
51 | Documents directory.
52 | */
53 | @property (nonatomic, strong, nullable) NSArray *excludedItems;
54 |
55 | /**
56 | Optionally, the number of directory levels down that the observer will recursively traverse from the base target.
57 | This is useful to avoid performance overhead in instances such as if your app only has one level of directories. (Default is 0).
58 | */
59 | @property (nonatomic, assign) NSInteger includedDirectoryLevels;
60 |
61 | /**
62 | The item that represents the base directory that was set to be observed
63 | by this file system observer.
64 |
65 | (This item will be refetched from the database each time it is called,
66 | so it is completely thread-safe. Like most multi-threading cases, please ensure it
67 | is kept in an autorelease pool in dispatch queues.)
68 | */
69 | @property (nonatomic, readonly) TOFileSystemItem *directoryItem;
70 |
71 | /**
72 | Turns on system-wide `NSNotification` broadcast events whenever a change is
73 | detected. This is YES for the singleton instance by default, but NO for all other instances.
74 | */
75 | @property (nonatomic, assign) BOOL broadcastsNotifications;
76 |
77 | /** Create a new instance of the observer with the base URL that will be observed. */
78 | - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL;
79 |
80 | /** A singleton instance that can be accessed globally. It is created the first time this is called. */
81 | + (instancetype)sharedObserver;
82 |
83 | /** If desired, promotes a locally created and configured observer to the singleton. */
84 | + (void)setSharedObserver:(TOFileSystemObserver *)observer;
85 |
86 | /**
87 | Starts the file system observer monitoring the target directory for changes.
88 | Upon starting, the observer will perform a full file system scan, and will post a 'did discover'
89 | event for every item it finds. This can be used in order to ensure any caches, or previously
90 | expected locations of files can be updated.
91 | */
92 | - (void)start;
93 |
94 | /** Completely stops the file observer from running and resets all of the internal state. When
95 | calling 'start' from after this state, another full file system scan will be performed. */
96 | - (void)stop;
97 |
98 | /**
99 | Returns a list of directories and files inside the directory specified.
100 | While the file observer is running, this list is live, and will be automatically
101 | updated whenever any of the underlying files on disk are changed.
102 |
103 | @param directoryURL The URL to target. Use `nil` for the observer's base directory.
104 | */
105 | - (nullable TOFileSystemItemList *)itemListForDirectoryAtURL:(nullable NSURL *)directoryURL;
106 |
107 | /**
108 | Returns an item object representing the file or directory at the URL specified.
109 | While the file observer is running, this object is live, and will be automatically
110 | updated whenever the system detects that the file has changed.
111 |
112 | @param fileURL The URL to target.
113 | */
114 | - (nullable TOFileSystemItem *)itemForFileAtURL:(NSURL *)fileURL;
115 |
116 | /**
117 | Returns the unique UUID string that's been associated with the file at the provided URL from disk.
118 | This will attempt to retrieve the UUID while avoiding performing a file read if it can help it.
119 | If the file has not yet had a UUID string assigned, it will generate a new one in a thread-safe manner.
120 |
121 | @param itemURL The url of the item whose UUID that will be accessed.
122 | @return The UUID string associated with the file on disk. Returns nil if the URL is invalid.
123 | */
124 | - (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL;
125 |
126 | /**
127 | Returns the unique UUID for the parent directory of the item at the provided URL.
128 | This will attempt to retrieve the UUID while avoiding performing a file read if it can help it.
129 | If the item doesn't yet have a UUID, it will create one in a thread safe manner.
130 | */
131 | - (nullable NSString *)uuidForParentOfItemAtURL:(NSURL *)itemURL;
132 |
133 | /**
134 | Registers a new notification block that will be triggered each time an update is detected.
135 | It is your responsibility to strongly retain the token object, and release it only
136 | when you wish to stop receiving notifications.
137 |
138 | @param block A block that will be called each time a file system event is detected.
139 | */
140 | - (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemNotificationBlock)block;
141 |
142 | /**
143 | When a notification token is no longer needed, it can be removed from the observer
144 | and freed from memory. This method will automatically be called if `invalidate` is
145 | called from the token.
146 |
147 | @param token The token that will be removed from the observer and deallocated.
148 | */
149 | - (void)removeNotificationToken:(TOFileSystemNotificationToken *)token;
150 |
151 | @end
152 |
153 | FOUNDATION_EXPORT double TOFileSystemObserverVersionNumber;
154 | FOUNDATION_EXPORT const unsigned char TOFileSystemObserverVersionString[];
155 |
156 | NS_ASSUME_NONNULL_END
157 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Utilities/TOFileSystemObserver+AppKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemObserver+UIKit.h
3 | //
4 | // Copyright 2019-2022 Anatoly Rosencrantz, Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | //
24 | // TOFileSystemObserver+UIKit.h
25 | // TOFileSystemObserverMacExample
26 | //
27 | // Created by Anatoly Rosencrantz on 26/03/2020.
28 | // Copyright © 2020 Tim Oliver. All rights reserved.
29 | //
30 |
31 | #if TARGET_OS_OSX
32 |
33 | #import
34 | #import
35 | #import "TOFileSystemItemListChanges.h"
36 |
37 | /// A convenience function to update a standard `NSTableView` with a new set
38 | /// of changes reported by a file system observer.
39 | /// @param tableView The `NSTableView` to update
40 | /// @param changes The changes that were returned from the file system obserrver.
41 | /// @param section The section in the table view that the changes should be applied to.
42 | static inline void TOFileSystemItemListUpdateTableView(NSTableView * _Nonnull tableView,
43 | TOFileSystemItemListChanges * _Nonnull changes,
44 | NSInteger section)
45 | {
46 | // Perform any cell re-ordering in its own batch operation first
47 | if (changes.hasItemMovements) {
48 | [tableView beginUpdates];
49 | {
50 | NSArray *sourceMovements = [changes indexPathsForMovementSourcesInSection:section];
51 | NSArray *destinationMovements = [changes indexPathsForMovementDestinationsWithSourceIndexPaths:sourceMovements];
52 | for (NSInteger i = 0; i < sourceMovements.count; i++) {
53 | [tableView moveRowAtIndex:sourceMovements[i].item toIndex:destinationMovements[i].item];
54 | }
55 | }
56 | [tableView endUpdates];
57 | }
58 |
59 | // Perform all additional cell update operations afterwards
60 | if (changes.hasItemChanges) {
61 | [tableView beginUpdates];
62 | {
63 | NSMutableIndexSet *changesList = [[NSMutableIndexSet alloc] init];
64 | [[changes indexPathsForDeletionsInSection:section] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
65 | [changesList addIndex:(NSUInteger)obj.item];
66 | }];
67 | [tableView removeRowsAtIndexes:changesList withAnimation:NSTableViewAnimationEffectFade];
68 |
69 | NSMutableIndexSet *insertsList = [[NSMutableIndexSet alloc] init];
70 | [[changes indexPathsForInsertionsInSection:section] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
71 | [insertsList addIndex:(NSUInteger)obj.item];
72 | }];
73 | [tableView insertRowsAtIndexes:insertsList withAnimation:NSTableViewAnimationEffectFade];
74 |
75 | NSMutableIndexSet *reloadsList = [[NSMutableIndexSet alloc] init];
76 | [[changes indexPathsForModificationsInSection:section] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
77 | [reloadsList addIndex:(NSUInteger)obj.item];
78 | }];
79 | [tableView reloadDataForRowIndexes:reloadsList columnIndexes:[NSIndexSet indexSetWithIndex:0]];
80 | }
81 | [tableView endUpdates];
82 | }
83 | }
84 |
85 | #endif
86 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Utilities/TOFileSystemObserver+UIKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemObserver+UIKit.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #if TARGET_OS_IOS
24 |
25 | #import
26 | #import
27 | #import "TOFileSystemItemListChanges.h"
28 |
29 | /// A convenience function to update a standard `UITableView` with a new set
30 | /// of changes reported by a file system observer.
31 | /// @param tableView The `UITableView` to update
32 | /// @param changes The changes that were returned from the file system obserrver.
33 | /// @param section The section in the table view that the changes should be applied to.
34 | static inline void TOFileSystemItemListUpdateTableView(UITableView * _Nonnull tableView,
35 | TOFileSystemItemListChanges * _Nonnull changes,
36 | NSInteger section)
37 | {
38 | // Perform any cell re-ordering in its own batch operation first
39 | if (changes.hasItemMovements) {
40 | [tableView beginUpdates];
41 | {
42 | NSArray *sourceMovements = [changes indexPathsForMovementSourcesInSection:section];
43 | NSArray *destinationMovements = [changes indexPathsForMovementDestinationsWithSourceIndexPaths:sourceMovements];
44 | for (NSInteger i = 0; i < sourceMovements.count; i++) {
45 | [tableView moveRowAtIndexPath:sourceMovements[i] toIndexPath:destinationMovements[i]];
46 | }
47 | }
48 | [tableView endUpdates];
49 | }
50 |
51 | // Perform all additional cell update operations afterwards
52 | if (changes.hasItemChanges) {
53 | [tableView beginUpdates];
54 | {
55 | [tableView deleteRowsAtIndexPaths:[changes indexPathsForDeletionsInSection:section]
56 | withRowAnimation:UITableViewRowAnimationAutomatic];
57 | [tableView insertRowsAtIndexPaths:[changes indexPathsForInsertionsInSection:section]
58 | withRowAnimation:UITableViewRowAnimationAutomatic];
59 | [tableView reloadRowsAtIndexPaths:[changes indexPathsForModificationsInSection:section]
60 | withRowAnimation:UITableViewRowAnimationAutomatic];
61 | }
62 | [tableView endUpdates];
63 | }
64 | }
65 |
66 | /// A convenience function to update a standard `UICollectionView` with a new set
67 | /// of changes reported by a file system observer.
68 | /// @param collectionView The `UICollectionView` to update
69 | /// @param changes The changes that were returned from the file system obserrver.
70 | /// @param section The section in the table view that the changes should be applied to.
71 | static inline void TOFileSystemItemListUpdateCollectionView(UICollectionView * _Nonnull collectionView,
72 | TOFileSystemItemListChanges * _Nonnull changes,
73 | NSInteger section)
74 | {
75 | // Perform any cell re-ordering in its own batch operation first
76 | [collectionView performBatchUpdates:^{
77 | NSArray *sourceMovements = [changes indexPathsForMovementSourcesInSection:section];
78 | NSArray *destinationMovements = [changes indexPathsForMovementDestinationsWithSourceIndexPaths:sourceMovements];
79 | for (NSInteger i = 0; i < sourceMovements.count; i++) {
80 | [collectionView moveItemAtIndexPath:sourceMovements[i] toIndexPath:destinationMovements[i]];
81 | }
82 | } completion:nil];
83 |
84 | // Perform all additional cell update operations afterwards
85 | [collectionView performBatchUpdates:^{
86 | [collectionView deleteItemsAtIndexPaths:[changes indexPathsForDeletionsInSection:section]];
87 | [collectionView insertItemsAtIndexPaths:[changes indexPathsForInsertionsInSection:section]];
88 | [collectionView reloadItemsAtIndexPaths:[changes indexPathsForModificationsInSection:section]];
89 | } completion:nil];
90 | }
91 |
92 | #endif
93 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/Utilities/TOFileSystemObserverConstants.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemObserverConstants.h
3 | //
4 | // Copyright 2019-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | @class TOFileSystemObserver;
26 | @class TOFileSystemChanges;
27 | @class TOFileSystemItemList;
28 | @class TOFileSystemItemListChanges;
29 |
30 | NS_ASSUME_NONNULL_BEGIN
31 |
32 | /** When calling a notification block, these are the types of events available. */
33 | typedef NS_ENUM(NSInteger, TOFileSystemObserverNotificationType) {
34 | TOFileSystemObserverNotificationTypeDidChange, // The scanner detected a file change
35 | TOFileSystemObserverNotificationTypeWillBeginFullScan, // The scanner is about to perform a full-scan
36 | TOFileSystemObserverNotificationTypeDidCompleteFullScan // The scanner just completed a full scan
37 | } NS_SWIFT_NAME(FileSystemObserver.NotificationType);
38 |
39 | /** The different options for ordering item lists */
40 | typedef NS_ENUM(NSInteger, TOFileSystemItemListOrder) {
41 | TOFileSystemItemListOrderAlphanumeric, // Alphanumeric ordering
42 | TOFileSystemItemListOrderDate, // Creation date
43 | TOFileSystemItemListOrderSize // File size
44 | } NS_SWIFT_NAME(FileSystemItemList.Order);
45 |
46 | // The different types of items stored in the file system
47 | typedef NS_ENUM(NSInteger, TOFileSystemItemType) {
48 | TOFileSystemItemTypeFile, // A standard file
49 | TOFileSystemItemTypeDirectory // A folder
50 | } NS_SWIFT_NAME(FileSystemItem.Type);
51 |
52 | /** Whenever a scan event occurs, all registered notification blocks will be called with the change information. */
53 | typedef void (^TOFileSystemNotificationBlock)(TOFileSystemObserver * _Nonnull observer,
54 | TOFileSystemObserverNotificationType type,
55 | TOFileSystemChanges * _Nullable changes)
56 | NS_SWIFT_NAME(FileSystemNotificationBlock);
57 |
58 | /** A block that may be registered in order to observe when items in the list change. */
59 | typedef void (^TOFileSystemItemListNotificationBlock)(TOFileSystemItemList * _Nonnull itemList,
60 | TOFileSystemItemListChanges * _Nullable changes)
61 | NS_SWIFT_NAME(FileSystemItemListNotificationBlock);
62 |
63 | /**
64 | A notification that will be broadcast before a full file system scan starts.
65 | It will be called on the same thread as the scan operation.
66 | */
67 | extern NSNotificationName const TOFileSystemObserverWillBeginFullScanNotification
68 | NS_SWIFT_NAME(FileSystemObserverWillBeginFullScanNotification);
69 |
70 | /**
71 | A notification that will be broadcast once a full scan has completed.
72 | It will be called on the same thread as the scan operation.
73 | */
74 | extern NSNotificationName const TOFileSystemObserverDidCompleteFullScanNotification
75 | NS_SWIFT_NAME(FileSystemObserverWillDidCompleteScanNotification);
76 |
77 | /**
78 | A notification that will be broadcast when a file change was observed.
79 |
80 | It will be called on the same thread as the scan operation.
81 | */
82 | extern NSNotificationName const TOFileSystemObserverDidChangeNotification
83 | NS_SWIFT_NAME(FileSystemObserverDidChangeNotification);
84 |
85 | /**
86 | When a notification is broadcasted from an observer, a reference to that
87 | observer object will be included in `userInfo` under this key.
88 | */
89 | extern NSString * const TOFileSystemObserverUserInfoKey
90 | NS_SWIFT_NAME(FileSystemObserverUserInfoKey);
91 |
92 | /**
93 | When a notification is broadcasted, all of the changes that were detected
94 | will be provided in the notification `userInfo` dictionary under this key.
95 | */
96 | extern NSString * const TOFileSystemObserverChangesUserInfoKey
97 | NS_SWIFT_NAME(FileSystemObserverChangesUserInfoKey);
98 |
99 | /**
100 | When checking for if a file is still copying, a little bit of a delay is used when
101 | comparing the file's modification date to the current date since there will always be
102 | around a second of drift.
103 | */
104 | static NSTimeInterval const kTOFileSystemObserverCopyingTimeDelay = 3.0f;
105 |
106 | NS_ASSUME_NONNULL_END
107 |
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/NSFileManager+TOFileSystemDirectoryEnumerator.h:
--------------------------------------------------------------------------------
1 | ../Categories/NSFileManager+TOFileSystemDirectoryEnumerator.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/NSIndexPath+AppKitAdditions.h:
--------------------------------------------------------------------------------
1 | ../Categories/NSIndexPath+AppKitAdditions.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/NSURL+TOFileSystemAttributes.h:
--------------------------------------------------------------------------------
1 | ../Categories/NSURL+TOFileSystemAttributes.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/NSURL+TOFileSystemUUID.h:
--------------------------------------------------------------------------------
1 | ../Categories/NSURL+TOFileSystemUUID.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemChanges+Private.h:
--------------------------------------------------------------------------------
1 | ../Entities/Changes/TOFileSystemChanges+Private.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemChanges.h:
--------------------------------------------------------------------------------
1 | ../Entities/Changes/TOFileSystemChanges.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItem+Private.h:
--------------------------------------------------------------------------------
1 | ../Entities/Items/TOFileSystemItem+Private.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItem.h:
--------------------------------------------------------------------------------
1 | ../Entities/Items/TOFileSystemItem.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItemList+Private.h:
--------------------------------------------------------------------------------
1 | ../Entities/Items/TOFileSystemItemList+Private.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItemList.h:
--------------------------------------------------------------------------------
1 | ../Entities/Items/TOFileSystemItemList.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItemListChanges+Private.h:
--------------------------------------------------------------------------------
1 | ../Entities/Changes/TOFileSystemItemListChanges+Private.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItemListChanges.h:
--------------------------------------------------------------------------------
1 | ../Entities/Changes/TOFileSystemItemListChanges.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItemMapTable.h:
--------------------------------------------------------------------------------
1 | ../Entities/Collections/TOFileSystemItemMapTable.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemItemURLDictionary.h:
--------------------------------------------------------------------------------
1 | ../Entities/Collections/TOFileSystemItemURLDictionary.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemNotificationToken+Private.h:
--------------------------------------------------------------------------------
1 | ../Entities/Notifications/TOFileSystemNotificationToken+Private.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemNotificationToken.h:
--------------------------------------------------------------------------------
1 | ../Entities/Notifications/TOFileSystemNotificationToken.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemObserver+AppKit.h:
--------------------------------------------------------------------------------
1 | ../Utilities/TOFileSystemObserver+AppKit.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemObserver+UIKit.h:
--------------------------------------------------------------------------------
1 | ../Utilities/TOFileSystemObserver+UIKit.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemObserver.h:
--------------------------------------------------------------------------------
1 | ../TOFileSystemObserver.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemObserverConstants.h:
--------------------------------------------------------------------------------
1 | ../Utilities/TOFileSystemObserverConstants.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemPath.h:
--------------------------------------------------------------------------------
1 | ../Entities/FilePaths/TOFileSystemPath.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemPresenter.h:
--------------------------------------------------------------------------------
1 | ../Scanning/TOFileSystemPresenter.h
--------------------------------------------------------------------------------
/TOFileSystemObserver/include/TOFileSystemScanOperation.h:
--------------------------------------------------------------------------------
1 | ../Scanning/TOFileSystemScanOperation.h
--------------------------------------------------------------------------------
/TOFileSystemObserverExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample.xcodeproj/xcshareddata/xcschemes/TOFileSystemObserverExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
71 |
73 |
79 |
80 |
81 |
82 |
84 |
85 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample.xcodeproj/xcshareddata/xcschemes/TOFileSystemObserverMacExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample.xcodeproj/xcshareddata/xcschemes/TOFileSystemObserverTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
61 |
62 |
64 |
65 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | LSSupportsOpeningDocumentsInPlace
24 |
25 | UIFileSharingEnabled
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/TOAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // TOFileSystemObserverExample
4 | //
5 | // Created by Tim Oliver on 28/9/19.
6 | // Copyright © 2019 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface TOAppDelegate : UIResponder
12 |
13 | @property (nonatomic, strong) UIWindow *window;
14 |
15 | @end
16 |
17 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/TOAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // TOFileSystemObserverExample
4 | //
5 | // Created by Tim Oliver on 28/9/19.
6 | // Copyright © 2019 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import "TOAppDelegate.h"
10 | #import "TOViewController.h"
11 | #import "TOFileSystemPath.h"
12 |
13 | @implementation TOAppDelegate
14 |
15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
16 | self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
17 |
18 | [self setUpDefaultData];
19 |
20 | TOViewController *viewController = [[TOViewController alloc] init];
21 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
22 | self.window.rootViewController = navigationController;
23 | [self.window makeKeyAndVisible];
24 |
25 | return YES;
26 | }
27 |
28 | - (void)setUpDefaultData
29 | {
30 | NSURL *documentsDirectory = [TOFileSystemPath documentsDirectoryURL];
31 | NSFileManager *fileManager = [NSFileManager defaultManager];
32 | NSInteger numberOfItems = [fileManager contentsOfDirectoryAtURL:documentsDirectory
33 | includingPropertiesForKeys:nil
34 | options:0
35 | error:nil].count;
36 | if (numberOfItems > 1) { return; }
37 |
38 | // Create 5 folders
39 | for (NSInteger i = 0; i < 5; i++) {
40 | NSString *folderName = [NSString stringWithFormat:@"Folder %d", (int)i+1];
41 | NSURL *folderURL = [documentsDirectory URLByAppendingPathComponent:folderName];
42 | [fileManager createDirectoryAtURL:folderURL withIntermediateDirectories:YES attributes:nil error:nil];
43 | }
44 |
45 | // Create 5 arbitrary files
46 | NSArray *sizes = @[@(5), @(12), @(33), @(15), @(20)];
47 | for (NSInteger i = 0; i < sizes.count; i++) {
48 | NSString *fileName = [NSString stringWithFormat:@"File-%d.dat", (int)i+1];
49 | NSURL *fileURL = [documentsDirectory URLByAppendingPathComponent:fileName];
50 | FILE *file = fopen([fileURL.path cStringUsingEncoding:NSUTF8StringEncoding], "wb");
51 |
52 | NSInteger byteCount = [sizes[i] intValue] * 1000000;
53 | for (NSInteger j = 0; j < byteCount; j++) {
54 | fwrite("0", 1, 1, file);
55 | }
56 | fclose(file);
57 | }
58 | }
59 |
60 | @end
61 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/TOImages.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOImages.h
3 | // TOFileSystemObserverExample
4 | //
5 | // Created by Tim Oliver on 2020/01/27.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface TOImages : UIImage
14 |
15 | + (UIImage *)downloadIcon;
16 |
17 | + (UIImage *)documentPickerDefaultFolderForStyle:(BOOL)darkMode;
18 |
19 | + (UIImage *)documentPickerFolderIconWithSize:(CGSize)size
20 | backgroundColor:(UIColor *)backgroundColor
21 | foregroundBottomColor:(UIColor *)foregroundBottomColor
22 | foregroundTopColor:(UIColor *)foregroundTopColor;
23 |
24 | + (UIImage *)documentPickerDefaultFileIconWithExtension:(NSString *)extension
25 | tintColor:(UIColor *)tintColor
26 | style:(BOOL)darkMode;
27 |
28 | + (UIImage *)documentPickerIconWithSize:(CGSize)size
29 | outlineColor:(UIColor *)outlineColor
30 | backgroundColor:(UIColor *)backgroundColor
31 | cornerColor:(UIColor *)cornerColor
32 | formatNameString:(NSString *)formatNameString
33 | formatNameFont:(UIFont *)formatNameFont
34 | formatNameColor:(UIColor *)formatNameColor;
35 |
36 | @end
37 |
38 | NS_ASSUME_NONNULL_END
39 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/TOViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // TOFileSystemObserverExample
4 | //
5 | // Created by Tim Oliver on 28/9/19.
6 | // Copyright © 2019 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface TOViewController : UITableViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/TOViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // TOFileSystemObserverExample
4 | //
5 | // Created by Tim Oliver on 28/9/19.
6 | // Copyright © 2019 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import "TOViewController.h"
10 |
11 | #import "TOImages.h"
12 | #import "TOFileSystemObserver.h"
13 | #import "TOFileSystemObserver+UIKit.h"
14 |
15 | @interface TOViewController ()
16 |
17 | @property (nonatomic, strong) TOFileSystemObserver *observer;
18 | @property (nonatomic, strong) TOFileSystemItemList *fileItemList;
19 | @property (nonatomic, strong) TOFileSystemNotificationToken *listToken;
20 | @property (nonatomic, strong) TOFileSystemNotificationToken *observerToken;
21 |
22 | @property (nonatomic, strong) UIImage *folderIcon;
23 | @property (nonatomic, strong) UIImage *fileIcon;
24 |
25 | @end
26 |
27 | @implementation TOViewController
28 |
29 | - (void)viewDidLoad {
30 | [super viewDidLoad];
31 |
32 | self.title = @"TOFileSystemObserver";
33 | self.tableView.rowHeight = 74;
34 |
35 | self.folderIcon = [TOImages documentPickerDefaultFolderForStyle:NO];
36 | self.fileIcon = [TOImages documentPickerDefaultFileIconWithExtension:@"" tintColor:self.view.tintColor style:NO];
37 |
38 | // Create a file system observer and start it
39 | self.observer = [[TOFileSystemObserver alloc] init];
40 | [self.observer start];
41 |
42 | // Create a live list of the base folder for this controller
43 | self.fileItemList = [self.observer itemListForDirectoryAtURL:nil];
44 |
45 | __weak typeof(self) weakSelf = self;
46 | self.listToken = [self.fileItemList addNotificationBlock:^(TOFileSystemItemList *itemList,
47 | TOFileSystemItemListChanges *changes)
48 | {
49 | TOFileSystemItemListUpdateTableView(weakSelf.tableView, changes, 0);
50 | }];
51 |
52 | self.observerToken = [self.observer addNotificationBlock:^(TOFileSystemObserver *observer,
53 | TOFileSystemObserverNotificationType type,
54 | TOFileSystemChanges *changes)
55 | {
56 | if (type == TOFileSystemObserverNotificationTypeWillBeginFullScan) {
57 | NSLog(@"Scan Will Start!");
58 | return;
59 | }
60 |
61 | if (type == TOFileSystemObserverNotificationTypeDidCompleteFullScan) {
62 | NSLog(@"Scan Complete!");
63 | return;
64 | }
65 |
66 | NSLog(@"%@", changes);
67 | }];
68 |
69 | // Add test button for flipping direction
70 | UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"Asc"
71 | style:UIBarButtonItemStylePlain
72 | target:self
73 | action:@selector(leftButtonTapped:)];
74 | self.navigationItem.leftBarButtonItem = leftButton;
75 |
76 | // Add test button for rotating list order
77 | UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"Name"
78 | style:UIBarButtonItemStylePlain
79 | target:self
80 | action:@selector(rightButtonTapped:)];
81 | self.navigationItem.rightBarButtonItem = rightButton;
82 | }
83 |
84 | - (void)leftButtonTapped:(id)sender
85 | {
86 | BOOL descending = !self.fileItemList.isDescending;
87 | UIBarButtonItem *item = (UIBarButtonItem *)sender;
88 | item.title = descending ? @"Desc" : @"Asc";
89 | self.fileItemList.isDescending = descending;
90 | }
91 |
92 | - (void)rightButtonTapped:(id)sender
93 | {
94 | TOFileSystemItemListOrder order = self.fileItemList.listOrder + 1;
95 | if (order > TOFileSystemItemListOrderSize) { order = 0; }
96 |
97 | UIBarButtonItem *item = (UIBarButtonItem *)sender;
98 | switch (order) {
99 | case TOFileSystemItemListOrderSize:
100 | item.title = @"Size";
101 | break;
102 | case TOFileSystemItemListOrderDate:
103 | item.title = @"Date";
104 | break;
105 | default:
106 | item.title = @"Name";
107 | break;
108 | }
109 |
110 | self.fileItemList.listOrder = order;
111 | }
112 |
113 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
114 | {
115 | return 1;
116 | }
117 |
118 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
119 | {
120 | return self.fileItemList.count;
121 | }
122 |
123 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
124 | {
125 | static NSString *cellIdentifier = @"Cell";
126 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
127 | if (cell == nil) {
128 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
129 | }
130 |
131 | TOFileSystemItem *fileItem = self.fileItemList[indexPath.row];
132 | cell.textLabel.text = fileItem.name;
133 |
134 | if (fileItem.isCopying) {
135 | cell.detailTextLabel.text = @"Copying";
136 | }
137 | else {
138 | if (fileItem.type == TOFileSystemItemTypeDirectory) {
139 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
140 | if (fileItem.numberOfSubItems == 1) {
141 | cell.detailTextLabel.text = @"1 item";
142 | }
143 | else {
144 | cell.detailTextLabel.text = [NSString stringWithFormat:@"%ld items", (long)fileItem.numberOfSubItems];
145 | }
146 | cell.imageView.image = self.folderIcon;
147 |
148 | UIEdgeInsets insets = cell.layoutMargins;
149 | insets.left = 16;
150 | cell.layoutMargins = insets;
151 | }
152 | else {
153 | cell.accessoryType = UITableViewCellAccessoryNone;
154 | cell.detailTextLabel.text = [NSByteCountFormatter stringFromByteCount:fileItem.size
155 | countStyle:NSByteCountFormatterCountStyleFile];
156 | cell.imageView.image = self.fileIcon;
157 |
158 | UIEdgeInsets insets = cell.layoutMargins;
159 | insets.left = 27;
160 | cell.layoutMargins = insets;
161 | }
162 | }
163 |
164 | return cell;
165 | }
166 |
167 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
168 | {
169 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
170 | }
171 |
172 | @end
173 |
--------------------------------------------------------------------------------
/TOFileSystemObserverExample/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // TOFileSystemObserverExample
4 | //
5 | // Created by Tim Oliver on 28/9/19.
6 | // Copyright © 2019 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "TOAppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | NSString * appDelegateClassName;
14 | @autoreleasepool {
15 | // Setup code that might create autoreleased objects goes here.
16 | appDelegateClassName = NSStringFromClass([TOAppDelegate class]);
17 | }
18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName);
19 | }
20 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/ARAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // TOFileSystemObserverMacExample
4 | //
5 | // Created by Anatoly Rosencrantz on 26/03/2020.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ARAppDelegate : NSObject
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/ARAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // TOFileSystemObserverMacExample
4 | //
5 | // Created by Anatoly Rosencrantz on 26/03/2020.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import "ARAppDelegate.h"
10 | #import "TOFileSystemPath.h"
11 |
12 | @interface ARAppDelegate ()
13 |
14 | @end
15 |
16 | @implementation ARAppDelegate
17 |
18 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
19 | [self setUpDefaultData];
20 | }
21 |
22 |
23 | - (void)setUpDefaultData
24 | {
25 | NSURL *documentsDirectory = [TOFileSystemPath documentsDirectoryURL];
26 | NSFileManager *fileManager = [NSFileManager defaultManager];
27 | NSInteger numberOfItems = [fileManager contentsOfDirectoryAtURL:documentsDirectory
28 | includingPropertiesForKeys:nil
29 | options:0
30 | error:nil].count;
31 | if (numberOfItems > 1) { return; }
32 |
33 | // Create 5 folders
34 | for (NSInteger i = 0; i < 5; i++) {
35 | NSString *folderName = [NSString stringWithFormat:@"Folder %d", (int)i+1];
36 | NSURL *folderURL = [documentsDirectory URLByAppendingPathComponent:folderName];
37 | [fileManager createDirectoryAtURL:folderURL withIntermediateDirectories:YES attributes:nil error:nil];
38 | }
39 |
40 | // Create 5 arbitrary files
41 | NSArray *sizes = @[@(5), @(12), @(33), @(15), @(20)];
42 | for (NSInteger i = 0; i < sizes.count; i++) {
43 | NSString *fileName = [NSString stringWithFormat:@"File-%d.dat", (int)i+1];
44 | NSURL *fileURL = [documentsDirectory URLByAppendingPathComponent:fileName];
45 | FILE *file = fopen([fileURL.path cStringUsingEncoding:NSUTF8StringEncoding], "wb");
46 |
47 | NSInteger byteCount = [sizes[i] intValue] * 1000000;
48 | for (NSInteger j = 0; j < byteCount; j++) {
49 | fwrite("0", 1, 1, file);
50 | }
51 | fclose(file);
52 | }
53 | }
54 |
55 | @end
56 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/ARImages.h:
--------------------------------------------------------------------------------
1 | //
2 | // ARImages.h
3 | // TOFileSystemObserverExample
4 | //
5 | // Created by Anatoly Rosencrantz on 26/03/2020.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface ARImages : NSImage
14 |
15 | + (NSImage *)documentPickerDefaultFolderForStyle:(BOOL)darkMode;
16 |
17 | + (NSImage *)documentPickerDefaultFileIconWithExtension:(NSString *)extension
18 | tintColor:(NSColor *)tintColor
19 | style:(BOOL)darkMode;
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
24 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/ARImages.m:
--------------------------------------------------------------------------------
1 | //
2 | // ARImages.m
3 | // TOFileSystemObserverMacExample
4 | //
5 | // Created by Anatoly Rosencrantz on 26/03/2020.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ARImages.h"
11 |
12 | @implementation ARImages
13 | + (NSURL *)documentsDirectoryURL
14 | {
15 | NSFileManager *fileManager = [NSFileManager defaultManager];
16 | return [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject;
17 | }
18 |
19 | + (NSImage *)documentPickerDefaultFolderForStyle:(BOOL)darkMode
20 | {
21 | NSURL* url = [[self documentsDirectoryURL] URLByAppendingPathComponent:@".tmp"];
22 | [[NSFileManager defaultManager] createDirectoryAtURL:url withIntermediateDirectories:false attributes:nil error:nil];
23 | NSImage *icon = [[url resourceValuesForKeys:@[NSURLEffectiveIconKey] error:nil] objectForKey:NSURLEffectiveIconKey];
24 | [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
25 | return icon;
26 | }
27 |
28 | + (NSImage *)documentPickerDefaultFileIconWithExtension:(NSString *)extension
29 | tintColor:(NSColor *)tintColor
30 | style:(BOOL)darkMode
31 | {
32 | NSURL* url = [[self documentsDirectoryURL] URLByAppendingPathComponent:@".tmp"];
33 | NSData* data = [[NSData alloc] init];
34 | [[NSFileManager defaultManager] createFileAtPath:url.path contents:data attributes:nil];
35 | NSImage *icon = [[url resourceValuesForKeys:@[NSURLEffectiveIconKey] error:nil] objectForKey:NSURLEffectiveIconKey];
36 | [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
37 | return icon;
38 | }
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/ARViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // TOFileSystemObserverMacExample
4 | //
5 | // Created by Anatoly Rosencrantz on 26/03/2020.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ARViewController : NSViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/ARViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // TOFileSystemObserverMacExample
4 | //
5 | // Created by Anatoly Rosencrantz on 26/03/2020.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import "ARViewController.h"
10 | #import "TOFileSystemObserver.h"
11 | #import "TOFileSystemObserver+AppKit.h"
12 | #import "ARImages.h"
13 |
14 | @interface ARViewController ()
15 |
16 | @property (nonatomic, strong) TOFileSystemObserver *observer;
17 | @property (nonatomic, strong) TOFileSystemItemList *fileItemList;
18 | @property (nonatomic, strong) TOFileSystemNotificationToken *listToken;
19 | @property (nonatomic, strong) TOFileSystemNotificationToken *observerToken;
20 |
21 | @property (nonatomic, strong) NSImage *folderIcon;
22 | @property (nonatomic, strong) NSImage *fileIcon;
23 |
24 | @property (weak) IBOutlet NSTableView *tableView;
25 |
26 | @end
27 |
28 | @implementation ARViewController
29 |
30 | - (void)viewDidLoad {
31 | [super viewDidLoad];
32 |
33 | self.folderIcon = [ARImages documentPickerDefaultFolderForStyle:NO];
34 | self.fileIcon = [ARImages documentPickerDefaultFileIconWithExtension:@"" tintColor:[NSColor colorForControlTint:[NSColor currentControlTint]] style:NO];
35 |
36 | // Create a file system observer and start it
37 | self.observer = [[TOFileSystemObserver alloc] init];
38 | [self.observer start];
39 |
40 | // Create a live list of the base folder for this controller
41 | self.fileItemList = [self.observer itemListForDirectoryAtURL:nil];
42 |
43 | __weak typeof(self) weakSelf = self;
44 | self.listToken = [self.fileItemList addNotificationBlock:^(TOFileSystemItemList *itemList,
45 | TOFileSystemItemListChanges *changes)
46 | {
47 | TOFileSystemItemListUpdateTableView(weakSelf.tableView, changes, 0);
48 | }];
49 |
50 | self.observerToken = [self.observer addNotificationBlock:^(TOFileSystemObserver *observer,
51 | TOFileSystemObserverNotificationType type,
52 | TOFileSystemChanges *changes)
53 | {
54 | if (type == TOFileSystemObserverNotificationTypeWillBeginFullScan) {
55 | NSLog(@"Scan Will Start!");
56 | return;
57 | }
58 |
59 | if (type == TOFileSystemObserverNotificationTypeDidCompleteFullScan) {
60 | NSLog(@"Scan Complete!");
61 | return;
62 | }
63 |
64 | NSLog(@"%@", changes);
65 | }];
66 | }
67 |
68 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
69 | return self.fileItemList.count;
70 | }
71 |
72 | - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
73 | TOFileSystemItem *fileItem = self.fileItemList[row];
74 |
75 | if ([tableColumn.identifier isEqualToString: @"NameColumn"]) {
76 | static NSString *cellIdentifier = @"NameCell";
77 | NSTableCellView* cell = [tableView makeViewWithIdentifier:cellIdentifier owner:nil];
78 |
79 | cell.textField.stringValue = fileItem.name;
80 |
81 | if (fileItem.type == TOFileSystemItemTypeDirectory) {
82 | cell.imageView.image = self.folderIcon;
83 | }
84 | else {
85 | cell.imageView.image = self.fileIcon;
86 | }
87 |
88 | return cell;
89 | }
90 | else if ([tableColumn.identifier isEqualToString: @"StatusColumn"]) {
91 | static NSString *cellIdentifier = @"StatusCell";
92 | NSTableCellView* cell = [tableView makeViewWithIdentifier:cellIdentifier owner:nil];
93 |
94 | if (fileItem.isCopying) {
95 | cell.textField.stringValue = @"Copying";
96 | }
97 | else {
98 | if (fileItem.type == TOFileSystemItemTypeDirectory) {
99 | if (fileItem.numberOfSubItems == 1) {
100 | cell.textField.stringValue = @"1 item";
101 | }
102 | else {
103 | cell.textField.stringValue = [NSString stringWithFormat:@"%ld items", (long)fileItem.numberOfSubItems];
104 | }
105 | }
106 | else {
107 | cell.textField.stringValue = [NSByteCountFormatter stringFromByteCount:fileItem.size
108 | countStyle:NSByteCountFormatterCountStyleFile];
109 | }
110 | }
111 | return cell;
112 | }
113 |
114 | return nil;
115 | }
116 |
117 |
118 | @end
119 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSApplicationCategoryType
24 | public.app-category.utilities
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2020 Tim Oliver. All rights reserved.
29 | NSMainStoryboardFile
30 | Main
31 | NSPrincipalClass
32 | NSApplication
33 | NSSupportsAutomaticTermination
34 |
35 | NSSupportsSuddenTermination
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/TOFileSystemObserverMacExample.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/TOFileSystemObserverMacExample/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // TOFileSystemObserverMacExample
4 | //
5 | // Created by Anatoly Rosencrantz on 26/03/2020.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | int main(int argc, const char * argv[]) {
12 | @autoreleasepool {
13 | // Setup code that might create autoreleased objects goes here.
14 | }
15 | return NSApplicationMain(argc, argv);
16 | }
17 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Categories/TOFileSystemEnumeratorTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemEnumeratorTests.m
3 | //
4 | // Copyright 2019-2020 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "NSFileManager+TOFileSystemDirectoryEnumerator.h"
25 |
26 | @interface TOFileSystemEnumeratorTests : XCTestCase
27 |
28 | @property (nonatomic, strong) NSDirectoryEnumerator *enumerator;
29 | @property (nonatomic, strong) NSURL *folderURL;
30 |
31 | @end
32 |
33 | @implementation TOFileSystemEnumeratorTests
34 |
35 | - (void)setUp
36 | {
37 | NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory()];
38 | self.folderURL = [url URLByAppendingPathComponent:@"Folder"];
39 | [NSFileManager.defaultManager createDirectoryAtURL:self.folderURL withIntermediateDirectories:YES attributes:nil error:nil];
40 |
41 | self.enumerator = [NSFileManager.defaultManager to_fileSystemEnumeratorForDirectoryAtURL:url];
42 | }
43 |
44 | - (void)tearDown
45 | {
46 | self.enumerator = nil;
47 | [NSFileManager.defaultManager removeItemAtURL:self.folderURL error:nil];
48 | }
49 |
50 | - (void)testEnumerator
51 | {
52 | NSInteger i = 0;
53 | for (NSURL *url in self.enumerator) {
54 | if (!url) { break; } // Supress unused warning
55 | i++;
56 | }
57 |
58 | XCTAssertTrue(i > 0);
59 | }
60 |
61 | @end
62 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Categories/TOFileSystemFileAttributesTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemFileAttributesTests.m
3 | //
4 | // Copyright 2019-2020 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "NSURL+TOFileSystemAttributes.h"
25 |
26 | const NSInteger kTOFileSystemTestFileSize = 3 * 1000000;
27 |
28 | @interface TOFileSystemFileAttributesTests : XCTestCase
29 |
30 | @property (nonatomic, strong) NSURL *fileURL;
31 | @property (nonatomic, strong) NSURL *directoryURL;
32 | @property (nonatomic, strong) NSURL *subdirectoryURL;
33 |
34 | @end
35 |
36 | @implementation TOFileSystemFileAttributesTests
37 |
38 | - (void)setUp
39 | {
40 | //Generate a file we can use to test
41 | NSURL *tempDirectory = [NSURL fileURLWithPath:NSTemporaryDirectory()];
42 |
43 | // Create a file
44 | self.fileURL = [tempDirectory URLByAppendingPathComponent:@"File-1.dat"];
45 | FILE *file = fopen([self.fileURL.path cStringUsingEncoding:NSUTF8StringEncoding], "wb");
46 | NSInteger byteCount = kTOFileSystemTestFileSize;
47 | for (NSInteger j = 0; j < byteCount; j++) {
48 | fwrite("0", 1, 1, file);
49 | }
50 | fclose(file);
51 |
52 | // Create the folder
53 | self.directoryURL = [tempDirectory URLByAppendingPathComponent:@"TestFolder"];
54 | [NSFileManager.defaultManager createDirectoryAtURL:self.directoryURL withIntermediateDirectories:YES attributes:nil error:nil];
55 |
56 | self.subdirectoryURL = [self.directoryURL URLByAppendingPathComponent:@"SubFolder"];
57 | [NSFileManager.defaultManager createDirectoryAtURL:self.subdirectoryURL withIntermediateDirectories:YES attributes:nil error:nil];
58 | }
59 |
60 | - (void)tearDown
61 | {
62 | [NSFileManager.defaultManager removeItemAtURL:self.fileURL error:nil];
63 | }
64 |
65 | - (void)testCopying
66 | {
67 | // Since this is executed right after creation, this will be true
68 | XCTAssertTrue([self.fileURL to_isCopying]);
69 |
70 | // Wait a second, and it should transition to "completed'
71 | XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Copying File Completed"];
72 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
73 | if ([self.fileURL to_isCopying] == NO) {
74 | [expectation fulfill];
75 | }
76 | });
77 |
78 | [self waitForExpectations:@[expectation] timeout:4.0f];
79 | }
80 |
81 | - (void)testIsDirectory
82 | {
83 | XCTAssertTrue(self.directoryURL.to_isDirectory);
84 | }
85 |
86 | - (void)testSize
87 | {
88 | XCTAssertTrue(self.fileURL.to_size == kTOFileSystemTestFileSize);
89 | }
90 |
91 | - (void)testSubItemCount
92 | {
93 | XCTAssertTrue(self.directoryURL.to_numberOfSubItems == 1);
94 | }
95 |
96 | - (void)testCreationDate
97 | {
98 | XCTAssertNotNil(self.fileURL.to_creationDate);
99 | }
100 |
101 | - (void)testModificationDate
102 | {
103 | XCTAssertNotNil(self.fileURL.to_modificationDate);
104 | }
105 |
106 | @end
107 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Categories/TOFileSystemUUIDTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemUUIDTests.m
3 | //
4 | // Copyright 2019-2020 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 | #import "NSURL+TOFileSystemUUID.h"
25 |
26 | @interface TOFileSystemUUIDTests : XCTestCase
27 | @property (nonatomic, strong) NSURL *itemURL;
28 | @property (nonatomic, strong) NSURL *childItemURL;
29 | @end
30 |
31 | @implementation TOFileSystemUUIDTests
32 |
33 | - (void)setUp {
34 | // Create a temp folder to test
35 | NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"Folder"];
36 | self.itemURL = [NSURL fileURLWithPath:filePath];
37 | [NSFileManager.defaultManager createDirectoryAtURL:self.itemURL
38 | withIntermediateDirectories:YES
39 | attributes:nil
40 | error:nil];
41 |
42 | // Create a temp child folder
43 | self.childItemURL = [self.itemURL URLByAppendingPathComponent:@"ChildFolder"];
44 | [NSFileManager.defaultManager createDirectoryAtURL:self.childItemURL
45 | withIntermediateDirectories:YES
46 | attributes:nil
47 | error:nil];
48 | }
49 |
50 | - (void)tearDown {
51 | // Delete the folder so we can start fresh
52 | [[NSFileManager defaultManager] removeItemAtURL:self.itemURL error:nil];
53 | }
54 |
55 | - (void)testCreatingUUID
56 | {
57 | // Confirm it's nil at the start
58 | XCTAssert([self.itemURL to_fileSystemUUID].length == 0);
59 |
60 | // Generate a UUID for the item
61 | NSString *uuid = [self.itemURL to_generateFileSystemUUID];
62 |
63 | // Compare to the one on disk now
64 | XCTAssert([[self.itemURL to_fileSystemUUID] isEqualToString:uuid]);
65 | }
66 |
67 | - (void)testRepairingUUID
68 | {
69 | // Confirm it's nil at the start
70 | XCTAssertNil([self.itemURL to_fileSystemUUID]);
71 |
72 | // Set a non-uuid value to the file
73 | [self.itemURL to_setFileSystemUUID:@"000000000000000000000000000000000000"];
74 |
75 | // Regenerate a new uuid
76 | NSString *newUUID = [self.itemURL to_fileSystemUUID];
77 |
78 | // Sanity check it's not matching the dummy
79 | XCTAssertNil(newUUID);
80 | }
81 |
82 | @end
83 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Entities/TOFileSystemChangesTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemChangesTests.m
3 | // TOFileSystemObserverTests
4 | //
5 | // Created by Tim Oliver on 2020/01/28.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "TOFileSystemObserver.h"
12 | #import "TOFileSystemChanges+Private.h"
13 |
14 | @interface TOFileSystemChangesTests : XCTestCase
15 |
16 | @property (nonatomic, strong) NSURL *url;
17 | @property (nonatomic, copy) NSString *uuid;
18 |
19 | @property (nonatomic, strong) TOFileSystemObserver *observer;
20 | @property (nonatomic, strong) TOFileSystemChanges *changes;
21 |
22 | @end
23 |
24 | @implementation TOFileSystemChangesTests
25 |
26 | - (void)setUp
27 | {
28 | // Create test data
29 | self.url = [NSURL fileURLWithPath:@"/Documents"];
30 | self.uuid = @"0256d425-f081-4bc3-8db5-bcb158568abb";
31 |
32 | // Create test objects
33 | self.observer = [[TOFileSystemObserver alloc] init];
34 | self.changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self.observer];
35 | }
36 |
37 | - (void)tearDown
38 | {
39 | self.url = nil;
40 | self.uuid = nil;
41 | self.observer = nil;
42 | self.changes = nil;
43 | }
44 |
45 | - (void)testObserverInChanges
46 | {
47 | XCTAssertEqual(self.changes.fileSystemObserver, self.observer);
48 | }
49 |
50 | - (void)testDiscoveries
51 | {
52 | // Test we can properly retrieve discovered items that were added
53 | [self.changes addDiscoveredItemWithUUID:self.uuid fileURL:self.url];
54 | XCTAssert(self.changes.discoveredItems[self.uuid] == self.url);
55 | }
56 |
57 | - (void)testModifications
58 | {
59 | // Test we can properly retrieve modified items that were added
60 | [self.changes addModifiedItemWithUUID:self.uuid fileURL:self.url];
61 | XCTAssert(self.changes.modifiedItems[self.uuid] == self.url);
62 | }
63 |
64 | - (void)testDeletions
65 | {
66 | // Test we can properly retrieve deleted items that were added
67 | [self.changes addDeletedItemWithUUID:self.uuid fileURL:self.url];
68 | XCTAssert(self.changes.deletedItems[self.uuid] == self.url);
69 | }
70 |
71 | - (void)testMovedItems
72 | {
73 | // Test we can properly retrieve moved items that were added
74 | NSURL *newURL = [NSURL fileURLWithPath:@"/Documents/Folder"];
75 | [self.changes addMovedItemWithUUID:self.uuid oldFileURL:self.url newFileURL:newURL];
76 | XCTAssert(self.changes.movedItems[self.uuid].firstObject == self.url);
77 | XCTAssert(self.changes.movedItems[self.uuid].lastObject == newURL);
78 | }
79 |
80 | @end
81 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Entities/TOFileSystemItemDictionaryTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemDictionaryTests.m
3 | //
4 | // Copyright 2019-2020 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | #import "TOFileSystemItemURLDictionary.h"
26 |
27 | @interface TOFileSystemItemDictionaryTests : XCTestCase
28 |
29 | @property (nonatomic, strong) NSString *tempDirectory;
30 | @property (nonatomic, strong) NSURL *baseURL;
31 |
32 | @end
33 |
34 | @implementation TOFileSystemItemDictionaryTests
35 |
36 | - (void)setUp
37 | {
38 | self.tempDirectory = @"/Users/XD/Documents";
39 | self.baseURL = [NSURL fileURLWithPath:self.tempDirectory];
40 | }
41 |
42 | - (void)testCreation
43 | {
44 | TOFileSystemItemURLDictionary *dict = [[TOFileSystemItemURLDictionary alloc] initWithBaseURL:self.baseURL];
45 | XCTAssertNotNil(dict);
46 | }
47 |
48 | - (void)testInsertionAndDeletion
49 | {
50 | TOFileSystemItemURLDictionary *dict = [[TOFileSystemItemURLDictionary alloc] initWithBaseURL:self.baseURL];
51 |
52 | // Test first insertion
53 | NSString *folder1URL = [NSString stringWithFormat:@"%@/Folder1", self.tempDirectory];
54 | NSURL *url = [NSURL fileURLWithPath:folder1URL].URLByStandardizingPath;
55 | dict[@"folder1"] = url;
56 | XCTAssert([url isEqual:dict[@"folder1"]]);
57 |
58 | // Test second insertion
59 | NSString *folder2URL = [NSString stringWithFormat:@"%@/Folder2", self.tempDirectory];
60 | url = [NSURL fileURLWithPath:folder2URL].URLByStandardizingPath;
61 | dict[@"folder2"] = url;
62 | XCTAssert([url isEqual:dict[@"folder2"]]);
63 |
64 | // Test deletion
65 | dict[@"folder1"] = nil;
66 | XCTAssertNil(dict[@"folder1"]);
67 | }
68 |
69 | - (void)testConcurrentReads
70 | {
71 | TOFileSystemItemURLDictionary *dict = [[TOFileSystemItemURLDictionary alloc] initWithBaseURL:self.baseURL];
72 |
73 | NSString *folder1URL = [NSString stringWithFormat:@"%@/Folder1", self.tempDirectory];
74 | NSURL *url = [NSURL fileURLWithPath:folder1URL].URLByStandardizingPath;
75 | dict[@"folder1"] = url;
76 |
77 | // Check read is working on the main thread
78 | XCTAssert([url isEqual:dict[@"folder1"]]);
79 |
80 | // Create expectation to test concurrent execution
81 | XCTestExpectation *expectation = [[XCTestExpectation alloc]
82 | initWithDescription:@"Dictionary Reads Succeeded"];
83 |
84 | // Create dispatch group so we can call a trigger when both queues finish executing
85 | dispatch_group_t dispatchGroup = dispatch_group_create();
86 |
87 | // Kickstart a read on the first thread
88 | dispatch_queue_t firstQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
89 | dispatch_group_async(dispatchGroup, firstQueue, ^ {
90 | XCTAssert([url isEqual:dict[@"folder1"]]);
91 | });
92 |
93 | // Kickstart a read on the second thread
94 | dispatch_queue_t secondQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
95 | dispatch_group_async(dispatchGroup, secondQueue, ^ {
96 | XCTAssert([url isEqual:dict[@"folder1"]]);
97 | });
98 |
99 | // Upon completion of both reads, ensure the dictionary contains both values
100 | dispatch_queue_t mainQueue = dispatch_get_main_queue();
101 | dispatch_group_notify(dispatchGroup, mainQueue, ^ {
102 | [expectation fulfill];
103 | });
104 |
105 | // Wait for the expectation
106 | [self waitForExpectations:@[expectation] timeout:1.0f];
107 | }
108 |
109 | - (void)testConcurrentWrite
110 | {
111 | TOFileSystemItemURLDictionary *dict = [[TOFileSystemItemURLDictionary alloc] initWithBaseURL:self.baseURL];
112 |
113 | // Create some test URLs to inject
114 | NSString *folder1URL = [NSString stringWithFormat:@"%@/Folder1", self.tempDirectory];
115 | NSURL *firstUrl = [NSURL fileURLWithPath:folder1URL].URLByStandardizingPath;
116 |
117 | NSString *folder2URL = [NSString stringWithFormat:@"%@/Folder2", self.tempDirectory];
118 | NSURL *secondUrl = [NSURL fileURLWithPath:folder2URL].URLByStandardizingPath;
119 |
120 | // Create expectation to test concurrent execution
121 | XCTestExpectation *expectation = [[XCTestExpectation alloc]
122 | initWithDescription:@"Dictionary Writes Succeeded"];
123 |
124 | // Create dispatch group so we can call a trigger when both queues finish executing
125 | dispatch_group_t dispatchGroup = dispatch_group_create();
126 |
127 | // Kickstart a write on the first thread
128 | dispatch_queue_t firstQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
129 | dispatch_group_async(dispatchGroup, firstQueue, ^ {
130 | [dict setItemURL:firstUrl forUUID:@"folder1"];
131 | });
132 |
133 | // Kickstart a write on the second thread
134 | dispatch_queue_t secondQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
135 | dispatch_group_async(dispatchGroup, secondQueue, ^ {
136 | [dict setItemURL:secondUrl forUUID:@"folder2"];
137 | });
138 |
139 | // Upon completion of both writes, ensure the dictionary contains both values
140 | dispatch_queue_t mainQueue = dispatch_get_main_queue();
141 | dispatch_group_notify(dispatchGroup, mainQueue, ^ {
142 | XCTAssert([firstUrl isEqual:dict[@"folder1"]]);
143 | XCTAssert([secondUrl isEqual:dict[@"folder2"]]);
144 | XCTAssertNil([dict itemURLForUUID:@"folder3"]);
145 | [expectation fulfill];
146 | });
147 |
148 | // Wait for the expectation
149 | [self waitForExpectations:@[expectation] timeout:1.0f];
150 | }
151 |
152 | @end
153 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Entities/TOFileSystemItemListChangesTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemListChangesTests.m
3 | //
4 | // Copyright 2019-2020 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | #if TARGET_OS_OSX
26 | #import "NSIndexPath+UIKitAdditions.h"
27 | #endif
28 |
29 | #import "TOFileSystemItemListChanges.h"
30 | #import "TOFileSystemItemListChanges+Private.h"
31 |
32 | @interface TOFileSystemItemListChangesTests : XCTestCase
33 |
34 | @end
35 |
36 | @implementation TOFileSystemItemListChangesTests
37 |
38 | - (void)testInsertionIndices
39 | {
40 | // Add an insertion index
41 | TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init];
42 | [changes addInsertionIndex:1];
43 |
44 | // Test that changes are recognized
45 | XCTAssertTrue(changes.hasItemChanges);
46 |
47 | // Check the insertion state
48 | XCTAssert([changes.insertions isEqualToArray:@[@1]]);
49 |
50 | // Check the index path mapping
51 | NSIndexPath *indexPath = [changes indexPathsForInsertionsInSection:1].firstObject;
52 | XCTAssertNotNil(indexPath);
53 | XCTAssertEqual(indexPath.row, 1);
54 | XCTAssertEqual(indexPath.section, 1);
55 | }
56 |
57 | - (void)testDeletionsIndices
58 | {
59 | // Add a deletion index
60 | TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init];
61 | [changes addDeletionIndex:1];
62 |
63 | // Test that changes are recognized
64 | XCTAssertTrue(changes.hasItemChanges);
65 |
66 | // Check the deletion state
67 | XCTAssert([changes.deletions isEqualToArray:@[@1]]);
68 |
69 | // Check the index path mapping
70 | NSIndexPath *indexPath = [changes indexPathsForDeletionsInSection:1].firstObject;
71 | XCTAssertNotNil(indexPath);
72 | XCTAssertEqual(indexPath.row, 1);
73 | XCTAssertEqual(indexPath.section, 1);
74 | }
75 |
76 | - (void)testModificationIndices
77 | {
78 | // Add a deletion index
79 | TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init];
80 | [changes addModificationIndex:1];
81 |
82 | // Test that changes are recognized
83 | XCTAssertTrue(changes.hasItemChanges);
84 |
85 | // Check the deletion state
86 | XCTAssert([changes.modificatons isEqualToArray:@[@1]]);
87 |
88 | // Check the index path mapping
89 | NSIndexPath *indexPath = [changes indexPathsForModificationsInSection:1].firstObject;
90 | XCTAssertNotNil(indexPath);
91 | XCTAssertEqual(indexPath.row, 1);
92 | XCTAssertEqual(indexPath.section, 1);
93 | }
94 |
95 | - (void)testIndexMovements
96 | {
97 | // Add a movement index
98 | TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init];
99 | [changes addMovementWithSourceIndex:1 destinationIndex:2];
100 |
101 | // Check that the state has been validated
102 | XCTAssertTrue(changes.hasItemMovements);
103 |
104 | // Check the movement state
105 | XCTAssert([changes.movements[@1] isEqualToNumber:@2]);
106 |
107 | // Check the index path mapping
108 | NSArray *sourceIndices = [changes indexPathsForMovementSourcesInSection:1];
109 | NSArray *destIndices = [changes indexPathsForMovementDestinationsWithSourceIndexPaths:sourceIndices];
110 | XCTAssert(sourceIndices.count == destIndices.count);
111 |
112 | // Check the index paths
113 | NSIndexPath *sourceIndexPath = sourceIndices.firstObject;
114 | XCTAssertEqual(sourceIndexPath.section, 1);
115 | XCTAssertEqual(sourceIndexPath.row, 1);
116 |
117 | NSIndexPath *destIndexPath = destIndices.firstObject;
118 | XCTAssertEqual(destIndexPath.section, 1);
119 | XCTAssertEqual(destIndexPath.row, 2);
120 | }
121 |
122 | @end
123 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Entities/TOFileSystemItemMapTableTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemMapTableTests.m
3 | // TOFileSystemObserverTests
4 | //
5 | // Created by Tim Oliver on 2020/01/28.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "TOFileSystemItemMapTable.h"
12 |
13 | @interface TOFileSystemItemMapTableTests : XCTestCase
14 |
15 | @property (nonatomic, strong) TOFileSystemItemMapTable *mapTable;
16 | @property (nonatomic, copy) NSString *uuid;
17 | @property (nonatomic, strong) NSString *object;
18 |
19 | @end
20 |
21 | @implementation TOFileSystemItemMapTableTests
22 |
23 | - (void)setUp
24 | {
25 | @autoreleasepool {
26 | self.mapTable = [[TOFileSystemItemMapTable alloc] init];
27 | self.uuid = @"0afec03c-ba74-4b87-9941-9c59bb97ccc4";
28 | self.object = @"XD";
29 | }
30 |
31 | // Insert the item
32 | [self.mapTable setItem:self.object forUUID:self.uuid];
33 | }
34 |
35 | - (void)tearDown
36 | {
37 | self.mapTable = nil;
38 | self.uuid = nil;
39 | self.object = nil;
40 | }
41 |
42 | - (void)testInsertion
43 | {
44 | XCTAssertEqual(self.mapTable.count, 1);
45 | }
46 |
47 | - (void)testRetrieval
48 | {
49 | XCTAssert([[self.mapTable itemForUUID:self.uuid] isEqualToString:self.object]);
50 | }
51 |
52 | - (void)testDeletion
53 | {
54 | [self.mapTable removeItemForUUID:self.uuid];
55 | XCTAssertEqual(self.mapTable.count, 0);
56 | }
57 |
58 | @end
59 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Entities/TOFileSystemItemURLDictionaryTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOFileSystemItemURLDictionaryTests.m
3 | // TOFileSystemObserverTests
4 | //
5 | // Created by Tim Oliver on 2020/01/28.
6 | // Copyright © 2020 Tim Oliver. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "TOFileSystemPath.h"
12 | #import "TOFileSystemItemURLDictionary.h"
13 |
14 |
15 | @interface TOFileSystemItemURLDictionaryTests : XCTestCase
16 |
17 | @property (nonatomic, strong) NSURL *baseURL;
18 | @property (nonatomic, strong) TOFileSystemItemURLDictionary *dictionary;
19 |
20 | @property (nonatomic, strong) NSURL *url;
21 | @property (nonatomic, copy) NSString *uuid;
22 |
23 | @end
24 |
25 | @implementation TOFileSystemItemURLDictionaryTests
26 |
27 | - (void)setUp
28 | {
29 | // Create a test object
30 | self.baseURL = [TOFileSystemPath documentsDirectoryURL];
31 | self.dictionary = [[TOFileSystemItemURLDictionary alloc] initWithBaseURL:self.baseURL];
32 |
33 | // Create some basic test data
34 | self.url = [self.baseURL URLByAppendingPathComponent:@"Folder"];
35 | self.uuid = @"f2a5bc6d-0eab-4970-8650-8629fdc3a866";
36 |
37 | // Insert the test data
38 | [self.dictionary setItemURL:self.url forUUID:self.uuid];
39 | }
40 |
41 | - (void)tearDown
42 | {
43 | self.baseURL = nil;
44 | self.dictionary = nil;
45 | self.url = nil;
46 | self.uuid = nil;
47 | }
48 |
49 | - (void)testInserting
50 | {
51 | // Verify the insertion was stored correctly
52 | XCTAssertEqual(self.dictionary.count, 1);
53 | }
54 |
55 | - (void)testRetrieval
56 | {
57 | // Retrieve the URL and verify it matches what was inserted
58 | XCTAssert([[self.dictionary itemURLForUUID:self.uuid] isEqual:self.url]);
59 |
60 | // Perform an inverse lookup
61 | XCTAssert([[self.dictionary uuidForItemWithURL:self.url] isEqualToString:self.uuid]);
62 | }
63 |
64 | - (void)testSubscripting
65 | {
66 | self.dictionary[self.uuid] = self.url;
67 |
68 | // Verify the insertion was stored correctly
69 | XCTAssertEqual(self.dictionary.count, 1);
70 |
71 | // Retrieve the URL and verify it matches what was inserted
72 | XCTAssert([self.dictionary[self.uuid] isEqual:self.url]);
73 | }
74 |
75 | - (void)testNullability
76 | {
77 | // Test nilling the value removes it
78 | self.dictionary[self.uuid] = nil;
79 | XCTAssertEqual(self.dictionary.count, 0);
80 | }
81 |
82 | - (void)testRemovingSpecificItem
83 | {
84 | // Test deleting a specific item works
85 | [self.dictionary removeItemURLForUUID:self.uuid];
86 | XCTAssertEqual(self.dictionary.count, 0);
87 | }
88 |
89 | - (void)testRemovingAllItems
90 | {
91 | // Test deleting all items
92 | [self.dictionary removeAllItems];
93 | XCTAssertEqual(self.dictionary.count, 0);
94 | }
95 |
96 | - (void)testConcurrency
97 | {
98 | dispatch_group_t group = dispatch_group_create();
99 |
100 | XCTestExpectation *expectation = [[XCTestExpectation alloc]
101 | initWithDescription:@"Both queues executed as expected"];
102 |
103 | // Blank the dictionary before we start
104 | [self.dictionary removeAllItems];
105 |
106 | // First queue execution
107 | dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
108 | self.dictionary[self.uuid] = self.url;
109 | });
110 |
111 | // Second queue execution
112 | dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
113 | NSURL *url = [self.baseURL URLByAppendingPathComponent:@"Folder2"];
114 | NSString *uuid = @"3ccd0073-e57c-42c7-b3be-6410a051c900";
115 | self.dictionary[uuid] = url;
116 | });
117 |
118 | // Wait for completion
119 | dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
120 | XCTAssertEqual(self.dictionary.count, 2);
121 | [expectation fulfill];
122 | });
123 |
124 | [self waitForExpectations:@[expectation] timeout:0.5f];
125 | }
126 |
127 | @end
128 |
--------------------------------------------------------------------------------
/TOFileSystemObserverTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/TOFileSystemObserver/c2d49bd5ef51188df96245a22c0dbadad412c5b2/screenshot.jpg
--------------------------------------------------------------------------------