Compare commits

...

10 Commits

Author SHA1 Message Date
Amir Abbas 93359d5173 Made FileProviders equatable 2017-02-13 16:45:34 +03:30
Amir Abbas 12ff3a410d Fixed critical bug in LocalFileObject when decoding path 2017-02-13 16:31:29 +03:30
Amir Abbas a7de09362b Equatable FileObject, async completion handler in cloud provider 2017-02-13 12:06:09 +03:30
Amir Abbas b1fe37cf2a Updated Readme, fixed several bugs
- non-locking snapshot of file while uploading to iCloud
- fixed: `CloudFIleProvider.doOperation()` couldn’t handle local urls
- fixed: FileProvider.type didn’t work correctly
2017-02-12 22:45:30 +03:30
Amir Abbas bd59eacee2 Fixed bugs in providers and LocalFileObject initializing 2017-02-12 03:07:40 +03:30
Amir Abbas 42579be371 Added FileProviderHTTPError protocol
- All errors from local provider is now from Cocoa error domain. No URL domain anymore.
- All errors from WebDAV/OneDrive/Dropbox conform to FileProviderHTTPError
2017-02-11 18:05:21 +03:30
Amir Abbas edd4914ca7 Linting, fixed travis wrong device 2017-02-11 06:08:17 +03:30
Amir Abbas bd3e1bd74f Fixed typos in travis.yml 2017-02-10 02:57:15 +03:30
Amir Abbas 371c481482 Fixed project name typo in travis.yml 2017-02-09 15:02:39 +03:30
Amir Abbas dea1d01bf3 Made travis.yml more generic 2017-02-09 14:46:29 +03:30
17 changed files with 209 additions and 113 deletions
+55 -23
View File
@@ -1,46 +1,78 @@
language: objective-c
osx_image: xcode8.2
xcode_project: FileProvider.xcodeproj
xcode_project: $PROJECTNAME.xcodeproj
env:
global:
- PROJECTNAME="FileProvider"
- FRAMEWORK_NAME="FileProvider.framework"
# matrix:
# - SHCEME="FileProvider OSX" SDK="macosx" ACTION="build"
# - SHCEME="FileProvider iOS" SDK="iphonesimulator" ACTION="build"
# - SHCEME="FileProvider tvOS" SDK="appletvsimulator" ACTION="build"
- PROJECT="$PROJECTNAME.xcodeproj"
- FRAMEWORK_NAME="$PROJECTNAME.framework"
- IOS_FRAMEWORK_SCHEME="$PROJECTNAME iOS"
- MACOS_FRAMEWORK_SCHEME="$PROJECTNAME OSX"
- TVOS_FRAMEWORK_SCHEME="$PROJECTNAME tvOS"
- IOS_SDK=iphonesimulator
- MACOS_SDK=macosx
- TVOS_SDK=appletvsimulator
matrix:
- DESTINATION="OS=10.2,name=iPad Air 2" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="YES" CARTHAGEDEPLOY="NO"
- DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO"
- DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO"
- DESTINATION="OS=10.1,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO"
- DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO"
- DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="YES"
before_install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- brew update
- brew outdated carthage || brew upgrade carthage
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
# - gem install xcpretty-travis-formatter
script:
- set pipefail
- set -o pipefail
- xcodebuild -version
- pod lib lint --quick
# - xcodebuild -project $PROJECTNAME.xcodeproj -scheme "$SCHEME" -sdk $SDK $ACTION ONLY_ACTIVE_ARCH=NO
- xcodebuild -project $PROJECTNAME.xcodeproj -scheme "FileProvider OSX" -sdk macosx build | xcpretty
- xcodebuild -project $PROJECTNAME.xcodeproj -scheme "FileProvider iOS" -sdk iphonesimulator build ONLY_ACTIVE_ARCH=NO | xcpretty
- xcodebuild -project $PROJECTNAME.xcodeproj -scheme "FileProvider tvOS" -sdk appletvsimulator build ONLY_ACTIVE_ARCH=NO | xcpretty
# after_success:
# Build Example in Debug if specified
- if [ $BUILD_EXAMPLE == "YES" ]; then
xcodebuild -project "$WORKSPACE" -scheme "$EXAMPLE_SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty;
fi
# Build Framework in Debug and Run Tests if specified
- if [ $RUN_TESTS == "YES" ]; then
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
else
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty;
fi
# Build Framework in Release and Run Tests if specified
- if [ $RUN_TESTS == "YES" ]; then
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
else
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty;
fi
# Run `pod lib lint` if specified
- if [ $POD == "YES" ]; then
pod lib lint;
fi
after_success:
# Run `pod trunk push` if specified
- if [ $POD == "YES" ] && [ -n "$TRAVIS_TAG" ]; then
pod trunk push;
fi
# - bash <(curl -s https://codecov.io/bash)
before_deploy:
- brew update
- brew outdated carthage || brew upgrade carthage
- carthage version
- carthage build --no-skip-current --verbose
- carthage archive $PROJECTNAME
deploy:
provider: releases
api_key: "$GITHUBTOKEN"
file: $FRAMEWORK_NAME.zip
skip_cleanup: true
on:
repo: amosavian/FileProvider
tags: true
repo: amosavian/$PROJECTNAME
tags: true
comdition: "$CARTHAGEDEPLOY = YES"
+1 -1
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.12.4"
s.version = "0.12.7"
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.
+2 -8
View File
@@ -162,14 +162,11 @@
79BD63A71E2CC2940035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
79BD63A91E2CC2BB0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
79BD63AB1E2CC2C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
79BD63AF1E2CC3300035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
79BD63B11E2CC3350035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; };
79BD63B31E2CC33D0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
79BD63B51E2CC3860035128C /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
79BD63B71E2CC38D0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
79BD63B91E2CC39B0035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
79BD63BD1E2CC3C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
79BD63C11E2CC3D30035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
@@ -221,14 +218,11 @@
79BD63C11E2CC3D30035128C /* AVFoundation.framework */,
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */,
79BD63BD1E2CC3C20035128C /* ImageIO.framework */,
79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */,
79BD63B91E2CC39B0035128C /* libxml2.tbd */,
79BD63B71E2CC38D0035128C /* AVFoundation.framework */,
79BD63B51E2CC3860035128C /* CoreFoundation.framework */,
79BD63B31E2CC33D0035128C /* MediaPlayer.framework */,
79BD63B11E2CC3350035128C /* ImageIO.framework */,
79BD63AF1E2CC3300035128C /* libxml2.tbd */,
79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */,
79BD63AB1E2CC2C20035128C /* ImageIO.framework */,
79BD63A91E2CC2BB0035128C /* AVFoundation.framework */,
79BD63A71E2CC2940035128C /* CoreGraphics.framework */,
@@ -603,7 +597,7 @@
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.12.4;
BUNDLE_VERSION_STRING = 0.12.7;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -633,7 +627,7 @@
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.12.4;
BUNDLE_VERSION_STRING = 0.12.7;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
+17 -12
View File
@@ -1,4 +1,4 @@
# FileProvider
![File Provider](fileprovider.png)
>This Swift library provide a swifty way to deal with local and remote files and directories in a unified way.
@@ -24,8 +24,6 @@ This library provides implementaion of WebDav, Dropbox, OneDrive and SMB2 (incom
All functions are async calls and it wont block your main thread.
Local and WebDAV providers are fully tested and can be used in production environment.
## Features
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like searching and reading a portion of file.
@@ -125,7 +123,7 @@ let documentsProvider = LocalFileProvider(sharedContainerId: "group.yourcompany.
You can't change the base url later. and all paths are related to this base url by default.
To initialize an iCloud Container provider use below code, This will automatically manager creating Documents folder in container:
To initialize an iCloud Container provider look at [here](https://medium.com/ios-os-x-development/icloud-drive-documents-1a46b5706fe1) to see how to update project settings then use below code, This will automatically manager creating Documents folder in container:
```swift
let documentsProvider = CloudFileProvider(containerId: nil)
@@ -141,11 +139,11 @@ let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example
* In case you want to connect non-secure servers for WebDAV (http) in iOS 9+ / macOS 10.11+ you should disable App Transport Security (ATS) according to [this guide.](https://gist.github.com/mlynch/284699d676fe9ed0abfa)
* For Dropbox & OneDrive, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered. Also you can use [auth0/Lock](https://github.com/auth0/Lock.iOS-OSX) which provides graphical user interface.
* For Dropbox & OneDrive, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered.
For interaction with UI, set delegate variable of `FileProvider` object
You can use `absoluteURL()` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support and returns path simply wrapped in URL)
You can use `url(of:)` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support and returns path simply wrapped in URL)
### Delegates
@@ -264,7 +262,13 @@ You can then pass "" (empty string) to `contentsOfDirectory` method to list file
Creating new directory:
```swift
documentsProvider.create(folder: "new folder", at: "/", completionHandler: nil)
documentsProvider.create(folder: "new folder", at: "/", completionHandler: { error in
if let error = error {
// Error handling here
} else {
// The operation succeed
}
})
```
Creating new file from data:
@@ -384,8 +388,7 @@ You can monitor updates in some file system (Local and SMB2), there is three met
```swift
// to register a new notification handler
documentsProvider.registerNotifcation(path: provider.currentPath)
{
documentsProvider.registerNotifcation(path: provider.currentPath) {
// calling functions to update UI
}
@@ -408,7 +411,7 @@ To check either file thumbnail is supported or not and fetch thumbnail, use (and
let path = "/newImage.jpg"
let thumbSize = CGSize(width: 64, height: 64)
if documentsProvider.thumbnailOfFileSupported(path: path {
documentsProvider..thumbnailOfFile(path: file.path, dimension: thumbSize, completionHandler: { (image, error) in
documentsProvider.thumbnailOfFile(path: file.path, dimension: thumbSize, completionHandler: { (image, error) in
DispatchQueue.main.async {
self.previewImage.image = image
}
@@ -420,7 +423,7 @@ if documentsProvider.thumbnailOfFileSupported(path: path {
##### Meta-informations
To get meta-information like image/video taken date, dimension, etc., use (and modify) these example code:
To get meta-information like image/video taken date, location, dimension, etc., use (and modify) these example code:
```swift
if documentsProvider..propertiesOfFile(path: file.path, completionHandler: { (propertiesDictionary, keys, error) in
@@ -445,7 +448,9 @@ If you used this library in your project, you can open an issue to inform us.
## Meta
Amir-Abbas Mousavian [@amosavian](https://twitter.com/amosavian)
Amir-Abbas Mousavian [@amosavian](https://twitter.com/amosavian)
Thanks to [Hootan Moradi](https://github.com/hoootan) for designing logo.
Distributed under the MIT license. See `LICENSE` for more information.
+37 -15
View File
@@ -18,6 +18,7 @@ open class CloudFileProvider: LocalFileProvider {
return true
}
set {
assert(true, "CloudFileProvider.isCoorinating can't be set")
return
}
}
@@ -28,6 +29,7 @@ open class CloudFileProvider: LocalFileProvider {
/// Scope of container, indicates user can manipulate data/files or not.
open fileprivate(set) var scope: UbiquitousScope
static open var asserting: Bool = true
/**
Initializes the provider for the iCloud container associated with the specified identifier and
establishes access to that container.
@@ -40,8 +42,8 @@ open class CloudFileProvider: LocalFileProvider {
- Parameter scope: Use `.documents` (default) to put documents that the user is allowed to access inside a Documents subdirectory. Otherwise use `.data` to store user-related data files that your app needs to share but that are not files you want the user to manipulate directly.
*/
public init? (containerId: String?, scope: UbiquitousScope = .documents) {
assert(!Thread.isMainThread, "LocalFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
guard FileManager.default.ubiquityIdentityToken == nil else {
assert(!CloudFileProvider.asserting || !Thread.isMainThread, "LocalFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
guard FileManager.default.ubiquityIdentityToken != nil else {
return nil
}
guard let ubiquityURL = FileManager.default.url(forUbiquityContainerIdentifier: containerId) else {
@@ -51,9 +53,9 @@ open class CloudFileProvider: LocalFileProvider {
self.scope = scope
let baseURL: URL
if scope == .documents {
baseURL = ubiquityURL.standardized.appendingPathComponent("Documents/")
baseURL = ubiquityURL.appendingPathComponent("Documents/")
} else {
baseURL = ubiquityURL.standardized
baseURL = ubiquityURL
}
super.init(baseURL: baseURL)
@@ -106,11 +108,16 @@ open class CloudFileProvider: LocalFileProvider {
}
query.stop()
completionHandler(contents, nil)
self.dispatch_queue.async {
completionHandler(contents, nil)
}
})
DispatchQueue.main.async {
if !query.start() {
completionHandler([], self.throwError(path, code: CocoaError.fileReadNoPermission))
self.dispatch_queue.async {
completionHandler([], self.throwError(path, code: CocoaError.fileReadNoPermission))
}
}
}
}
@@ -138,20 +145,28 @@ open class CloudFileProvider: LocalFileProvider {
guard let result = (query.results as? [NSMetadataItem])?.first, let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
let error = self.throwError(path, code: CocoaError.fileNoSuchFile)
completionHandler(nil, error)
self.dispatch_queue.async {
completionHandler(nil, error)
}
return
}
if let file = self.mapFileObject(attributes: attribs) {
completionHandler(file, nil)
self.dispatch_queue.async {
completionHandler(file, nil)
}
} else {
let noFileError = self.throwError(path, code: CocoaError.fileNoSuchFile)
completionHandler(nil, noFileError)
self.dispatch_queue.async {
completionHandler(nil, noFileError)
}
}
})
DispatchQueue.main.async {
if !query.start() {
completionHandler(nil, self.throwError(path, code: CocoaError.fileReadNoPermission))
self.dispatch_queue.async {
completionHandler(nil, self.throwError(path, code: CocoaError.fileReadNoPermission))
}
}
}
}
@@ -321,13 +336,16 @@ open class CloudFileProvider: LocalFileProvider {
contents.append(file)
}
}
completionHandler(contents, nil)
self.dispatch_queue.async {
completionHandler(contents, nil)
}
})
DispatchQueue.main.async {
if !query.start() {
completionHandler([], self.throwError(path, code: CocoaError.fileReadNoPermission))
self.dispatch_queue.async {
completionHandler([], self.throwError(path, code: CocoaError.fileReadNoPermission))
}
}
}
}
@@ -421,9 +439,13 @@ open class CloudFileProvider: LocalFileProvider {
do {
var expiration: NSDate?
let url = try self.opFileManager.url(forPublishingUbiquitousItemAt: self.url(of: path), expiration: &expiration)
completionHandler(url, nil, expiration as Date?, nil)
self.dispatch_queue.async {
completionHandler(url, nil, expiration as Date?, nil)
}
} catch let e {
completionHandler(nil, nil, nil, e)
self.dispatch_queue.async {
completionHandler(nil, nil, nil, e)
}
}
}
}
+2 -6
View File
@@ -8,14 +8,10 @@
import Foundation
public struct FileProviderDropboxError: Error, CustomStringConvertible {
public struct FileProviderDropboxError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
public var description: String {
return code.description
}
}
public final class DropboxFileObject: FileObject {
@@ -69,7 +65,7 @@ public final class DropboxFileObject: FileObject {
}
}
// codebeat:disable[ARITY]
// codebeat:disable[ARITY]
internal extension DropboxFileProvider {
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
var requestDictionary = [String: AnyObject]()
+1 -1
View File
@@ -166,7 +166,7 @@ public struct LocalFileInformationGenerator {
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
if let cgImage = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
#if os(macOS)
return ImageClass(cgImage: cgImage, size: NSSize.zero)
return ImageClass(cgImage: cgImage, size: .zero)
#else
return ImageClass(cgImage: cgImage)
#endif
+13 -3
View File
@@ -9,7 +9,7 @@
import Foundation
/// Containts path and attributes of a file or resource.
open class FileObject {
open class FileObject: Equatable {
/// A `Dictionary` contains file information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
@@ -142,6 +142,16 @@ open class FileObject {
open var isSymLink: Bool {
return self.type == .symbolicLink
}
public static func ==(rhs: FileObject, lhs: FileObject) -> Bool {
if rhs === lhs {
return true
}
if let rurl = rhs.url, let lurl = lhs.url {
return rurl == lurl
}
return rhs.path == lhs.path && rhs.size == lhs.size && rhs.modifiedDate == lhs.modifiedDate
}
}
internal func resolve(dateString: String) -> Date? {
@@ -270,14 +280,14 @@ public struct FileObjectSorting {
extension Array where Element: FileObject {
/// Returns a sorted array of `FileObject`s by criterias set in properties.
public func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) -> [Element] {
public func sort(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) -> [Element] {
let sorting = FileObjectSorting(type: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
return sorting.sort(self) as! [Element]
}
/// Sorts array of `FileObject`s by criterias set in properties
public mutating func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
self = self.sorted(by: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
self = self.sort(by: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
}
}
+12 -6
View File
@@ -97,7 +97,7 @@ public protocol FileProviderBasic: class {
func url(of path: String?) -> URL
}
public extension FileProviderBasic {
extension FileProviderBasic {
/// The maximum number of queued operations that can execute at the same time.
///
/// The default value of this property is `OperationQueue.defaultMaxConcurrentOperationCount`.
@@ -111,6 +111,12 @@ public extension FileProviderBasic {
}
}
func ==(lhs: FileProviderBasic, rhs: FileProviderBasic) -> Bool {
if lhs === rhs { return true }
return lhs.baseURL == rhs.baseURL && lhs.isPathRelative == rhs.isPathRelative && lhs.currentPath == rhs.currentPath && lhs.credential == rhs.credential
}
/// Cancels all active underlying tasks
public var fileProviderCancelTasksOnInvalidating = true
public protocol FileProviderBasicRemote: FileProviderBasic {
@@ -547,7 +553,7 @@ public protocol FileProvider: FileProviderBasic, FileProviderOperations, FilePro
internal let pathTrimSet = CharacterSet(charactersIn: " /")
extension FileProviderBasic {
public var type: String {
return Self.type
return type(of: self).type
}
/// path without heading and trailing slash
@@ -643,7 +649,7 @@ extension FileProviderBasic {
}
group.leave()
}
_ = group.wait(timeout: DispatchTime.distantFuture)
_ = group.wait(timeout: DispatchTime.now() + 0.5)
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
return (dirPath as NSString).appendingPathComponent(finalFile)
}
@@ -832,14 +838,14 @@ extension ExtendedFileProvider {
let newSize = CGSize(width: width, height: height)
#if os(macOS)
var imageRect = NSRect(origin: CGPoint.zero, size: image.size)
var imageRect = NSRect(origin: .zero, size: image.size)
let imageRef = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
// Create NSImage from the CGImage using the new size
return NSImage(cgImage: imageRef!, size: newSize)
#else
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
image.draw(in: CGRect(origin: CGPoint.zero, size: newSize))
image.draw(in: CGRect(origin: .zero, size: newSize))
let newImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
UIGraphicsEndImageContext()
return newImage
@@ -876,7 +882,7 @@ public enum FileOperationType: CustomStringConvertible {
}
}
/// present participle of action, like 'Copying`.
/// present participle of action, like `Copying`.
public var actionDescription: String {
return description.trimmingCharacters(in: CharacterSet(charactersIn: "e")) + "ing"
}
+28 -12
View File
@@ -73,7 +73,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
case .cachesDirectory:
finalBaseURL = baseURL.appendingPathComponent("Library/Caches")
case .applicationSupportDirectory:
finalBaseURL = baseURL.appendingPathComponent("Library/Application%20support")
finalBaseURL = baseURL.appendingPathComponent("Library/Application support")
default:
break
}
@@ -161,7 +161,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
let opType = FileOperationType.move(source: path, destination: toPath)
if !overwrite && self.fileManager.fileExists(atPath: self.url(of: toPath).path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
completionHandler?(self.throwError(toPath, code: CocoaError.fileWriteFileExists as FoundationErrorEnum))
return nil
}
@@ -173,7 +173,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
let opType = FileOperationType.copy(source: path, destination: toPath)
if !overwrite && self.fileManager.fileExists(atPath: self.url(of: toPath).path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotWriteToFile as FoundationErrorEnum))
completionHandler?(self.throwError(toPath, code: CocoaError.fileWriteFileExists as FoundationErrorEnum))
return nil
}
@@ -188,9 +188,12 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
@discardableResult
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
// TODO: Make use of overwrite parameter
if !overwrite && self.fileManager.fileExists(atPath: self.url(of: toPath).path) {
completionHandler?(self.throwError(toPath, code: CocoaError.fileWriteFileExists as FoundationErrorEnum))
return nil
}
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
return self.doOperation(opType, completionHandler: completionHandler)
return self.doOperation(opType, forUploading: true, completionHandler: completionHandler)
}
@discardableResult
@@ -207,14 +210,27 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
@discardableResult
fileprivate func doOperation(_ opType: FileOperationType, data: Data? = nil, atomically: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
fileprivate func doOperation(_ opType: FileOperationType, data: Data? = nil, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
guard let sourcePath = opType.source else { return nil }
let destPath = opType.destination
let source: URL = sourcePath.hasPrefix("file://") ? URL(string: sourcePath)! : self.url(of: sourcePath)
let dest: URL?
let source: URL
if sourcePath.hasPrefix("file://") {
let removedSchemePath = sourcePath.replacingOccurrences(of: "file://", with: "", options: .anchored)
let pDecodedPath = removedSchemePath.removingPercentEncoding ?? removedSchemePath
source = URL(fileURLWithPath: pDecodedPath)
} else {
source = self.url(of: sourcePath)
}
let dest: URL?
if let destPath = destPath {
dest = destPath.hasPrefix("file://") ? URL(string: destPath)! : self.url(of: destPath)
if destPath.hasPrefix("file://") {
let removedSchemePath = destPath.replacingOccurrences(of: "file://", with: "", options: .anchored)
let pDecodedPath = removedSchemePath.removingPercentEncoding ?? removedSchemePath
dest = URL(fileURLWithPath: pDecodedPath)
} else {
dest = self.url(of: destPath)
}
} else {
dest = nil
}
@@ -277,7 +293,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
intents.append(NSFileAccessIntent.writingIntent(with: source, options: .forReplacing))
case .copy:
guard let dest = dest else { return nil }
intents.append(NSFileAccessIntent.readingIntent(with: source, options: .withoutChanges))
intents.append(NSFileAccessIntent.readingIntent(with: source, options: forUploading ? .forUploading : .withoutChanges))
intents.append(NSFileAccessIntent.writingIntent(with: dest, options: .forReplacing))
case .move:
guard let dest = dest else { return nil }
@@ -451,11 +467,11 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
open func copy(with zone: NSZone? = nil) -> Any {
let copy = LocalFileProvider(baseURL: self.baseURL!)
copy.currentPath = self.currentPath
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.isPathRelative = self.isPathRelative
copy.undoManager = self.undoManager
copy.isCoorinating = self.isCoorinating
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
return copy
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ public final class LocalFileObject: FileObject {
public convenience init? (fileWithPath path: String, relativeTo relativeURL: URL?) {
var fileURL: URL?
var rpath = path.replacingOccurrences(of: relativeURL?.absoluteString ?? "", with: "", options: .anchored)
var rpath = path.replacingOccurrences(of: relativeURL?.path ?? "", with: "", options: .anchored)
if path.hasPrefix("/") {
rpath.remove(at: rpath.startIndex)
}
+2 -1
View File
@@ -67,7 +67,8 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
- cache: A URLCache to cache downloaded files and contents. If set to nil, URLCache.shared object will be used.
*/
public init(credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) {
self.baseURL = (serverURL ?? URL(string: "https://api.onedrive.com/")!).appendingPathComponent("")
let baseURL = serverURL ?? URL(string: "https://api.onedrive.com/")!
self.baseURL = baseURL.path.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.drive = drive
self.isPathRelative = true
self.currentPath = ""
+1 -5
View File
@@ -8,14 +8,10 @@
import Foundation
public struct FileProviderOneDriveError: Error, CustomStringConvertible {
public struct FileProviderOneDriveError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
public var description: String {
return code.description
}
}
public final class OneDriveFileObject: FileObject {
+14
View File
@@ -61,6 +61,20 @@ open class RemoteOperationHandle: OperationHandle {
}
}
public protocol FileProviderHTTPError: Error, CustomStringConvertible {
var code: FileProviderHTTPErrorCode { get }
var path: String { get }
var errorDescription: String? { get }
var description: String { get }
}
extension FileProviderHTTPError {
public var description: String {
return code.description
}
}
class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate {
weak var fileProvider: FileProvider?
+2
View File
@@ -106,6 +106,7 @@ extension SMB2 {
let flags: WriteRequest.Flags
}
// codebeat:disable[ARITY]
init(fileId: FileId, offset: UInt64, remainingBytes: UInt32 = 0, data: Data, channel: Channel = .NONE, channelInfo: ChannelInfo? = nil, flags: WriteRequest.Flags = []) {
var channelInfoOffset: UInt16 = 0
var channelInfoLength: UInt16 = 0
@@ -118,6 +119,7 @@ extension SMB2 {
self.channelInfo = channelInfo
self.fileData = data
}
// codebeat:enable[ARITY]
func data() -> Data {
var result = Data(value: self.header)
+21 -19
View File
@@ -57,7 +57,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
return nil
}
self.baseURL = baseURL.appendingPathComponent("")
self.baseURL = baseURL.path.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.isPathRelative = true
self.currentPath = ""
self.useCache = false
@@ -89,7 +89,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
var fileObjects = [WebDavFileObject]()
if let data = data {
@@ -116,7 +116,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
runDataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
if let data = data {
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
@@ -172,7 +172,7 @@ extension WebDAVFileProvider: FileProviderOperations {
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: url.relativePath, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
@@ -194,7 +194,7 @@ extension WebDAVFileProvider: FileProviderOperations {
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
@@ -232,7 +232,8 @@ extension WebDAVFileProvider: FileProviderOperations {
}
func doOperation(operation opType: FileOperationType, overwrite: Bool? = nil, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let sourceURL = self.url(of:opType.source!)
let source = opType.source!
let sourceURL = self.url(of: source)
var request = URLRequest(url: sourceURL)
if let dest = opType.destination {
request.setValue(url(of:dest).absoluteString, forHTTPHeaderField: "Destination")
@@ -255,12 +256,13 @@ extension WebDAVFileProvider: FileProviderOperations {
var responseError: FileProviderWebDavError?
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
if response.statusCode >= 300 {
responseError = FileProviderWebDavError(code: code, url: sourceURL)
responseError = FileProviderWebDavError(code: code, path: source, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: sourceURL)
}
if code == .multiStatus, let data = data {
let xresponses = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
completionHandler?(FileProviderWebDavError(code: code, url: sourceURL))
let error = FileProviderWebDavError(code: code, path: source, errorDescription: String(data: data, encoding: .utf8), url: sourceURL)
completionHandler?(error)
}
}
}
@@ -277,18 +279,20 @@ extension WebDAVFileProvider: FileProviderOperations {
@discardableResult
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
// TODO: Make use of overwrite parameter
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = self.url(of:toPath)
var request = URLRequest(url: url)
if !overwrite {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
request.httpMethod = "PUT"
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: toPath, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
@@ -309,7 +313,7 @@ extension WebDAVFileProvider: FileProviderOperations {
let task = session.downloadTask(with: request, completionHandler: { (sourceFileURL, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: nil, url: url)
}
if let sourceFileURL = sourceFileURL {
do {
@@ -351,7 +355,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
runDataTask(with: request, operationHandle: handle, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
completionHandler(data, responseError ?? error)
})
@@ -374,7 +378,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: self.url(of: path))
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: self.url(of: path))
}
defer {
self.delegateNotify(opType, error: responseError ?? error)
@@ -403,7 +407,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
// FIXME: paginating results
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
if let data = data {
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
@@ -600,11 +604,9 @@ public final class WebDavFileObject: FileObject {
}
}
public struct FileProviderWebDavError: Error, CustomStringConvertible {
public struct FileProviderWebDavError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
public let url: URL
public var description: String {
return code.description
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB