Compare commits

...

8 Commits

Author SHA1 Message Date
Amir Abbas Mousavian dca5dddbfd Code beat exceptions added 2016-08-20 19:19:05 +04:30
Amir Abbas Mousavian 6764c23f1b Update README.md and some refactors 2016-08-15 13:50:03 +04:30
Amir Abbas Mousavian 955a86372b Refactored NSURLSession delegate in Dropbox and WebDAV 2016-08-15 13:30:37 +04:30
Amir Abbas Mousavian 32a20fbc49 CodeCov added 2016-08-11 14:26:24 +04:30
Amir Abbas Mousavian 921ba19afa travis yml update 2016-08-11 14:16:04 +04:30
Amir Abbas Mousavian 8625b0464c Travis CI integration 2016-08-11 14:04:37 +04:30
Amir Abbas Mousavian 1bf9d1eadd Swift Package Manager Added 2016-08-11 13:49:28 +04:30
Amir Abbas Mousavian cbf774ba9e update README.md 2016-08-11 12:52:04 +04:30
12 changed files with 313 additions and 293 deletions
+10
View File
@@ -0,0 +1,10 @@
language: objective-c
osx_image: xcode7.3
script:
- set pipefail
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider OSX" -sdk macosx | xcpretty
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider iOS" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider tvOS" -sdk appletvsimulator ONLY_ACTIVE_ARCH=NO | xcpretty
- pod lib lint --quick
after_success:
- bash <(curl -s https://codecov.io/bash)
+8
View File
@@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
794C21FE1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
794C21FF1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
794C22001D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
@@ -76,6 +79,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
7902C0851D61B56D00564440 /* SessionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionDelegate.swift; sourceTree = "<group>"; };
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxHelper.swift; sourceTree = "<group>"; };
794C22091D5893F800EC49B8 /* SMB2Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Notification.swift; sourceTree = "<group>"; };
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2QueryTypes.swift; sourceTree = "<group>"; };
@@ -171,6 +175,7 @@
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
799396961D48C02300086753 /* LocalFileProvider.swift */,
7902C0851D61B56D00564440 /* SessionDelegate.swift */,
799396971D48C02300086753 /* SMBClient.swift */,
799396981D48C02300086753 /* SMBFileProvider.swift */,
794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */,
@@ -359,6 +364,7 @@
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */,
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */,
799396A71D48C02300086753 /* AEXML.swift in Sources */,
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
@@ -388,6 +394,7 @@
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */,
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */,
799396A81D48C02300086753 /* AEXML.swift in Sources */,
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
@@ -417,6 +424,7 @@
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */,
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */,
799396A91D48C02300086753 /* AEXML.swift in Sources */,
799396D01D48C02300086753 /* SMB2Session.swift in Sources */,
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
+5
View File
@@ -0,0 +1,5 @@
import PackageDescription
let package = Package(
name: "FileProvider"
)
+12 -13
View File
@@ -4,12 +4,15 @@
[![Swift Version][swift-image]][swift-url]
[![License][license-image]][license-url]
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20OSX-lightgray.svg)]()
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://img.shields.io/cocoapods/v/FileProvider.svg)
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20macOS-lightgray.svg)]()
[![Build Status][travis-image]][travis-url]
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://cocoapods.org/pods/FileProvider)
[![codebeat badge][codebeat-image]][codebeat-url]
<!---
[![Build Status][travis-image]][travis-url]
[![codecov](https://codecov.io/gh/amosavian/FileProvider/branch/master/graph/badge.svg)](https://codecov.io/gh/amosavian/FileProvider)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
--->
@@ -22,10 +25,10 @@ Local and WebDAV providers are fully tested and can be used in production enviro
## Features
- [x] **LocalFileProvider** a wrapper around `NSFileManager` with some additions like searching and reading a portion of file.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission protocol standard, replaced FTP.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, replaced FTP.
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API. For now it has limitation in uploading files up to 150MB.
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!*. SMB1/CIFS is depericated and very tricky to be implemented
- [ ] **FTPFileProvider** while depericated in 1990s, it's still in use on some Web hosts.
- [ ] **FTPFileProvider** while deprecated in 1990s, it's still in use on some Web hosts.
- [ ] **AmazonS3FileProvider**
## Requirements
@@ -277,17 +280,13 @@ Amir-Abbas Mousavian [@amosavian](https://twitter.com/amosavian)
Distributed under the MIT license. See `LICENSE` for more information.
[https://github.com/yourname/github-link](https://github.com/dbader/)
[https://github.com/amosavian/](https://github.com/amosavian/)
[swift-image]:https://img.shields.io/badge/swift-2.2%2C%202.3-green.svg
[swift-image]:https://img.shields.io/badge/Swift-2.2%2C%202.3-green.svg
[swift-url]: https://swift.org/
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
[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/dbader/node-datadog-metrics/master.svg?style=flat-square
[travis-url]: https://travis-ci.org/dbader/node-datadog-metrics
--->
[travis-image]: https://img.shields.io/travis/amosavian/FileProvider/master.svg
[travis-url]: https://travis-ci.org/amosavian/FileProvider
+8 -55
View File
@@ -26,11 +26,13 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
public let credential: NSURLCredential?
private var _session: NSURLSession?
private var sessionDelegate: SessionDelegate?
internal var session: NSURLSession {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = NSOperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: queue)
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: sessionDelegate, delegateQueue: queue)
}
return _session!
}
@@ -67,7 +69,7 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
}
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) : nil
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = self.jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) {
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) {
completionHandler(attributes: file, error: dbError)
return
}
@@ -85,7 +87,7 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
request.HTTPMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = self.jsonToDictionary(jsonStr) {
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = jsonToDictionary(jsonStr) {
let totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.longLongValue ?? -1
let usedSize = (json["used"] as? NSNumber)?.longLongValue ?? 0
completionHandler(total: totalSize, used: usedSize)
@@ -177,7 +179,7 @@ extension DropboxFileProvider: FileProviderOperations {
completionHandler?(error: error)
return
}
upload_simple(toPath, data: data, overwrite: true, operation: .Copy(source: localFile.absoluteString, destination: toPath), completionHandler: completionHandler)
upload_simple(toPath, data: data, overwrite: true, operation: .Copy(source: localFile.uw_absoluteString, destination: toPath), completionHandler: completionHandler)
}
public func copyPathToLocalFile(path: String, toLocalURL destURL: NSURL, completionHandler: SimpleCompletionHandler) {
@@ -202,7 +204,7 @@ extension DropboxFileProvider: FileProviderOperations {
completionHandler?(error: e)
}
})
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": destURL.uw_absoluteString])
task.taskDescription = dictionaryToJSON(["type": "Copy", "source": path, "dest": destURL.uw_absoluteString])
task.resume()
}
}
@@ -309,7 +311,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
self.session.dataTaskWithRequest(request) { (data, response, error) in
var image: ImageClass? = nil
if let r = response as? NSHTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = self.jsonToDictionary(result) {
if let r = response as? NSHTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = jsonToDictionary(result) {
if jsonResult["error"] != nil {
completionHandler(image: nil, error: self.throwError(path, code: NSURLError.CannotDecodeRawData))
}
@@ -327,52 +329,3 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
extension DropboxFileProvider: FileProvider {}
// MARK: URLSession delegate
extension DropboxFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
return
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .Create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .Copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .Move(source: source, destination: dest)
case "Modify":
op = .Modify(path: source)
case "Remove":
op = .Remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .Link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.delegate?.fileproviderProgress(self, operation: op, progress: progress)
}
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, dest = json["dest"] as? String else {
return
}
self.delegate?.fileproviderProgress(self, operation: .Copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
}
+8 -4
View File
@@ -23,14 +23,17 @@ public final class DropboxFileObject: FileObject {
public let id: String?
public let rev: String?
// codebeat:disable[ARITY]
public init(name: String, path: String, size: Int64 = -1, serverTime: NSDate? = nil, modifiedDate: NSDate? = nil, fileType: FileType = .Regular, isHidden: Bool = false, isReadOnly: Bool = false, id: String? = nil, rev: String? = nil) {
self.serverTime = serverTime
self.id = id
self.rev = rev
super.init(absoluteURL: NSURL(string: path), name: name, path: path, size: size, createdDate: nil, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
// codebeat:disable[ARITY]
internal extension DropboxFileProvider {
func list(path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: ((contents: [FileObject], cursor: String?, error: ErrorType?) -> Void)) {
var requestDictionary = [String: AnyObject]()
@@ -54,7 +57,7 @@ internal extension DropboxFileProvider {
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
}
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
let json = jsonToDictionary(jsonStr)
if let entries = json?["entries"] as? [AnyObject] where entries.count > 0 {
var files = prevContents
for entry in entries {
@@ -118,7 +121,7 @@ internal extension DropboxFileProvider {
default:
break
}
task.taskDescription = self.dictionaryToJSON(dic)
task.taskDescription = dictionaryToJSON(dic)
task.resume()
}
@@ -139,7 +142,7 @@ internal extension DropboxFileProvider {
responseError = FileProviderDropboxError(code: rCode, path: startPath, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
}
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
let json = jsonToDictionary(jsonStr)
if let entries = json?["matches"] as? [AnyObject] where entries.count > 0 {
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
@@ -161,10 +164,11 @@ internal extension DropboxFileProvider {
task.resume()
}
}
// codebeat:enable[ARITY]
internal extension DropboxFileProvider {
func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
guard let json = jsonToDictionary(jsonStr) else { return nil }
return self.mapToFileObject(json)
}
+17 -17
View File
@@ -260,23 +260,6 @@ extension FileProviderBasic {
//self.init()
return nil
}
internal func jsonToDictionary(jsonString: String) -> [String: AnyObject]? {
guard let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) else {
return nil
}
if let dic = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject] {
return dic
}
return nil
}
internal func dictionaryToJSON(dictionary: [String: AnyObject]) -> String? {
if let data = try? NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions()) {
return String(data: data, encoding: NSUTF8StringEncoding)
}
return nil
}
}
@@ -369,3 +352,20 @@ internal extension NSURL {
#endif
}
}
internal func jsonToDictionary(jsonString: String) -> [String: AnyObject]? {
guard let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) else {
return nil
}
if let dic = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject] {
return dic
}
return nil
}
internal func dictionaryToJSON(dictionary: [String: AnyObject]) -> String? {
if let data = try? NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions()) {
return String(data: data, encoding: NSUTF8StringEncoding)
}
return nil
}
+3 -2
View File
@@ -10,11 +10,12 @@ import Foundation
public final class LocalFileObject: FileObject {
public let allocatedSize: Int64
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, allocatedSize: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
// codebeat:disable[ARITY]
public init(absoluteURL: NSURL, name: String, path: String, size: Int64 = -1, allocatedSize: Int64 = 0, createdDate: NSDate? = nil, modifiedDate: NSDate? = nil, fileType: FileType = .Regular, isHidden: Bool = false, isReadOnly: Bool = false) {
self.allocatedSize = allocatedSize
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
public class LocalFileProvider: FileProvider, FileProviderMonitor {
+1 -1
View File
@@ -75,7 +75,7 @@ struct SMB1 {
}
}
init(command: Command, ntStatus: UInt32 = 0, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16) {
init(command: Command, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], ntStatus: UInt32 = 0, securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0) {
self.protocolID = Header.protocolConst
self._command = command.rawValue
_status = (UInt8(ntStatus & 0xff), UInt8(ntStatus >> 8 & 0xff), UInt8(ntStatus >> 16 & 0xff), UInt8(ntStatus >> 24 & 0xff))
+2
View File
@@ -45,6 +45,7 @@ struct SMB2 {
let sessionId: UInt64
let signature: (UInt64, UInt64)
// codebeat:disable[ARITY]
init(command: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [], nextCommand: UInt32 = 0, messageId: UInt64, treeId: UInt32, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = self.dynamicType.protocolConst
self.size = 64
@@ -76,6 +77,7 @@ struct SMB2 {
self.sessionId = sessionId
self.signature = signature
}
// codebeat:enable[ARITY]
}
struct Flags: OptionSetType {
+179
View File
@@ -0,0 +1,179 @@
//
// SessionDelegate.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
class SessionDelegate: NSObject, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
weak var fileProvider: FileProvider?
var credential: NSURLCredential?
var finishDownloadHandler: ((session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL: NSURL) -> Void)?
var didSendDataHandler: ((session: NSURLSession, task: NSURLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void)?
var didReceivedData: ((session: NSURLSession, downloadTask: NSURLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) -> Void)?
init(fileProvider: FileProvider, credential: NSURLCredential?) {
self.fileProvider = fileProvider
self.credential = credential
}
// codebeat:disable[ARITY]
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
self.finishDownloadHandler?(session: session, downloadTask: downloadTask, didFinishDownloadingToURL: location)
return
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
self.didSendDataHandler?(session: session, task: task, bytesSent: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .Create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .Copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .Move(source: source, destination: dest)
case "Modify":
op = .Modify(path: source)
case "Remove":
op = .Remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .Link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: op, progress: progress)
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
self.didReceivedData?(session: session, downloadTask: downloadTask, bytesWritten: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, dest = json["dest"] as? String else {
return
}
fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: .Copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
}
public enum FileProviderHTTPErrorCode: Int {
case Continue = 100
case SwitchingProtocols = 101
case Processing = 102
case OK = 200
case Created = 201
case Accepted = 202
case NonAuthoritativeInformation = 203
case NoContent = 204
case ResetContent = 205
case PartialContent = 206
case MultiStatus = 207
case AlreadyReported = 208
case IMUsed = 226
case MultipleChoices = 300
case MovedPermanently = 301
case Found = 302
case SeeOther = 303
case NotModified = 304
case UseProxy = 305
case SwitchProxy = 306
case TemporaryRedirect = 307
case PermanentRedirect = 308
case BadRequest = 400
case Unauthorized = 401
case PaymentRequired = 402
case Forbidden = 403
case NotFound = 404
case MethodNotAllowed = 405
case NotAcceptable = 406
case ProxyAuthenticationRequired = 407
case RequestTimeout = 408
case Conflict = 409
case Gone = 410
case LengthRequired = 411
case PreconditionFailed = 412
case PayloadTooLarge = 413
case URITooLong = 414
case UnsupportedMediaType = 415
case RangeNotSatisfiable = 416
case ExpectationFailed = 417
case MisdirectedRequest = 421
case UnprocessableEntity = 422
case Locked = 423
case FailedDependency = 424
case UnorderedCollection = 425
case UpgradeRequired = 426
case PreconditionRequired = 428
case TooManyRequests = 429
case RequestHeaderFieldsTooLarge = 431
case UnavailableForLegalReasons = 451
case InternalServerError = 500
case BadGateway = 502
case ServiceUnavailable = 503
case GatewayTimeout = 504
case HTTPVersionNotSupported = 505
case VariantlsoNegotiates = 506
case InsufficientStorage = 507
case LoopDetected = 508
case BandwidthLimitExceeded = 509
case NotExtended = 510
case NetworkAuthenticationRequired = 511
private static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
private static let status2xx = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
private static let status3xx = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
private static let status4xx = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
private static let status5xx = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
public var description: String {
switch self.rawValue {
case 100...102: return FileProviderHTTPErrorCode.status1xx[self.rawValue]!
case 200...208, 226: return FileProviderHTTPErrorCode.status2xx[self.rawValue]!
case 300...308: return FileProviderHTTPErrorCode.status3xx[self.rawValue]!
case 400...417, 421...426: fallthrough
case 428, 429, 431, 451: return FileProviderHTTPErrorCode.status4xx[self.rawValue]!
case 500...511: return FileProviderHTTPErrorCode.status5xx[self.rawValue]!
default: return typeDescription
}
}
public var typeDescription: String {
switch self.rawValue {
case 100...199: return "Informational"
case 200...299: return "Success"
case 300...399: return "Redirection"
case 400...499: return "Client Error"
case 500...599: return "Server Error"
default: return "Server Error"
}
}
}
+60 -201
View File
@@ -12,11 +12,13 @@ public final class WebDavFileObject: FileObject {
public let contentType: String
public let entryTag: String?
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, contentType: String, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, entryTag: String?) {
// codebeat:disable[ARITY]
public init(absoluteURL: NSURL, name: String, path: String, size: Int64 = -1, contentType: String = "", createdDate: NSDate? = nil, modifiedDate: NSDate? = nil, fileType: FileType = .Regular, isHidden: Bool = false, isReadOnly: Bool = false, entryTag: String? = nil) {
self.contentType = contentType
self.entryTag = entryTag
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
@@ -36,11 +38,13 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
public let credential: NSURLCredential?
private var _session: NSURLSession?
private var sessionDelegate: SessionDelegate?
private var session: NSURLSession {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = NSOperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: queue)
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: sessionDelegate, delegateQueue: queue)
}
return _session!
}
@@ -177,7 +181,7 @@ extension WebDAVFileProvider: FileProviderOperations {
completionHandler?(error: responseError ?? error)
self.delegateNotify(.Create(path: (path as NSString).stringByAppendingPathComponent(fileAttribs.name)), error: responseError ?? error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Create", "source": (path as NSString).stringByAppendingPathComponent(fileAttribs.name)])
task.taskDescription = dictionaryToJSON(["type": "Create", "source": (path as NSString).stringByAppendingPathComponent(fileAttribs.name)])
task.resume()
}
@@ -258,7 +262,7 @@ extension WebDAVFileProvider: FileProviderOperations {
completionHandler?(error: responseError ?? error)
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: responseError ?? error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
task.taskDescription = dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
task.resume()
}
@@ -280,7 +284,7 @@ extension WebDAVFileProvider: FileProviderOperations {
}
completionHandler?(error: responseError ?? error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
task.taskDescription = dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
task.resume()
}
}
@@ -330,7 +334,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
}
}
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
task.taskDescription = dictionaryToJSON(["type": "Modify", "source": path])
task.resume()
}
@@ -407,49 +411,8 @@ internal extension WebDAVFileProvider {
break
}
for responseNode in rootnode[responsetag].all ?? [] {
var hreftag = "href"
var statustag = "status"
var propstattag = "propstat"
for node in responseNode.children ?? [] {
if node.name.lowercaseString.hasSuffix("href") {
hreftag = node.name
}
if node.name.lowercaseString.hasSuffix("status") {
statustag = node.name
}
if node.name.lowercaseString.hasSuffix("propstat") {
propstattag = node.name
}
}
let href = responseNode[hreftag].value
if let href = href, hrefURL = NSURL(string: href) {
var status: Int?
let statusDesc = (responseNode[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
}
var propDic = [String: String]()
let propStatNode = responseNode[propstattag]
for node in propStatNode.children ?? [] where node.name.lowercaseString.hasSuffix("status"){
statustag = node.name
break
}
let statusDesc2 = (propStatNode[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children ?? [] where tnode.name.lowercaseString.hasSuffix("prop") {
proptag = tnode.name
break
}
for propItemNode in propStatNode[proptag].children ?? [] {
propDic[propItemNode.name.componentsSeparatedByString(":").last!.lowercaseString] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xmlString.containsString("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
}
result.append(DavResponse(href: hrefURL, hrefString: href, status: status, prop: propDic))
if let davResponse = mapNodeToDavResponse(responseNode) {
result.append(davResponse)
}
}
} catch _ {
@@ -457,6 +420,54 @@ internal extension WebDAVFileProvider {
return result
}
private func mapNodeToDavResponse(node: AEXMLElement) -> DavResponse? {
var hreftag = "href"
var statustag = "status"
var propstattag = "propstat"
for node in node.children ?? [] {
if node.name.lowercaseString.hasSuffix("href") {
hreftag = node.name
}
if node.name.lowercaseString.hasSuffix("status") {
statustag = node.name
}
if node.name.lowercaseString.hasSuffix("propstat") {
propstattag = node.name
}
}
let href = node[hreftag].value
if let href = href, hrefURL = NSURL(string: href) {
var status: Int?
let statusDesc = (node[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
}
var propDic = [String: String]()
let propStatNode = node[propstattag]
for node in propStatNode.children ?? [] where node.name.lowercaseString.hasSuffix("status"){
statustag = node.name
break
}
let statusDesc2 = (propStatNode[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children ?? [] where tnode.name.lowercaseString.hasSuffix("prop") {
proptag = tnode.name
break
}
for propItemNode in propStatNode[proptag].children ?? [] {
propDic[propItemNode.name.componentsSeparatedByString(":").last!.lowercaseString] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xmlString.containsString("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
}
return DavResponse(href: hrefURL, hrefString: href, status: status, prop: propDic)
}
return nil
}
private func mapToFileObject(davResponse: DavResponse) -> WebDavFileObject {
var href = davResponse.href
if href.baseURL == nil {
@@ -483,65 +494,6 @@ internal extension WebDAVFileProvider {
}
}
// MARK: URLSession delegate
extension WebDAVFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
return
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .Create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .Copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .Move(source: source, destination: dest)
case "Modify":
op = .Modify(path: source)
case "Remove":
op = .Remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .Link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.delegate?.fileproviderProgress(self, operation: op, progress: progress)
}
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, dest = json["dest"] as? String else {
return
}
self.delegate?.fileproviderProgress(self, operation: .Copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
}
public struct FileProviderWebDavError: ErrorType, CustomStringConvertible {
public let code: FileProviderHTTPErrorCode
public let url: NSURL
@@ -549,97 +501,4 @@ public struct FileProviderWebDavError: ErrorType, CustomStringConvertible {
public var description: String {
return code.description
}
}
public enum FileProviderHTTPErrorCode: Int {
case Continue = 100
case SwitchingProtocols = 101
case Processing = 102
case OK = 200
case Created = 201
case Accepted = 202
case NonAuthoritativeInformation = 203
case NoContent = 204
case ResetContent = 205
case PartialContent = 206
case MultiStatus = 207
case AlreadyReported = 208
case IMUsed = 226
case MultipleChoices = 300
case MovedPermanently = 301
case Found = 302
case SeeOther = 303
case NotModified = 304
case UseProxy = 305
case SwitchProxy = 306
case TemporaryRedirect = 307
case PermanentRedirect = 308
case BadRequest = 400
case Unauthorized = 401
case PaymentRequired = 402
case Forbidden = 403
case NotFound = 404
case MethodNotAllowed = 405
case NotAcceptable = 406
case ProxyAuthenticationRequired = 407
case RequestTimeout = 408
case Conflict = 409
case Gone = 410
case LengthRequired = 411
case PreconditionFailed = 412
case PayloadTooLarge = 413
case URITooLong = 414
case UnsupportedMediaType = 415
case RangeNotSatisfiable = 416
case ExpectationFailed = 417
case MisdirectedRequest = 421
case UnprocessableEntity = 422
case Locked = 423
case FailedDependency = 424
case UnorderedCollection = 425
case UpgradeRequired = 426
case PreconditionRequired = 428
case TooManyRequests = 429
case RequestHeaderFieldsTooLarge = 431
case UnavailableForLegalReasons = 451
case InternalServerError = 500
case BadGateway = 502
case ServiceUnavailable = 503
case GatewayTimeout = 504
case HTTPVersionNotSupported = 505
case VariantlsoNegotiates = 506
case InsufficientStorage = 507
case LoopDetected = 508
case BandwidthLimitExceeded = 509
case NotExtended = 510
case NetworkAuthenticationRequired = 511
private static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
private static let status2xx = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
private static let status3xx = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
private static let status4xx = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
private static let status5xx = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
public var description: String {
switch self.rawValue {
case 100...102: return FileProviderHTTPErrorCode.status1xx[self.rawValue]!
case 200...208, 226: return FileProviderHTTPErrorCode.status2xx[self.rawValue]!
case 300...308: return FileProviderHTTPErrorCode.status3xx[self.rawValue]!
case 400...417, 421...426: fallthrough
case 428, 429, 431, 451: return FileProviderHTTPErrorCode.status4xx[self.rawValue]!
case 500...511: return FileProviderHTTPErrorCode.status5xx[self.rawValue]!
default: return typeDescription
}
}
public var typeDescription: String {
switch self.rawValue {
case 100...199: return "Informational"
case 200...299: return "Success"
case 300...399: return "Redirection"
case 400...499: return "Client Error"
case 500...599: return "Server Error"
default: return "Server Error"
}
}
}