Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28d01a9c59 | |||
| 5d5e6811fd | |||
| 1f2aa4888c | |||
| cf1b226417 | |||
| 1ddb4b140d | |||
| 86fe84351f | |||
| ca248f08dd | |||
| b17d92350c | |||
| d38748153d | |||
| 17140f40d7 | |||
| 2820b4d3fe | |||
| 7cd1528d75 | |||
| 14b4bcefe4 | |||
| a43640ecfd | |||
| f7f2d7f734 | |||
| 5aaf8f2bf2 | |||
| 026e496512 |
+5
-8
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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]()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user