diff --git a/.travis.yml b/.travis.yml index 2c00112..e1e3de1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,7 @@ before_deploy: - brew update - brew outdated carthage || brew upgrade carthage - carthage version + - carthage build --no-skip-current --verbose - carthage archive $PROJECTNAME deploy: diff --git a/FileProvider.podspec b/FileProvider.podspec index b44ee35..9bded2d 100644 --- a/FileProvider.podspec +++ b/FileProvider.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "FileProvider" - s.version = "0.12.3" + s.version = "0.12.4" s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/OneDrive/SMB2) files on iOS and macOS." # This description is used to generate tags and improve search results. diff --git a/FileProvider.xcodeproj/project.pbxproj b/FileProvider.xcodeproj/project.pbxproj index d4dee0b..5aa7042 100644 --- a/FileProvider.xcodeproj/project.pbxproj +++ b/FileProvider.xcodeproj/project.pbxproj @@ -603,7 +603,7 @@ 799396601D48B7BF00086753 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_VERSION_STRING = 0.12.3; + BUNDLE_VERSION_STRING = 0.12.4; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -633,7 +633,7 @@ 799396611D48B7BF00086753 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_VERSION_STRING = 0.12.3; + BUNDLE_VERSION_STRING = 0.12.4; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; diff --git a/README.md b/README.md index ac483ef..002d039 100644 --- a/README.md +++ b/README.md @@ -329,13 +329,56 @@ let data = "What's up Newyork!".data(encoding: .utf8) documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil) ``` +### Undo Operations + +Providers conform to `FileProviderUndoable` can perform undo for **some** operations like moving/renaming, copying and creating (file or folder). **Now, only `LocalFileProvider` supports this feature.** To implement: + +```swift +// To setup a new UndoManager: +documentsProvider.setupUndoManager() +// or if you have an UndoManager object already: +documentsProvider.undoManager = self.undoManager + +// e.g.: To undo last operation manually: +documentsProvider.undoManager?.undo() +``` + +You can also bind `UndoManager` object with view controller to use shake gesture and builtin undo support in iOS/macOS, add these code to your ViewController class like this sample code: + +```swift +class ViewController: UIViewController + override var canBecomeFirstResponder: Bool { + return true + } + + override var undoManager: UndoManager? { + return (provider as? FileProvideUndoable)?.undoManager + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + // Your code here + UIApplication.shared.applicationSupportsShakeToEdit = true + self.becomeFirstResponder() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + // Your code here + UIApplication.shared.applicationSupportsShakeToEdit = false + self.resignFirstResponder() + } + // The rest of your implementation +} +``` + ### Operation Handle Creating/Copying/Deleting functions return a `OperationHandle` for remote operations. It provides operation type, progress and a `.cancel()` method which allows you to cancel operation in midst. It's not supported by native `(NS)FileManager` so `LocalFileProvider`, but this functionality will be added to future `PosixFileProvider` class. -### Monitoring FIle Changes +### Monitoring File Changes You can monitor updates in some file system (Local and SMB2), there is three methods in supporting provider you can use to register a handler, to unregister and to check whether it's being monitored or not. It's useful to find out when new files added or removed from directory and update user interface. The handler will be dispatched to main threads to avoid UI bugs with a 0.25 sec delay. diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index 288f2a6..fea4405 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -493,9 +493,15 @@ public protocol FileProviderMonitor: FileProviderBasic { } public protocol FileProvideUndoable: FileProviderOperations { + /// To initialize undo manager either call `setupUndoManager()` or set it manually. + /// + /// - Note: Only some operations (moving/renaming, copying and creating) are supported for undoing. + /// - Note: recording operations will occur after setting this object. var undoManager: UndoManager? { get set } + /// UndoManager supports undoing this file operation func canUndo(handle: OperationHandle) -> Bool + /// UndoManager supports undoing this operation func canUndo(operation: FileOperationType) -> Bool } @@ -526,6 +532,13 @@ public extension FileProvideUndoable { return nil } } + + /// Initiates `self.undoManager` if equals with `nil`, and set `levelsOfUndo` to 10. + public func setupUndoManager() { + guard self.undoManager == nil else { return } + self.undoManager = UndoManager() + self.undoManager?.levelsOfUndo = 10 + } } public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying { diff --git a/Sources/LocalFileProvider.swift b/Sources/LocalFileProvider.swift index 1a552b9..0b60143 100644 --- a/Sources/LocalFileProvider.swift +++ b/Sources/LocalFileProvider.swift @@ -200,7 +200,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo } dynamic func doSimpleOperation(_ box: UndoBox) { - _ = self.doOperation(box.operation) { (_) in + guard let _ = self.undoManager else { return } + _ = self.doOperation(box.undoOperation) { (_) in return } } @@ -209,24 +210,23 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo fileprivate func doOperation(_ opType: FileOperationType, data: Data? = nil, atomically: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? { guard let sourcePath = opType.source else { return nil } let destPath = opType.destination - let source: URL, dest: URL? - - if sourcePath.hasPrefix("file://") { - source = URL(string: sourcePath)! - } else { - source = self.url(of: sourcePath) - } + let source: URL = sourcePath.hasPrefix("file://") ? URL(string: sourcePath)! : self.url(of: sourcePath) + let dest: URL? if let destPath = destPath { - if destPath.hasPrefix("file://") { - dest = URL(string: destPath)! - } else { - dest = self.url(of: destPath) - } + dest = destPath.hasPrefix("file://") ? URL(string: destPath)! : self.url(of: destPath) } else { dest = nil } + if let undoManager = self.undoManager, let undoOp = self.undoOperation(for: opType) { + let undoBox = UndoBox(provider: self, operation: opType, undoOperation: undoOp) + undoManager.beginUndoGrouping() + undoManager.registerUndo(withTarget: self, selector: #selector(LocalFileProvider.doSimpleOperation(_:)), object: undoBox) + undoManager.setActionName(opType.actionDescription) + undoManager.endUndoGrouping() + } + let operationHandler: (URL, URL?) -> Void = { source, dest in let successfulSecurityScopedResourceAccess = source.startAccessingSecurityScopedResource() do { @@ -254,11 +254,6 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo source.stopAccessingSecurityScopedResource() } - if let undoOp = self.undoOperation(for: opType) { - let undoBox = UndoBox(provider: self, operation: undoOp) - self.undoManager?.registerUndo(withTarget: self, selector: #selector(LocalFileProvider.doSimpleOperation(_:)), object: undoBox) - } - completionHandler?(nil) DispatchQueue.main.async { self.delegate?.fileproviderSucceed(self, operation: opType) @@ -354,12 +349,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo let url = self.url(of: path) let operationHandler: (URL) -> Void = { url in - guard self.fileManager.fileExists(atPath: url.path) && !url.fileIsDirectory else { - completionHandler(nil, self.throwError(path, code: CocoaError.fileNoSuchFile as FoundationErrorEnum)) - return - } guard let handle = FileHandle(forReadingAtPath: url.path) else { - completionHandler(nil, self.throwError(path, code: CocoaError.fileReadNoPermission as FoundationErrorEnum)) + completionHandler(nil, self.throwError(path, code: CocoaError.fileNoSuchFile as FoundationErrorEnum)) return } @@ -367,17 +358,18 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo handle.closeFile() } + let size = LocalFileObject(fileWithURL: url)?.size ?? -1 + guard size > offset else { + completionHandler(nil, self.throwError(path, code: CocoaError.fileReadTooLarge as FoundationErrorEnum)) + return + } handle.seek(toFileOffset: UInt64(offset)) guard Int64(handle.offsetInFile) == offset else { - completionHandler(nil, self.throwError(path, code: CocoaError.fileReadUnknown as FoundationErrorEnum)) + completionHandler(nil, self.throwError(path, code: CocoaError.fileReadTooLarge as FoundationErrorEnum)) return } let data = handle.readData(ofLength: length) - guard length > 0 && data.count == length else { - completionHandler(nil, self.throwError(path, code: CocoaError.fileReadTooLarge as FoundationErrorEnum)) - return - } completionHandler(data, nil) } @@ -462,6 +454,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo copy.delegate = self.delegate copy.fileOperationDelegate = self.fileOperationDelegate copy.isPathRelative = self.isPathRelative + copy.undoManager = self.undoManager + copy.isCoorinating = self.isCoorinating return copy } } diff --git a/Sources/LocalHelper.swift b/Sources/LocalHelper.swift index 5b27082..05ada33 100644 --- a/Sources/LocalHelper.swift +++ b/Sources/LocalHelper.swift @@ -308,10 +308,12 @@ open class LocalOperationHandle: OperationHandle { class UndoBox: NSObject { weak var provider: FileProvideUndoable? let operation: FileOperationType + let undoOperation: FileOperationType - init(provider: FileProvideUndoable, operation: FileOperationType) { + init(provider: FileProvideUndoable, operation: FileOperationType, undoOperation: FileOperationType) { self.provider = provider self.operation = operation + self.undoOperation = undoOperation } }