Compare commits

...

17 Commits

Author SHA1 Message Date
Amir Abbas 28d01a9c59 Fix StreamTask read data bug
- Single stream upload file
- Removed redundant ftp helper functions
2017-12-29 19:46:46 +03:30
Amir Abbas 5d5e6811fd Fix Swift 3 compile error 2017-12-27 18:39:15 +03:30
Amir Abbas 1f2aa4888c Fixed FTP errors to bypass test cases and build error on macOS 2017-12-27 18:38:55 +03:30
Amir Abbas cf1b226417 Fix FTP provider crashes and errors on uploading and downloading 2017-12-27 17:36:35 +03:30
Amir Abbas 1ddb4b140d Remove testing on swift 3 2017-12-26 11:41:20 +03:30
Amir Abbas 86fe84351f Probable fix #76 , deinit to cleanup providers 2017-12-26 11:23:07 +03:30
Amir Abbas ca248f08dd Fix #72 (WebDAV result contains space) 2017-12-26 11:22:46 +03:30
Amir Abbas b17d92350c Fix. #71, Temp workaround for StreamTask issue 2017-12-26 11:22:05 +03:30
Amir Abbas d38748153d Fixed typo in WebDAV 2017-11-16 12:19:16 +03:30
Amir Abbas 17140f40d7 Fix #70 (WebDAV folder), Better URLRequest header interface 2017-11-14 21:32:41 +03:30
Amir Abbas 2820b4d3fe Fixed test scheme 2017-11-12 18:31:02 +03:30
Amir Abbas 7cd1528d75 Updated readme and travis for tests 2017-11-05 08:26:53 +03:30
Amir Abbas 14b4bcefe4 Fix Dropbox attributesOfItem, fix test cases 2017-11-04 16:23:44 +03:30
Amir Abbas a43640ecfd Fixed Test project settings, test for Dropbox and OneDrive 2017-11-03 01:30:23 +03:30
Amir Abbas f7f2d7f734 Fix error 2017-11-03 01:18:52 +03:30
Amir Abbas 5aaf8f2bf2 Added test 2017-11-03 01:10:05 +03:30
Amir Abbas 026e496512 Fixed macOS swift 4 convertToImage() compile issue 2017-11-03 01:09:26 +03:30
20 changed files with 1217 additions and 647 deletions
+5 -8
View File
@@ -12,14 +12,11 @@ env:
- 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="NO" CARTHAGEDEPLOY="YES"
- DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="YES" 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="NO"
- DESTINATION="OS=10.2,name=iPad Air 2" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" CARTHAGEDEPLOY="YES"
- DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" POD="YES"
- DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK"
# - DESTINATION="arch=x86_64" SCHEME="FilesProviderTests" SDK="$MACOS_SDK" RUN_TESTS="YES"
- DESTINATION="OS=10.1,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
before_install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
+155
View File
@@ -128,6 +128,8 @@
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
79D903541FAB647400D61D31 /* FilesProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D903531FAB647400D61D31 /* FilesProviderTests.swift */; };
79D903561FAB647400D61D31 /* FilesProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 799396751D48B80D00086753 /* FilesProvider.framework */; };
79F4678B1E8B80F200C91A85 /* FTPHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798654321E8874BC002FA550 /* FTPHelper.swift */; };
79F4678C1E8B80F200C91A85 /* FTPHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798654321E8874BC002FA550 /* FTPHelper.swift */; };
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
@@ -135,6 +137,16 @@
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
79D903571FAB647400D61D31 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7993965C1D48B7BF00086753 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 799396741D48B80D00086753;
remoteInfo = "FilesProvider OSX";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
7902C0851D61B56D00564440 /* RemoteSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteSession.swift; sourceTree = "<group>"; };
791950F41DE58A5400B4426E /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.1.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
@@ -192,6 +204,9 @@
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; };
79BD63C31E2D17880035128C /* OneDriveFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveFileProvider.swift; sourceTree = "<group>"; };
79BD63C41E2D17880035128C /* OneDriveHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveHelper.swift; sourceTree = "<group>"; };
79D903511FAB647400D61D31 /* FilesProviderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FilesProviderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
79D903531FAB647400D61D31 /* FilesProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesProviderTests.swift; sourceTree = "<group>"; };
79D903551FAB647400D61D31 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -229,6 +244,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
79D9034E1FAB647400D61D31 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
79D903561FAB647400D61D31 /* FilesProvider.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -278,6 +301,7 @@
79E34A101E2AC6C600E1293B /* Extra */,
799396911D48C02300086753 /* Sources */,
7993968A1D48B8C700086753 /* Pod */,
79D903521FAB647400D61D31 /* Tests */,
799396681D48B7F600086753 /* Products */,
791950F31DE58A5300B4426E /* Frameworks */,
);
@@ -289,6 +313,7 @@
799396671D48B7F600086753 /* FilesProvider.framework */,
799396751D48B80D00086753 /* FilesProvider.framework */,
799396821D48B82700086753 /* FilesProvider.framework */,
79D903511FAB647400D61D31 /* FilesProviderTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -352,6 +377,15 @@
path = SMBTypes;
sourceTree = "<group>";
};
79D903521FAB647400D61D31 /* Tests */ = {
isa = PBXGroup;
children = (
79D903531FAB647400D61D31 /* FilesProviderTests.swift */,
79D903551FAB647400D61D31 /* Info.plist */,
);
path = Tests;
sourceTree = "<group>";
};
79E34A101E2AC6C600E1293B /* Extra */ = {
isa = PBXGroup;
children = (
@@ -440,6 +474,24 @@
productReference = 799396821D48B82700086753 /* FilesProvider.framework */;
productType = "com.apple.product-type.framework";
};
79D903501FAB647400D61D31 /* FilesProviderTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 79D9035B1FAB647400D61D31 /* Build configuration list for PBXNativeTarget "FilesProviderTests" */;
buildPhases = (
79D9034D1FAB647400D61D31 /* Sources */,
79D9034E1FAB647400D61D31 /* Frameworks */,
79D9034F1FAB647400D61D31 /* Resources */,
);
buildRules = (
);
dependencies = (
79D903581FAB647400D61D31 /* PBXTargetDependency */,
);
name = FilesProviderTests;
productName = FilesProviderTests;
productReference = 79D903511FAB647400D61D31 /* FilesProviderTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -475,6 +527,7 @@
799396661D48B7F600086753 /* FilesProvider iOS */,
799396741D48B80D00086753 /* FilesProvider OSX */,
799396811D48B82700086753 /* FilesProvider tvOS */,
79D903501FAB647400D61D31 /* FilesProviderTests */,
);
};
/* End PBXProject section */
@@ -501,6 +554,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
79D9034F1FAB647400D61D31 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -639,8 +699,24 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
79D9034D1FAB647400D61D31 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
79D903541FAB647400D61D31 /* FilesProviderTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
79D903581FAB647400D61D31 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 799396741D48B80D00086753 /* FilesProvider OSX */;
targetProxy = 79D903571FAB647400D61D31 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
@@ -1007,6 +1083,76 @@
};
name = Release;
};
79D903591FAB647400D61D31 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_BITCODE = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.mousavian.FilesProviderTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
};
name = Debug;
};
79D9035A1FAB647400D61D31 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.mousavian.FilesProviderTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1046,6 +1192,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
79D9035B1FAB647400D61D31 /* Build configuration list for PBXNativeTarget "FilesProviderTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
79D903591FAB647400D61D31 /* Debug */,
79D9035A1FAB647400D61D31 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7993965C1D48B7BF00086753 /* Project object */;
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "79D903501FAB647400D61D31"
BuildableName = "FilesProviderTests.xctest"
BlueprintName = "FilesProviderTests"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+2 -6
View File
@@ -16,10 +16,6 @@
[![Cocoapods Downloads][cocoapods-downloads]][cocoapods]
[![Cocoapods Apps][cocoapods-apps]][cocoapods]
Old Cocoapods repo stats:
[![Cocoapods Downloads][cocoapods-downloads-old]][cocoapods-old]
[![Cocoapods Apps][cocoapods-apps-old]][cocoapods-old]
</center>
<!---
@@ -469,8 +465,8 @@ We would love for you to contribute to **FileProvider**, check the `LICENSE` fil
Things you may consider to help us:
- [x] Implement Test-case (`XCTest`)
- [ ] Implement request/response stack for `SMBClient`
- [ ] Implement Test-case (`XCTest`)
- [ ] Add Sample project for iOS
- [ ] Add Sample project for macOS
@@ -500,7 +496,7 @@ Distributed under the MIT license. See `LICENSE` for more information.
[license-url]: LICENSE
[codebeat-image]: https://codebeat.co/badges/7b359f48-78eb-4647-ab22-56262a827517
[codebeat-url]: https://codebeat.co/projects/github-com-amosavian-fileprovider
[travis-image]: https://img.shields.io/travis/amosavian/FileProvider/master.svg
[travis-image]: https://api.travis-ci.org/amosavian/FileProvider.svg?branch=swift-3
[travis-url]: https://travis-ci.org/amosavian/FileProvider
[release-url]: https://github.com/amosavian/FileProvider/releases
[release-image]: https://img.shields.io/github/release/amosavian/FileProvider.svg
+8
View File
@@ -106,6 +106,14 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
self.isCoorinating = aDecoder.decodeBool(forKey: "isCoorinating")
}
deinit {
let monitors = self.monitors
self.monitors = [:]
for monitor in monitors {
self.unregisterNotifcation(path: monitor.key)
}
}
open override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(self.containerId, forKey: "containerId")
+25 -25
View File
@@ -87,19 +87,19 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var fileObject: DropboxFileObject?
if let response = response as? HTTPURLResponse {
if let response = response as? HTTPURLResponse, response.statusCode >= 400 {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON(), let file = DropboxFileObject(json: json) {
fileObject = file
}
}
if let json = data?.deserializeJSON(), let file = DropboxFileObject(json: json) {
fileObject = file
}
completionHandler(fileObject, serverError ?? error)
})
@@ -112,7 +112,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
guard let json = data?.deserializeJSON() else {
completionHandler(nil)
@@ -194,12 +194,12 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url: URL = URL(string: "files/upload", relativeTo: contentURL)!
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
requestDictionary["client_modified"] = (attributes[.contentModificationDateKey] as? Date)?.format(with: .rfc3339) as NSString?
//requestDictionary["client_modified"] = (attributes[.contentModificationDateKey] as? Date)?.format(with: .rfc3339) as NSString?
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(httpContentType: .stream)
request.set(dropboxArgKey: requestDictionary)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .stream)
request.setValue(dropboxArgKey: requestDictionary)
return request
}
@@ -207,8 +207,8 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url = URL(string: "files/download", relativeTo: contentURL)!
var request = URLRequest(url: url)
request = URLRequest(url: url)
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(dropboxArgKey: ["path": correctPath(path)! as NSString])
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(dropboxArgKey: ["path": correctPath(path)! as NSString])
return request
}
@@ -249,8 +249,8 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
}
var request = URLRequest(url: URL(string: url, relativeTo: apiURL)!)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
if let dest = correctPath(destPath) as NSString? {
requestDictionary["from_path"] = correctPath(sourcePath) as NSString?
requestDictionary["to_path"] = dest
@@ -295,8 +295,8 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -341,8 +341,8 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url = URL(string: "files/save_url", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "url" : remoteURL.absoluteString as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -376,8 +376,8 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url = URL(string: "files/copy_reference/save", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "copy_reference" : reference as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -441,7 +441,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
return
}
var request = URLRequest(url: url)
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: credential, with: .oAuth2)
var requestDictionary: [String: AnyObject] = ["path": path as NSString]
if thumbAPI {
requestDictionary["format"] = "jpeg" as NSString
@@ -455,7 +455,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
requestDictionary["size"] = size as NSString
}
request.set(dropboxArgKey: requestDictionary)
request.setValue(dropboxArgKey: requestDictionary)
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = result.deserializeJSON() {
@@ -485,8 +485,8 @@ extension DropboxFileProvider: ExtendedFileProvider {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
+4 -4
View File
@@ -92,8 +92,8 @@ internal extension DropboxFileProvider {
let url = URL(string: "files/search", relativeTo: self.apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: self.credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: self.credential, with: .oAuth2)
request.setValue(contentType: .json)
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path) as NSString!]
requestDictionary["query"] = queryStr as NSString
requestDictionary["start"] = NSNumber(value: (token.flatMap( { Int($0) } ) ?? 0))
@@ -115,8 +115,8 @@ internal extension DropboxFileProvider {
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: self.credential, with: .oAuth2)
request.set(httpContentType: .json)
request.setValue(authentication: self.credential, with: .oAuth2)
request.setValue(contentType: .json)
request.httpBody = Data(jsonDictionary: requestDictionary)
return request
}
+156 -99
View File
@@ -22,6 +22,7 @@ public extension Array where Element: FileObject {
}
public extension Sequence where Iterator.Element == UInt8 {
/// Converts a byte array into hexadecimal string representation
func hexString() -> String {
return self.map{String(format: "%02X", $0)}.joined()
}
@@ -97,27 +98,128 @@ public extension URLRequest {
}
}
struct Quality<T> {
let value: T
let quality: Float
/// Holds file MIME, and introduces selected type MIME as constants
public struct ContentMIMEType: RawRepresentable, Hashable, Equatable {
public var rawValue: String
public typealias RawValue = String
var stringifed: String {
var representaion: String = String(describing: value)
let quality: Float = min(1, max(self.quality, 0))
if let value = value as? Locale {
representaion = value.identifier.replacingOccurrences(of: "_", with: "-")
}
if let value = value as? String.Encoding {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(value.rawValue)
representaion = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? ?? "*"
}
let qualityDesc = String(format: "%.1f", quality)
return "\(representaion); q=\(qualityDesc)"
public init(rawValue: String) {
self.rawValue = rawValue
}
public var hashValue: Int { return rawValue.hashValue }
public static func == (lhs: ContentMIMEType, rhs: ContentMIMEType) -> Bool {
return lhs.rawValue == rhs.rawValue
}
/// Directory
static public let directory = ContentMIMEType(rawValue: "httpd/unix-directory")
// Archive and Binary
/// Binary stream and unknown types
static public let stream = ContentMIMEType(rawValue: "application/octet-stream")
/// Protable document format
static public let pdf = ContentMIMEType(rawValue: "application/pdf")
/// Zip archive
static public let zip = ContentMIMEType(rawValue: "application/zip")
/// Rar archive
static public let rarArchive = ContentMIMEType(rawValue: "application/x-rar-compressed")
/// 7-zip archive
static public let lzma = ContentMIMEType(rawValue: "application/x-7z-compressed")
/// Adobe Flash
static public let flash = ContentMIMEType(rawValue: "application/x-shockwave-flash")
/// ePub book
static public let epub = ContentMIMEType(rawValue: "application/epub+zip")
/// Java archive (jar)
static public let javaArchive = ContentMIMEType(rawValue: "application/java-archive")
// Texts
/// Text file
static public let plainText = ContentMIMEType(rawValue: "text/plain")
/// Coma-separated values
static public let csv = ContentMIMEType(rawValue: "text/csv")
/// Hyper-text markup language
static public let html = ContentMIMEType(rawValue: "text/html")
/// Common style sheet
static public let css = ContentMIMEType(rawValue: "text/css")
/// eXtended Markup language
static public let xml = ContentMIMEType(rawValue: "text/xml")
/// Javascript code file
static public let javascript = ContentMIMEType(rawValue: "application/javascript")
/// Javascript notation
static public let json = ContentMIMEType(rawValue: "application/json")
// Documents
/// Rich text file (RTF)
static public let richText = ContentMIMEType(rawValue: "application/rtf")
/// Excel 2013 (OOXML) document
static public let excel = ContentMIMEType(rawValue: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
/// Powerpoint 2013 (OOXML) document
static public let powerpoint = ContentMIMEType(rawValue: "application/vnd.openxmlformats-officedocument.presentationml.slideshow")
/// Word 2013 (OOXML) document
static public let word = ContentMIMEType(rawValue: "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
// Images
/// Bitmap
static public let bmp = ContentMIMEType(rawValue: "image/bmp")
/// Graphics Interchange Format photo
static public let gif = ContentMIMEType(rawValue: "image/gif")
/// JPEG photo
static public let jpeg = ContentMIMEType(rawValue: "image/jpeg")
/// Portable network graphics
static public let png = ContentMIMEType(rawValue: "image/png")
// Audio & Video
/// MPEG Audio
static public let mpegAudio = ContentMIMEType(rawValue: "audio/mpeg")
/// MPEG Video
static public let mpeg = ContentMIMEType(rawValue: "video/mpeg")
/// MPEG4 Audio
static public let mpeg4Audio = ContentMIMEType(rawValue: "audio/mp4")
/// MPEG4 Video
static public let mpeg4 = ContentMIMEType(rawValue: "video/mp4")
/// OGG Audio
static public let ogg = ContentMIMEType(rawValue: "audio/ogg")
/// Advanced Audio Coding
static public let aac = ContentMIMEType(rawValue: "audio/x-aac")
/// Microsoft Audio Video Interleaved
static public let avi = ContentMIMEType(rawValue: "video/x-msvideo")
/// Microsoft Wave audio
static public let wav = ContentMIMEType(rawValue: "audio/x-wav")
/// Apple QuickTime format
static public let quicktime = ContentMIMEType(rawValue: "video/quicktime")
/// 3GPP
static public let threegp = ContentMIMEType(rawValue: "video/3gpp")
/// Adobe Flash video
static public let flashVideo = ContentMIMEType(rawValue: "video/x-flv")
/// Adobe Flash video
static public let flv = ContentMIMEType.flashVideo
// Google Drive
/// Google Drive: Folder
static public let googleFolder = ContentMIMEType(rawValue: "application/vnd.google-apps.folder")
/// Google Drive: Document (word processor)
static public let googleDocument = ContentMIMEType(rawValue: "application/vnd.google-apps.document")
/// Google Drive: Sheets (spreadsheet)
static public let googleSheets = ContentMIMEType(rawValue: "application/vnd.google-apps.spreadsheet")
/// Google Drive: Slides (presentation)
static public let googleSlides = ContentMIMEType(rawValue: "application/vnd.google-apps.presentation")
/// Google Drive: Drawing (vector draw)
static public let googleDrawing = ContentMIMEType(rawValue: "application/vnd.google-apps.drawing")
/// Google Drive: Audio
static public let googleAudio = ContentMIMEType(rawValue: "application/vnd.google-apps.audio")
/// Google Drive: Video
static public let googleVideo = ContentMIMEType(rawValue: "application/vnd.google-apps.video")
}
internal extension URLRequest {
mutating func set(httpAuthentication credential: URLCredential?, with type: AuthenticationType) {
mutating func setValue(authentication credential: URLCredential?, with type: AuthenticationType) {
func base64(_ str: String) -> String {
let plainData = str.data(using: .utf8)
let base64String = plainData!.base64EncodedString(options: [])
@@ -147,31 +249,24 @@ internal extension URLRequest {
}
}
mutating func set(httpAcceptCharset acceptCharset: String.Encoding) {
mutating func setValue(acceptCharset: String.Encoding, quality: Double? = nil) {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(acceptCharset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
self.addValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
}
mutating func set(httpAcceptCharset acceptCharset: Quality<String.Encoding>) {
self.addValue(acceptCharset.stringifed, forHTTPHeaderField: "Accept-Charset")
}
mutating func set(httpAcceptCharsets acceptCharsets: [String.Encoding]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Charset")
for charset in acceptCharsets {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(charset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
self.addValue(charsetString, forHTTPHeaderField: "Accept-Charset")
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(charsetString); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
} else {
self.setValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
}
}
mutating func set(httpAcceptCharsets acceptCharsets: [Quality<String.Encoding>]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Charset")
for charset in acceptCharsets.sorted(by: { $0.quality > $1.quality }) {
self.addValue(charset.stringifed, forHTTPHeaderField: "Accept-Charset")
mutating func addValue(acceptCharset: String.Encoding, quality: Double? = nil) {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(acceptCharset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(charsetString); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
} else {
self.addValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
}
}
@@ -182,53 +277,41 @@ internal extension URLRequest {
case deflate
}
mutating func set(httpAcceptEncoding acceptEncoding: Encoding) {
self.addValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
mutating func set(httpAcceptEncoding acceptEncoding: Quality<Encoding>) {
self.addValue(acceptEncoding.stringifed, forHTTPHeaderField: "Accept-Encoding")
}
mutating func set(httpAcceptEncodings acceptEncodings: [Encoding]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Encoding")
for encoding in acceptEncodings {
self.addValue(encoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
mutating func setValue(acceptEncoding: Encoding, quality: Double? = nil) {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(acceptEncoding.rawValue); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
} else {
self.setValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
}
mutating func set(httpAcceptEncodings acceptEncodings: [Quality<Encoding>]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Encoding")
for encoding in acceptEncodings.sorted(by: { $0.quality > $1.quality }) {
self.addValue(encoding.stringifed, forHTTPHeaderField: "Accept-Encoding")
mutating func addValue(acceptEncoding: Encoding, quality: Double? = nil) {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(acceptEncoding.rawValue); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
} else {
self.addValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
}
mutating func set(httpAcceptLanguage acceptLanguage: Locale) {
mutating func setValue(acceptLanguage: Locale, quality: Double? = nil) {
let langCode = acceptLanguage.identifier.replacingOccurrences(of: "_", with: "-")
self.addValue(langCode, forHTTPHeaderField: "Accept-Language")
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(langCode); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
} else {
self.setValue(langCode, forHTTPHeaderField: "Accept-Language")
}
}
mutating func set(httpAcceptLanguage acceptLanguage: Quality<Locale>) {
self.addValue(acceptLanguage.stringifed, forHTTPHeaderField: "Accept-Language")
}
mutating func set(httpAcceptLanguages acceptLanguages: [Locale]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Language")
for lang in acceptLanguages {
let langCode = lang.identifier.replacingOccurrences(of: "_", with: "-")
mutating func addValue(acceptLanguage: Locale, quality: Double? = nil) {
let langCode = acceptLanguage.identifier.replacingOccurrences(of: "_", with: "-")
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(langCode); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
} else {
self.addValue(langCode, forHTTPHeaderField: "Accept-Language")
}
}
mutating func set(httpAcceptLanguages acceptLanguages: [Quality<Locale>]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Language")
for lang in acceptLanguages.sorted(by: { $0.quality > $1.quality} ) {
self.addValue(lang.stringifed, forHTTPHeaderField: "Accept-Language")
}
}
mutating func set(httpRangeWithOffset offset: Int64, length: Int) {
mutating func setValue(rangeWithOffset offset: Int64, length: Int) {
if length > 0 {
self.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
@@ -236,7 +319,7 @@ internal extension URLRequest {
}
}
mutating func set(httpRange range: Range<Int>) {
mutating func setValue(range: Range<Int>) {
let range = max(0, range.lowerBound)..<range.upperBound
if range.upperBound < Int.max && range.count > 0 {
self.setValue("bytes=\(range.lowerBound)-\(range.upperBound - 1)", forHTTPHeaderField: "Range")
@@ -245,33 +328,7 @@ internal extension URLRequest {
}
}
struct ContentMIMEType: RawRepresentable {
public var rawValue: String
public typealias RawValue = String
public init(rawValue: String) {
self.rawValue = rawValue
}
static let javascript = ContentMIMEType(rawValue: "application/javascript")
static let json = ContentMIMEType(rawValue: "application/json")
static let pdf = ContentMIMEType(rawValue: "application/pdf")
static let stream = ContentMIMEType(rawValue: "application/octet-stream")
static let zip = ContentMIMEType(rawValue: "application/zip")
// Texts
static let css = ContentMIMEType(rawValue: "text/css")
static let html = ContentMIMEType(rawValue: "text/html")
static let plainText = ContentMIMEType(rawValue: "text/plain")
static let xml = ContentMIMEType(rawValue: "text/xml")
// Images
static let gif = ContentMIMEType(rawValue: "image/gif")
static let jpeg = ContentMIMEType(rawValue: "image/jpeg")
static let png = ContentMIMEType(rawValue: "image/png")
}
mutating func set(httpContentType contentType: ContentMIMEType, charset: String.Encoding? = nil) {
mutating func setValue(contentType: ContentMIMEType, charset: String.Encoding? = nil) {
var parameter = ""
if let charset = charset {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(charset.rawValue)
@@ -283,7 +340,7 @@ internal extension URLRequest {
self.setValue(contentType.rawValue + parameter, forHTTPHeaderField: "Content-Type")
}
mutating func set(dropboxArgKey requestDictionary: [String: AnyObject]) {
mutating func setValue(dropboxArgKey requestDictionary: [String: AnyObject]) {
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestDictionary, options: []) else {
return
}
+8 -6
View File
@@ -265,7 +265,9 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
fileprivate var host: (hostname: String, port: Int)?
fileprivate var service: NetService?
internal init(session: URLSession, host: String, port: Int, useURLSession: Bool = true) {
internal static let defaultUseURLSession = false
internal init(session: URLSession, host: String, port: Int, useURLSession: Bool = defaultUseURLSession) {
self._underlyingSession = session
self.useURLSession = useURLSession
if #available(iOS 9.0, macOS 10.11, *) {
@@ -285,7 +287,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
self.operation_queue.maxConcurrentOperationCount = 1
}
internal init(session: URLSession, netService: NetService, useURLSession: Bool = true) {
internal init(session: URLSession, netService: NetService, useURLSession: Bool = defaultUseURLSession) {
self._underlyingSession = session
self.useURLSession = useURLSession
if #available(iOS 9.0, macOS 10.11, *) {
@@ -324,8 +326,8 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
}
self._state = .canceling
inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
//inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
//outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
self.inputStream?.close()
self.outputStream?.close()
@@ -466,11 +468,11 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
if self.dataReceived.count > maxBytes {
let range: Range = 0..<maxBytes
dR = self.dataReceived.subdata(in: range)
self.dataReceived.replaceSubrange(range, with: Data())
self.dataReceived.removeFirst(maxBytes)
} else {
if self.dataReceived.count > 0 {
dR = self.dataReceived
self.dataReceived.count = 0
self.dataReceived.removeAll(keepingCapacity: false)
}
}
let isEOF = inputStream.streamStatus == .atEnd && self.dataReceived.count == 0
+48 -126
View File
@@ -39,9 +39,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
/// Determine either FTP session is in passive or active mode.
public let passiveMode: Bool
/// Force to use URLSessionDownloadTask/URLSessionDataTask when possible
public var useAppleImplementation = true
fileprivate var _session: URLSession!
internal var sessionDelegate: SessionDelegate?
public var session: URLSession {
@@ -93,6 +90,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
self.validatingCache = true
self.cache = cache
self.credential = credential
self.supportsRFC3659 = true
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
@@ -109,7 +107,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
self.init(baseURL: baseURL, passive: aDecoder.decodeBool(forKey: "passiveMode"), credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
self.useAppleImplementation = aDecoder.decodeBool(forKey: "useAppleImplementation")
self.supportsRFC3659 = aDecoder.decodeBool(forKey: "supportsRFC3659")
}
public func encode(with aCoder: NSCoder) {
@@ -117,8 +115,8 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
aCoder.encode(self.credential, forKey: "credential")
aCoder.encode(self.useCache, forKey: "useCache")
aCoder.encode(self.validatingCache, forKey: "validatingCache")
aCoder.encode(self.useAppleImplementation, forKey: "useAppleImplementation")
aCoder.encode(self.passiveMode, forKey: "passiveMode")
aCoder.encode(self.supportsRFC3659, forKey: "supportsRFC3659")
}
public static var supportsSecureCoding: Bool {
@@ -131,7 +129,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
copy.fileOperationDelegate = self.fileOperationDelegate
copy.useCache = self.useCache
copy.validatingCache = self.validatingCache
copy.useAppleImplementation = self.useAppleImplementation
copy.supportsRFC3659 = self.supportsRFC3659
return copy
}
@@ -147,10 +145,21 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
}
}
internal var serverSupportsRFC3659: Bool = true
internal var supportsRFC3659: Bool
/**
Uploads files in chunk if `true`, Otherwise It will uploads entire file/data as single stream.
- Note: Due to an internal bug in `NSURLSessionStreamTask`, it must be true when using Apple's stream task,
otherwise it will occasionally throw `Assertion failed: (_writeBufferAlreadyWrittenForNextWrite == 0)`
fatal error. My implementation of `FileProviderStreamTask` doesn't have this bug.
- Note: Disabling this option will increase upload speed.
*/
public var uploadByREST: Bool = FileProviderStreamTask.defaultUseURLSession
open func contentsOfDirectory(path: String, completionHandler: @escaping ([FileObject], Error?) -> Void) {
self.contentsOfDirectory(path: path, rfc3659enabled: serverSupportsRFC3659, completionHandler: completionHandler)
self.contentsOfDirectory(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler)
}
/**
@@ -205,7 +214,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
}
open func attributesOfItem(path: String, completionHandler: @escaping (FileObject?, Error?) -> Void) {
self.attributesOfItem(path: path, rfc3659enabled: serverSupportsRFC3659, completionHandler: completionHandler)
self.attributesOfItem(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler)
}
/**
@@ -246,7 +255,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
}
if response.hasPrefix("500") {
self.serverSupportsRFC3659 = false
self.supportsRFC3659 = false
self.attributesOfItem(path: path, rfc3659enabled: false, completionHandler: completionHandler)
}
@@ -395,6 +404,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { bytesSent, totalSent, expectedBytes in
progress.totalUnitCount = expectedBytes
progress.completedUnitCount = totalSent
self.delegateNotify(operation, progress: progress.fractionCompleted)
}, completionHandler: { (error) in
@@ -417,22 +427,33 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
var progress = Progress(totalUnitCount: 0)
let progress = Progress(totalUnitCount: 0)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
if self.useAppleImplementation {
self.attributesOfItem(path: path, completionHandler: { (file, error) in
do {
if let error = error {
throw error
}
if file?.isDirectory ?? false {
throw self.urlError(path, code: .fileIsDirectory)
}
} catch {
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
}
return
}
self.ftpDownload(task, filePath: self.ftpPath(path), onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { recevied, totalReceived, totalSize in
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (tmpurl, error) in
if let error = error {
progress.cancel()
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
@@ -440,69 +461,11 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
return
}
progress.totalUnitCount = file?.size ?? 0
let task = self.session.downloadTask(with: self.url(of: path))
completionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = completionHandler
downloadCompletionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
var error: NSError?
NSFileCoordinator().coordinate(writingItemAt: tempURL, options: .forMoving, writingItemAt: destURL, options: .forReplacing, error: &error, byAccessor: { (tempURL, destURL) in
do {
try FileManager.default.moveItem(at: tempURL, to: destURL)
completionHandler?(nil)
} catch {
completionHandler?(error)
}
})
if let error = error {
completionHandler?(error)
}
}
task.taskDescription = operation.json
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
})
} else {
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
if let tmpurl = tmpurl {
try? FileManager.default.moveItem(at: tmpurl, to: destURL)
self.dispatch_queue.async {
completionHandler?(error)
}
return
}
self.ftpRetrieveFile(task, filePath: self.ftpPath(path), onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { recevied, totalReceived, totalSize in
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (tmpurl, error) in
if let error = error {
progress.cancel()
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
if let tmpurl = tmpurl {
try? FileManager.default.moveItem(at: tmpurl, to: destURL)
self.dispatch_queue.async {
completionHandler?(nil)
self.delegateNotify(operation)
}
completionHandler?(nil)
self.delegateNotify(operation)
}
}
}
@@ -510,47 +473,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
return progress
}
open func contents(path: String, completionHandler: @escaping ((Data?, Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
if self.useAppleImplementation {
var progress = Progress(totalUnitCount: 0)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.downloadTask(with: url(of: path))
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
if error != nil {
progress.cancel()
}
completionHandler(nil, error)
}
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
do {
let data = try Data(contentsOf: tempURL)
completionHandler(data, nil)
} catch {
completionHandler(nil, error)
}
}
task.taskDescription = operation.json
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
return progress
} else {
return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
}
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
if length == 0 || offset < 0 {
@@ -574,7 +496,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
return
}
self.ftpRetrieveData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
self.ftpFileData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
+319 -303
View File
@@ -9,32 +9,9 @@
import Foundation
internal extension FTPFileProvider {
func readDataUntilEOF(of task: FileProviderStreamTask, minLength: Int, receivedData: Data? = nil, timeout: TimeInterval, completionHandler: @escaping (_ data: Data?, _ errror:Error?) -> Void) {
task.readData(ofMinLength: minLength, maxLength: 65535, timeout: timeout) { (data, eof, error) in
if let error = error {
completionHandler(nil, error)
return
}
var receivedData = receivedData
if let data = data {
if receivedData != nil {
receivedData!.append(data)
} else {
receivedData = data
}
}
if eof {
completionHandler(receivedData, nil)
} else {
self.readDataUntilEOF(of: task, minLength: 0, receivedData: receivedData, timeout: timeout, completionHandler: completionHandler)
}
}
}
func execute(command: String, on task: FileProviderStreamTask, minLength: Int = 4, afterSend: ((_ error: Error?) -> Void)? = nil, completionHandler: @escaping (_ response: String?, _ error: Error?) -> Void) {
func execute(command: String, on task: FileProviderStreamTask, minLength: Int = 4,
afterSend: ((_ error: Error?) -> Void)? = nil,
completionHandler: @escaping (_ response: String?, _ error: Error?) -> Void) {
let timeout = session.configuration.timeoutIntervalForRequest
let terminalcommand = command + "\r\n"
task.write(terminalcommand.data(using: .utf8)!, timeout: timeout) { (error) in
@@ -64,11 +41,43 @@ internal extension FTPFileProvider {
}
}
func ftpUserPass(_ task: FileProviderStreamTask, completionHandler: @escaping (_ error: Error?) -> Void) {
self.execute(command: "USER \(credential?.user ?? "anonymous")", on: task) { (response, error) in
if let error = error {
completionHandler(error)
return
}
guard let response = response else {
completionHandler(self.urlError("", code: .badServerResponse))
return
}
// successfully logged in
if response.hasPrefix("23") {
completionHandler(nil)
return
}
// needs password
if FileProviderFTPError(message: response).code == 331 {
self.execute(command: "PASS \(self.credential?.password ?? "fileprovider@")", on: task) { (response, error) in
if response?.hasPrefix("23") ?? false {
completionHandler(nil)
} else {
completionHandler(self.urlError("", code: .userAuthenticationRequired))
}
}
return
}
let error = FileProviderFTPError(message: response)
completionHandler(error)
}
}
func ftpLogin(_ task: FileProviderStreamTask, completionHandler: @escaping (_ error: Error?) -> Void) {
let timeout = session.configuration.timeoutIntervalForRequest
if task.state == .suspended {
task.resume()
}
var isSecure = false
// Implicit FTP Connection
@@ -76,8 +85,9 @@ internal extension FTPFileProvider {
task.startSecureConnection()
isSecure = true
}
let credential = self.credential
if task.state == .suspended {
task.resume()
}
task.readData(ofMinLength: 4, maxLength: 2048, timeout: timeout) { (data, eof, error) in
do {
@@ -89,7 +99,7 @@ internal extension FTPFileProvider {
throw self.urlError("", code: .cannotParseResponse)
}
guard response.hasPrefix("22") else {
guard response.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("22") else {
throw FileProviderFTPError(message: response)
}
} catch {
@@ -97,41 +107,6 @@ internal extension FTPFileProvider {
return
}
let loginHandle: () -> Void = {
self.execute(command: "USER \(credential?.user ?? "anonymous")", on: task) { (response, error) in
if let error = error {
completionHandler(error)
return
}
guard let response = response else {
completionHandler(self.urlError("", code: .badServerResponse))
return
}
// successfully logged in
if response.hasPrefix("23") {
completionHandler(nil)
return
}
// needs password
if FileProviderFTPError(message: response).code == 331 {
self.execute(command: "PASS \(credential?.password ?? "fileprovider@")", on: task) { (response, error) in
if response?.hasPrefix("23") ?? false {
completionHandler(nil)
} else {
completionHandler(self.urlError("", code: .userAuthenticationRequired))
}
}
return
}
let error = FileProviderFTPError(message: response)
completionHandler(error)
}
}
if !isSecure && self.baseURL?.scheme == "ftpes" {
// Explicit FTP Connection, by upgrading connection to FTP/SSL
self.execute(command: "AUTH TLS", on: task, minLength: 0, completionHandler: { (response, error) in
@@ -149,7 +124,7 @@ internal extension FTPFileProvider {
return
}
loginHandle()
self.ftpUserPass(task, completionHandler: completionHandler)
})
}
})
@@ -160,35 +135,10 @@ internal extension FTPFileProvider {
return
}
loginHandle()
self.ftpUserPass(task, completionHandler: completionHandler)
})
} else {
loginHandle()
}
}
}
func ftpCwd(_ task: FileProviderStreamTask, to path: String, completionHandler: @escaping (_ error: Error?) -> Void) {
self.execute(command: "CWD \(path)", on: task) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(path, code: .badServerResponse)
}
// successfully logged in
if response.hasPrefix("25") {
completionHandler(nil)
}
// not logged in
else if response.hasPrefix("55") {
throw FileProviderFTPError(message: response)
}
} catch {
completionHandler(error)
self.ftpUserPass(task, completionHandler: completionHandler)
}
}
}
@@ -204,7 +154,7 @@ internal extension FTPFileProvider {
throw error
}
guard let response = response, let destString = response.components(separatedBy: " ").flatMap({ $0 }).last.flatMap(String.init) else {
guard let response = response, let destString = response.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: " ").last else {
throw self.urlError("", code: .badServerResponse)
}
@@ -282,32 +232,10 @@ internal extension FTPFileProvider {
}
}
func ftpRest(_ task: FileProviderStreamTask, startPosition: Int64, completionHandler: @escaping (_ error: Error?) -> Void) {
self.execute(command: "REST \(startPosition)", on: task) { (response, error) in
do {
if let error = error {
throw error
}
// Successful
guard let response = response else {
throw self.urlError("", code: .badServerResponse)
}
if response.hasPrefix("35") {
completionHandler(nil)
} else {
throw FileProviderFTPError(message: response, path: "")
}
} catch {
completionHandler(error)
}
}
}
func ftpList(_ task: FileProviderStreamTask, of path: String, useMLST: Bool, completionHandler: @escaping (_ contents: [String], _ error: Error?) -> Void) {
func ftpList(_ task: FileProviderStreamTask, of path: String, useMLST: Bool,
completionHandler: @escaping (_ contents: [String], _ error: Error?) -> Void) {
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler([], error)
return
@@ -321,10 +249,8 @@ internal extension FTPFileProvider {
var success = false
let command = useMLST ? "MLSD \(path)" : "LIST \(path)"
self.execute(command: command, on: task, minLength: 20, afterSend: { error in
// starting passive task
let timeout = self.session.configuration.timeoutIntervalForRequest
DispatchQueue.global().async {
let timeout = self.session.configuration.timeoutIntervalForRequest
var finalData = Data()
var eof = false
var error: Error?
@@ -333,18 +259,19 @@ internal extension FTPFileProvider {
while !eof {
let group = DispatchGroup()
group.enter()
dataTask.readData(ofMinLength: 1, maxLength: 65535, timeout: timeout, completionHandler: { (data, seof, serror) in
dataTask.readData(ofMinLength: 1, maxLength: Int.max, timeout: timeout) { (data, seof, serror) in
if let data = data {
finalData.append(data)
}
eof = seof
error = serror
group.leave()
})
}
let waitResult = group.wait(timeout: .now() + timeout)
if let error = error {
if !((error as NSError).domain == URLError.errorDomain && (error as NSError).code == URLError.cancelled.rawValue) {
if !((error as NSError).domain == URLError.errorDomain
&& (error as NSError).code == URLError.cancelled.rawValue) {
throw error
}
return
@@ -359,7 +286,8 @@ internal extension FTPFileProvider {
throw self.urlError(path, code: .badServerResponse)
}
let contents: [String] = response.components(separatedBy: "\n").flatMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
let contents: [String] = response.components(separatedBy: "\n")
.flatMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
success = true
completionHandler(contents, nil)
} catch {
@@ -378,7 +306,7 @@ internal extension FTPFileProvider {
if response.hasPrefix("500") && useMLST {
dataTask.cancel()
self.serverSupportsRFC3659 = false
self.supportsRFC3659 = false
throw self.urlError(path, code: .unsupportedURL)
}
@@ -394,7 +322,8 @@ internal extension FTPFileProvider {
}
}
func recursiveList(path: String, useMLST: Bool, foundItemsHandler: ((_ contents: [FileObject]) -> Void)? = nil, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress? {
func recursiveList(path: String, useMLST: Bool, foundItemsHandler: ((_ contents: [FileObject]) -> Void)? = nil,
completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
let queue = DispatchQueue(label: "\(self.type).recursiveList")
queue.async {
@@ -418,7 +347,8 @@ internal extension FTPFileProvider {
progress.becomeCurrent(withPendingUnitCount: Int64(directories.count))
for dir in directories {
group.enter()
_=self.recursiveList(path: dir.path, useMLST: useMLST, foundItemsHandler: foundItemsHandler, completionHandler: { (contents, error) in
_=self.recursiveList(path: dir.path, useMLST: useMLST, foundItemsHandler: foundItemsHandler) {
(contents, error) in
success = success && (error == nil)
if let error = error {
completionHandler([], error)
@@ -430,7 +360,7 @@ internal extension FTPFileProvider {
result.append(contentsOf: contents)
group.leave()
})
}
}
progress.resignCurrent()
group.leave()
@@ -446,27 +376,22 @@ internal extension FTPFileProvider {
return progress
}
func ftpRetrieveData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) {
// Check cache
if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 {
dispatch_queue.async {
completionHandler(cachedResponse.data, nil)
}
return
}
func ftpRetrieve(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: @escaping (_ data: Data, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void,
completionHandler: @escaping (_ error: Error?) -> Void) {
self.attributesOfItem(path: filePath) { (file, error) in
let totalSize = file?.size ?? -1
// Retreive data from server
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler(nil, error)
completionHandler(error)
return
}
guard let dataTask = dataTask else {
completionHandler(nil, self.urlError(filePath, code: .badServerResponse))
completionHandler(self.urlError(filePath, code: .badServerResponse))
return
}
@@ -478,45 +403,42 @@ internal extension FTPFileProvider {
let timeout = self.session.configuration.timeoutIntervalForRequest
DispatchQueue.global().async {
var finalData = Data()
var totalReceived: Int64 = 0
var eof = false
var error: Error?
while !eof {
let group = DispatchGroup()
group.enter()
dataTask.readData(ofMinLength: 0, maxLength: 65535, timeout: timeout, completionHandler: { (data, seof, serror) in
dataTask.readData(ofMinLength: 1, maxLength: Int.max, timeout: timeout) { (data, segeof, segerror) in
if let data = data {
finalData.append(data)
onProgress?(Int64(data.count), Int64(finalData.count), totalSize)
var data = data
if length > 0, Int64(data.count) + totalReceived > Int64(length) {
data.count = Int(Int64(length) - totalReceived)
}
totalReceived += Int64(data.count)
onProgress(data, totalReceived, totalSize)
}
eof = seof || (length > 0 && finalData.count >= length)
if length > 0 && finalData.count > length {
finalData.count = length
}
error = serror
eof = segeof || (length > 0 && totalReceived >= Int64(length))
error = segerror
group.leave()
})
}
let waitResult = group.wait(timeout: .now() + timeout)
if let error = error {
completionHandler(nil, error)
completionHandler(error)
return
}
if waitResult == .timedOut {
completionHandler(nil, self.urlError(filePath, code: .timedOut))
completionHandler(self.urlError(filePath, code: .timedOut))
return
}
}
if let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL {
let urlresponse = URLResponse(url: url, mimeType: nil, expectedContentLength: finalData.count, textEncodingName: nil)
let cachedResponse = CachedURLResponse(response: urlresponse, data: finalData)
let request = URLRequest(url: url)
self.cache?.storeCachedResponse(cachedResponse, for: request)
}
dataTask.closeRead()
dataTask.closeWrite()
completionHandler(finalData, nil)
completionHandler(nil)
return
}
}) { (response, error) in
@@ -529,126 +451,12 @@ internal extension FTPFileProvider {
throw self.urlError(filePath, code: .cannotParseResponse)
}
if !(response.hasPrefix("1") || !response.hasPrefix("2")) {
throw FileProviderFTPError(message: response)
}
} catch {
self.dispatch_queue.async {
completionHandler(nil, error)
}
}
}
}
}
}
func ftpRetrieveFile(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ file: URL?, _ error: Error?) -> Void) {
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
// Check cache
if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 {
dispatch_queue.async {
do {
try cachedResponse.data.write(to: tempURL)
completionHandler(tempURL, nil)
} catch {
completionHandler(nil, error)
}
try? FileManager.default.removeItem(at: tempURL)
}
return
}
self.attributesOfItem(path: filePath) { (file, error) in
let totalSize = file?.size ?? -1
// Retreive data from server
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler(nil, error)
return
}
guard let dataTask = dataTask else {
completionHandler(nil, self.urlError(filePath, code: .badServerResponse))
return
}
// Send retreive command
let len = 19 /* TYPE response */ + 65 + String(position).count /* REST Response */ + 53 + filePath.count + String(totalSize).count /* RETR open response */ + 26 /* RETR Transfer complete message. */
self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "RETR \(filePath)", on: task, minLength: len, afterSend: { error in
// starting passive task
onTask?(dataTask)
let timeout = self.session.configuration.timeoutIntervalForRequest
DispatchQueue.global().async {
var finalData = Data()
var eof = false
var error: Error?
while !eof {
let group = DispatchGroup()
group.enter()
dataTask.readData(ofMinLength: 0, maxLength: 65535, timeout: timeout, completionHandler: { (data, seof, serror) in
if let data = data {
finalData.append(data)
onProgress?(Int64(data.count), Int64(finalData.count), totalSize)
}
eof = seof || (length > 0 && finalData.count >= length)
if length > 0 && finalData.count > length {
finalData.count = length
}
error = serror
group.leave()
})
let waitResult = group.wait(timeout: .now() + timeout)
if let error = error {
completionHandler(nil, error)
return
}
if waitResult == .timedOut {
error = self.urlError("", code: .timedOut)
completionHandler(nil, error)
return
}
}
if let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL {
let urlresponse = URLResponse(url: url, mimeType: nil, expectedContentLength: finalData.count, textEncodingName: nil)
let cachedResponse = CachedURLResponse(response: urlresponse, data: finalData)
let request = URLRequest(url: url)
self.cache?.storeCachedResponse(cachedResponse, for: request)
}
self.dispatch_queue.async {
do {
try finalData.write(to: tempURL)
completionHandler(tempURL, nil)
// Removing temporary file after coordinating
NSFileCoordinator().coordinate(writingItemAt: tempURL, options: .forDeleting, error: nil, byAccessor: { (tempURL) in
try? FileManager.default.removeItem(at: tempURL)
})
} catch {
completionHandler(nil, error)
}
}
}
}) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(filePath, code: .cannotParseResponse)
}
if !(response.hasPrefix("1") || response.hasPrefix("2")) {
throw FileProviderFTPError(message: response)
}
} catch {
self.dispatch_queue.async {
completionHandler(nil, error)
completionHandler(error)
}
}
}
@@ -656,19 +464,215 @@ internal extension FTPFileProvider {
}
}
func ftpStore(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ error: Error?) -> Void) {
let timeout = self.session.configuration.timeoutIntervalForRequest
func ftpFileData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) {
// Check cache
if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 {
dispatch_queue.async {
completionHandler(cachedResponse.data, nil)
}
return
}
var finalData = Data()
self.ftpRetrieve(task, filePath: filePath, from: position, length: length, onTask: onTask, onProgress: { (data, total, expected) in
finalData.append(data)
onProgress?(Int64(data.count), total, expected)
}) { (error) in
if let error = error {
completionHandler(nil, error)
}
if let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL {
let urlresponse = URLResponse(url: url, mimeType: nil, expectedContentLength: finalData.count, textEncodingName: nil)
let cachedResponse = CachedURLResponse(response: urlresponse, data: finalData)
let request = URLRequest(url: url)
self.cache?.storeCachedResponse(cachedResponse, for: request)
}
completionHandler(finalData, nil)
}
}
func ftpDownload(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ file: URL?, _ error: Error?) -> Void) {
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
// Check cache
if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 {
dispatch_queue.async {
do {
try cachedResponse.data.write(to: tempURL)
completionHandler(tempURL, nil)
} catch {
completionHandler(nil, error)
}
try? FileManager.default.removeItem(at: tempURL)
}
return
}
do {
try Data().write(to: tempURL, options: [])
let fileHandle = try FileHandle(forWritingTo: tempURL)
self.ftpRetrieve(task, filePath: filePath, from: position, length: length, onTask: onTask, onProgress: { (data, total, expected) in
fileHandle.write(data)
onProgress?(Int64(data.count), total, expected)
}) { (error) in
defer {
try? FileManager.default.removeItem(at: tempURL)
}
fileHandle.closeFile()
if let error = error {
completionHandler(nil, error)
return
}
completionHandler(tempURL, nil)
}
} catch {
completionHandler(nil, error)
}
}
func ftpStore(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ error: Error?) -> Void) {
if self.uploadByREST {
ftpStoreParted(task, filePath: filePath, fromData: fromData, fromFile: fromFile, onTask: onTask, onProgress: onProgress, completionHandler: completionHandler)
} else {
ftpStoreSerial(task, filePath: filePath, fromData: fromData, fromFile: fromFile, onTask: onTask, onProgress: onProgress, completionHandler: completionHandler)
}
}
func ftpStoreSerial(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ error: Error?) -> Void) {
guard let size: Int64 = (fromData != nil ? Int64(fromData!.count) : nil) ?? fromFile?.fileSize else { return }
ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler(error)
return
}
guard let dataTask = dataTask else {
completionHandler(self.urlError(filePath, code: .badServerResponse))
return
}
let len = 19 /* TYPE response */ + 44 + filePath.count /* STOR open response */ + 10 /* RETR Transfer complete message. */
var success = false
self.execute(command: "TYPE I" + "\r\n" + "STOR \(filePath)", on: task, minLength: len, afterSend: { error in
onTask?(dataTask)
let timeout = self.session.configuration.timeoutIntervalForResource
var error: Error?
var fileHandle: FileHandle?
if let file = fromFile {
fileHandle = FileHandle(forReadingAtPath: file.path)
}
defer {
fileHandle?.closeFile()
dataTask.closeRead()
dataTask.closeWrite()
}
let chunkSize = 4096
var eof = false
var sent: Int64 = 0
repeat {
let subdata: Data
if let data = fromData {
let endIndex = min(data.count, Int(sent) + chunkSize)
subdata = data.subdata(in: Int(sent)..<endIndex)
} else if let fileHandle = fileHandle {
fileHandle.seek(toFileOffset: UInt64(sent))
subdata = fileHandle.readData(ofLength: chunkSize)
} else {
return
}
if subdata.count == 0 { return }
let group = DispatchGroup()
group.enter()
dataTask.write(subdata, timeout: timeout, completionHandler: { (serror) in
error = serror
sent += Int64(subdata.count)
group.leave()
onProgress?(Int64(subdata.count), sent, size)
//print("ftp \(filePath): \(subdata.count), \(sent), \(size)")
})
let waitResult = group.wait(timeout: .now() + timeout)
if waitResult == .timedOut {
error = self.urlError(filePath, code: .timedOut)
}
if let error = error {
completionHandler(error)
return
}
if let data = fromData {
let endIndex = min(data.count, Int(sent) + chunkSize)
eof = endIndex == data.count
} else if let fileHandle = fileHandle {
eof = Int64(fileHandle.offsetInFile) == size
}
} while !eof
success = true
}) { (response, error) in
guard success else { return }
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(filePath, code: .cannotParseResponse)
}
if !(response.hasPrefix("1") || response.hasPrefix("2")) {
throw FileProviderFTPError(message: response)
}
completionHandler(nil)
} catch {
self.dispatch_queue.async {
completionHandler(error)
}
}
}
}
}
func ftpStoreParted(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?, from position: Int64 = 0,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ error: Error?) -> Void) {
operation_queue.addOperation {
guard let size: Int64 = (fromData != nil ? Int64(fromData!.count) : nil) ?? fromFile?.fileSize else { return }
let timeout = self.session.configuration.timeoutIntervalForResource
var error: Error?
let chunkSize: Int
switch size {
case 0..<262_144: chunkSize = 32_768 // 0KB To 256KB, chunk size is 32KB
case 262_144..<1_048_576: chunkSize = 65_536 // 256KB To 1MB, chunk size is 64KB
case 1_048_576..<10_485_760: chunkSize = 131_072 // 1MB To 10MB, chunk size is 128KB
case 10_048_576..<33_554_432: chunkSize = 262_144 // 10MB To 32MB, chunk size is 256KB
default: chunkSize = 524_288 // Larger than 32MB, chunk size is 512KB
case 0..<262_144:
chunkSize = 32_768 // 0KB To 256KB, chunk size is 32KB
case 262_144..<1_048_576:
chunkSize = 65_536 // 256KB To 1MB, chunk size is 64KB
case 1_048_576..<10_485_760:
chunkSize = 131_072 // 1MB To 10MB, chunk size is 128KB
case 0_048_576..<33_554_432:
chunkSize = 262_144 // 10MB To 32MB, chunk size is 256KB
default:
chunkSize = 524_288 // Larger than 32MB, chunk size is 512KB
}
var fileHandle: FileHandle?
@@ -680,48 +684,60 @@ internal extension FTPFileProvider {
}
var eof = false
var sent: Int64 = 0
var sent: Int64 = position
var retried = 0
while !eof {
repeat {
let subdata: Data
if let data = fromData {
let endIndex = min(data.count, Int(sent) + chunkSize)
eof = endIndex == data.count
subdata = data.subdata(in: Int(sent)..<endIndex)
}else if let fileHandle = fileHandle {
let endIndex = min(data.count, Int(sent - position) + chunkSize)
subdata = data.subdata(in: Int(sent - position)..<endIndex)
} else if let fileHandle = fileHandle {
fileHandle.seek(toFileOffset: UInt64(sent))
subdata = fileHandle.readData(ofLength: chunkSize)
eof = Int64(fileHandle.offsetInFile) == size
} else {
return
}
if subdata.count == 0 { continue }
if subdata.count == 0 { break }
let group = DispatchGroup()
group.enter()
self.ftpStore(task, data: subdata, to: filePath, from: sent, onTask: onTask, completionHandler: { (serror) in
error = serror
sent += Int64(subdata.count)
retried = 0
group.leave()
onProgress?(Int64(subdata.count), sent, size)
})
let waitResult = group.wait(timeout: .now() + timeout)
if let error = error {
completionHandler(error)
return
}
if waitResult == .timedOut {
error = self.urlError(filePath, code: .timedOut)
completionHandler(error)
return
}
}
if let error = error {
retried += 1
print(error.localizedDescription)
if retried > 3 {
completionHandler(error)
return
}
}
if let data = fromData {
let endIndex = min(data.count, Int(sent) + chunkSize)
eof = endIndex == data.count
} else if let fileHandle = fileHandle {
eof = Int64(fileHandle.offsetInFile) == size
}
} while !eof
completionHandler(nil)
}
}
func ftpStore(_ task: FileProviderStreamTask, data: Data, to filePath: String, from position: Int64, onTask: ((_ task: FileProviderStreamTask) -> Void)?, completionHandler: @escaping (_ error: Error?) -> Void) {
func ftpStore(_ task: FileProviderStreamTask, data: Data, to filePath: String, from position: Int64,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
completionHandler: @escaping (_ error: Error?) -> Void) {
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler(error)
@@ -823,7 +839,7 @@ internal extension FTPFileProvider {
guard name != "." && name != ".." else { return nil }
let path = (path as NSString).appendingPathComponent(name).replacingOccurrences(of: "/", with: "", options: .anchored)
let file = FileObject(url: url(of: path), name: name, path: path)
let file = FileObject(url: url(of: path), name: name, path: "/" + path)
#if swift(>=4.0)
let typeChar = posixPermission.first ?? Character(" ")
#else
@@ -874,7 +890,7 @@ internal extension FTPFileProvider {
attributes[keyValue[0].lowercased()] = keyValue.dropFirst().joined(separator: "=")
}
let file = FileObject(url: url(of: correctedPath), name: name, path: correctedPath)
let file = FileObject(url: url(of: correctedPath), name: name, path: "/" + correctedPath)
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .gregorian)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+12 -6
View File
@@ -170,9 +170,13 @@ open class FileObject: Equatable {
}
internal func mapPredicate() -> [String: Any] {
let mapDict: [URLResourceKey: String] = [.fileURLKey: "url", .nameKey: "name", .pathKey: "path", .fileSizeKey: "filesize", .creationDateKey: "creationDate",
.contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden", .isWritableKey: "isWritable", .serverDateKey: "serverDate", .entryTagKey: "entryTag", .mimeTypeKey: "mimeType"]
let typeDict: [URLFileResourceType: String] = [.directory: "directory", .regular: "regular", .symbolicLink: "symbolicLink", .unknown: "unknown"]
let mapDict: [URLResourceKey: String] = [.fileURLKey: "url", .nameKey: "name", .pathKey: "path",
.fileSizeKey: "filesize", .creationDateKey: "creationDate",
.contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden",
.isWritableKey: "isWritable", .serverDateKey: "serverDate",
.entryTagKey: "entryTag", .mimeTypeKey: "mimeType"]
let typeDict: [URLFileResourceType: String] = [.directory: "directory", .regular: "regular",
.symbolicLink: "symbolicLink", .unknown: "unknown"]
var result = [String: Any]()
for (key, value) in allValues {
if let convertkey = mapDict[key] {
@@ -190,9 +194,11 @@ open class FileObject: Equatable {
/// Converts macOS spotlight query for searching files to a query that can be used for `searchFiles()` method
static public func convertPredicate(fromSpotlight query: NSPredicate) -> NSPredicate {
let mapDict: [String: URLResourceKey] = [NSMetadataItemURLKey: .fileURLKey, NSMetadataItemFSNameKey: .nameKey, NSMetadataItemPathKey: .pathKey,
NSMetadataItemFSSizeKey: .fileSizeKey, NSMetadataItemFSCreationDateKey: .creationDateKey,
NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey, "kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey, "kMDItemKind": .mimeTypeKey]
let mapDict: [String: URLResourceKey] = [NSMetadataItemURLKey: .fileURLKey, NSMetadataItemFSNameKey: .nameKey,
NSMetadataItemPathKey: .pathKey, NSMetadataItemFSSizeKey: .fileSizeKey,
NSMetadataItemFSCreationDateKey: .creationDateKey, NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey,
"kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey,
"kMDItemKind": .mimeTypeKey]
if let cQuery = query as? NSCompoundPredicate {
let newSub = cQuery.subpredicates.map { convertPredicate(fromSpotlight: $0 as! NSPredicate) }
+17 -11
View File
@@ -173,14 +173,6 @@ extension FileProviderBasic {
operation_queue.maxConcurrentOperationCount = newValue
}
}
/// Returns total and used capacity in provider container asynchronously.
@available(*, deprecated, message: "Use storageProperties which returns VolumeObject")
func storageProperties(completionHandler: @escaping (_ total: Int64, _ used: Int64) -> Void) {
self.storageProperties { (info) in
completionHandler(info?.totalCapacity ?? -1, info?.usage ?? 0)
}
}
}
/// Checking equality of two file provider, regardless of current path queues and delegates.
@@ -410,8 +402,8 @@ public protocol FileProviderOperations: FileProviderBasic {
}
public extension FileProviderOperations {
/// *DEPRECATED:* Use Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.
@available(*, deprecated, message: "Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.")
/// *OBSOLETED:* Use Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.
@available(*, obsoleted: 0.23, message: "Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.")
@discardableResult
public func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (at as NSString).appendingPathComponent(file)
@@ -868,21 +860,35 @@ extension ExtendedFileProvider {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
#if os(macOS)
#if swift(>=3.2)
let ppp = Int(NSScreen.main?.backingScaleFactor ?? 1) // fetch device is retina or not
#else
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
#endif
size.width *= CGFloat(ppp)
size.height *= CGFloat(ppp)
#if swift(>=3.2)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB,
bytesPerRow: 0, bitsPerPixel: 0)
#else
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
bytesPerRow: 0, bitsPerPixel: 0)
#endif
guard let context = NSGraphicsContext(bitmapImageRep: rep!) else {
return nil
}
NSGraphicsContext.saveGraphicsState()
#if swift(>=4.0)
NSGraphicsContext.current = context
#else
NSGraphicsContext.setCurrent(context)
#endif
let transform = pdfPage.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
context.cgContext.concatenate(transform)
+24 -19
View File
@@ -108,6 +108,18 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
deinit {
if let sessionuuid = _session?.sessionDescription {
removeSessionHandler(for: sessionuuid)
}
if fileProviderCancelTasksOnInvalidating {
_session?.invalidateAndCancel()
} else {
_session?.finishTasksAndInvalidate()
}
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(self.baseURL, forKey: "baseURL")
aCoder.encode(self.credential, forKey: "credential")
@@ -123,18 +135,6 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
deinit {
if let sessionuuid = _session?.sessionDescription {
removeSessionHandler(for: sessionuuid)
}
if fileProviderCancelTasksOnInvalidating {
_session?.invalidateAndCancel()
} else {
_session?.finishTasksAndInvalidate()
}
}
open func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
@@ -243,11 +243,14 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
- error: `Error` returned by system if occured.
- Returns: An `Progress` to get progress or cancel progress.
*/
open func contents(path: String, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
open func contents(path: String, offset: Int64 = 0, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.fetch(path: path)
let request = self.request(for: operation)
var position: Int64 = 0
return download_progressive(path: path, request: request, operation: operation, progressHandler: { data in
var request = self.request(for: operation)
if offset > 0 {
request.addValue("bytes \(offset)-", forHTTPHeaderField: "Range")
}
var position: Int64 = offset
return download_progressive(path: path, request: request, operation: operation, responseHandler: responseHandler, progressHandler: { data in
progressHandler(position, data)
position += Int64(data.count)
}, completionHandler: (completionHandler ?? { _ in return }))
@@ -264,7 +267,7 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
let operation = FileOperationType.fetch(path: path)
var request = self.request(for: operation)
let cantLoadError = urlError(path, code: .cannotLoadFromNetwork)
request.set(httpRangeWithOffset: offset, length: length)
request.setValue(rangeWithOffset: offset, length: length)
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { (tempURL, error) in
do {
if let error = error {
@@ -479,8 +482,10 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.dataTask(with: request)
responseCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { response in
responseHandler?(response)
if let responseHandler = responseHandler {
responseCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { response in
responseHandler(response)
}
}
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { data in
+8
View File
@@ -125,6 +125,14 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
self.isCoorinating = aDecoder.decodeBool(forKey: "isCoorinating")
}
deinit {
let monitors = self.monitors
self.monitors = []
for monitor in monitors {
monitor.stop()
}
}
open func encode(with aCoder: NSCoder) {
aCoder.encode(self.baseURL, forKey: "currentPath")
aCoder.encode(self.isCoorinating, forKey: "isCoorinating")
+10 -9
View File
@@ -171,7 +171,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
let url = token.flatMap(URL.init(string:)) ?? self.url(of: path, modifier: "children")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.set(httpAuthentication: self.credential, with: .oAuth2)
request.setValue(authentication: self.credential, with: .oAuth2)
return request
}, pageHandler: { [weak self] (data, _) -> (files: [FileObject], error: Error?, newToken: String?) in
guard let `self` = self else { return ([], nil, nil) }
@@ -194,7 +194,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: self.credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var fileObject: OneDriveFileObject?
@@ -213,7 +213,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
open override func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
var request = URLRequest(url: url(of: ""))
request.httpMethod = "GET"
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: self.credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
guard let json = data?.deserializeJSON() else {
completionHandler(nil)
@@ -309,7 +309,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
var request = URLRequest(url: url(of: ""))
request.httpMethod = "HEAD"
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
let status = (response as? HTTPURLResponse)?.statusCode ?? 400
completionHandler(status == 200)
@@ -364,14 +364,15 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
}
var request = URLRequest(url: url)
request.httpMethod = method
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: self.credential, with: .oAuth2)
// Remove gzip to fix availability of progress per (Oleg Marchik)[https://github.com/evilutioner] PR (#61)
request.set(httpAcceptEncodings: [.deflate, .identity])
request.setValue(acceptEncoding: .deflate)
request.addValue(acceptEncoding: .identity)
switch operation {
case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"),
.move(source: let source, destination: let dest):
request.set(httpContentType: .json)
request.setValue(contentType: .json)
let cdest = correctPath(dest) as NSString
var parentRefrence: [String: AnyObject] = [:]
if cdest.hasPrefix("id:") {
@@ -476,7 +477,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
}
var request = URLRequest(url: url)
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: credential, with: .oAuth2)
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
@@ -495,7 +496,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.set(httpAuthentication: credential, with: .oAuth2)
request.setValue(authentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var dic = [String: Any]()
+4 -4
View File
@@ -41,7 +41,7 @@ public final class OneDriveFileObject: FileObject {
self.modifiedDate = (json["lastModifiedDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.type = json["folder"] != nil ? .directory : .regular
self.contentType = json["file"]?["mimeType"] as? String ?? "application/octet-stream"
self.contentType = (json["file"]?["mimeType"] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
self.id = json["id"] as? String
self.entryTag = json["eTag"] as? String
let hashes = json["file"]?["hashes"] as? NSDictionary
@@ -61,12 +61,12 @@ public final class OneDriveFileObject: FileObject {
}
/// MIME type of file contents returned by OneDrive server.
open internal(set) var contentType: String {
open internal(set) var contentType: ContentMIMEType {
get {
return allValues[.mimeTypeKey] as? String ?? "application/octet-stream"
return (allValues[.mimeTypeKey] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
}
set {
allValues[.mimeTypeKey] = newValue
allValues[.mimeTypeKey] = newValue.rawValue
}
}
+30 -21
View File
@@ -103,8 +103,8 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.setValue(authentication: credential, with: credentialType)
request.setValue(contentType: .xml, charset: .utf8)
request.httpBody = WebDavFileObject.xmlProp(including)
runDataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderHTTPError?
@@ -132,8 +132,8 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
var request = URLRequest(url: baseURL)
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.setValue(authentication: credential, with: credentialType)
request.setValue(contentType: .xml, charset: .utf8)
request.httpBody = WebDavFileObject.xmlProp([.volumeTotalCapacityKey, .volumeAvailableCapacityKey, .creationDateKey])
runDataTask(with: request, completionHandler: { (data, response, error) in
guard let data = data, let attr = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL).first else {
@@ -176,8 +176,8 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.httpMethod = "PROPFIND"
// Depth infinity is disabled on some servers. Implement workaround?!
request.setValue(recursive ? "infinity" : "1", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.setValue(authentication: credential, with: credentialType)
request.setValue(contentType: .xml, charset: .utf8)
request.httpBody = WebDavFileObject.xmlProp([])
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(url, forKey: .fileURLKey)
@@ -220,8 +220,8 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
var request = URLRequest(url: baseURL!)
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.setValue(authentication: credential, with: credentialType)
request.setValue(contentType: .xml, charset: .utf8)
request.httpBody = WebDavFileObject.xmlProp([.volumeTotalCapacityKey, .volumeAvailableCapacityKey])
runDataTask(with: request, completionHandler: { (data, response, error) in
let status = (response as? HTTPURLResponse)?.statusCode ?? 400
@@ -240,8 +240,8 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPPATCH"
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.setValue(authentication: credential, with: credentialType)
request.setValue(contentType: .xml, charset: .utf8)
let body = "<propertyupdate xmlns=\"DAV:\">\n<set><prop>\n<public_url xmlns=\"urn:yandex:disk:meta\">true</public_url>\n</prop></set>\n</propertyupdate>"
request.httpBody = body.data(using: .utf8)
runDataTask(with: request, completionHandler: { (data, response, error) in
@@ -303,7 +303,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
var request = URLRequest(url: url)
request.httpMethod = method
request.set(httpAuthentication: credential, with: credentialType)
request.setValue(authentication: credential, with: credentialType)
request.setValue(overwrite ? "T" : "F", forHTTPHeaderField: "Overwrite")
if let dest = operation.destination, !dest.hasPrefix("file://") {
request.setValue(self.url(of:dest).absoluteString, forHTTPHeaderField: "Destination")
@@ -362,7 +362,7 @@ extension WebDAVFileProvider: ExtendedFileProvider {
let url = URL(string: self.url(of: path).absoluteString + "?preview&size=\(dimension.width)x\(dimension.height)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.set(httpAuthentication: credential, with: credentialType)
request.setValue(authentication: credential, with: credentialType)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderHTTPError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
@@ -395,6 +395,8 @@ struct DavResponse {
let status: Int?
let prop: [String: String]
static let urlAllowed = CharacterSet(charactersIn: " ").inverted
init? (_ node: AEXMLElement, baseURL: URL?) {
func standardizePath(_ str: String) -> String {
@@ -424,8 +426,10 @@ struct DavResponse {
guard let hrefString = node[hreftag].value else { return nil }
// Percent-encoding space, some servers return invalid urls which space is not encoded to %20
let hrefStrPercented = hrefString.addingPercentEncoding(withAllowedCharacters: DavResponse.urlAllowed) ?? hrefString
// trying to figure out relative path out of href
let hrefAbsolute = URL(string: hrefString, relativeTo: baseURL)?.absoluteURL
let hrefAbsolute = URL(string: hrefStrPercented, relativeTo: baseURL)?.absoluteURL
let relativePath: String
if hrefAbsolute?.host?.replacingOccurrences(of: "www.", with: "", options: .anchored) == baseURL?.host?.replacingOccurrences(of: "www.", with: "", options: .anchored) {
relativePath = hrefAbsolute?.path.replacingOccurrences(of: baseURL?.absoluteURL.path ?? "", with: "", options: .anchored, range: nil) ?? hrefString
@@ -458,9 +462,11 @@ struct DavResponse {
break
}
for propItemNode in propStatNode[proptag].children {
propDic[propItemNode.name.components(separatedBy: ":").last!.lowercased()] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xml.contains("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
let key = propItemNode.name.components(separatedBy: ":").last!.lowercased()
guard propDic.index(forKey: key) == nil else { continue }
propDic[key] = propItemNode.value
if key == "resourcetype" && propItemNode.xml.contains("collection") {
propDic["getcontenttype"] = ContentMIMEType.directory.rawValue
}
}
self.href = href
@@ -501,19 +507,20 @@ public final class WebDavFileObject: FileObject {
self.size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
self.creationDate = davResponse.prop["creationdate"].flatMap { Date(rfcString: $0) }
self.modifiedDate = davResponse.prop["getlastmodified"].flatMap { Date(rfcString: $0) }
self.contentType = davResponse.prop["getcontenttype"] ?? "application/octet-stream"
self.contentType = davResponse.prop["getcontenttype"].flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
self.isHidden = (Int(davResponse.prop["ishidden"] ?? "0") ?? 0) > 0
self.type = self.contentType == "httpd/unix-directory" ? .directory : .regular
self.isReadOnly = (Int(davResponse.prop["isreadonly"] ?? "0") ?? 0) > 0
self.type = (self.contentType == .directory) ? .directory : .regular
self.entryTag = davResponse.prop["getetag"]
}
/// MIME type of the file.
open internal(set) var contentType: String {
open internal(set) var contentType: ContentMIMEType {
get {
return allValues[.mimeTypeKey] as? String ?? "application/octet-stream"
return (allValues[.mimeTypeKey] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
}
set {
allValues[.mimeTypeKey] = newValue
allValues[.mimeTypeKey] = newValue.rawValue
}
}
@@ -560,6 +567,8 @@ public final class WebDavFileObject: FileObject {
}
if propKeys.isEmpty {
propKeys = "<D:allprop/>"
} else {
propKeys += "<D:prop><D:resourcetype/></D:prop>"
}
return propKeys
}
+302
View File
@@ -0,0 +1,302 @@
//
// FilesProviderTests.swift
// FilesProviderTests
//
// Created by Amir Abbas on 8/11/1396 AP.
//
import XCTest
import FilesProvider
class FilesProviderTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testLocal() {
let provider = LocalFileProvider()
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testBasic(provider)
testOperations(provider)
}
func testWebDav() {
guard let urlStr = ProcessInfo.processInfo.environment["webdav_url"] else { return }
let url = URL(string: urlStr)!
let cred: URLCredential?
if let user = ProcessInfo.processInfo.environment["webdav_user"], let pass = ProcessInfo.processInfo.environment["webdav_password"] {
cred = URLCredential(user: user, password: pass, persistence: .forSession)
} else {
cred = nil
}
let provider = WebDAVFileProvider(baseURL: url, credential: cred)!
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testOperations(provider)
}
func testDropbox() {
guard let pass = ProcessInfo.processInfo.environment["dropbox_token"] else {
return
}
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = DropboxFileProvider(credential: cred)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testOperations(provider)
}
func testOneDrive() {
guard let pass = ProcessInfo.processInfo.environment["onedrive_token"] else {
return
}
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = OneDriveFileProvider(credential: cred)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testOperations(provider)
}
/*
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
*/
let timeout: Double = 20.0
let testFolderName = "Test"
let textFilePath = "/Test/file.txt"
let renamedFilePath = "/Test/renamed.txt"
let uploadFilePath = "/Test/uploaded.dat"
let sampleText = "Hello world!"
fileprivate func testCreateFolder(_ provider: FileProvider, folderName: String) {
let desc = "Creating folder at root in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.create(folder: folderName, at: "/") { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testContentsOfDirectory(_ provider: FileProvider) {
let desc = "Enumerating files list in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.contentsOfDirectory(path: "/") { (files, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertGreaterThan(files.count, 0, "list is empty")
let testFolder = files.filter({ $0.name == self.testFolderName }).first
XCTAssertNotNil(testFolder, "Test folder didn't listed")
guard testFolder != nil else { return }
XCTAssertTrue(testFolder!.isDirectory, "Test entry is not a folder")
XCTAssertLessThanOrEqual(testFolder!.size, 0, "folder size is not -1")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testAttributesOfFile(_ provider: FileProvider, filePath: String) {
let desc = "Attrubutes of file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.attributesOfItem(path: filePath) { (fileObject, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertNotNil(fileObject, "file '\(filePath)' didn't exist")
guard fileObject != nil else { return }
XCTAssertEqual(fileObject!.path, filePath, "file path is different from '\(filePath)'")
XCTAssertEqual(fileObject!.type, URLFileResourceType.regular, "file '\(filePath)' is not a regular file")
XCTAssertGreaterThan(fileObject!.size, 0, "file '\(filePath)' is empty")
XCTAssertNotNil(fileObject!.modifiedDate, "file '\(filePath)' has no modification date")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testCreateFile(_ provider: FileProvider, filePath: String) {
let desc = "Creating file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
let data = sampleText.data(using: .ascii)
provider.writeContents(path: filePath, contents: data, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testContentsFile(_ provider: FileProvider, filePath: String, hasSampleText: Bool = true) {
let desc = "Reading file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.contents(path: filePath) { (data, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertNotNil(data, "no data for test file")
if data != nil && hasSampleText {
let str = String(data: data!, encoding: .ascii)
XCTAssertNotNil(str, "test file data not readable")
XCTAssertEqual(str, self.sampleText, "test file data didn't matched")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testRenameFile(_ provider: FileProvider, filePath: String, to toPath: String) {
let desc = "Renaming file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.moveItem(path: filePath, to: toPath, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testCopyFile(_ provider: FileProvider, filePath: String, to toPath: String) {
let desc = "Copying file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.copyItem(path: filePath, to: toPath, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testRemoveFile(_ provider: FileProvider, filePath: String) {
let desc = "Deleting file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.removeItem(path: filePath) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
private func randomData(size: Int = 262144) -> Data {
var keyData = Data(count: size)
let result = keyData.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, keyData.count, $0)
}
if result == errSecSuccess {
return keyData
} else {
fatalError("Problem generating random bytes")
}
}
fileprivate func dummyFile() -> URL {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("dummyfile.dat")
if !FileManager.default.fileExists(atPath: url.path) {
let data = randomData()
try! data.write(to: url)
}
return url
}
fileprivate func testUploadFile(_ provider: FileProvider, filePath: String) {
// test Upload/Download
let url = dummyFile()
let desc = "Uploading file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
let dummy = dummyFile()
provider.copyItem(localFile: dummy, to: filePath) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
// TODO: check file existance of server
try? FileManager.default.removeItem(at: url)
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
}
fileprivate func testDownloadFile(_ provider: FileProvider, filePath: String) {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("downloadedfile.dat")
let desc = "Downloading file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.copyItem(path: filePath, toLocalURL: url) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertTrue(FileManager.default.fileExists(atPath: url.path), "downloaded file doesn't exist")
let size = (try? FileManager.default.attributesOfItem(atPath: url.path))?[FileAttributeKey.size] as? Int64
XCTAssertEqual(size, 262144, "downloaded file size is unexpected")
try? FileManager.default.removeItem(at: url)
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
}
fileprivate func testStorageProperties(_ provider: FileProvider, isExpected: Bool) {
let desc = "Querying volume in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.storageProperties { (volume) in
if !isExpected {
XCTAssertNotNil(volume, "volume information is nil")
guard volume != nil else { return }
XCTAssertGreaterThan(volume!.totalCapacity, 0, "capacity must be greater than 0")
XCTAssertGreaterThan(volume!.availableCapacity, 0, "available capacity must be greater than 0")
XCTAssertEqual(volume!.totalCapacity, volume!.availableCapacity + volume!.usage, "total capacity is not equal to usage + available")
} else {
XCTAssertNil(volume, "volume information must be nil")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testReachability(_ provider: FileProvider) {
// Test file operations
let desc = "Reachability of \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.isReachable { (status) in
XCTAssertTrue(status, "\(provider.type) not reachable")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testBasic(_ provider: FileProvider) {
let filepath = "/test/file.txt"
let fileurl = provider.url(of: filepath)
let composedfilepath = provider.relativePathOf(url: fileurl)
XCTAssertEqual(composedfilepath, "test/file.txt", "file url synthesis error")
let dirpath = "/test/"
let dirurl = provider.url(of: dirpath)
let composeddirpath = provider.relativePathOf(url: dirurl)
XCTAssertEqual(composeddirpath, "test", "directory url synthesis error")
let rooturl1 = provider.url(of: "")
let rooturl2 = provider.url(of: "/")
XCTAssertEqual(rooturl1, rooturl2, "root url synthesis error")
}
fileprivate func testOperations(_ provider: FileProvider) {
testReachability(provider)
testCreateFolder(provider, folderName: testFolderName)
testContentsOfDirectory(provider)
testCreateFile(provider, filePath: textFilePath)
testAttributesOfFile(provider, filePath: textFilePath)
testContentsFile(provider, filePath: textFilePath)
testRenameFile(provider, filePath: textFilePath, to: renamedFilePath)
testCopyFile(provider, filePath: renamedFilePath, to: textFilePath)
testRemoveFile(provider, filePath: textFilePath)
// TODO: Test search
// TODO: Test provider delegate
// Test upload/download
testUploadFile(provider, filePath: uploadFilePath)
testDownloadFile(provider, filePath: uploadFilePath)
}
}
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>