Compare commits

...

14 Commits

Author SHA1 Message Date
Amir Abbas ccb7961171 Minor performance improvement on iCloud file progress monitor 2018-05-04 00:42:25 +04:30
Amir Abbas e4fc6b24c0 Updated to version 0.24.1, Pods Swift version to 4.1 2018-05-04 00:25:27 +04:30
Amir Abbas f22af8d002 Fixed possible leak in iCloud provider when uploading file 2018-05-03 19:48:24 +04:30
Amir Abbas e5e5faa4e8 Fixed iCloud download/upload progress report, Fix #93
- Fixed FileObject comparison
- Fixed image GPS metadata population in ExtendedLocalFileProvider
- More items for image metadata in ExtendedLocalFileProvider
2018-05-03 19:39:18 +04:30
Amir Abbas f2cd571d7a Fixed LocalFileMonitor dir not refreshing, Fixed FTP connection refused error by retrying 2018-04-25 13:23:15 +04:30
Amir Abbas 4202f5e1bd FPStreamTask returning real error instead of timeout 2018-04-22 17:50:42 +04:30
Amir Abbas 3040215ce3 Fixed race conditions, Possible fix #79 2018-04-22 14:17:42 +04:30
Amir Abbas bd2f2b3954 Addded progressive read to FTP and HTTP providers
- Fix OneDive returned code 200 as Error
2018-04-16 11:06:54 +04:30
Amir Abbas 9d19768e1c Added test for NSCoding, fixed archiving 2018-04-08 10:22:59 +04:30
Amir Abbas bcc774d3a8 Fix podspec error 2018-04-07 21:52:49 +04:30
Amir Abbas ad9768a584 Secure encoding enabled, Fixed podspec 2018-04-07 12:19:45 +04:30
Amir Abbas a089cbc21c Fixed Swift 3 build error 2018-04-01 11:29:21 +04:30
Amir Abbas 090baa3b61 Fix Swift 4.0 compile error 2018-03-31 22:22:36 +04:30
Amir Abbas fc75c85b14 Fixed warnings on Swift 4.1, LocalFileMonitor is now public 2018-03-31 19:19:25 +04:30
28 changed files with 656 additions and 233 deletions
+7 -5
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FilesProvider"
s.version = "0.24.0"
s.version = "0.25.0"
s.summary = "FileManager replacement for Local and Remote (WebDAV/FTP/Dropbox/OneDrive/SMB2) files on iOS and macOS."
# This description is used to generate tags and improve search results.
@@ -55,9 +55,8 @@ Pod::Spec.new do |s|
# profile URL.
#
s.author = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
# Or just: s.author = "Amir Abbas Mousavian"
# s.authors = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
s.authors = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
s.social_media_url = "https://twitter.com/amosavian"
# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
@@ -66,7 +65,7 @@ Pod::Spec.new do |s|
# the deployment target. You can optionally include the target after the platform.
#
s.swift_version = "4.0"
s.swift_version = "4.1"
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.10"
# s.watchos.deployment_target = "2.0"
@@ -117,7 +116,10 @@ Pod::Spec.new do |s|
#
# s.framework = "SomeFramework"
# s.frameworks = "SomeFramework", "AnotherFramework"
s.frameworks = "AVFoundation", "ImageIO", "CoreGraphics"
s.ios.framework = "UIKit"
s.tvos.framework = "UIKit"
s.osx.framework = "AppKit"
s.library = "xml2"
# s.libraries = "iconv", "xml2"
+7 -3
View File
@@ -496,7 +496,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0910;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 0930;
TargetAttributes = {
799396661D48B7F600086753 = {
CreatedOnToolsVersion = 7.3.1;
@@ -720,16 +720,18 @@
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.24.0;
BUNDLE_VERSION_STRING = 0.25.0;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
@@ -759,16 +761,18 @@
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.24.0;
BUNDLE_VERSION_STRING = 0.25.0;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
@@ -0,0 +1,8 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0920"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -37,7 +36,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0920"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -37,7 +36,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0920"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -37,7 +36,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
+1 -1
View File
@@ -471,7 +471,7 @@ We would love for you to contribute to **FileProvider**, check the `LICENSE` fil
Things you may consider to help us:
- [ ] Implement request/response stack for `SMBClient`
- [ ] Implement Test-case (`XCTest`)
- [x] Implement Test-case (`XCTest`)
- [ ] Add Sample project for iOS
- [ ] Add Sample project for macOS
+77 -60
View File
@@ -93,11 +93,11 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
}
public required convenience init?(coder aDecoder: NSCoder) {
if let containerId = aDecoder.decodeObject(forKey: "containerId") as? String,
let scopeString = aDecoder.decodeObject(forKey: "scope") as? String,
if let containerId = aDecoder.decodeObject(of: NSString.self, forKey: "containerId") as String?,
let scopeString = aDecoder.decodeObject(of: NSString.self, forKey: "scope") as String?,
let scope = UbiquitousScope(rawValue: scopeString) {
self.init(containerId: containerId, scope: scope)
} else if let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL {
} else if let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? {
self.init(baseURL: baseURL)
} else {
return nil
@@ -583,16 +583,84 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
return file
}
lazy fileprivate var observer: KVOObserver = KVOObserver()
fileprivate func monitorFile(path: String, operation: FileOperationType, progress: Progress?) {
var isDownloadingOperation: Bool
let isUploadingOperation: Bool
switch operation {
case .copy(_, destination: let dest) where dest.hasPrefix("file://"), .move(_, destination: let dest) where dest.hasPrefix("file://"):
fallthrough
case .fetch:
isDownloadingOperation = true
isUploadingOperation = false
case .copy(source: let source, _) where source.hasPrefix("file://"), .move(source: let source, _) where source.hasPrefix("file://"):
fallthrough
case .modify, .create:
isDownloadingOperation = false
isUploadingOperation = true
default:
return
}
let pathURL = self.url(of: path).standardizedFileURL
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K LIKE[CD] %@", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataUbiquitousItemPercentDownloadedKey, NSMetadataUbiquitousItemPercentUploadedKey, NSMetadataUbiquitousItemDownloadingStatusKey, NSMetadataItemFSSizeKey]
query.predicate = NSPredicate(format: "(%K LIKE[CD] %@)", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataUbiquitousItemPercentDownloadedKey,
NSMetadataUbiquitousItemPercentUploadedKey,
NSMetadataUbiquitousItemIsUploadedKey,
NSMetadataUbiquitousItemDownloadingStatusKey,
NSMetadataItemFSSizeKey]
query.searchScopes = [self.scope.rawValue]
var context = QueryProgressWrapper(provider: self, progress: progress, operation: operation)
query.addObserver(self.observer, forKeyPath: "results", options: [.initial, .new, .old], context: &context)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: .main) { [weak self] (notification) in
guard let items = notification.userInfo?[NSMetadataQueryUpdateChangedItemsKey] as? NSArray,
let item = items.firstObject as? NSMetadataItem else {
return
}
func terminateAndRemoveObserver() {
guard observer != nil else { return }
query.stop()
observer.flatMap(NotificationCenter.default.removeObserver)
observer = nil
}
func updateProgress(_ percent: NSNumber) {
let fraction = percent.doubleValue / 100
self?.delegateNotify(operation, progress: fraction)
if let progress = progress {
if progress.totalUnitCount < 1, let size = item.value(forAttribute: NSMetadataItemFSSizeKey) as? NSNumber {
progress.totalUnitCount = size.int64Value
}
progress.completedUnitCount = progress.totalUnitCount > 0 ? Int64(Double(progress.totalUnitCount) * fraction) : 0
}
if percent.doubleValue == 100.0 {
terminateAndRemoveObserver()
}
}
for attrName in item.attributes {
switch attrName {
case NSMetadataUbiquitousItemPercentDownloadedKey:
guard isDownloadingOperation, let percent = item.value(forAttribute: attrName) as? NSNumber else { break }
updateProgress(percent)
case NSMetadataUbiquitousItemPercentUploadedKey:
guard isUploadingOperation, let percent = item.value(forAttribute: attrName) as? NSNumber else { break }
updateProgress(percent)
case NSMetadataUbiquitousItemDownloadingStatusKey:
if isDownloadingOperation, let value = item.value(forAttribute: attrName) as? String,
value == NSMetadataUbiquitousItemDownloadingStatusDownloaded {
terminateAndRemoveObserver()
}
case NSMetadataUbiquitousItemIsUploadedKey:
if isUploadingOperation, let value = item.value(forAttribute: attrName) as? NSNumber, value.boolValue {
terminateAndRemoveObserver()
}
default:
break
}
}
}
DispatchQueue.main.async {
query.start()
@@ -691,57 +759,6 @@ public enum UbiquitousScope: RawRepresentable {
}
}
}
struct QueryProgressWrapper {
weak var provider: CloudFileProvider?
weak var progress: Progress?
let operation: FileOperationType
}
fileprivate class KVOObserver: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let query = object as? NSMetadataQuery else {
return
}
guard let wrapper = context?.load(as: QueryProgressWrapper.self) else {
query.stop()
query.removeObserver(self, forKeyPath: "results")
return
}
let provider = wrapper.provider
let progress = wrapper.progress
let operation = wrapper.operation
guard let results = change?[.newKey], let item = (results as? [NSMetadataItem])?.first else {
return
}
query.disableUpdates()
var size = progress?.totalUnitCount ?? -1
if size < 0, let size_d = item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 {
size = size_d
progress?.totalUnitCount = size
}
let downloadStatus = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? String ?? ""
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
progress?.completedUnitCount = Int64(uploaded / 100 * Double(size))
provider?.delegateNotify(operation, progress: uploaded / 100)
} else if (uploaded == 0 || uploaded == 100) && downloadStatus != NSMetadataUbiquitousItemDownloadingStatusCurrent {
progress?.completedUnitCount = Int64(downloaded / 100 * Double(size))
provider?.delegateNotify(operation, progress: downloaded / 100)
} else if uploaded == 100 || downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent {
progress?.completedUnitCount = size
query.stop()
query.removeObserver(self, forKeyPath: "results")
provider?.delegateNotify(operation)
}
query.enableUpdates()
}
}
/*
func getMetadataItem(url: URL) -> NSMetadataItem? {
let query = NSMetadataQuery()
+1 -1
View File
@@ -44,7 +44,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
}
public required convenience init?(coder aDecoder: NSCoder) {
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.init(credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"))
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
}
+1 -1
View File
@@ -94,7 +94,7 @@ internal extension DropboxFileProvider {
request.httpMethod = "POST"
request.setValue(authentication: self.credential, with: .oAuth2)
request.setValue(contentType: .json)
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path) as NSString!]
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path)! as NSString]
requestDictionary["query"] = queryStr as NSString
requestDictionary["start"] = NSNumber(value: (token.flatMap( { Int($0) } ) ?? 0))
request.httpBody = Data(jsonDictionary: requestDictionary)
+38 -11
View File
@@ -282,26 +282,31 @@ public struct LocalFileInformationGenerator {
let imageDict = cfImageDict as NSDictionary
let tiffDict = imageDict[kCGImagePropertyTIFFDictionary as String] as? NSDictionary ?? [:]
let exifDict = imageDict[kCGImagePropertyExifDictionary as String] as? NSDictionary ?? [:]
let gpsDict = imageDict[kCGImagePropertyGPSDictionary as String] as? NSDictionary ?? [:]
if let pixelWidth = imageDict.object(forKey: kCGImagePropertyPixelWidth) as? NSNumber, let pixelHeight = imageDict.object(forKey: kCGImagePropertyPixelHeight) as? NSNumber {
add(key: "Dimensions", value: "\(pixelWidth)x\(pixelHeight)")
}
add(key: "DPI", value: imageDict[kCGImagePropertyDPIWidth as String])
add(key: "Device make", value: tiffDict[kCGImagePropertyTIFFMake as String])
add(key: "Device maker", value: tiffDict[kCGImagePropertyTIFFMake as String])
add(key: "Device model", value: tiffDict[kCGImagePropertyTIFFModel as String])
add(key: "Lens model", value: exifDict[kCGImagePropertyExifLensModel as String])
add(key: "Artist", value: tiffDict[kCGImagePropertyTIFFArtist as String] as? String)
add(key: "Copyright", value: tiffDict[kCGImagePropertyTIFFCopyright as String] as? String)
add(key: "Date taken", value: tiffDict[kCGImagePropertyTIFFDateTime as String] as? String)
if let latitude = tiffDict[kCGImagePropertyGPSLatitude as String] as? NSNumber, let longitude = tiffDict[kCGImagePropertyGPSLongitude as String] as? NSNumber {
add(key: "Location", value: "\(latitude), \(longitude)")
if let latitude = gpsDict[kCGImagePropertyGPSLatitude as String] as? NSNumber,
let longitude = gpsDict[kCGImagePropertyGPSLongitude as String] as? NSNumber {
let altitudeDesc = (gpsDict[kCGImagePropertyGPSAltitude as String] as? NSNumber).map({ " at \($0.format(precision: 0))m" }) ?? ""
add(key: "Location", value: "\(latitude.format()), \(longitude.format())\(altitudeDesc)")
}
add(key: "Altitude", value: tiffDict[kCGImagePropertyGPSAltitude as String] as? NSNumber)
add(key: "Area", value: tiffDict[kCGImagePropertyGPSAreaInformation as String])
add(key: "Area", value: gpsDict[kCGImagePropertyGPSAreaInformation as String])
add(key: "Color space", value: imageDict[kCGImagePropertyColorModel as String])
add(key: "Color depth", value: (imageDict[kCGImagePropertyDepth as String] as? NSNumber).map({ "\($0) bits" }))
add(key: "Color profile", value: imageDict[kCGImagePropertyProfileName as String])
add(key: "Focal length", value: exifDict[kCGImagePropertyExifFocalLength as String])
add(key: "White banance", value: exifDict[kCGImagePropertyExifWhiteBalance as String])
add(key: "F number", value: exifDict[kCGImagePropertyExifFNumber as String])
add(key: "Exposure program", value: exifDict[kCGImagePropertyExifExposureProgram as String])
@@ -325,7 +330,7 @@ public struct LocalFileInformationGenerator {
}
}
func makeDescription(_ key: String?) -> String? {
func makeKeyDescription(_ key: String?) -> String? {
guard let key = key else {
return nil
}
@@ -336,6 +341,18 @@ public struct LocalFileInformationGenerator {
return newKey.capitalized
}
func parseLocationData(_ value: String) -> (latitude: Double, longitude: Double, height: Double?)? {
let scanner = Scanner.init(string: value)
var latitude: Double = 0.0, longitude: Double = 0.0, height: Double = 0
if scanner.scanDouble(&latitude), scanner.scanDouble(&longitude) {
scanner.scanDouble(&height)
return (latitude, longitude, height)
} else {
return nil
}
}
guard fileURL.fileExists else {
return (dic, keys)
}
@@ -347,10 +364,20 @@ public struct LocalFileInformationGenerator {
#else
let commonKey = item.commonKey
#endif
if let description = makeDescription(commonKey) {
if let value = item.stringValue {
keys.append(description)
dic[description] = value
if let key = makeKeyDescription(commonKey) {
if commonKey == "location", let value = item.stringValue, let loc = parseLocationData(value) {
keys.append(key)
let heightStr: String = (loc.height as NSNumber?).map({ ", \($0.format(precision: 0))m" }) ?? ""
dic[key] = "\((loc.latitude as NSNumber).format())°, \((loc.longitude as NSNumber).format())°\(heightStr)"
} else if let value = item.dateValue {
keys.append(key)
dic[key] = value
} else if let value = item.numberValue {
keys.append(key)
dic[key] = value
} else if let value = item.stringValue {
keys.append(key)
dic[key] = value
}
}
}
+34 -12
View File
@@ -410,12 +410,6 @@ internal extension Data {
guard self.count >= start + length else { return nil }
return String(data: self.subdata(in: start..<start+length), encoding: encoding)
}
static func mapMemory<T, U>(from: T) -> U? {
guard MemoryLayout<T>.size >= MemoryLayout<U>.size else { return nil }
let data = Data(value: from)
return data.scanValue()
}
}
internal extension String {
@@ -447,14 +441,14 @@ internal extension String {
}
}
#if swift(>=4.0)
#else
extension String {
var count: Int {
return self.characters.count
internal extension NSNumber {
internal func format(precision: Int = 2, style: NumberFormatter.Style = .decimal) -> String {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = precision
formatter.numberStyle = style
return formatter.string(from: self)!
}
}
#endif
internal extension TimeInterval {
internal var formatshort: String {
@@ -592,3 +586,31 @@ func hasSuffix(_ suffix: String) -> (_ value: String) -> Bool {
value.hasSuffix(suffix)
}
}
// Legacy Swift versions support
#if swift(>=4.0)
#else
extension String {
var count: Int {
return self.characters.count
}
}
#endif
#if swift(>=4.1)
#else
extension Array {
func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try self.flatMap(transform)
}
}
extension ArraySlice {
func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try self.flatMap(transform)
}
}
#endif
+5 -2
View File
@@ -410,9 +410,12 @@ fileprivate func arrayOfBytes<T>(_ value:T, length:Int? = nil) -> [UInt8] {
bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
}
valuePointer.deinitialize()
valuePointer.deinitialize(count: 1)
#if swift(>=4.1)
valuePointer.deallocate()
#else
valuePointer.deallocate(capacity: 1)
#endif
return bytes
}
+150 -27
View File
@@ -8,14 +8,67 @@
import Foundation
private var lasttaskIdAssociated = 1_000_000_000
private var _lasttaskIdAssociated = 1_000_000_000
let _lasttaskIdAssociated_lock: NSLock = NSLock()
var lasttaskIdAssociated: Int {
get {
_lasttaskIdAssociated_lock.try()
defer {
_lasttaskIdAssociated_lock.unlock()
}
return _lasttaskIdAssociated
}
set {
_lasttaskIdAssociated_lock.try()
defer {
_lasttaskIdAssociated_lock.unlock()
}
_lasttaskIdAssociated = newValue
}
}
// codebeat:disable[TOTAL_LOC,TOO_MANY_IVARS]
/// This class is a replica of NSURLSessionStreamTask with same api for iOS 7/8
/// while it can actually fallback to NSURLSessionStreamTask in iOS 9.
public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
fileprivate var inputStream: InputStream?
fileprivate var outputStream: OutputStream?
fileprivate var _inputStream: InputStream?
fileprivate var _outputStream: OutputStream?
let _inputStream_lock: NSLock = NSLock()
var inputStream: InputStream? {
get {
_inputStream_lock.try()
defer {
_inputStream_lock.unlock()
}
return _inputStream
}
set {
_inputStream_lock.try()
defer {
_inputStream_lock.unlock()
}
_inputStream = newValue
}
}
let _outputStream_lock: NSLock = NSLock()
var outputStream: OutputStream? {
get {
_outputStream_lock.try()
defer {
_outputStream_lock.unlock()
}
return _outputStream
}
set {
_outputStream_lock.try()
defer {
_outputStream_lock.unlock()
}
_outputStream = newValue
}
}
fileprivate var operation_queue: OperationQueue!
internal var _underlyingSession: URLSession
@@ -331,17 +384,18 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
}
}
self.operation_queue.isSuspended = true
self._state = .canceling
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.outputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream = nil
self.outputStream = nil
@@ -350,7 +404,25 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
self._countOfBytesRecieved = 0
}
var _error: Error? = nil
var __error: Error? = nil
let _error_lock: NSLock = NSLock()
var _error: Error? {
get {
_error_lock.try()
defer {
_error_lock.unlock()
}
return __error
}
set {
_error_lock.try()
defer {
_error_lock.unlock()
}
__error = newValue
}
}
/**
* An error object that indicates why the task failed.
@@ -398,29 +470,29 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
if inputStream == nil || outputStream == nil {
if let host = host {
if self.inputStream == nil || self.outputStream == nil {
if let host = self.host {
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host.hostname as CFString, UInt32(host.port), &readStream, &writeStream)
} else if let service = service {
} else if let service = self.service {
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain as CFString, service.type as CFString, service.name as CFString, Int32(service.port))
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
}
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, let outputStream = outputStream else {
self.inputStream = readStream?.takeRetainedValue()
self.outputStream = writeStream?.takeRetainedValue()
guard let inputStream = self.inputStream, let outputStream = self.outputStream else {
return
}
streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
self.streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
}
guard let inputStream = inputStream, let outputStream = outputStream else {
guard let inputStream = self.inputStream, let outputStream = self.outputStream else {
return
}
if isSecure {
inputStream.setProperty(securityLevel.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(securityLevel.rawValue, forKey: .socketSecurityLevelKey)
if self.isSecure {
inputStream.setProperty(self.securityLevel.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(self.securityLevel.rawValue, forKey: .socketSecurityLevelKey)
} else {
inputStream.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
@@ -435,12 +507,37 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
inputStream.open()
outputStream.open()
operation_queue.isSuspended = false
_state = .running
self.operation_queue.isSuspended = false
self._state = .running
}
fileprivate var dataToBeSent: Data = Data()
fileprivate var dataReceived: Data = Data()
fileprivate var _dataReceived: Data = Data()
// We are updating `dataReceived` from main thread while reading it from operation_queue.
let _dataReceived_lock: NSLock = NSLock()
var dataReceived: Data {
get {
if !_dataReceived_lock.try() {
print("not locked")
}
defer {
_dataReceived_lock.unlock()
}
return _dataReceived
}
set {
if !_dataReceived_lock.try() {
print("not locked")
}
defer {
_dataReceived_lock.unlock()
}
_dataReceived = newValue
}
}
/**
* Asynchronously reads a number of bytes from the stream, and calls a handler upon completion.
@@ -471,6 +568,11 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
operation_queue.addOperation {
var timedOut: Bool = false
while (self.dataReceived.count == 0 || self.dataReceived.count < minBytes) && !timedOut {
if let error = inputStream.streamError {
completionHandler(nil, inputStream.streamStatus == .atEnd, error)
return
}
Thread.sleep(forTimeInterval: 0.1)
timedOut = expireDate < Date()
}
@@ -584,6 +686,10 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
var byteSent: Int = 0
let expireDate = Date(timeIntervalSinceNow: timeout)
while self.dataToBeSent.count > 0 && (timeout == 0 || expireDate > Date()) {
if let _ = outputStream.streamError {
return byteSent == 0 ? -1 : byteSent
}
let bytesWritten = self.dataToBeSent.withUnsafeBytes {
outputStream.write($0, maxLength: self.dataToBeSent.count)
}
@@ -684,18 +790,35 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
}
}
private var _retriesForInputStream: Int = 0
private var _retriesForOutputStream: Int = 0
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if eventCode.contains(.errorOccurred) {
self._error = aStream.streamError
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
let retries: Int
if aStream == self.inputStream {
retries = _retriesForInputStream
_retriesForInputStream += 1
} else if aStream == self.outputStream {
retries = _retriesForOutputStream
_retriesForOutputStream += 1
} else {
return
}
if retries < 3 {
aStream.open()
} else {
self._error = aStream.streamError
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
}
}
if aStream == inputStream && eventCode.contains(.hasBytesAvailable) {
while (inputStream!.hasBytesAvailable) {
if aStream == self.inputStream && eventCode.contains(.hasBytesAvailable) {
while (self.inputStream!.hasBytesAvailable) {
var buffer = [UInt8](repeating: 0, count: 2048)
let len = inputStream!.read(&buffer, maxLength: buffer.count)
let len = self.inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
dataReceived.append(&buffer, count: len)
self.dataReceived.append(&buffer, count: len)
self._countOfBytesRecieved += Int64(len)
}
}
+65 -9
View File
@@ -12,7 +12,7 @@ import Foundation
Allows accessing to FTP files and directories. This provider doesn't cache or save files internally.
It's a complete reimplementation and doesn't use CFNetwork deprecated API.
*/
open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite, FileProviderReadWriteProgressive {
/// FTP data connection mode.
public enum Mode: String {
@@ -114,6 +114,8 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
super.init()
}
/**
@@ -135,13 +137,13 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else { return nil }
let mode: Mode
if let modeStr = aDecoder.decodeObject(forKey: "mode") as? String, let mode_v = Mode(rawValue: modeStr) {
if let modeStr = aDecoder.decodeObject(of: NSString.self, forKey: "mode") as String?, let mode_v = Mode(rawValue: modeStr) {
mode = mode_v
} else {
let passiveMode = aDecoder.decodeBool(forKey: "passiveMode")
mode = passiveMode ? .passive : .active
}
self.init(baseURL: baseURL, mode: mode, credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.init(baseURL: baseURL, mode: mode, credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"))
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
self.supportsRFC3659 = aDecoder.decodeBool(forKey: "supportsRFC3659")
@@ -248,7 +250,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
}
let files: [FileObject] = contents.flatMap {
let files: [FileObject] = contents.compactMap {
rfc3659enabled ? self.parseMLST($0, in: path) : (self.parseUnixList($0, in: path) ?? self.parseDOSList($0, in: path))
}
@@ -305,7 +307,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
self.attributesOfItem(path: path, rfc3659enabled: false, completionHandler: completionHandler)
}
let lines = response.components(separatedBy: "\n").flatMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) }
let lines = response.components(separatedBy: "\n").compactMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) }
guard lines.count > 2 else {
throw self.urlError(path, code: .badServerResponse)
}
@@ -559,7 +561,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { recevied, totalReceived, totalSize in
}, onProgress: { _, recevied, totalReceived, totalSize in
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
@@ -643,6 +645,60 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
return progress
}
public func contents(path: String, offset: Int64, length: Int, responseHandler: ((URLResponse) -> Void)?, progressHandler: @escaping (Int64, Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.fetch(path: path)
if length == 0 || offset < 0 {
dispatch_queue.async {
completionHandler?(nil)
self.delegateNotify(operation)
}
return nil
}
let progress = Progress(totalUnitCount: 0)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
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.ftpFileData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { data, recevied, totalReceived, totalSize in
progressHandler(totalReceived - recevied, data)
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (data, error) in
if let error = error {
progress.cancel()
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
self.dispatch_queue.async {
completionHandler?(nil)
self.delegateNotify(operation)
}
}
}
return progress
}
/**
Creates a symbolic link at the specified path that points to an item at the given path.
This method does not traverse symbolic links contained in destination path, making it possible
@@ -705,9 +761,9 @@ extension FTPFileProvider {
return
}
let codes: [Int] = response.components(separatedBy: .newlines).flatMap({ $0.isEmpty ? nil : $0})
.flatMap {
let code = $0.components(separatedBy: .whitespaces).flatMap({ $0.isEmpty ? nil : $0}).first
let codes: [Int] = response.components(separatedBy: .newlines).compactMap({ $0.isEmpty ? nil : $0})
.compactMap {
let code = $0.components(separatedBy: .whitespaces).compactMap({ $0.isEmpty ? nil : $0}).first
return code != nil ? Int(code!) : nil
}
+57 -12
View File
@@ -178,13 +178,13 @@ internal extension FTPFileProvider {
throw self.urlError("", code: .badServerResponse)
}
let destArray = destString.components(separatedBy: ",").flatMap({ UInt32(trimmedNumber($0)) })
let destArray = destString.components(separatedBy: ",").compactMap({ UInt32(trimmedNumber($0)) })
guard destArray.count == 6 else {
throw self.urlError("", code: .badServerResponse)
}
// first 4 elements are ip, 2 next are port, as byte
var host = destArray.prefix(4).flatMap(String.init).joined(separator: ".")
var host = destArray.prefix(4).compactMap(String.init).joined(separator: ".")
let portHi = Int(destArray[4]) << 8
let portLo = Int(destArray[5])
let port = portHi + portLo
@@ -319,6 +319,7 @@ internal extension FTPFileProvider {
return
}
let success_lock = NSLock()
var success = false
let command = useMLST ? "MLSD \(path)" : "LIST \(path)"
self.execute(command: command, on: task, minLength: 20, afterSend: { error in
@@ -326,6 +327,7 @@ internal extension FTPFileProvider {
let timeout = self.session.configuration.timeoutIntervalForRequest
var finalData = Data()
var eof = false
let error_lock = NSLock()
var error: Error?
do {
@@ -337,17 +339,22 @@ internal extension FTPFileProvider {
finalData.append(data)
}
eof = seof
error_lock.try()
error = serror
error_lock.unlock()
group.leave()
}
let waitResult = group.wait(timeout: .now() + timeout)
error_lock.try()
if let error = error {
error_lock.unlock()
if (error as? URLError)?.code != .cancelled {
throw error
}
return
}
error_lock.unlock()
if waitResult == .timedOut {
throw self.urlError(path, code: .timedOut)
@@ -359,8 +366,10 @@ internal extension FTPFileProvider {
}
let contents: [String] = response.components(separatedBy: "\n")
.flatMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
.compactMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
success_lock.try()
success = true
success_lock.unlock()
completionHandler(contents, nil)
} catch {
completionHandler([], error)
@@ -382,8 +391,12 @@ internal extension FTPFileProvider {
throw self.urlError(path, code: .unsupportedURL)
}
success_lock.try()
if !success && !(response.hasPrefix("25") || response.hasPrefix("15")) {
success_lock.unlock()
throw FileProviderFTPError(message: response, path: path)
} else {
success_lock.unlock()
}
} catch {
self.dispatch_queue.async {
@@ -477,6 +490,7 @@ internal extension FTPFileProvider {
DispatchQueue.global().async {
var totalReceived: Int64 = 0
var eof = false
let error_lock = NSLock()
var error: Error?
while !eof {
let group = DispatchGroup()
@@ -491,15 +505,20 @@ internal extension FTPFileProvider {
onProgress(data, totalReceived, totalSize)
}
eof = segeof || (length > 0 && totalReceived >= Int64(length))
error_lock.try()
error = segerror
error_lock.unlock()
group.leave()
}
let waitResult = group.wait(timeout: .now() + timeout)
error_lock.try()
if let error = error {
error_lock.unlock()
completionHandler(error)
return
}
error_lock.unlock()
if waitResult == .timedOut {
completionHandler(self.urlError(filePath, code: .timedOut))
@@ -538,7 +557,7 @@ internal extension FTPFileProvider {
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)?,
onProgress: ((_ data: Data, _ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) {
// Check cache
@@ -552,7 +571,7 @@ internal extension FTPFileProvider {
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)
onProgress?(data, Int64(data.count), total, expected)
}) { (error) in
if let error = error {
completionHandler(nil, error)
@@ -637,6 +656,7 @@ internal extension FTPFileProvider {
return
}
let len = 19 /* TYPE response */ + 44 + filePath.count /* STOR open response */ + 10 /* RETR Transfer complete message. */
let success_lock = NSLock()
var success = false
self.execute(command: "TYPE I" + "\r\n" + "STOR \(filePath)", on: task, minLength: len, afterSend: { error in
onTask?(dataTask)
@@ -655,6 +675,7 @@ internal extension FTPFileProvider {
}
let chunkSize = 4096
let lock = NSLock()
var eof = false
var sent: Int64 = 0
repeat {
@@ -673,33 +694,49 @@ internal extension FTPFileProvider {
let group = DispatchGroup()
group.enter()
dataTask.write(subdata, timeout: timeout, completionHandler: { (serror) in
lock.try()
error = serror
sent += Int64(subdata.count)
let totalsent = sent
let sentbytes = Int64(subdata.count)
lock.unlock()
group.leave()
onProgress?(Int64(subdata.count), sent, size)
onProgress?(sentbytes, totalsent, size)
//print("ftp \(filePath): \(subdata.count), \(sent), \(size)")
})
let waitResult = group.wait(timeout: .now() + timeout)
lock.try()
if waitResult == .timedOut {
error = self.urlError(filePath, code: .timedOut)
}
if let error = error {
lock.unlock()
completionHandler(error)
return
}
if let data = fromData {
lock.try()
let endIndex = min(data.count, Int(sent) + chunkSize)
eof = endIndex == data.count
lock.unlock()
} else if let fileHandle = fileHandle {
eof = Int64(fileHandle.offsetInFile) == size
}
} while !eof
success_lock.try()
success = true
success_lock.unlock()
}) { (response, error) in
guard success else { return }
success_lock.try()
guard success else {
success_lock.unlock()
return
}
success_lock.unlock()
do {
if let error = error {
@@ -822,6 +859,7 @@ internal extension FTPFileProvider {
}
// Send retreive command
let success_lock = NSLock()
var success = false
let len = 19 /* TYPE response */ + 65 + String(position).count /* REST Response */ + 44 + filePath.count /* STOR open response */ + 10 /* RETR Transfer complete message. */
self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "STOR \(filePath)", on: task, minLength: len, afterSend: { error in
@@ -836,13 +874,20 @@ internal extension FTPFileProvider {
completionHandler(error)
return
}
success_lock.try()
success = true
success_lock.unlock()
dataTask.closeRead()
dataTask.closeWrite()
})
}) { (response, error) in
guard success else { return }
success_lock.try()
guard success else {
success_lock.unlock()
return
}
success_lock.unlock()
do {
if let error = error {
@@ -899,7 +944,7 @@ internal extension FTPFileProvider {
farDateFormatter.dateFormat = "MMM dd yyyy"
let thisYear = gregorian.component(.year, from: Date())
let components = text.components(separatedBy: " ").flatMap { $0.isEmpty ? nil : $0 }
let components = text.components(separatedBy: " ").compactMap { $0.isEmpty ? nil : $0 }
guard components.count >= 9 else { return nil }
let posixPermission = components[0]
let linksCount = Int(components[1]) ?? 0
@@ -947,7 +992,7 @@ internal extension FTPFileProvider {
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "M-d-y hh:mma"
let components = text.components(separatedBy: " ").flatMap { $0.isEmpty ? nil : $0 }
let components = text.components(separatedBy: " ").compactMap { $0.isEmpty ? nil : $0 }
guard components.count >= 4 else { return nil }
let size = Int64(components[2]) ?? -1
let date = components[0..<2].joined(separator: " ")
@@ -968,7 +1013,7 @@ internal extension FTPFileProvider {
}
func parseMLST(_ text: String, in path: String) -> FileObject? {
var components = text.components(separatedBy: ";").flatMap { $0.isEmpty ? nil : $0 }
var components = text.components(separatedBy: ";").compactMap { $0.isEmpty ? nil : $0 }
guard components.count > 1 else { return nil }
let nameOrPath = components.removeLast().trimmingCharacters(in: .whitespacesAndNewlines)
@@ -985,7 +1030,7 @@ internal extension FTPFileProvider {
var attributes = [String: String]()
for component in components {
let keyValue = component.components(separatedBy: "=") .flatMap { $0.isEmpty ? nil : $0 }
let keyValue = component.components(separatedBy: "=").compactMap { $0.isEmpty ? nil : $0 }
guard keyValue.count >= 2, !keyValue[0].isEmpty else { continue }
attributes[keyValue[0].lowercased()] = keyValue.dropFirst().joined(separator: "=")
}
+23 -3
View File
@@ -9,7 +9,7 @@
import Foundation
/// Containts path, url and attributes of a file or resource.
open class FileObject: Equatable {
open class FileObject: NSObject {
/// A `Dictionary` contains file information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
@@ -19,6 +19,7 @@ open class FileObject: Equatable {
internal init(url: URL?, name: String, path: String) {
self.allValues = [URLResourceKey: Any]()
super.init()
if let url = url {
self.url = url
}
@@ -147,6 +148,18 @@ open class FileObject: Equatable {
open var isSymLink: Bool {
return self.type == .symbolicLink
}
}
extension FileObject {
open override var hashValue: Int {
let hashURL = self.url.hashValue
let hashSize = self.size.hashValue
return (hashURL << 7) &+ hashURL &+ hashSize
}
open override var hash: Int {
return self.hashValue
}
/// Check `FileObject` equality
public static func ==(lhs: FileObject, rhs: FileObject) -> Bool {
@@ -159,7 +172,7 @@ open class FileObject: Equatable {
}
#else
if type(of: lhs) != type(of: rhs) {
return false
return false
}
#endif
@@ -169,6 +182,13 @@ open class FileObject: Equatable {
return rhs.path == lhs.path && rhs.size == lhs.size && rhs.modifiedDate == lhs.modifiedDate
}
open override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? FileObject else { return false }
return self == object
}
}
extension FileObject {
internal func mapPredicate() -> [String: Any] {
let mapDict: [URLResourceKey: String] = [.fileURLKey: "url", .nameKey: "name", .pathKey: "path",
.fileSizeKey: "fileSize", .creationDateKey: "creationDate",
@@ -225,7 +245,7 @@ open class FileObject: Equatable {
}
/// Containts attributes of a provider.
open class VolumeObject {
open class VolumeObject: NSObject {
/// A `Dictionary` contains volume information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
+87 -17
View File
@@ -569,6 +569,72 @@ extension FileProviderReadWrite {
}
}
/// Defines method for fetching file contents progressivly
public protocol FileProviderReadWriteProgressive {
/**
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- progressHandler: a closure which will be called every time a new data received from server.
- position: Start position of returned data, indexed from zero.
- data: returned `Data` from server.
- completionHandler: a closure which will be called after receiving is completed or an error occureed.
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
func contents(path: String, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress?
/**
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- responseHandler: a closure which will be called after fetching server response.
- response: `URLResponse` returned from server.
- progressHandler: a closure which will be called every time a new data received from server.
- position: Start position of returned data, indexed from zero.
- data: returned `Data` from server.
- completionHandler: a closure which will be called after receiving is completed or an error occureed.
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
func contents(path: String, responseHandler: ((_ response: URLResponse) -> Void)?, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress?
/**
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- offset: First byte index which should be read. **Starts from 0.**
- length: Bytes count of data. Pass `-1` to read until the end of file.
- responseHandler: a closure which will be called after fetching server response.
- response: `URLResponse` returned from server.
- progressHandler: a closure which will be called every time a new data received from server.
- position: Start position of returned data, indexed from offset.
- data: returned `Data` from server.
- completionHandler: a closure which will be called after receiving is completed or an error occureed.
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
func contents(path: String, offset: Int64, length: Int, responseHandler: ((_ response: URLResponse) -> Void)?, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress?
}
public extension FileProviderReadWriteProgressive {
@discardableResult
public func contents(path: String, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
return contents(path: path, offset: 0, length: -1, responseHandler: nil, progressHandler: progressHandler, completionHandler: completionHandler)
}
@discardableResult
public func contents(path: String, responseHandler: ((_ response: URLResponse) -> Void)?, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
return contents(path: path, offset: 0, length: -1, responseHandler: responseHandler, progressHandler: progressHandler, completionHandler: completionHandler)
}
}
/// Allows a file provider to notify changes occured
public protocol FileProviderMonitor: FileProviderBasic {
@@ -764,27 +830,19 @@ public extension FileProviderBasic {
internal func urlError(_ path: String, code: URLError.Code) -> Error {
let fileURL = self.url(of: path)
var userInfo: [String: Any] = [NSURLErrorKey: fileURL,
let userInfo: [String: Any] = [NSURLErrorKey: fileURL,
NSURLErrorFailingURLErrorKey: fileURL,
NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString,
]
let error = NSError(domain: NSURLErrorDomain, code: code.rawValue, userInfo: nil)
for (key, value) in error.userInfo {
userInfo[key] = value
}
return URLError(code, userInfo: userInfo)
}
internal func cocoaError(_ path: String, code: CocoaError.Code) -> Error {
let fileURL = self.url(of: path)
var userInfo: [String: Any] = [NSFilePathErrorKey: path,
let userInfo: [String: Any] = [NSFilePathErrorKey: path,
NSURLErrorKey: fileURL,
]
let error = NSError(domain: NSCocoaErrorDomain, code: code.rawValue, userInfo: nil)
for (key, value) in error.userInfo {
userInfo[key] = value
}
return cocoaError(fileURL.path, code: code)
return CocoaError(code, userInfo: userInfo)
}
internal func NotImplemented(_ fn: String = #function, file: StaticString = #file) {
@@ -890,7 +948,11 @@ extension ExtendedFileProvider {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
#if os(macOS)
#if swift(>=3.2)
#if swift(>=4.0)
let ppp = Int(NSScreen.main?.backingScaleFactor ?? 1) // fetch device is retina or not
#elseif swift(>=3.3)
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
#elseif 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
@@ -898,15 +960,23 @@ extension ExtendedFileProvider {
size.width *= CGFloat(ppp)
size.height *= CGFloat(ppp)
#if swift(>=3.2)
#if swift(>=4.0)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB,
bytesPerRow: 0, bitsPerPixel: 0)
#elseif swift(>=3.3)
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)
#elseif 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: .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)
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 {
+5 -5
View File
@@ -14,7 +14,7 @@ import Foundation
No instance of this class should (and can) be created. Use derived classes instead. It leads to a crash with `fatalError()`.
*/
open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite, FileProviderReadWriteProgressive {
open class var type: String { fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.") }
open let baseURL: URL?
open var dispatch_queue: DispatchQueue
@@ -97,6 +97,8 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
super.init()
}
public required convenience init?(coder aDecoder: NSCoder) {
@@ -285,12 +287,10 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
- Returns: An `Progress` to get progress or cancel progress.
*/
@discardableResult
open func contents(path: String, offset: Int64 = 0, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
open func contents(path: String, offset: Int64 = 0, length: Int = -1, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.fetch(path: path)
var request = self.request(for: operation)
if offset > 0 {
request.addValue("bytes \(offset)-", forHTTPHeaderField: "Range")
}
request.setValue(rangeWithOffset: offset, length: length)
var position: Int64 = offset
return download_progressive(path: path, request: request, operation: operation, responseHandler: responseHandler, progressHandler: { data in
progressHandler(position, data)
+9 -11
View File
@@ -14,7 +14,7 @@ import Foundation
it uses `FileManager` foundation class with some additions like searching and reading a portion of file.
*/
open class LocalFileProvider: FileProvider, FileProviderMonitor {
open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
open class var type: String { return "Local" }
open fileprivate(set) var baseURL: URL?
open var dispatch_queue: DispatchQueue
@@ -114,12 +114,14 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
super.init()
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
return nil
}
self.init(baseURL: baseURL)
@@ -158,7 +160,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
dispatch_queue.async {
do {
let contents = try self.fileManager.contentsOfDirectory(at: self.url(of: path), includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants)
let filesAttributes = contents.flatMap({ (fileURL) -> LocalFileObject? in
let filesAttributes = contents.compactMap({ (fileURL) -> LocalFileObject? in
let path = self.relativePathOf(url: fileURL)
return LocalFileObject(fileWithPath: path, relativeTo: self.baseURL)
})
@@ -580,16 +582,12 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
return self.doOperation(operation, data: data ?? Data(), atomically: atomically, completionHandler: completionHandler)
}
fileprivate var monitors = [LocalFolderMonitor]()
fileprivate var monitors = [LocalFileMonitor]()
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
let dirurl = self.url(of: path)
let isdir = (try? dirurl.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false
if !isdir {
return
}
let monitor = LocalFolderMonitor(url: dirurl) {
let url = self.url(of: path)
let monitor = LocalFileMonitor(url: url) {
eventHandler()
}
monitor.start()
@@ -597,7 +595,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
}
open func unregisterNotifcation(path: String) {
var removedMonitor: LocalFolderMonitor?
var removedMonitor: LocalFileMonitor?
for (i, monitor) in monitors.enumerated() {
if self.relativePathOf(url: monitor.url) == path {
removedMonitor = monitors.remove(at: i)
+9 -7
View File
@@ -89,19 +89,21 @@ public final class LocalFileObject: FileObject {
}
}
internal final class LocalFolderMonitor {
public final class LocalFileMonitor {
fileprivate let source: DispatchSourceFileSystemObject
fileprivate let descriptor: CInt
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: .default)
fileprivate var state: Bool = false
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
var url: URL
public var url: URL
/// Creates a folder monitor object with monitoring enabled.
init(url: URL, handler: @escaping ()->Void) {
public init(url: URL, handler: @escaping ()->Void) {
self.url = url
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: .write, queue: qq)
descriptor = open((url.absoluteURL as NSURL).fileSystemRepresentation, O_EVTONLY)
let event: DispatchSource.FileSystemEvent = url.fileIsDirectory ? [.write] : .all
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: event, queue: qq)
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
@@ -126,7 +128,7 @@ internal final class LocalFolderMonitor {
}
/// Starts sending notifications if currently stopped
func start() {
public func start() {
if !state {
state = true
source.resume()
@@ -134,7 +136,7 @@ internal final class LocalFolderMonitor {
}
/// Stops sending notifications if currently enabled
func stop() {
public func stop() {
if state {
state = false
source.suspend()
+16 -17
View File
@@ -146,13 +146,13 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
public required convenience init?(coder aDecoder: NSCoder) {
let route: Route
if let driveId = aDecoder.decodeObject(forKey: "drive") as? String, let uuid = UUID(uuidString: driveId) {
if let driveId = aDecoder.decodeObject(of: NSString.self, forKey: "drive") as String?, let uuid = UUID(uuidString: driveId) {
route = .drive(uuid: uuid)
} else {
route = (aDecoder.decodeObject(forKey: "route") as? String).flatMap({ Route(rawValue: $0) }) ?? .me
}
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential,
serverURL: aDecoder.decodeObject(forKey: "baseURL") as? URL,
self.init(credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"),
serverURL: aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL?,
route: route)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
@@ -227,12 +227,12 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var fileObject: OneDriveFileObject?
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 = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: json) {
fileObject = file
}
}
if let json = data?.deserializeJSON(), let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: json) {
fileObject = file
}
completionHandler(fileObject, serverError ?? error)
})
@@ -290,7 +290,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
*/
@discardableResult
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).flatMap { $0.value as? String }.first
let queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).compactMap { $0.value as? String }.first
return paginated(path, requestHandler: { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
@@ -555,16 +555,15 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var link: URL?
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() {
if let linkDic = json["link"] as? NSDictionary, let linkStr = linkDic["webUrl"] as? String {
link = URL(string: linkStr)
}
}
if let json = data?.deserializeJSON() {
if let linkDic = json["link"] as? NSDictionary, let linkStr = linkDic["webUrl"] as? String {
link = URL(string: linkStr)
}
}
completionHandler(link, nil, nil, serverError ?? error)
})
task.resume()
@@ -589,9 +588,9 @@ extension OneDriveFileProvider: ExtendedFileProvider {
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON() {
(dic, keys) = self.mapMediaInfo(json)
}
}
if let json = data?.deserializeJSON() {
(dic, keys) = self.mapMediaInfo(json)
}
completionHandler(dic, keys, serverError ?? error)
})
+2 -2
View File
@@ -53,7 +53,7 @@ public final class OneDriveFileObject: FileObject {
self.entryTag = json["eTag"] as? String
let hashes = json["file"]?["hashes"] as? NSDictionary
// checks for both sha1 or quickXor. First is available in personal drives, second in business one.
self.hash = (hashes?["sha1Hash"] as? String) ?? (hashes?["quickXorHash"] as? String)
self.fileHash = (hashes?["sha1Hash"] as? String) ?? (hashes?["quickXorHash"] as? String)
}
/// The document identifier is a value assigned by the OneDrive to a file.
@@ -88,7 +88,7 @@ public final class OneDriveFileObject: FileObject {
}
/// Calculated hash from OneDrive server. Hex string SHA1 in personal or Base65 string [QuickXOR](https://dev.onedrive.com/snippets/quickxorhash.htm) in business drives.
open internal(set) var hash: String? {
open internal(set) var fileHash: String? {
get {
return allValues[.documentIdentifierKey] as? String
}
+2 -2
View File
@@ -28,7 +28,7 @@ class SMBClient: NSObject, StreamDelegate {
public var timeout: TimeInterval = 30
internal private(set) var messageId: UInt64 = 0
private func createMessageId() -> UInt64 {
fileprivate func createMessageId() -> UInt64 {
defer {
messageId += 1
}
@@ -36,7 +36,7 @@ class SMBClient: NSObject, StreamDelegate {
}
internal private(set) var credit: UInt16 = 0
private func consumeCredit() -> UInt16 {
fileprivate func consumeCredit() -> UInt16 {
if credit > 0 {
credit -= 1
return credit
+2 -2
View File
@@ -310,11 +310,11 @@ extension SMB2 {
static let ipv6: sa_family_t = 0x17
var sockaddr: sockaddr_in {
return Data.mapMemory(from: self.sockaddrStorage)!
return unsafeBitCast(self.sockaddrStorage, to: sockaddr_in.self)
}
var sockaddr6: sockaddr_in6 {
return Data.mapMemory(from: self.sockaddrStorage)!
return unsafeBitCast(self.sockaddrStorage, to: sockaddr_in6.self)
}
}
}
+18 -9
View File
@@ -72,17 +72,23 @@ extension SMB2 {
let header: SMB2FilesInformationHeader
switch type {
case .fileDirectoryInformation:
header = buffer.scanValue(start: offset) as FileDirectoryInformationHeader!
let tHeader: FileDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
case .fileFullDirectoryInformation:
header = buffer.scanValue(start: offset) as FileFullDirectoryInformationHeader!
let tHeader: FileFullDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
case .fileIdFullDirectoryInformation:
header = buffer.scanValue(start: offset) as FileIdFullDirectoryInformationHeader!
let tHeader: FileIdFullDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
case .fileBothDirectoryInformation:
header = buffer.scanValue(start: offset) as FileBothDirectoryInformationHeader!
let tHeader: FileBothDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
case .fileIdBothDirectoryInformation:
header = buffer.scanValue(start: offset) as FileIdBothDirectoryInformationHeader!
let tHeader: FileIdBothDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
case .fileNamesInformation:
header = buffer.scanValue(start: offset) as FileNamesInformationHeader!
let tHeader: FileNamesInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
default:
return []
}
@@ -98,8 +104,10 @@ extension SMB2 {
}
init? (data: Data) {
let offset = Int(data.scanValue(start: 2) as UInt16!)
let length = Int(data.scanValue(start: 4) as UInt32!)
let tOffset: UInt16 = data.scanValue(start: 2)!
let offset = Int(tOffset)
let tLength: UInt32 = data.scanValue(start: 4)!
let length = Int(tLength)
guard data.count > offset + length else {
return nil
}
@@ -200,7 +208,8 @@ extension SMB2 {
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
let offset: UInt16 = decode(offsetData)*/
let length = Int(data.scanValue(start: 4) as UInt32!)
let tLength: UInt32 = data.scanValue(start: 4)!
let length = Int(tLength)
guard data.count >= 8 + length else {
return nil
+2 -2
View File
@@ -46,11 +46,11 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
return nil
}
self.init(baseURL: baseURL,
credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"))
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
}
+27 -3
View File
@@ -27,6 +27,7 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testBasic(provider)
testArchiving(provider)
testOperations(provider)
}
@@ -41,6 +42,7 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
let provider = WebDAVFileProvider(baseURL: url, credential: cred)!
provider.delegate = self
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
@@ -54,6 +56,7 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = DropboxFileProvider(credential: cred)
provider.delegate = self
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
@@ -72,6 +75,7 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
let provider = FTPFileProvider(baseURL: url, mode: .extendedPassive, credential: cred)!
provider.delegate = self
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
@@ -85,10 +89,11 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = OneDriveFileProvider(credential: cred)
provider.delegate = self
testBasic(provider)
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testBasic(provider)
testOperations(provider)
}
@@ -207,7 +212,12 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
if provider is FTPFileProvider {
// FTP will need to download and upload file again.
wait(for: [expectation], timeout: timeout * 6)
} else {
wait(for: [expectation], timeout: timeout)
}
print("Test fulfilled: \(desc).")
}
@@ -247,7 +257,6 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
fileprivate func testUploadFile(_ provider: FileProvider, filePath: String) {
// test Upload/Download
let url = dummyFile()
let desc = "Uploading file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
@@ -310,6 +319,21 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
print("Test fulfilled: \(desc).")
}
fileprivate func testArchiving(_ provider: FileProvider) {
let archivedData = NSKeyedArchiver.archivedData(withRootObject: provider)
let unarchived = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as? FileProvider
XCTAssertNotNil(unarchived)
XCTAssertEqual(unarchived?.baseURL, provider.baseURL, "archived provider is not same with original")
XCTAssertEqual(unarchived?.credential, provider.credential, "archived provider is not same with original")
if let provider = provider as? FileProviderBasicRemote, let unarchived_r = unarchived as? FileProviderBasicRemote {
XCTAssertEqual(unarchived_r.useCache, provider.useCache, "archived provider is not same with original")
XCTAssertEqual(unarchived_r.validatingCache, provider.validatingCache, "archived provider is not same with original")
}
if let provider = provider as? OneDriveFileProvider, let unarchived_o = unarchived as? OneDriveFileProvider {
XCTAssertEqual(unarchived_o.route.rawValue, provider.route.rawValue, "archived provider is not same with original")
}
}
fileprivate func testBasic(_ provider: FileProvider) {
let filepath = "/test/file.txt"
let fileurl = provider.url(of: filepath)