Compare commits

...

12 Commits

Author SHA1 Message Date
Amir Abbas Mousavian 5e2f911cda Dropbox provider bug fixes 2016-08-11 01:06:53 +04:30
Amir Abbas Mousavian f423834021 HTTP error codes enum optimization 2016-08-10 12:35:11 +04:30
Amir Abbas Mousavian 0772c87122 FPSStreamTass bugs fixed and now working like a charm! 2016-08-09 23:37:28 +04:30
Amir Abbas Mousavian 011c535760 FPSStreamTask (iOS7,8) on a par with NSURLSessionStreamTask in iOS9 2016-08-09 13:45:35 +04:30
Amir Abbas Mousavian 468a2bc9e9 SMB2.QueryInfo implementation 2016-08-09 02:04:42 +04:30
Amir Abbas Mousavian 33be492499 Implemented SMB.ChangeNotify command, syntax fixed 2016-08-08 15:57:08 +04:30
Amir Abbas Mousavian c4049b961f Dropbox updates, SMB2 QueryDirectory implementation
- ExtendedFileProvider protocol is implemented for Dropbox
-
2016-08-08 12:47:13 +04:30
Amir Abbas Mousavian 940c7c1028 Dropbox implementation completed and getting storage size, used 2016-08-04 23:06:24 +04:30
Amir Abbas Mousavian b4ace7e680 Unified HTTP based services (WebDAV/Dropbox) Error Handling
- DropboxFileProvider.contentsOfDirectory() method implemented
2016-08-03 13:40:12 +04:30
Amir Abbas Mousavian eccbeb7174 Fixed WebDAV connection error for Apache servers 2016-08-02 14:59:32 +04:30
Amir Abbas Mousavian a077d000bc Updated Readme 2016-07-31 01:27:13 +04:30
Amir Abbas Mousavian 6a3ea633bf WebDAV file protocol conformance to FileProvider 2016-07-29 10:57:18 +04:30
22 changed files with 2388 additions and 561 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.3.2"
s.version = "0.4.1"
s.summary = "NSFileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
# This description is used to generate tags and improve search results.
+40 -13
View File
@@ -7,6 +7,18 @@
objects = {
/* Begin PBXBuildFile section */
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 */; };
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
794C22121D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
794C22131D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
794C22141D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
799396A71D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396A81D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396A91D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
@@ -58,15 +70,16 @@
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
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>"; };
794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSStreamTask.swift; sourceTree = "<group>"; };
799396671D48B7F600086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396751D48B80D00086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396821D48B82700086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -91,7 +104,6 @@
799396A21D48C02300086753 /* SMB2Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Tree.swift; sourceTree = "<group>"; };
799396A31D48C02300086753 /* SMB2Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Types.swift; sourceTree = "<group>"; };
799396A41D48C02300086753 /* SMBErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBErrorType.swift; sourceTree = "<group>"; };
799396A51D48C02300086753 /* TCPSocketClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TCPSocketClient.swift; sourceTree = "<group>"; };
799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -155,12 +167,13 @@
799396991D48C02300086753 /* SMBTypes */,
799396921D48C02300086753 /* AEXML.swift */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
799396961D48C02300086753 /* LocalFileProvider.swift */,
799396971D48C02300086753 /* SMBClient.swift */,
799396981D48C02300086753 /* SMBFileProvider.swift */,
799396A51D48C02300086753 /* TCPSocketClient.swift */,
794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */,
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
);
path = Sources;
@@ -170,16 +183,18 @@
isa = PBXGroup;
children = (
7993969A1D48C02300086753 /* CIFSTypes.swift */,
799396A41D48C02300086753 /* SMBErrorType.swift */,
7993969B1D48C02300086753 /* SMB2DataTypes.swift */,
7993969C1D48C02300086753 /* SMB2FileHandle.swift */,
799396A31D48C02300086753 /* SMB2Types.swift */,
799396A21D48C02300086753 /* SMB2Tree.swift */,
799396A01D48C02300086753 /* SMB2Session.swift */,
7993969D1D48C02300086753 /* SMB2FileOperation.swift */,
7993969C1D48C02300086753 /* SMB2FileHandle.swift */,
7993969E1D48C02300086753 /* SMB2IOCtl.swift */,
7993969F1D48C02300086753 /* SMB2Query.swift */,
799396A01D48C02300086753 /* SMB2Session.swift */,
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */,
794C22091D5893F800EC49B8 /* SMB2Notification.swift */,
799396A11D48C02300086753 /* SMB2SetInfo.swift */,
799396A21D48C02300086753 /* SMB2Tree.swift */,
799396A31D48C02300086753 /* SMB2Types.swift */,
799396A41D48C02300086753 /* SMBErrorType.swift */,
);
path = SMBTypes;
sourceTree = "<group>";
@@ -335,12 +350,14 @@
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */,
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */,
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
794C21FE1D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A71D48C02300086753 /* AEXML.swift in Sources */,
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */,
@@ -348,6 +365,7 @@
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C21D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CB1D48C02300086753 /* SMB2Query.swift in Sources */,
794C22121D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
799396B01D48C02300086753 /* FileProvider.swift in Sources */,
799396B61D48C02300086753 /* SMBClient.swift in Sources */,
@@ -361,12 +379,14 @@
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */,
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */,
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
794C21FF1D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A81D48C02300086753 /* AEXML.swift in Sources */,
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */,
@@ -374,6 +394,7 @@
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C31D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CC1D48C02300086753 /* SMB2Query.swift in Sources */,
794C22131D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
799396B11D48C02300086753 /* FileProvider.swift in Sources */,
799396B71D48C02300086753 /* SMBClient.swift in Sources */,
@@ -387,12 +408,14 @@
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */,
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */,
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
794C22001D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A91D48C02300086753 /* AEXML.swift in Sources */,
799396D01D48C02300086753 /* SMB2Session.swift in Sources */,
@@ -400,6 +423,7 @@
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C41D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CD1D48C02300086753 /* SMB2Query.swift in Sources */,
794C22141D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
799396B21D48C02300086753 /* FileProvider.swift in Sources */,
799396B81D48C02300086753 /* SMBClient.swift in Sources */,
@@ -778,6 +802,7 @@
7993966E1D48B7F600086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7993967A1D48B80D00086753 /* Build configuration list for PBXNativeTarget "FileProvider OSX" */ = {
isa = XCConfigurationList;
@@ -786,6 +811,7 @@
7993967C1D48B80D00086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
799396871D48B82700086753 /* Build configuration list for PBXNativeTarget "FileProvider tvOS" */ = {
isa = XCConfigurationList;
@@ -794,6 +820,7 @@
799396891D48B82700086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
+35 -16
View File
@@ -22,15 +22,15 @@ 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 usual file transmission system on Macs.
- [ ] **SMBFileProvider** SMB/CIFS and SMB2/3 are file and printer sharing protocol which is originated from IBM & Microsoft and SMB2/3 is now replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!*
- [ ] **DropboxFileProvider** *partially implemented*
- [ ] **FTPFileProvider**
- [ ] **AmazonS3FileProvider**
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission protocol 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.
- [ ] **AmazonS3FileProvider**
## Requirements
- **Swift 2.2**
- **Swift 2.2 or 2.3**
- iOS 8.0 , OSX 10.10
- XCode 7.3
@@ -81,7 +81,9 @@ You can't change the base url later. and all paths are related to this base url
For remote file providers authentication may be necessary:
let credential = NSURLCredential(user: "user", password: "pass", persistence: NSURLCredentialPersistence.Permanent)
let webdavProvider = WebDAVFileProvider(baseURL: "http://www.example.com/dav", credential: credential)
let webdavProvider = WebDAVFileProvider(baseURL: NSURL(string: "http://www.example.com/dav")!, credential: credential)
* In case you want to connect non-secure servers for WebDAV (http) in iOS 9+ / macOS 10.11+ you should disable App Transport Security (ATS) according to [this guide.](https://gist.github.com/mlynch/284699d676fe9ed0abfa)
* For Dropbox, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token.
@@ -101,7 +103,7 @@ Your class should conforms `FileProviderDelegate` class:
documentsProvider.delegate = self
}
func fileproviderSucceed(fileProvider: FileProvider, operation: FileOperation) {
func fileproviderSucceed(fileProvider: FileProviderOperations, operation: FileOperation) {
switch operation {
case .Copy(source: let source, destination: let dest):
NSLog("\(source) copied to \(dest).")
@@ -112,7 +114,7 @@ Your class should conforms `FileProviderDelegate` class:
}
}
func fileproviderFailed(fileProvider: FileProvider, operation: FileOperation) {
func fileproviderFailed(fileProvider: FileProviderOperations, operation: FileOperation) {
switch operation {
case .Copy(source: let source, destination: let dest):
NSLog("copy of \(source) failed.")
@@ -123,7 +125,7 @@ Your class should conforms `FileProviderDelegate` class:
}
}
func fileproviderProgress(fileProvider: FileProvider, operation: FileOperation, progress: Float) {
func fileproviderProgress(fileProvider: FileProviderOperations, operation: FileOperation, progress: Float) {
switch operation {
case .Copy(source: let source, destination: let dest):
NSLog("Copy\(source) to \(dest): \(progress * 100) completed.")
@@ -153,26 +155,36 @@ There is a `FileObject` class which holds file attributes like size and creation
For a single file:
documentsProvider.attributesOfItemAtPath(path: "/file.txt", completionHandler: {
(attributes: LocalFileObject?, error: ErrorType?) -> Void} in
(attributes: LocalFileObject?, error: ErrorType?) -> Void in
if let attributes = attributes {
print("File Size: \(attributes.size)")
print("Creation Date: \(attributes.createdDate)")
print("Modification Date: \(modifiedDate)")
print("Is Read Only: \(isReadOnly)")
}
)
})
To get list of files in a directory:
documentsProvider.contentsOfDirectoryAtPath(path: "/", completionHandler: {
(contents: [LocalFileObject], error: ErrorType?) -> Void} in
(contents: [LocalFileObject], error: ErrorType?) -> Void in
for file in contents {
print("Name: \(attributes.name)")
print("Size: \(attributes.size)")
print("Creation Date: \(attributes.createdDate)")
print("Modification Date: \(modifiedDate)")
}
)
})
To get size of strage and used/free space:
func storageProperties(completionHandler: {(total: Int64, used: Int64) -> Void in
print("Total Storage Space: \(total)")
print("Used Space: \(used)")
print("Free Space: \(total - frees)")
})
* if this function is unavailable on provider or an error has been occurred, total space will be reported "-1" and used space "0"
### Change current directory
@@ -211,7 +223,7 @@ Move file old.txt to new.txt in current path:
### Retrieve Content of File
THere is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
There is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
documentsProvider.contentsAtPath(path: "old.txt", completionHandler: {
(contents: NSData?, error: ErrorType?) -> Void
@@ -252,6 +264,13 @@ You can monitor updates in some file system (Local and SMB2), there is three met
We would love for you to contribute to **FileProvider**, check the `LICENSE` file for more info.
## Projects in use
* [EDM - Browse and Receive Files](https://itunes.apple.com/us/app/edm-browse-and-receive-files/id948397575?ls=1&mt=8)
* [File Manager - PDF Reader & Music Player](https://itunes.apple.com/us/app/file-manager-pdf-reader-music/id1017809685?ls=1&mt=8)
If you used this library in your project, you can open an issue to inform us.
## Meta
Amir-Abbas Mousavian [@amosavian](https://twitter.com/amosavian)
@@ -260,7 +279,7 @@ Distributed under the MIT license. See `LICENSE` for more information.
[https://github.com/yourname/github-link](https://github.com/dbader/)
[swift-image]:https://img.shields.io/badge/swift-2.2-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
+130 -137
View File
@@ -7,46 +7,7 @@
//
import Foundation
public enum FileProviderDropboxErrorCode: Int {
case BadInputParameter = 400
case ExpiredToken = 401
case Forbidden = 403
case Endpoint = 409
case TooManyRequests = 429
case InternalServer = 500
case BadGateway = 502
}
public struct FileProviderDropboxError: ErrorType, CustomStringConvertible {
public let code: FileProviderDropboxErrorCode
public let path: String
public var description: String {
switch code {
case .BadInputParameter: return "Bad input parameter."
case .ExpiredToken: return "Bad or expired token. To fix this, you should re-authenticate the user."
case .Forbidden: return "Forbidden."
case .Endpoint: return "Endpoint-specific error."
case .TooManyRequests: return "Your app is making too many requests"
case .InternalServer: return "An error occurred on the Dropbox servers."
case .BadGateway: return "An error occurred on the Dropbox servers."
}
}
}
public final class DropboxFileObject: FileObject {
public let serverTime: NSDate?
public let id: String?
public let rev: String?
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, serverTime: NSDate?, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, id: String?, rev: String?) {
self.serverTime = serverTime
self.id = id
self.rev = rev
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
}
import CoreGraphics
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
@@ -65,7 +26,7 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
public let credential: NSURLCredential?
private var _session: NSURLSession?
private var session: NSURLSession {
internal var session: NSURLSession {
if _session == nil {
let queue = NSOperationQueue()
//queue.underlyingQueue = dispatch_queue
@@ -74,11 +35,8 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
return _session!
}
public init? (baseURL: NSURL, credential: NSURLCredential?) {
if !["http", "https"].contains(baseURL.uw_scheme.lowercaseString) {
return nil
}
self.baseURL = baseURL
public init? (credential: NSURLCredential?) {
self.baseURL = nil
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
//let url = baseURL.uw_absoluteString
self.credential = credential
@@ -89,11 +47,13 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
NotImplemented()
list(path) { (contents, cursor, error) in
completionHandler(contents: contents, error: error)
}
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/files/list_revisions")!
let url = NSURL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -105,14 +65,11 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
defer {
self.delegateNotify(FileOperation.Create(path: path), error: error)
}
let code = FileProviderDropboxErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
if (json?["is_deleted"] as? NSNumber)?.boolValue ?? false, let entries = json?["entries"] as? [AnyObject] where entries.count > 0 , let entry = entries[0] as? [String: AnyObject], let file = self.mapToFileObject(entry) {
completionHandler(attributes: file, error: dbError)
return
}
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) {
completionHandler(attributes: file, error: dbError)
return
}
completionHandler(attributes: nil, error: dbError)
return
@@ -122,6 +79,23 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
task.resume()
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
let request = NSMutableURLRequest(URL: url)
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) {
let totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.longLongValue ?? -1
let usedSize = (json["used"] as? NSNumber)?.longLongValue ?? 0
completionHandler(total: totalSize, used: usedSize)
return
}
completionHandler(total: -1, used: 0)
}
task.resume()
}
public weak var fileOperationDelegate: FileOperationDelegate?
}
@@ -132,7 +106,7 @@ extension DropboxFileProvider: FileProviderOperations {
}
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
NotImplemented()
self.writeContentsAtPath(path, contents: data ?? NSData(), completionHandler: completionHandler)
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
@@ -181,8 +155,8 @@ extension DropboxFileProvider: FileProviderOperations {
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse {
let code = FileProviderDropboxErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "") : nil
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "", errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) : nil
defer {
self.delegateNotify(operation, error: error ?? dbError)
}
@@ -198,32 +172,37 @@ extension DropboxFileProvider: FileProviderOperations {
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
let request = NSMutableURLRequest(URL: absoluteURL(toPath))
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
guard let data = NSData(contentsOfURL: localFile) else {
let error = throwError(localFile.uw_absoluteString, code: NSURLError.FileDoesNotExist)
completionHandler?(error: error)
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: error)
return
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
task.resume()
upload_simple(toPath, data: data, overwrite: true, operation: .Copy(source: localFile.absoluteString, destination: toPath), completionHandler: completionHandler)
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
NotImplemented()
let request = NSMutableURLRequest(URL: absoluteURL(path))
let task = session.downloadTaskWithRequest(request) { (sourceFileURL, response, error) in
if let sourceFileURL = sourceFileURL {
do {
try NSFileManager.defaultManager().copyItemAtURL(sourceFileURL, toURL: toLocalURL)
} catch let e {
completionHandler?(error: e)
return
}
public func copyPathToLocalFile(path: String, toLocalURL destURL: NSURL, completionHandler: SimpleCompletionHandler) {
let url = NSURL(string: "https://content.dropboxapi.com/2/files/download")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary = ["path": path]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.downloadTaskWithRequest(request, completionHandler: { (cacheURL, response, error) in
guard let cacheURL = cacheURL, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: nil) : nil
completionHandler?(error: dbError ?? error)
return
}
completionHandler?(error: error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
do {
try NSFileManager.defaultManager().moveItemAtURL(cacheURL, toURL: destURL)
completionHandler?(error: nil)
} catch let e {
completionHandler?(error: e)
}
})
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": destURL.uw_absoluteString])
task.resume()
}
}
@@ -234,12 +213,10 @@ extension DropboxFileProvider: FileProviderReadWrite {
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/files/download")!
let url = NSURL(string: "https://content.dropboxapi.com/2/files/download")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
@@ -247,51 +224,35 @@ extension DropboxFileProvider: FileProviderReadWrite {
}
let requestDictionary = ["path": path]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.downloadTaskWithRequest(request, completionHandler: { (cacheURL, response, error) in
guard let cacheURL = cacheURL, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else {
let code = FileProviderDropboxErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil
let task = session.dataTaskWithRequest(request, completionHandler: { (datam, response, error) in
guard let data = datam, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: datam ?? NSData(), encoding: NSUTF8StringEncoding)) : nil
completionHandler(contents: nil, error: dbError ?? error)
return
}
let destURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).uw_URLByAppendingPathComponent(cacheURL.lastPathComponent ?? "tmpfile")
do {
try NSFileManager.defaultManager().moveItemAtURL(cacheURL, toURL: destURL)
completionHandler(contents: NSData(contentsOfURL: destURL), error: error)
} catch let e {
completionHandler(contents: nil, error: e)
}
completionHandler(contents: data, error: error)
})
task.resume()
}
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
NotImplemented()
let url = atomically ? absoluteURL(path).uw_URLByAppendingPathExtension("tmp") : absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
defer {
self.delegateNotify(.Modify(path: path), error: error)
}
if atomically {
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
}
if let error = error {
// If there is no error, completionHandler has been executed by move command
completionHandler?(error: error)
}
}
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
task.resume()
// FIXME: remove 150MB restriction
upload_simple(path, data: data, overwrite: true, operation: .Modify(path: path), completionHandler: completionHandler)
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
NotImplemented()
var foundFiles = [DropboxFileObject]()
search(path, query: query, foundItem: { (file) in
foundFiles.append(file)
foundItemHandler?(file)
}, completionHandler: { (error) in
completionHandler(files: foundFiles, error: error)
})
}
private func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is two ways to monitor folders chaging in Dropbox. Either using webooks
/* There is two ways to monitor folders changing in Dropbox. Either using webooks
* which means you have to implement a server to translate it to push notifications
* or using apiv2 list_folder/longpoll method. The second one is implemeted here.
* Tough webhooks are much more efficient, longpoll is much simpler to implement!
@@ -302,39 +263,71 @@ extension DropboxFileProvider: FileProviderReadWrite {
private func unregisterNotifcation(path: String) {
NotImplemented()
}
// TODO: Implement /copy_reference, /get_temporary_link, /save_url, /get_account & /get_current_account
}
internal extension DropboxFileProvider {
private func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
return self.mapToFileObject(json)
extension DropboxFileProvider: ExtendedFileProvider {
public func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercaseString {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
return true
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
return true
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
return true
case "rtf":
return true*/
default:
return false
}
}
private func mapToFileObject(json: [String: AnyObject]) -> DropboxFileObject? {
guard let name = json["name"] as? String else { return nil }
guard let path = json["path_display"] as? String else { return nil }
let href = NSURL(string: path)!
let size = (json["size"] as? NSNumber)?.longLongValue ?? -1
let serverTime = resolveDate(json["server_modified"] as? String ?? "")
let modifiedDate = resolveDate(json["client_modified"] as? String ?? "")
let isDirectory = (json[".tag"] as? String) == "folder"
let isReadonly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
let id = json["id"] as? String
let rev = json["id"] as? String
return DropboxFileObject(absoluteURL: href, name: name, path: path, size: size, serverTime: serverTime, createdDate: nil, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: isReadonly, id: id, rev: rev)
public func propertiesOfFileSupported(path: String) -> Bool {
return false
}
private func delegateNotify(operation: FileOperation, error: ErrorType?) {
dispatch_async(dispatch_get_main_queue(), {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
self.delegate?.fileproviderFailed(self, operation: operation)
public func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: ImageClass?, error: ErrorType?) -> Void)) {
let url: NSURL
switch (path as NSString).pathExtension.lowercaseString {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = NSURL(string: "https://content.dropboxapi.com/2/files/get_thumbnail")!
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
fallthrough
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
fallthrough
case "rtf":
url = NSURL(string: "https://content.dropboxapi.com/2/files/get_preview")!*/
default:
return
}
let request = NSMutableURLRequest(URL: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
var requestDictionary = ["path": path]
requestDictionary["format"] = "jpeg"
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))"
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 jsonResult["error"] != nil {
completionHandler(image: nil, error: self.throwError(path, code: NSURLError.CannotDecodeRawData))
}
}
})
if let data = data {
image = ImageClass(data: data)
}
completionHandler(image: image, error: error)
}
}
public func propertiesOfFileAtPath(path: String, completionHandler: ((propertiesDictionary: [String : AnyObject], keys: [String], error: ErrorType?) -> Void)) {
NotImplemented()
}
}
extension DropboxFileProvider: FileProvider {}
// MARK: URLSession delegate
extension DropboxFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
+193
View File
@@ -0,0 +1,193 @@
//
// DropboxHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/18/95.
//
//
import Foundation
public struct FileProviderDropboxError: ErrorType, CustomStringConvertible {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
public var description: String {
return code.description
}
}
public final class DropboxFileObject: FileObject {
public let serverTime: NSDate?
public let id: String?
public let rev: String?
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)
}
}
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]()
let url: NSURL
if let cursor = cursor {
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
requestDictionary["cursor"] = cursor
} else {
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder")!
requestDictionary["path"] = correctPath(path)
requestDictionary["recursive"] = recursive
}
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
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)
if let entries = json?["entries"] as? [AnyObject] where entries.count > 0 {
var files = prevContents
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
files.append(file)
}
}
let ncursor = json?["cursor"] as? String
let hasmore = (json?["has_more"] as? NSNumber)?.boolValue ?? false
if hasmore {
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
} else {
completionHandler(contents: files, cursor: ncursor, error: responseError ?? error)
}
return
}
}
completionHandler(contents: [], cursor: nil, error: responseError ?? error)
}
task.resume()
}
func upload_simple(targetPath: String, data: NSData, modifiedDate: NSDate = NSDate(), overwrite: Bool, operation: FileOperation, completionHandler: SimpleCompletionHandler) {
assert(data.length < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
var requestDictionary = [String: AnyObject]()
let url: NSURL
url = NSURL(string: "https://content.dropboxapi.com/2/files/upload")!
requestDictionary["path"] = correctPath(targetPath)
requestDictionary["mode"] = overwrite ? "overwrite" : "add"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
requestDictionary["client_modified"] = dateFormatter.stringFromDate(modifiedDate)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
request.HTTPBody = data
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
}
defer {
self.delegateNotify(.Create(path: targetPath), error: responseError ?? error)
}
completionHandler?(error: responseError ?? error)
}
var dic: [String: AnyObject] = ["type": operation.description]
switch operation {
case .Create(path: let s):
dic["source"] = s
case .Copy(source: let s, destination: let d):
dic["source"] = s
dic["dest"] = d
case .Modify(path: let s):
dic["source"] = s
case .Move(source: let s, destination: let d):
dic["source"] = s
dic["dest"] = d
default:
break
}
task.taskDescription = self.dictionaryToJSON(dic)
task.resume()
}
func search(startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:((file: DropboxFileObject) -> Void), completionHandler: ((error: ErrorType?) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/files/search")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var requestDictionary: [String: AnyObject] = ["path": startPath]
requestDictionary["query"] = query
requestDictionary["start"] = start
requestDictionary["max_results"] = maxResultPerPage
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
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)
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) {
foundItem(file: file)
}
}
let rstart = json?["start"] as? Int
let hasmore = (json?["more"] as? NSNumber)?.boolValue ?? false
if hasmore, let rstart = rstart {
self.search(startPath, query: query, start: rstart + entries.count, maxResultPerPage: maxResultPerPage, foundItem: foundItem, completionHandler: completionHandler)
} else {
completionHandler(error: responseError ?? error)
}
return
}
}
completionHandler(error: responseError ?? error)
}
task.resume()
}
}
internal extension DropboxFileProvider {
func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
return self.mapToFileObject(json)
}
func mapToFileObject(json: [String: AnyObject]) -> DropboxFileObject? {
guard let name = json["name"] as? String else { return nil }
guard let path = json["path_display"] as? String else { return nil }
let size = (json["size"] as? NSNumber)?.longLongValue ?? -1
let serverTime = resolveDate(json["server_modified"] as? String ?? "")
let modifiedDate = resolveDate(json["client_modified"] as? String ?? "")
let isDirectory = (json[".tag"] as? String) == "folder"
let isReadonly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
let id = json["id"] as? String
let rev = json["id"] as? String
return DropboxFileObject(name: name, path: path, size: size, serverTime: serverTime, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isReadOnly: isReadonly, id: id, rev: rev)
}
func delegateNotify(operation: FileOperation, error: ErrorType?) {
dispatch_async(dispatch_get_main_queue(), {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
self.delegate?.fileproviderFailed(self, operation: operation)
}
})
}
}
+523
View File
@@ -0,0 +1,523 @@
//
// FPSStreamTask.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
private var lasttaskIdAssociated = 1_000_000_000
/// This class is a replica of NSURLSessionStreamTask with same api for iOS 7/8
/// while it will fallback to NSURLSessionStreamTask in iOS 9.
@objc
public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
private var inputStream: NSInputStream?
private var outputStream: NSOutputStream?
private var dispatch_queue: dispatch_queue_t!
internal var _underlyingSession: NSURLSession
private var streamDelegate: FPSStreamDelegate? {
return (_underlyingSession.delegate as? FPSStreamDelegate)
}
private var _taskIdentifier: Int
@available(iOS 9.0, OSX 10.11, *)
static var streamTasks = [Int: NSURLSessionStreamTask]()
@available(iOS 9.0, OSX 10.11, *)
internal var _underlyingTask: NSURLSessionStreamTask? {
return FPSStreamTask.streamTasks[_taskIdentifier]
}
public override var taskIdentifier: Int {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.taskIdentifier
} else {
return _taskIdentifier
}
}
private var _state: NSURLSessionTaskState = .Suspended
override public var state: NSURLSessionTaskState {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.state
} else {
return _state
}
}
override public var originalRequest: NSURLRequest? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.originalRequest
} else {
return nil
}
}
override public var currentRequest: NSURLRequest? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.currentRequest
} else {
return nil
}
}
private var _countOfBytesSent: Int64 = 0
private var _countOfBytesRecieved: Int64 = 0
override public var countOfBytesSent: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesSent
} else {
return _countOfBytesSent
}
}
override public var countOfBytesReceived: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesReceived
} else {
return _countOfBytesRecieved
}
}
override public var countOfBytesExpectedToSend: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesExpectedToSend
} else {
return Int64(dataToBeSent.length)
}
}
override public var countOfBytesExpectedToReceive: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesExpectedToReceive
} else {
return Int64(dataReceived.length)
}
}
override public init() {
fatalError("Use NSURLSession.fpstreamTask() method")
}
var host: (hostname: String, port: Int)?
var service: NSNetService?
internal init(session: NSURLSession, host: String, port: Int) {
self._underlyingSession = session
if #available(iOS 9.0, OSX 10.11, *) {
let task = session.streamTaskWithHostName(host, port: port)
self._taskIdentifier = task.taskIdentifier
FPSStreamTask.streamTasks[_taskIdentifier] = task
} else {
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.host = (host, port)
self.dispatch_queue = dispatch_queue_create("FSPStreamTask", DISPATCH_QUEUE_CONCURRENT)
}
}
internal init(session: NSURLSession, netService: NSNetService) {
self._underlyingSession = session
if #available(iOS 9.0, OSX 10.11, *) {
let task = session.streamTaskWithNetService(netService)
self._taskIdentifier = task.taskIdentifier
FPSStreamTask.streamTasks[_taskIdentifier] = task
} else {
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.service = netService
self.dispatch_queue = dispatch_queue_create("FSPStreamTask", DISPATCH_QUEUE_CONCURRENT)
}
}
override public func cancel() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.cancel()
} else {
self._state = .Canceling
inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.outputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream = nil
self.outputStream = nil
self._state = .Completed
self._countOfBytesSent = 0
self._countOfBytesRecieved = 0
}
}
var _error: NSError? = nil
override public var error: NSError? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.error
} else {
return _error
}
}
override public func suspend() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.suspend()
} else {
inputStream?.close()
outputStream?.close()
streamDelegate?.URLSession?(_underlyingSession, readClosedForStreamTask: self)
streamDelegate?.URLSession?(_underlyingSession, writeClosedForStreamTask: self)
self._state = .Suspended
}
}
override public func resume() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.resume()
} else {
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
if inputStream == nil || outputStream == nil {
if let host = host {
let hostRef: CFString = NSString(string: host.hostname)
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, hostRef, UInt32(host.port), &readStream, &writeStream)
} else if let service = service {
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain, service.type, service.name, Int32(service.port))
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
}
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, outputStream = outputStream else {
return
}
streamDelegate?.URLSession?(self._underlyingSession, streamTask: self, didBecomeInputStream: inputStream, outputStream: outputStream)
}
guard let inputStream = inputStream, outputStream = outputStream else {
return
}
inputStream.delegate = self
outputStream.delegate = self
dispatch_sync(dispatch_queue, {
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
})
inputStream.open()
outputStream.open()
_state = .Running
}
}
private let dataToBeSent: NSMutableData = NSMutableData()
private let dataReceived: NSMutableData = NSMutableData()
/* Read minBytes, or at most maxBytes bytes and invoke the completion
* handler on the sessions delegate queue with the data or an error.
* If an error occurs, any outstanding reads will also fail, and new
* read requests will error out immediately.
*/
public func readDataOfMinLength(minBytes: Int, maxLength maxBytes: Int, timeout: NSTimeInterval, completionHandler: (NSData?, Bool, NSError?) -> Void) {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.readDataOfMinLength(minBytes, maxLength: maxBytes, timeout: timeout, completionHandler: completionHandler)
} else {
guard let inputStream = inputStream else {
return
}
var timedOut: Bool = false
dispatch_async(dispatch_queue) {
if timeout > 0 {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * 1_000_000_000)), self.dispatch_queue, {
timedOut = true
completionHandler(nil, inputStream.streamStatus == .AtEnd, inputStream.streamError)
})
}
while (self.dataReceived.length == 0 || self.dataReceived.length < minBytes) && !timedOut {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
let dR = NSMutableData()
if self.dataReceived.length > maxBytes {
let range = NSRange(location: 0, length: maxBytes - 1)
dR.appendData(self.dataReceived.subdataWithRange(range))
self.dataReceived.replaceBytesInRange(range, withBytes: nil, length: 0)
} else {
dR.appendData(self.dataReceived)
self.dataReceived.length = 0
}
completionHandler(dR, inputStream.streamStatus == .AtEnd, inputStream.streamError)
}
}
}
/* Write the data completely to the underlying socket. If all the
* bytes have not been written by the timeout, a timeout error will
* occur. Note that invocation of the completion handler does not
* guarantee that the remote side has received all the bytes, only
* that they have been written to the kernel. */
public func writeData(data: NSData, timeout: NSTimeInterval, completionHandler: (NSError?) -> Void) {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.writeData(data, timeout: timeout, completionHandler: completionHandler)
} else {
guard let outputStream = outputStream else {
return
}
var timedOut: Bool = false
dispatch_async(dispatch_queue) {
if timeout > 0 {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * 1_000_000_000)), self.dispatch_queue, {
timedOut = true
completionHandler(self._error)
})
}
self.dataToBeSent.appendData(data)
while !outputStream.hasSpaceAvailable && !timedOut {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
if self.dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(UnsafePointer(self.dataToBeSent.bytes), maxLength: self.dataToBeSent.length) ?? -1
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
self.dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
self._countOfBytesSent += bytesWritten
completionHandler(nil)
} else {
self._error = outputStream.streamError
completionHandler(outputStream.streamError)
}
}
}
}
}
/* -captureStreams completes any already enqueued reads
* and writes, and then invokes the
* URLSession:streamTask:didBecomeInputStream:outputStream: delegate
* message. When that message is received, the task object is
* considered completed and will not receive any more delegate
* messages. */
public func captureStreams() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.captureStreams()
} else {
guard let outputStream = outputStream, let inputStream = inputStream else {
return
}
dispatch_async(dispatch_queue) {
self.write(false)
while inputStream.streamStatus != .AtEnd {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
self.streamDelegate?.URLSession?(self._underlyingSession, streamTask: self, didBecomeInputStream: inputStream, outputStream: outputStream)
}
}
}
/* Enqueue a request to close the write end of the underlying socket.
* All outstanding IO will complete before the write side of the
* socket is closed. The server, however, may continue to write bytes
* back to the client, so best practice is to continue reading from
* the server until you receive EOF.
*/
public func closeWrite() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.closeWrite()
} else {
dispatch_async(dispatch_queue, {
self.write(true)
})
}
}
private func write(close: Bool) {
guard let outputStream = outputStream else {
return
}
while self.dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(UnsafePointer(self.dataToBeSent.bytes), maxLength: self.dataToBeSent.length) ?? -1
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
self.dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
self._countOfBytesSent += bytesWritten
} else {
self._error = outputStream.streamError
}
if self.dataToBeSent.length == 0 {
break
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
if close {
outputStream.close()
self.streamDelegate?.URLSession?(self._underlyingSession, writeClosedForStreamTask: self)
}
}
/* Enqueue a request to close the read side of the underlying socket.
* All outstanding IO will complete before the read side is closed.
* You may continue writing to the server.
*/
public func closeRead() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.closeRead()
} else {
guard let inputStream = inputStream else {
return
}
dispatch_async(dispatch_queue) {
while inputStream.streamStatus != .AtEnd {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
inputStream.close()
self.streamDelegate?.URLSession?(self._underlyingSession, readClosedForStreamTask: self)
}
}
}
/*
* Begin encrypted handshake. The hanshake begins after all pending
* IO has completed. TLS authentication callbacks are sent to the
* session's -URLSession:task:didReceiveChallenge:completionHandler:
*/
public func startSecureConnection() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.startSecureConnection()
} else {
inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
}
}
/*
* Cleanly close a secure connection after all pending secure IO has
* completed.
*/
public func stopSecureConnection() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.stopSecureConnection()
} else {
inputStream!.setProperty(NSStreamSocketSecurityLevelNone, forKey: NSStreamSocketSecurityLevelKey)
outputStream!.setProperty(NSStreamSocketSecurityLevelNone, forKey: NSStreamSocketSecurityLevelKey)
}
}
public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch (eventCode) {
case NSStreamEvent.ErrorOccurred:
self._error = aStream.streamError
streamDelegate?.URLSession?(_underlyingSession, task: self, didCompleteWithError: error)
case NSStreamEvent.EndEncountered:
break
case NSStreamEvent.None:
break
case NSStreamEvent.OpenCompleted:
break
case NSStreamEvent.HasBytesAvailable:
var buffer = [UInt8](count: 2048, repeatedValue: 0)
if (aStream == inputStream) {
while (inputStream!.hasBytesAvailable ?? false) {
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
dataReceived.appendBytes(&buffer, length: len)
self._countOfBytesRecieved += len
}
}
}
case NSStreamEvent.HasSpaceAvailable:
break
default:
break
}
}
}
extension NSURLSession {
/* Creates a bidirectional stream task to a given host and port.
*/
public func fpstreamTaskWithHostName(hostname: String, port: Int) -> FPSStreamTask {
return FPSStreamTask(session: self, host: hostname, port: port)
}
/* Creates a bidirectional stream task with an NSNetService to identify the endpoint.
* The NSNetService will be resolved before any IO completes.
*/
public func fpstreamTaskWithNetService(service: NSNetService) -> FPSStreamTask {
return fpstreamTaskWithNetService(service)
}
}
@objc
public protocol FPSStreamDelegate : NSURLSessionTaskDelegate {
/* Indiciates that the read side of a connection has been closed. Any
* outstanding reads complete, but future reads will immediately fail.
* This may be sent even when no reads are in progress. However, when
* this delegate message is received, there may still be bytes
* available. You only know that no more bytes are available when you
* are able to read until EOF. */
optional func URLSession(session: NSURLSession, readClosedForStreamTask streamTask: FPSStreamTask)
/* Indiciates that the write side of a connection has been closed.
* Any outstanding writes complete, but future writes will immediately
* fail.
*/
optional func URLSession(session: NSURLSession, writeClosedForStreamTask streamTask: FPSStreamTask)
/* A notification that the system has determined that a better route
* to the host has been detected (eg, a wi-fi interface becoming
* available.) This is a hint to the delegate that it may be
* desirable to create a new task for subsequent work. Note that
* there is no guarantee that the future task will be able to connect
* to the host, so callers should should be prepared for failure of
* reads and writes over any new interface. */
optional func URLSession(session: NSURLSession, betterRouteDiscoveredForStreamTask streamTask: FPSStreamTask)
/* The given task has been completed, and unopened NSInputStream and
* NSOutputStream objects are created from the underlying network
* connection. This will only be invoked after all enqueued IO has
* completed (including any necessary handshakes.) The streamTask
* will not receive any further delegate messages.
*/
optional func URLSession(session: NSURLSession, streamTask: FPSStreamTask, didBecomeInputStream inputStream: NSInputStream, outputStream: NSOutputStream)
}
private let ports = ["http": 80,
"https": 443,
"smb": 445,
"ftp": 21,
"sftp": 22,
"sftp": 2121,
"telnet": 23,
"pop": 110,
"smtp": 25,
"imap": 143]
private let securePorts = ["https": 443,
"smb": 445,
"sftp": 22,
"sftp": 2121,
"telnet": 992,
"pop": 995,
"smtp": 465,
"imap": 993]
+9 -15
View File
@@ -72,7 +72,7 @@ public class FileObject {
public let isHidden: Bool
public let isReadOnly: Bool
public init(absoluteURL: NSURL?, name: String, path: String, size: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
public init(absoluteURL: NSURL? = nil, name: String, path: String, size: Int64 = -1, createdDate: NSDate? = nil, modifiedDate: NSDate? = nil, fileType: FileType = .Regular, isHidden: Bool = false, isReadOnly: Bool = false) {
self.absoluteURL = absoluteURL
self.name = name
self.path = path
@@ -84,18 +84,6 @@ public class FileObject {
self.isReadOnly = isReadOnly
}
public init(name: String, path: String, createdDate: NSDate?, modifiedDate: NSDate?, isHidden: Bool, isReadOnly: Bool) {
self.absoluteURL = nil
self.name = name
self.path = path
self.size = -1
self.createdDate = createdDate
self.modifiedDate = modifiedDate
self.fileType = .Regular
self.isHidden = isHidden
self.isReadOnly = isReadOnly
}
public var isDirectory: Bool {
return self.fileType == .Directory
}
@@ -122,6 +110,8 @@ public protocol FileProviderBasic: class {
*/
func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void))
func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void))
func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void))
}
public protocol FileProviderOperations: FileProviderBasic {
@@ -187,7 +177,11 @@ extension FileProviderBasic {
internal func correctPath(path: String?) -> String? {
guard let path = path else { return nil }
return path.hasPrefix("/") ? path : "/" + path
var p = path.hasPrefix("/") ? path : "/" + path
if p.hasSuffix("/") {
p.removeAtIndex(p.endIndex.predecessor())
}
return p
}
public func fileByUniqueName(filePath: String) -> String {
@@ -336,7 +330,7 @@ public protocol FileOperationDelegate: class {
/// fileProvider(_:shouldOperate:) gives the delegate an opportunity to filter the file operation. Returning true from this method will allow the copy to happen. Returning false from this method causes the item in question to be skipped. If the item skipped was a directory, no children of that directory will be subject of the operation, nor will the delegate be notified of those children.
func fileProvider(fileProvider: FileProviderOperations, shouldDoOperation operation: FileOperation) -> Bool
/// fileProvider:shouldProceedAfterError:copyingItemAtPath:toPath: gives the delegate an opportunity to recover from or continue copying after an error. If an error occurs, the error object will contain an ErrorType indicating the problem. The source path and destination paths are also provided. If this method returns true, the FileProvider instance will continue as if the error had not occurred. If this method returns false, the NSFileManager instance will stop copying, return false from copyItemAtPath:toPath:error: and the error will be provied there.
/// fileProvider(_:shouldProceedAfterError:copyingItemAtPath:toPath:) gives the delegate an opportunity to recover from or continue copying after an error. If an error occurs, the error object will contain an ErrorType indicating the problem. The source path and destination paths are also provided. If this method returns true, the FileProvider instance will continue as if the error had not occurred. If this method returns false, the NSFileManager instance will stop copying, return false from copyItemAtPath:toPath:error: and the error will be provied there.
func fileProvider(fileProvider: FileProviderOperations, shouldProceedAfterError error: ErrorType, operation: FileOperation) -> Bool
}
+8
View File
@@ -85,6 +85,13 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
return fileAttr
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
let dict = (try? NSFileManager.defaultManager().attributesOfFileSystemForPath(baseURL?.path ?? "/")) as NSDictionary?;
let totalSize = dict?.objectForKey(NSFileSystemSize)?.longLongValue ?? -1;
let freeSize = dict?.objectForKey(NSFileSystemFreeSize)?.longLongValue ?? 0;
completionHandler(total: totalSize, used: totalSize - freeSize)
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
completionHandler(attributes: self.attributesOfItemAtURL(self.absoluteURL(path)), error: nil)
@@ -324,6 +331,7 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
for (i, monitor) in monitors.enumerate() {
if self.relativePathOf(url: monitor.url) == path {
removedMonitor = monitors.removeAtIndex(i)
break
}
}
removedMonitor?.stop()
+86 -29
View File
@@ -31,26 +31,83 @@ internal func decode<T>(data: NSData) -> T {
// This client implementation is for little-endian platform, namely x86, x64 & arm
// For big-endian platforms like PowerPC, there must be a huge overhaul
class SMBProtocolClient: TCPSocketClient {
protocol SMBProtocolClientDelegate: class {
func receivedSMB2Response(header: SMB2.Header, response: SMBResponse)
}
class SMB2ProtocolClient: FPSStreamTask {
var currentMessageID: UInt64 = 0
var sessionId: UInt64 = 0
func negotiateToSMB2() -> SMB2.NegotiateResponse? {
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: 126, messageId: messageId(), treeId: 0, sessionId: 0)
currentMessageID += 1
let negMessage = SMB2.NegotiateRequest(request: SMB2.NegotiateRequest.Header(capabilities: []))
SMBProtocolClient.createSMB2Message(smbHeader, message: negMessage)
do {
try self.send(data: nil)
} catch _ {
return nil
}
self.waitUntilResponse()
let response = try? SMBProtocolClient.digestSMB2Message(dataReceived)
return response??.message as? SMB2.NegotiateResponse
weak var delegate: SMBProtocolClientDelegate?
func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: 126, messageId: mId, treeId: 0, sessionId: 0)
let msg = SMB2.NegotiateRequest()
let data = createSMB2Message(smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
})
return mId
}
func sessionSetupForSMB2() -> SMB2.SessionSetupResponse? {
return nil
func sendSessionSetup(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .SESSION_SETUP, creditRequestResponse: sessionId > 0 ? 124 : 125, messageId: mId, treeId: 0, sessionId: sessionId)
let msg = SMB2.SessionSetupRequest(singing: [])
let data = createSMB2Message(smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
if self.sessionId == 0 {
self.readDataOfMinLength(64, maxLength: 65536, timeout: 30, completionHandler: { (data, eof, e2) in
// TODO: set session id
completionHandler?(error: e2 ?? e)
})
}
})
return mId
}
func sendTreeConnect(completionHandler: SimpleCompletionHandler) -> UInt64 {
let req = self.currentRequest ?? self.originalRequest
guard let url = req?.URL, let host = url.host else {
return 0
}
let mId = messageId()
let smbHeader = SMB2.Header(command: .TREE_CONNECT, creditRequestResponse: 123, messageId: mId, treeId: 0, sessionId: sessionId)
var share = ""
if let cmp = url.pathComponents where cmp.count > 0 {
share = cmp[0]
}
let tcHeader = SMB2.TreeConnectRequest.Header(flags: [])
let msg = SMB2.TreeConnectRequest(header: tcHeader, host: host, share: share)
let data = createSMB2Message(smbHeader, message: msg!)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
})
return mId
}
func sendTreeDisconnect(treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .TREE_DISCONNECT, creditRequestResponse: 111, messageId: mId, treeId: treeId, sessionId: sessionId)
let msg = SMB2.TreeDisconnect()
let data = createSMB2Message(smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
})
return mId
}
func sendLogoff(treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .LOGOFF, creditRequestResponse: 0, messageId: mId, treeId: 0, sessionId: sessionId)
let msg = SMB2.LogOff()
let data = createSMB2Message(smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
})
return mId
}
func messageId() -> UInt64 {
@@ -62,14 +119,14 @@ class SMBProtocolClient: TCPSocketClient {
// MARK: create and analyse messages
class func determineSMBVersion(data: NSData) -> Float {
func determineSMBVersion(data: NSData) -> Float {
var smbverChar: Int8 = 0
data.getBytes(&smbverChar, length: 1)
let version = 0 - smbverChar
return Float(version)
}
class func digestSMBMessage(data: NSData) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: NSData?)]) {
func digestSMBMessage(data: NSData) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: NSData?)]) {
guard data.length > 30 else {
throw NSURLError.BadServerResponse
}
@@ -106,7 +163,7 @@ class SMBProtocolClient: TCPSocketClient {
return (header, blocks)
}
class func digestSMB2Message(data: NSData) throws -> (header: SMB2.Header, message: SMBResponse?)? {
func digestSMB2Message(data: NSData) throws -> (header: SMB2.Header, message: SMBResponse?)? {
guard data.length > 65 else {
throw NSURLError.BadServerResponse
}
@@ -136,25 +193,25 @@ class SMBProtocolClient: TCPSocketClient {
case .FLUSH:
return (header, SMB2.FlushResponse(data: messageData))
case .READ:
return (header, nil) // FIXME:
return (header, SMB2.ReadRespone(data: messageData))
case .WRITE:
return (header, nil) // FIXME:
return (header, SMB2.WriteResponse(data: messageData))
case .LOCK:
return (header, nil) // FIXME:
return (header, SMB2.LockResponse(data: messageData))
case .IOCTL:
return (header, nil)
return (header, SMB2.IOCtlResponse(data: messageData))
case .CANCEL:
return (header, nil)
case .ECHO:
return (header, SMB2.Echo(data: messageData))
case .QUERY_DIRECTORY:
return (header, nil) // FIXME:
return (header, SMB2.QueryDirectoryResponse(data: messageData))
case .CHANGE_NOTIFY:
return (header, nil) // FIXME:
return (header, SMB2.ChangeNotifyResponse(data: messageData))
case .QUERY_INFO:
return (header, nil) // FIXME:
return (header, SMB2.QueryInfoResponse(data: messageData))
case .SET_INFO:
return (header, nil) // FIXME:
return (header, SMB2.SetInfoResponse(data: messageData))
case .OPLOCK_BREAK:
return (header, nil) // FIXME:
case .INVALID:
@@ -162,7 +219,7 @@ class SMBProtocolClient: TCPSocketClient {
}
}
class func createSMBMessage(header: SMB1.Header, blocks: [(params: NSData?, message: NSData?)]) -> NSData {
func createSMBMessage(header: SMB1.Header, blocks: [(params: NSData?, message: NSData?)]) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
for block in blocks {
@@ -180,7 +237,7 @@ class SMBProtocolClient: TCPSocketClient {
return result
}
class func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> NSData {
func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
result.appendData(message.data())
+4
View File
@@ -40,6 +40,10 @@ public class SMBFileProvider: FileProvider, FileProviderMonitor {
NotImplemented()
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
NotImplemented()
}
public weak var fileOperationDelegate: FileOperationDelegate?
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
+5 -4
View File
@@ -16,23 +16,24 @@ protocol SMBResponse {
init? (data: NSData)
}
protocol IOCtlRequestProtocol: SMBRequest {}
protocol IOCtlResponseProtocol: SMBResponse {}
struct SMBTime {
var time: UInt64
var time: Int64
init(time: UInt64) {
init(time: Int64) {
self.time = time
}
init(unixTime: UInt) {
self.time = (UInt64(unixTime) + 11644473600) * 10000000
self.time = (Int64(unixTime) + 11644473600) * 10000000
}
init(timeIntervalSince1970: NSTimeInterval) {
self.time = UInt64((timeIntervalSince1970 + 11644473600) * 10000000)
self.time = Int64((timeIntervalSince1970 + 11644473600) * 10000000)
}
init(date: NSDate) {
+2 -2
View File
@@ -26,7 +26,7 @@ extension SMB2 {
var header = self.header
var offset = 0x78 //UInt16(sizeof(SMB2.Header.self) + sizeof(CreateContext.Header.self) - 1)
let body = NSMutableData()
if let name = self.name, let nameData = name.dataUsingEncoding(NSUTF8StringEncoding) {
if let name = self.name, let nameData = name.dataUsingEncoding(NSUTF16StringEncoding) {
header.nameOffset = UInt16(offset)
header.nameLength = UInt16(nameData.length)
offset += nameData.length
@@ -226,7 +226,7 @@ extension SMB2 {
let buffer: NSData
init(name: ContextNames, data: NSData) {
let nameData = NSMutableData(data: (name.rawValue).dataUsingEncoding(NSUTF8StringEncoding)!)
let nameData = NSMutableData(data: (name.rawValue).dataUsingEncoding(NSUTF16StringEncoding)!)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.length))
self.buffer = data
}
-1
View File
@@ -247,5 +247,4 @@ extension SMB2 {
return encode(self)
}
}
}
+2 -1
View File
@@ -243,11 +243,12 @@ extension SMB2 {
// SRV_ENUMERATE_SNAPSHOTS
struct SrvSnapshots: IOCtlResponseProtocol {
let count: UInt32
let returnedCount: UInt32
let snapshots: [SMBTime]
init?(data: NSData) {
self.count = decode(data)
let returnedCount: UInt32 = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
self.returnedCount = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
//let size: UInt32 = decode(data.subdataWithRange(NSRange(location: 8, length: 4)))
var snapshots = [SMBTime]()
let dateFormatter = NSDateFormatter()
+140
View File
@@ -0,0 +1,140 @@
//
// SMB2Notification.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/18/95.
//
//
import Foundation
extension SMB2 {
// MARK: SMB2 Change Notify
struct ChangeNotifyRequest: SMBRequest {
let size: UInt16
let flags: ChangeNotifyRequest.Flags
let outputBufferLength: UInt32
let fileId: FileId
let completionFilters: CompletionFilter
private let reserved: UInt32
init(fileId: FileId, completionFilters: CompletionFilter, flags: ChangeNotifyRequest.Flags = [], outputBufferLength: UInt32 = 65535) {
self.size = 32
self.flags = flags
self.outputBufferLength = outputBufferLength
self.fileId = fileId
self.completionFilters = completionFilters
self.reserved = 0
}
func data() -> NSData {
return encode(self)
}
struct Flags: OptionSetType {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let WATCH_TREE = Flags(rawValue: 0x0001)
}
struct CompletionFilter: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// The client is notified if a file-name changes.
static let FILE_NAME = CompletionFilter(rawValue: 0x00000001)
/// The client is notified if a directory name changes.
static let DIR_NAME = CompletionFilter(rawValue: 0x00000002)
/// The client is notified if a file's attributes change.
static let ATTRIBUTES = CompletionFilter(rawValue: 0x00000004)
/// The client is notified if a file's size changes.
static let SIZE = CompletionFilter(rawValue: 0x00000008)
/// The client is notified if the last write time of a file changes.
static let LAST_WRITE = CompletionFilter(rawValue: 0x00000010)
/// The client is notified if the last access time of a file changes.
static let LAST_ACCESS = CompletionFilter(rawValue: 0x00000020)
/// The client is notified if the creation time of a file changes.
static let CREATION = CompletionFilter(rawValue: 0x00000040)
/// The client is notified if a file's extended attributes (EAs) change.
static let EA = CompletionFilter(rawValue: 0x00000080)
/// The client is notified of a file's access control list (ACL) settings change.
static let SECURITY = CompletionFilter(rawValue: 0x00000100)
/// The client is notified if a named stream is added to a file.
static let STREAM_NAME = CompletionFilter(rawValue: 0x00000200)
/// The client is notified if the size of a named stream is changed.
static let STREAM_SIZE = CompletionFilter(rawValue: 0x00000400)
/// The client is notified if a named stream is modified.
static let STREAM_WRITE = Flags(rawValue: 0x00000800)
static let all = CompletionFilter(rawValue: 0x00000FFF)
static let list: CompletionFilter = [.FILE_NAME, .DIR_NAME]
}
}
struct ChangeNotifyResponse: SMBResponse {
let notifications: [(action: FileNotifyAction, fileName: String)]
init?(data: NSData) {
let maxLoop = 1000
var i = 0
var result = [(action: FileNotifyAction, fileName: String)]()
var offset: UInt32 = 0
while i < maxLoop {
let actionData = data.subdataWithRange(NSRange(location: Int(offset + 4), length: 4))
let actionValue: UInt32 = decode(actionData)
guard let action = FileNotifyAction(rawValue: actionValue) else {
continue
}
let fileLenData = data.subdataWithRange(NSRange(location: Int(offset + 8), length: 4))
let fileNameLen: UInt32 = decode(fileLenData)
let fileNameData = data.subdataWithRange(NSRange(location: Int(offset + 12), length: Int(12 + fileNameLen)))
let fileName = String(data: fileNameData, encoding: NSUTF16StringEncoding) ?? ""
result.append((action: action, fileName: fileName))
let nextOffsetData = data.subdataWithRange(NSRange(location: Int(offset), length: 4))
let nextOffset: UInt32 = decode(nextOffsetData)
offset += nextOffset
if nextOffset == 0 {
break
}
i += 1
}
self.notifications = result
}
}
enum FileNotifyAction: UInt32 {
/// The file was added to the directory.
case ADDED = 0x00000001
/// The file was removed from the directory.
case REMOVED = 0x00000002
/// The file was modified. This can be a change to the data or attributes of the file.
case MODIFIED = 0x00000003
/// The file was renamed, and this is the old name. If the new name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAMED_NEW_NAME bit value.
case RENAMED_OLD_NAME = 0x00000004
/// The file was renamed, and this is the new name. If the old name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAME_OLD_NAME bit value.
case RENAMED_NEW_NAME = 0x00000005
/// The file was added to a named stream.
case ADDED_STREAM = 0x00000006
/// The file was removed from the named stream.
case REMOVED_STREAM = 0x00000007
/// The file was modified. This can be a change to the data or attributes of the file.
case MODIFIED_STREAM = 0x00000008
/// An object ID was removed because the file the object ID referred to was deleted. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
case REMOVED_BY_DELETE = 0x00000009
/// An attempt to tunnel object ID information to a file being created or renamed failed because the object ID is in use by another file on the same volume. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
case NOT_TUNNELLED = 0x0000000A
/// An attempt to tunnel object ID information to a file being renamed failed because the file already has an object ID. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
case TUNNELLED_ID_COLLISION = 0x0000000B
}
}
+322 -1
View File
@@ -11,8 +11,329 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Query Directory
// MARK: SMB2 Change Notify
struct QueryDirectoryRequest: SMBRequest {
let header: QueryDirectoryRequest.Header
let searchPattern: String?
/// - **bufferLength:** maximum number of bytes the server is allowed to return which is the same as maxTransactSize returned by negotiation.
/// - **searchPattern:** can hold wildcards or be nil if all entries should be returned.
/// - **fileIndex:** The byte offset within the directory, indicating the position at which to resume the enumeration.
init(fileId: FileId, infoClass: FileInformationEnum, flags: Flags, bufferLength: UInt32 = 65535, searchPattern: String? = nil, fileIndex: UInt32 = 0) {
assert(FileInformationEnum.queryDirectory.contains(infoClass), "Invalid FileInformationClass used for QueryDirectoryRequest")
let searchPatternOffset = searchPattern != nil ? sizeof(SMB2.Header.self) + sizeof(QueryDirectoryRequest.Header.self) : 0
let nflags = flags.intersect(fileIndex > 0 ? [.INDEX_SPECIFIED] : [])
let searchPatternLength = searchPattern?.dataUsingEncoding(NSUTF16StringEncoding)?.length ?? 0
self.header = Header(size: 53, infoClass: infoClass, flags: nflags, fileIndex: fileIndex, fileId: fileId, searchPatternOffset: UInt8(searchPatternOffset), searchPatternLength: UInt8(searchPatternLength), bufferLength: bufferLength)
self.searchPattern = searchPattern
}
func data() -> NSData {
let result = NSMutableData(data: encode(header))
if let patternData = searchPattern?.dataUsingEncoding(NSUTF16StringEncoding) {
result.appendData(patternData)
}
return result
}
struct Header {
let size: UInt8
let infoClass: FileInformationEnum
let flags: QueryDirectoryRequest.Flags
let fileIndex: UInt32
let fileId: FileId
let searchPatternOffset: UInt8
let searchPatternLength: UInt8
let bufferLength: UInt32
}
struct Flags: OptionSetType {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
static let RESTART_SCANS = Flags(rawValue: 0x01)
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x02)
static let INDEX_SPECIFIED = Flags(rawValue: 0x04)
static let REOPEN = Flags(rawValue: 0x10)
}
}
struct QueryDirectoryResponse: SMBResponse {
let buffer: NSData
func parseAs(type type: FileInformationEnum) -> [(header: SMB2FilesInformationHeader, fileName: String)] {
var offset = 0
var result = [(header: SMB2FilesInformationHeader, fileName: String)]()
while true {
let header: SMB2FilesInformationHeader
switch type {
case .FileDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileDirectoryInformationHeader)))
let h: FileDirectoryInformationHeader = decode(headerData)
header = h
case .FileFullDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileFullDirectoryInformationHeader)))
let h: FileFullDirectoryInformationHeader = decode(headerData)
header = h
case .FileIdFullDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileIdFullDirectoryInformationHeader)))
let h: FileIdFullDirectoryInformationHeader = decode(headerData)
header = h
case .FileBothDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileBothDirectoryInformationHeader)))
let h: FileBothDirectoryInformationHeader = decode(headerData)
header = h
case .FileIdBothDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileIdBothDirectoryInformationHeader)))
let h: FileIdBothDirectoryInformationHeader = decode(headerData)
header = h
case .FileNamesInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileNamesInformationHeader)))
let h: FileNamesInformationHeader = decode(headerData)
header = h
default:
return []
}
let fnData = buffer.subdataWithRange(NSRange(location: offset + sizeofValue(header), length: Int(header.fileNameLength)))
let fileName = String(data: fnData, usingEncoding: NSUTF16StringEncoding)
result.append((header: header, fileName: fileName))
if header.nextEntryOffset == 0 {
break
}
offset += Int(header.nextEntryOffset)
}
return result
}
init? (data: NSData) {
let offset: UInt16 = decode(data.subdataWithRange(NSRange(location: 2, length: 2)))
let length: UInt32 = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
guard data.length > Int(offset) + Int(length) else {
return nil
}
self.buffer = data.subdataWithRange(NSRange(location: Int(offset), length: Int(length)))
}
}
// MARK: SMB2 Query Info
struct QueryInfoRequest: SMBRequest {
let header: Header
let buffer: NSData?
init(fileId: FileId, infoClass: FileInformationEnum, outputBufferLength: UInt32 = 65535) {
self.header = Header(size: 41, infoType: 1, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
self.buffer = nil
}
init(fileId: FileId, extendedAttributes: [String], flags: Flags = [], outputBufferLength: UInt32 = 65535) {
let buffer = NSMutableData()
for ea in extendedAttributes {
let strData = ea.dataUsingEncoding(NSASCIIStringEncoding)!
let strLength = UInt8(strData.length)
let nextOffset = UInt32(4 + 1 + strData.length)
let data = encode(nextOffset).mutableCopy() as! NSMutableData
data.appendData(encode(strLength))
data.appendData(strData)
data.length += 1
let padSize = (data.length) % 4
data.length += padSize
buffer.appendData(data)
}
let bufferOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(QueryInfoRequest.Header.self))
self.header = Header(size: 41, infoType: 1, infoClass: FileInformationEnum.FileFullEaInformation.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: bufferOffset, reserved: 0, inputBufferLength: UInt32(buffer.length), additionalInformation: [], flags: flags, fileId: fileId)
self.buffer = buffer
}
init(fileId: FileId, infoClass: FileSystemInformationEnum, outputBufferLength: UInt32 = 65535) {
self.header = Header(size: 41, infoType: 2, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
self.buffer = nil
}
init(fileId: FileId, securityInfo: FileSecurityInfo, outputBufferLength: UInt32 = 65535) {
self.header = Header(size: 41, infoType: 3, infoClass: 0, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: securityInfo, flags: [], fileId: fileId)
self.buffer = nil
}
// TODO: Implement QUOTA_INFO init
func data() -> NSData {
let headerData = encode(header)
let result = NSMutableData(data: headerData)
if let buffer = buffer {
result.appendData(buffer)
}
return result
}
struct Header {
let size: UInt16
let infoType: UInt8
let infoClass: UInt8
let outputBufferLength: UInt32
let inputBufferOffset: UInt16
private let reserved: UInt16
let inputBufferLength: UInt32
let additionalInformation: FileSecurityInfo
let flags: QueryInfoRequest.Flags
let fileId: FileId
}
struct Flags: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let RESTART_SCAN = Flags(rawValue: 0x00000001)
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x00000002)
static let INDEX_SPECIFIED = Flags(rawValue: 0x00000004)
}
}
struct QueryInfoResponse: SMBResponse {
let buffer: NSData
init?(data: NSData) {
let structSizeData = data.subdataWithRange(NSRange(location: 0, length: 2))
let structSize: UInt16 = decode(structSizeData)
guard structSize == 9 else {
return nil
}
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
let offset: UInt16 = decode(offsetData)*/
let lengthData = data.subdataWithRange(NSRange(location: 4, length: 4))
let length: UInt32 = decode(lengthData)
guard data.length >= 8 + Int(length) else {
return nil
}
self.buffer = data.subdataWithRange(NSRange(location: 8, length: Int(length)))
}
var asAccessInformation: FileAccessInformation {
return decode(buffer)
}
var asAlignmentInformation: FileAlignmentInformation {
return decode(buffer)
}
var asAllInformation: (header: FileAllInformationHeader, name: String) {
let header: FileAllInformationHeader = decode(buffer)
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileAllInformationHeader), length: Int(header.nameLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
return (header, name)
}
var asAlternateNameInformation: String {
let b = UnsafePointer<CChar>(buffer.bytes)
return String(CString: b, encoding: NSUTF16StringEncoding) ?? ""
}
var asAttributeTagInformation: FileAttributeTagInformation {
return decode(buffer)
}
var asBasicInformation: FileBasicInformation {
return decode(buffer)
}
var asCompressionInformation: FileCompressionInformation {
return decode(buffer)
}
var asEaInformation: FileEaInformation {
return decode(buffer)
}
var asFullEaInformation: FileFullEaInformation {
// TODO:
return FileFullEaInformation()
}
var asInternalInformation: FileInternalInformation {
return decode(buffer)
}
var asModeInformation: FileModeInformation {
return decode(buffer)
}
var asNetworkOpenInformation: FileNetworkOpenInformation {
return decode(buffer)
}
var asPipeInformation: FilePipeInformation {
return decode(buffer)
}
var asPipeLocalInformation: FilePipeLocalInformation {
return decode(buffer)
}
var asPipeRemoteInformation: FilePipeRemoteInformation {
return decode(buffer)
}
var asPositionInformation: FilePositionInformation {
return decode(buffer)
}
var asStandardInformation: FileStandardInformation {
return decode(buffer)
}
var asStreamInformation: (header: FileStreamInformationHeader, name: String) {
let header: FileStreamInformationHeader = decode(buffer)
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileStreamInformationHeader), length: Int(header.streamNameLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
return (header, name)
}
var asFsVolumeInformation: (header: FileFsVolumeInformationHeader, name: String) {
let header: FileFsVolumeInformationHeader = decode(buffer)
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileFsVolumeInformationHeader), length: Int(header.labelLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
return (header, name)
}
var asFsSizeInformation: FileFsSizeInformation {
return decode(buffer)
}
var asFsDeviceInformation: FileFsDeviceInformation {
return decode(buffer)
}
var asFsAttributeInformation: (header: FileFsAttributeInformationHeader, name: String) {
let header: FileFsAttributeInformationHeader = decode(buffer)
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileFsAttributeInformationHeader), length: Int(header.nameLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
return (header, name)
}
var asFsControlInformation: FileFsControlInformation {
return decode(buffer)
}
var asFsFullSizeInformation: FileFsFullSizeInformation {
return decode(buffer)
}
var asFsObjectIdInformation: FileFsObjectIdInformation {
return decode(buffer)
}
var asFsSectorSizeInformation: FileFsSectorSizeInformation {
return decode(buffer)
}
}
}
+623
View File
@@ -0,0 +1,623 @@
//
// SMB2QueryTypes.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/19/95.
//
//
import Foundation
protocol SMB2FilesInformationHeader: SMBResponse {
var nextEntryOffset: UInt32 { get }
var fileIndex: UInt32 { get }
var fileNameLength : UInt32 { get }
}
extension SMB2 {
enum FileInformationEnum: UInt8 {
case Nil = 0x00
case FileDirectoryInformation = 0x01
case FileFullDirectoryInformation = 0x02
case FileBothDirectoryInformation = 0x03
case FileBasicInformation = 0x04
case FileStandardInformation = 0x05
case FileInternalInformation = 0x06
case FileEaInformation = 0x07
case FileAccessInformation = 0x08
case FileNameInformation = 0x09
case FileRenameInformation = 0x0A
case FileLinkInformation = 0x0B
case FileNamesInformation = 0x0C
case FileDispositionInformation = 0x0D
case FilePositionInformation = 0x0E
case FileFullEaInformation = 0x0F
case FileModeInformation = 0x10
case FileAlignmentInformation = 0x11
case FileAllInformation = 0x12
case FileAllocationInformation = 0x13
case FileEndOfFileInformation = 0x14
case FileAlternateNameInformation = 0x15
case FileStreamInformation = 0x16
case FilePipeInformation = 0x17
case FilePipeLocalInformation = 0x18
case FilePipeRemoteInformation = 0x19
case FileMailslotQueryInformation = 0x1A
case FileMailslotSetInformation = 0x1B
case FileCompressionInformation = 0x1C
case FileObjectIdInformation = 0x1D
case FileCompletionInformation = 0x1E
case FileMoveClusterInformation = 0x1F
case FileQuotaInformation = 0x20
case FileReparsePointInformation = 0x21
case FileNetworkOpenInformation = 0x22
case FileAttributeTagInformation = 0x23
case FileTrackingInformation = 0x24
case FileIdBothDirectoryInformation = 0x25
case FileIdFullDirectoryInformation = 0x26
case FileValidDataLengthInformation = 0x27
case FileShortNameInformation = 0x28
case FileIoCompletionNotificationInformation = 0x29
case FileIoStatusBlockRangeInformation = 0x2A
case FileIoPriorityHintInformation = 0x2B
case FileSfioReserveInformation = 0x2C
case FileSfioVolumeInformation = 0x2D
case FileHardLinkInformation = 0x2E
case FileProcessIdsUsingFileInformation = 0x2F
case FileNormalizedNameInformation = 0x30
case FileNetworkPhysicalNameInformation = 0x31
case FileIdGlobalTxDirectoryInformation = 0x32
case FileIsRemoteDeviceInformation = 0x33
case FileUnusedInformation = 0x34
case FileNumaNodeInformation = 0x35
case FileStandardLinkInformation = 0x36
case FileRemoteProtocolInformation = 0x37
case FileRenameInformationBypassAccessCheck = 0x38
case FileLinkInformationBypassAccessCheck = 0x39
case FileVolumeNameInformation = 0x3A
case FileIdInformation = 0x3B
case FileIdExtdDirectoryInformation = 0x3C
case FileReplaceCompletionInformation = 0x3D
case FileHardLinkFullIdInformation = 0x3E
case FileIdExtdBothDirectoryInformation = 0x3F
case FileMaximumInformation = 0x40
static let queryDirectory: [FileInformationEnum] = [.FileDirectoryInformation, .FileFullDirectoryInformation, .FileIdFullDirectoryInformation, .FileBothDirectoryInformation, .FileIdBothDirectoryInformation, .FileNamesInformation]
static let queryInfoFile: [FileInformationEnum] = [.FileAccessInformation, .FileAlignmentInformation, .FileAllInformation, .FileAlternateNameInformation, .FileAttributeTagInformation, .FileBasicInformation, .FileCompressionInformation, FileEaInformation, .FileFullEaInformation, .FileInternalInformation, .FileModeInformation, .FileNetworkOpenInformation, .FilePipeInformation, .FilePipeLocalInformation, .FilePipeRemoteInformation, .FilePositionInformation, .FileStandardInformation, .FileStreamInformation]
}
enum FileSystemInformationEnum: UInt8 {
case Nil = 0
case FileFsAttributeInformation
case FileFsControlInformation
case FileFsDeviceInformation
case FileFsFullSizeInformation
case FileFsObjectIdInformation
case FileFsSectorSizeInformation
case FileFsSizeInformation
case FileFsVolumeInformation
}
struct FileSecurityInfo: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let OWNER = FileSecurityInfo(rawValue: 0x00000001)
static let GROUP = FileSecurityInfo(rawValue: 0x00000002)
static let DACL = FileSecurityInfo(rawValue: 0x00000004)
static let SACL = FileSecurityInfo(rawValue: 0x00000008)
static let LABEL = FileSecurityInfo(rawValue: 0x00000010)
static let ATTRIBUTE = FileSecurityInfo(rawValue: 0x00000020)
static let SCOPE = FileSecurityInfo(rawValue: 0x00000040)
static let BACKUP = FileSecurityInfo(rawValue: 0x00010000)
}
struct FileDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
init?(data: NSData) {
self = decode(data)
}
}
struct FileFullDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
init?(data: NSData) {
self = decode(data)
}
}
struct FileIdFullDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
private let reserved: UInt32
let fileId: FileId
init?(data: NSData) {
self = decode(data)
}
}
struct FileBothDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
private let shortNameLen: UInt8
private let reserved: UInt8
private let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
let d = NSMutableData(data: s)
d.length = Int(shortNameLen)
return String(data: d, encoding: NSUTF16StringEncoding)
}
init?(data: NSData) {
self = decode(data)
}
}
struct FileIdBothDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: Int64
let allocationSize: Int64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
private let shortNameLen: UInt8
private let reserved: UInt8
private let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
let d = NSMutableData(data: s)
d.length = Int(shortNameLen)
return String(data: d, encoding: NSUTF16StringEncoding)
}
private let reserved2: UInt16
let fileId : FileId
init?(data: NSData) {
self = decode(data)
}
}
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let fileNameLength : UInt32
init?(data: NSData) {
self = decode(data)
}
}
typealias FileShortNameType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
struct FileAccessInformation {
let accessMask: FileAccessMask
}
struct FileAlignmentInformation {
private let _alignment: UInt32
var alignmentLength: UInt32 {
return _alignment + 1
}
}
struct FileAllInformationHeader {
let basic: FileBasicInformation
let standard: FileStandardInformation
let `internal`: FileInternalInformation
let ea: FileEaInformation
let access: FileAccessInformation
let position: FilePositionInformation
let mode: FileModeInformation
let alignment: FileAlignmentInformation
let nameLength: UInt32
}
struct FileAttributeTagInformation {
let fileAttributes: FileAttributes
let reparseTag: UInt32
}
struct FileBasicInformation {
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileAttributes: FileAttributes
private let reserved: UInt32 = 0
}
struct FileCompressionInformation {
let compressedFileSize: Int64
let compressionFormat: UInt16
static let COMPRESSION_FORMAT_LZNT1 = 0x0002
let compressionUnitShift: UInt8
let chunkShift: UInt8
let clusterShift: UInt8
private let reserved: (UInt8, UInt16)
}
struct FileEaInformation {
let eaSize: UInt32
}
struct FileFullEaInformation {
// TODO
}
struct FileInternalInformation {
let indexNumber: UInt64
}
struct FileModeInformation {
let mode: Mode
struct Mode: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let FILE_WRITE_THROUGH = Mode(rawValue: 0x00000002)
static let FILE_SEQUENTIAL_ONLY = Mode(rawValue: 0x00000004)
static let FILE_NO_INTERMEDIATE_BUFFERING = Mode(rawValue: 0x00000008)
static let FILE_SYNCHRONOUS_IO_ALERT = Mode(rawValue: 0x00000010)
static let FILE_SYNCHRONOUS_IO_NONALERT = Mode(rawValue: 0x00000020)
static let FILE_DELETE_ON_CLOSE = Mode(rawValue: 0x00001000)
}
}
struct FileNetworkOpenInformation {
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileAttributes: FileAttributes
private let reserved: UInt32
}
struct FilePipeInformation {
private let _readMode: UInt32
var readMode: ReadMode {
return ReadMode(rawValue: _readMode) ?? .BYTE_STREAM_MODE
}
private let _completionMode: UInt32
var completionMode: CompletionMode {
return CompletionMode(rawValue: _completionMode) ?? .QUEUE_OPERATION
}
enum ReadMode: UInt32 {
case BYTE_STREAM_MODE = 0x00000000
case MESSAGE_MODE = 0x00000001
}
enum CompletionMode: UInt32 {
case QUEUE_OPERATION = 0x00000000
case COMPLETE_OPERATION = 0x00000001
}
}
struct FilePipeLocalInformation {
private let _namedPipeType: UInt32
var namedPipeType: Type {
return Type(rawValue: _namedPipeType) ?? .BYTE_STREAM_TYPE
}
private let _namedPipeConfiguration: UInt32
var namedPipeConfiguration: Configuration {
return Configuration(rawValue: _namedPipeConfiguration) ?? .INBOUND
}
let maximumInstances: UInt32
let currentInstances: UInt32
let inboundQuota: UInt32
let readDataAvailable: UInt32
let outboundQuota: UInt32
let writeQuotaAvailable: UInt32
private let _namedPipeState: UInt32
var namedPipeState: State {
return State(rawValue: _namedPipeState) ?? .DISCONNECTED_STATE
}
private let _namedPipeEnd: UInt32
var namedPipeEnd: End {
return End(rawValue: _namedPipeEnd) ?? .CLIENT_END
}
enum Type: UInt32 {
case BYTE_STREAM_TYPE = 0x00000000
case MESSAGE_TYPE = 0x00000001
}
enum Configuration: UInt32 {
case INBOUND = 0x00000000
case OUTBOUND = 0x00000001
case FULL_DUPLEX = 0x00000002
}
enum State: UInt32 {
case DISCONNECTED_STATE = 0x00000001
case LISTENING_STATE = 0x00000002
case CONNECTED_STATE = 0x00000003
case CLOSING_STATE = 0x00000004
}
enum End: UInt32 {
case CLIENT_END = 0x00000000
case SERVER_END = 0x00000001
}
}
struct FilePipeRemoteInformation {
let collectDataTime: SMBTime
let maximumCollectionCount: UInt32
}
struct FilePositionInformation {
let currentByteOffset: Int64
}
struct FileStandardInformation {
let allocationSize: Int64
let fileSize: Int64
let numberOfLinks: UInt32
let deletePending: Bool
let directory: Bool
private let reserved: UInt16
}
struct FileStreamInformationHeader {
let nextEntryOffset: UInt32
let streamNameLength: UInt32
let streamSize: Int64
let streamAllocationSize: Int64
}
struct FileFsVolumeInformationHeader {
let creationTime: SMBTime
let serial: UInt32
let labelLength: UInt32
let supportObjects: Bool
let reserved: UInt8
}
struct FileFsSizeInformation {
let totalAllocationUnits: Int64
let availableAllocationUnits: Int64
let sectorsPerAllocationUnit: UInt32
let bytesPerSector: UInt32
}
struct FileFsDeviceInformation {
private let _deviceType: UInt32
var deviceType: DeviceType {
return DeviceType(rawValue: _deviceType) ?? .DISK
}
let charactristics: Charactristics
enum DeviceType: UInt32 {
case CD_ROM = 0x00000002
case DISK = 0x00000007
}
struct Charactristics: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// Storage device supports removable media. For example, drivers for JAZ drive devices specify this characteristic, but drivers for PCMCIA flash disks do not.
static let REMOVABLE_MEDIA = Charactristics(rawValue: 0x00000001)
/// Indicates that the device cannot be written to.
static let READ_ONLY_DEVICE = Charactristics(rawValue: 0x00000002)
/// Indicates that the device is a floppy disk device.
static let FLOPPY_DISKETTE = Charactristics(rawValue: 0x00000004)
/// Indicates that the device supports write-once media.
static let WRITE_ONCE_MEDIA = Charactristics(rawValue: 0x00000008)
/// ndicates that the volume is for a remote file system like SMB or CIFS.
static let REMOTE_DEVICE = Charactristics(rawValue: 0x00000010)
/// Indicates that a file system is mounted on the device.
static let DEVICE_IS_MOUNTED = Charactristics(rawValue: 0x00000020)
/// Indicates that the volume does not directly reside on storage media, but resides on some other type of media (memory for example).
static let VIRTUAL_VOLUME = Charactristics(rawValue: 0x00000040)
/// By default, volumes do not check the ACL associated with the volume, but instead use the ACLs associated with individual files on the volume. When this flag is set the volume ACL is also checked.
static let DEVICE_SECURE_OPEN = Charactristics(rawValue: 0x00000100)
/// Indicates that the device object is part of a Terminal Services device stack.
static let TS_DEVICE = Charactristics(rawValue: 0x00001000)
/// ndicates that a web-based Distributed Authoring and Versioning (WebDAV) file system is mounted on the device.
static let WEBDAV_DEVICE = Charactristics(rawValue: 0x00002000)
/// The IO Manager normally performs a full security check for traverse access on every file open when the client is an appcontainer. Setting of this flag bypasses this enforced traverse access check if the client token already has traverse privileges.
static let PORTABLE_DEVICE = Charactristics(rawValue: 0x0004000)
/// Indicates that the given device resides on a portable bus like USB or Firewire and that the entire device (not just the media) can be removed from the system.
static let DEVICE_ALLOW_APPCONTAINER_TRAVERSAL = Charactristics(rawValue: 0x00020000)
}
}
struct FileFsAttributeInformationHeader {
let attributes: Attributes
let maximumFileNameLength: Int32
let nameLength: UInt32
struct Attributes: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// The file system supports case-sensitive file names when looking up (searching for) file names in a directory.
static let CASE_SENSITIVE_SEARCH = Attributes(rawValue: 0x00000001)
/// The file system preserves the case of file names when it places a name on disk.
static let CASE_PRESERVED_NAMES = Attributes(rawValue: 0x00000002)
/// The file system supports Unicode in file and directory names. This flag applies only to file and directory names; the file system neither restricts nor interprets the bytes of data within a file.
static let UNICODE_ON_DISK = Attributes(rawValue: 0x00000004)
/// The file system preserves and enforces access control lists (ACLs).
static let PERSISTENT_ACLS = Attributes(rawValue: 0x00000008)
/// The file volume supports file-based compression. This flag is incompatible with the FILE_VOLUME_IS_COMPRESSED flag.
static let FILE_COMPRESSION = Attributes(rawValue: 0x00000010)
/// The file system supports per-user quotas.
static let VOLUME_QUOTAS = Attributes(rawValue: 0x00000020)
/// The file system supports sparse files.
static let SUPPORTS_SPARSE_FILES = Attributes(rawValue: 0x00000040)
/// The file system supports reparse points.
static let SUPPORTS_REPARSE_POINTS = Attributes(rawValue: 0x00000080)
/// The file system supports remote storage.
static let REMOTE_STORAGE = Attributes(rawValue: 0x00000100)
/// The specified volume is a compressed volume. This flag is incompatible with the FILE_FILE_COMPRESSION flag.
static let IS_COMPRESSED = Attributes(rawValue: 0x00008000)
/// The file system supports object identifiers.
static let OBJECT_IDS = Attributes(rawValue: 0x00010000)
/// The file system supports the Encrypted File System (EFS).
static let ENCRYPTION = Attributes(rawValue: 0x00020000)
/// The file system supports named streams. (aka. Resource Fork on MacOS)
static let NAMED_STREAMS = Attributes(rawValue: 0x00040000)
/// If set, the volume has been mounted in read-only mode.
static let READ_ONLY_VOLUME = Attributes(rawValue: 0x00080000)
/// The underlying volume is write once. (aka tapes)
static let SEQUENTIAL_WRITE_ONCE = Attributes(rawValue: 0x00100000)
/// The volume supports transactions.
static let SUPPORTS_TRANSACTIONS = Attributes(rawValue: 0x00200000)
/// The file system supports hard linking files.
static let SUPPORTS_HARD_LINKS = Attributes(rawValue: 0x00400000)
/// The file system persistently stores Extended Attribute information per file.
static let SUPPORTS_EXTENDED_ATTRIBUTES = Attributes(rawValue: 0x00800000)
/// The file system supports opening a file by FileID or ObjectID.
static let SUPPORTS_OPEN_BY_FILE_ID = Attributes(rawValue: 0x01000000)
/// The file system implements a USN change journal.
static let USN_JOURNAL = Attributes(rawValue: 0x02000000)
/// The file system supports integrity streams.
static let SUPPORT_INTEGRITY_STREAMS = Attributes(rawValue: 0x04000000)
/// The file system supports sharing logical clusters between files on the same volume. The file system reallocates on writes to shared clusters. Indicates that FSCTL_DUPLICATE_EXTENTS_TO_FILE is a supported operation.
static let SUPPORTS_BLOCK_REFCOUNTING = Attributes(rawValue: 0x08000000)
/// The file system tracks whether each cluster of a file contains valid data (either from explicit file writes or automatic zeros) or invalid data (has not yet been written to or zeroed).
static let SUPPORTS_SPARSE_VDL = Attributes(rawValue: 0x10000000)
}
}
struct FileFsControlInformation {
let freeSpaceStartFiltering: Int64
let freeSpaceThreshold: Int64
let freeSpaceStopFiltering: Int64
let defaultQuotaThreshold: UInt64
let defaultQuotaLimit: UInt64
let flags: Flags
private let padding: UInt32 = 0
struct Flags: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// Quotas are tracked on the volume, but they are not enforced. Tracked quotas enable reporting on the file system space used by system users. If both this flag and FILE_VC_QUOTA_ENFORCE are specified, FILE_VC_QUOTA_ENFORCE is ignored.
static let QUOTA_TRACK = Flags(rawValue: 0x00000001)
/// Quotas are tracked and enforced on the volume.
static let QUOTA_ENFORCE = Flags(rawValue: 0x00000002)
/// Content indexing is disabled.
static let CONTENT_INDEX_DISABLED = Flags(rawValue: 0x00000008)
/// An event log entry will be created when the user exceeds his or her assigned quota warning threshold.
static let LOG_QUOTA_THRESHOLD = Flags(rawValue: 0x00000010)
/// An event log entry will be created when the user exceeds the assigned disk quota limit.
static let LOG_QUOTA_LIMIT = Flags(rawValue: 0x00000020)
/// An event log entry will be created when the volume's free space threshold is exceeded.
static let LOG_VOLUME_THRESHOLD = Flags(rawValue: 0x00000040)
/// An event log entry will be created when the volume's free space limit is exceeded.
static let LOG_VOLUME_LIMIT = Flags(rawValue: 0x00000080)
/// The quota information for the volume is incomplete because it is corrupt, or the system is in the process of rebuilding the quota information.
static let QUOTAS_INCOMPLETE = Flags(rawValue: 0x00000100)
/// The file system is rebuilding the quota information for the volume.
static let QUOTAS_REBUILDING = Flags(rawValue: 0x00000200)
}
}
struct FileFsFullSizeInformation {
let totalAllocationUnits: Int64
let callerAvailableAllocationUnits: Int64
let actualAvailableAllocationUnits: Int64
let sectorsPerAllocationUnit: UInt32
let bytesPerSector: UInt32
}
struct FileFsObjectIdInformation {
let objectId: uuid_t
let extendedInfo: (UInt64, UInt64, UInt64, UInt64, UInt64, UInt64)
}
struct FileFsSectorSizeInformation {
let logicalBytesPerSector: UInt32
let physicalBytesPerSectorForAtomicity: UInt32
let physicalBytesPerSectorForPerformance: UInt32
let effectivePhysicalBytesPerSectorForAtomicity: UInt32
let flags: Flags
let byteOffsetForSectorAlignment: UInt32
let byteOffsetForPartitionAlignment: UInt32
struct Flags: OptionSetType {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// When set, this flag indicates that the first physical sector of the device is aligned with the first logical sector. When not set, the first physical sector of the device is misaligned with the first logical sector.
static let ALIGNED_DEVICE = Flags(rawValue: 0x00000001)
/// When set, this flag indicates that the partition is aligned to physical sector boundaries on the storage device.
static let PARTITION_ALIGNED_ON_DEVICE = Flags(rawValue: 0x00000002)
/// When set, the device reports that it does not incur a seek penalty (this typically indicates that the device does not have rotating media, such as flash-based disks).
static let NO_SEEK_PENALTY = Flags(rawValue: 0x00000008)
/// When set, the device supports TRIM operations, either T13 (ATA) TRIM or T10 (SCSI/SAS) UNMAP.
static let TRIM_ENABLED = Flags(rawValue: 0x00000010)
}
}
}
+26 -15
View File
@@ -12,19 +12,25 @@ extension SMB2 {
// MARK: SMB2 Negotiating
struct NegotiateRequest: SMBRequest {
let request: NegotiateRequest.Header
let header: NegotiateRequest.Header
let dialects: [UInt16]
let contexts: [(type: NegotiateContextType, data: NSData)]
init(request: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = []) {
self.request = request
init(header: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = []) {
self.header = header
self.dialects = dialects
self.contexts = contexts
}
init(dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = [],capabilities: GlobalCapabilities = [], clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
self.header = Header(capabilities: capabilities, clientStartTime: clientStartTime, guid: guid, signing: signing)
self.dialects = dialects
self.contexts = contexts
}
func data() -> NSData {
var request = self.request
request.dialectCount = UInt16(dialects.count)
var header = self.header
header.dialectCount = UInt16(dialects.count)
let dialectData = NSMutableData()
for dialect in dialects {
var dialect = dialect
@@ -32,8 +38,8 @@ extension SMB2 {
}
let pad = ((1024 - dialectData.length) % 8)
dialectData.increaseLengthBy(pad)
request.contextOffset = UInt32(sizeof(request.dynamicType.self)) + UInt32(dialectData.length)
request.contextCount = UInt16(contexts.count)
header.contextOffset = UInt32(sizeof(header.dynamicType.self)) + UInt32(dialectData.length)
header.contextCount = UInt16(contexts.count)
let contextData = NSMutableData()
for context in contexts {
@@ -43,7 +49,7 @@ extension SMB2 {
contextData.increaseLengthBy(4)
contextData.appendBytes(&dataLen, length: 2)
}
let result = NSMutableData(data: encode(&request))
let result = NSMutableData(data: encode(&header))
result.appendData(dialectData)
result.appendData(contextData)
return result
@@ -52,7 +58,7 @@ extension SMB2 {
struct Header {
var size: UInt16
var dialectCount: UInt16
let singing: NegotiateSinging
let signing: NegotiateSinging
private let reserved: UInt16
let capabilities: GlobalCapabilities
let guid: uuid_t
@@ -60,17 +66,17 @@ extension SMB2 {
var contextCount: UInt16
private let reserved2: UInt16
var clientStartTime: SMBTime {
let time = UInt64(contextOffset) + (UInt64(contextCount) << 32) + (UInt64(contextCount) << 48)
let time = Int64(contextOffset) + (Int64(contextCount) << 32) + (Int64(contextCount) << 48)
return SMBTime(time: time)
}
init(singing: NegotiateSinging = [.ENABLED], capabilities: GlobalCapabilities, guid: uuid_t? = nil, clientStartTime: SMBTime? = nil) {
init(capabilities: GlobalCapabilities, clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
self.size = 36
self.dialectCount = 0
self.singing = singing
self.signing = signing
self.reserved = 0
self.capabilities = capabilities
self.guid = guid ?? (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
self.guid = guid ?? (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
if let clientStartTime = clientStartTime {
let time = clientStartTime.time
self.contextOffset = UInt32(time & 0xffffffff)
@@ -179,6 +185,11 @@ extension SMB2 {
self.buffer = buffer
}
init(sessionId: UInt64 = 0, flags: SessionSetupRequest.Flags = [], singing: SessionSetupSinging = [.ENABLED], capabilities: GlobalCapabilities = [], securityData: NSData? = nil) {
self.header = Header(sessionId: sessionId, flags: flags, singing: singing, capabilities: capabilities)
self.buffer = securityData
}
func data() -> NSData {
var header = self.header
header.bufferOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(SessionSetupRequest.Header.self))
@@ -272,8 +283,8 @@ extension SMB2 {
self.rawValue = rawValue
}
static let ENABLED = NegotiateSinging(rawValue: 0x01)
static let REQUIRED = NegotiateSinging(rawValue: 0x02)
static let ENABLED = SessionSetupSinging(rawValue: 0x01)
static let REQUIRED = SessionSetupSinging(rawValue: 0x02)
}
// MARK: SMB2 Log off
+32
View File
@@ -10,5 +10,37 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Set Info
struct SetInfoRequest: SMBRequest {
let header: Header
let buffer: NSData?
func data() -> NSData {
return NSData()
}
struct Header {
let size: UInt16 = 33
let infoType: UInt8
private let infoClass: UInt8
let bufferLength: UInt32
let bufferOffset: UInt16
private let reserved: UInt16
let securityInfo: FileSecurityInfo
let fileId: FileId
}
}
struct SetInfoResponse: SMBResponse {
let size: UInt16
init() {
self.size = 2
}
init? (data: NSData) {
self = decode(data)
}
}
}
+2 -2
View File
@@ -22,12 +22,12 @@ extension SMB2 {
}
init? (header: TreeConnectRequest.Header, host: String, share: String) {
guard !host.containsString("/") && !host.containsString("/") && !share.containsString("/") && !share.containsString("/") else {
guard !host.containsString("/") && !share.containsString("/") else {
return nil
}
self.header = header
let path = "\\\\\(host)\\\(share)"
self.buffer = path.dataUsingEncoding(NSUTF8StringEncoding)
self.buffer = path.dataUsingEncoding(NSUTF16StringEncoding)
}
func data() -> NSData {
-249
View File
@@ -1,249 +0,0 @@
//
// SocketTransmitter.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
public class TCPSocketClient: NSObject, NSStreamDelegate {
public static let ports = ["http": 80,
"https": 443,
"smb": 445,
"ftp": 21,
"sftp": 22,
"sftp": 2121,
"telnet": 23,
"pop": 110,
"smtp": 25,
"imap": 143]
public static let securePorts = ["https": 443,
"smb": 445,
"sftp": 22,
"sftp": 2121,
"telnet": 992,
"pop": 995,
"smtp": 465,
"imap": 993]
private var inputStream: NSInputStream?
private var outputStream: NSOutputStream?
private var dataToBeSent: NSMutableData = NSMutableData()
/// holds data received from server
public let dataReceived: NSMutableData = NSMutableData()
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
public let baseURL: NSURL
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
public let secureConnection: Bool
/// server's ports which is value between 1 to 65535
private let port: UInt32
private var open = false
/**
* - parameter baseURL: a url with valid scheme, dns or ip host and ports
* path and query sections will be neglected
*
* **Note** Call `connect()` to establish connection
* - parameter secure: establishing connection using an SSL/TLS connection
*/
public init?(baseURL: NSURL, secure: Bool = false) {
self.baseURL = baseURL
self.secureConnection = secure
let scheme = baseURL.uw_scheme.lowercaseString
let defaultPort = secure ? UInt32(TCPSocketClient.securePorts[scheme] ?? 0) : UInt32(TCPSocketClient.ports[scheme] ?? 0)
self.port = baseURL.port?.unsignedIntValue ?? defaultPort
if self.port == 0 {
return nil
}
}
deinit {
disconnect()
}
/**
* Establshes a connection to desired server
* - returns: A bool value which indicated there where no system error during
* creating connection
*/
public func connect() -> Bool {
guard let hostStr = baseURL.host else {
return false
}
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
let host : CFString = NSString(string: hostStr)
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, self.port, &readStream, &writeStream)
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, outputStream = outputStream else {
return false
}
inputStream.delegate = self
outputStream.delegate = self
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
if secureConnection {
inputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
outputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
}
inputStream.open()
outputStream.open()
return true
}
/**
* Terminates connection to the server
*/
public func disconnect() {
inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.outputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream = nil
self.outputStream = nil
}
public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch (eventCode) {
case NSStreamEvent.ErrorOccurred:
open = false
case NSStreamEvent.EndEncountered:
break
case NSStreamEvent.None:
break
case NSStreamEvent.OpenCompleted:
let activeStatus: [NSStreamStatus] = [.Open, .Reading, .Writing, .AtEnd]
open = activeStatus.contains(inputStream?.streamStatus ?? .NotOpen) && activeStatus.contains(outputStream?.streamStatus ?? .NotOpen)
case NSStreamEvent.HasBytesAvailable:
var buffer = [UInt8](count: 2048, repeatedValue: 0)
if ( aStream == inputStream) {
while (inputStream!.hasBytesAvailable ?? false) {
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
dataReceived.appendBytes(&buffer, length: len)
}
}
}
case NSStreamEvent.HasSpaceAvailable:
if aStream == outputStream {
do {
try send(data: nil)
} catch _ {
NSLog("Sending error")
}
}
default:
break
}
}
/**
* Sends data to server
* - parameter data: data which is intended to be sent to server
* - throws: NSURLError.NetworkConnectionLost in case of server disconnects disgracefully
*/
public func send(data data: NSData?) throws {
guard let outputStream = outputStream else {
return
}
if outputStream.hasSpaceAvailable ?? false {
if let data = data {
dataToBeSent.appendData(data)
}
if dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(UnsafePointer(dataToBeSent.bytes), maxLength: dataToBeSent.length) ?? -1
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
} else {
throw NSError(domain: NSURLErrorDomain, code: NSURLError.NetworkConnectionLost.rawValue, userInfo: nil)
}
}
//println("Sent the following")
} else { //steam busy
if let data = data {
dataToBeSent.appendData(data)
}
}
}
/**
* Clears entire send and receive buffer
*/
public func flush() {
dataToBeSent.length = 0
dataReceived.length = 0
}
/**
* Put's thread in sleep until all data is sent
* **Note:** Don't call this method from main thread
*/
internal func waitUntillDataSent() {
if NSThread.isMainThread() {
assert(false, "waitUntillDataSent() method can't be called from main thread")
}
while true {
if dataToBeSent.length == 0 {
break
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
}
/**
* Put's thread in sleep until all response from server is loaded into tcp stack
* server response can be retrieved by `dataReceived` property
* **Note:** Don't call this method from main thread
* - returns: A Bool value indicates all response loaded from server successfullt
*/
internal func waitUntilResponse() -> Bool {
if NSThread.isMainThread() {
assert(false, "waitUntilResponse() method can't be called from main thread")
}
var finished = false
while !finished {
switch inputStream?.streamStatus ?? .Error {
case .AtEnd:
finished = true
return true
case .Closed, .Error:
return false
default:
finished = false
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
return false
}
}
+205 -75
View File
@@ -8,45 +8,6 @@
import Foundation
public enum FileProviderWebDavErrorCode: Int {
case OK = 200
case Created = 201
case NoContent = 204
case MultiStatus = 207
case Forbidden = 403
case MethodNotAllowed = 405
case Conflict = 409
case PreconditionFailed = 412
case UnsupportedMediaType = 415
case Locked = 423
case FailedDependency = 424
case BadGateway = 502
case InsufficientStorage = 507
}
public struct FileProviderWebDavError: ErrorType, CustomStringConvertible {
public let code: FileProviderWebDavErrorCode
public let url: NSURL
public var description: String {
switch code {
case .OK: return "OK"
case .Created: return "Created"
case .NoContent: return "No Content"
case .MultiStatus: return ""
case .Forbidden: return "Forbidden"
case .MethodNotAllowed: return "Method Not Allowed"
case .Conflict: return "Conflict"
case .PreconditionFailed: return "Precondition Failed"
case .UnsupportedMediaType: return "Unsupported Media Type"
case .Locked: return "Locked"
case .FailedDependency: return "Failed Dependency"
case .BadGateway: return "Bad Gateway"
case .InsufficientStorage: return "Insufficient Storage"
}
}
}
public final class WebDavFileObject: FileObject {
public let contentType: String
public let entryTag: String?
@@ -102,12 +63,15 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let data = data {
let xresponse = self.parseXMLResponse(data)
var fileObjects = [WebDavFileObject]()
@@ -117,31 +81,63 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
}
fileObjects.append(self.mapToFileObject(attr))
}
completionHandler(contents: fileObjects, error: error)
completionHandler(contents: fileObjects, error: responseError ?? error)
return
}
completionHandler(contents: [], error: error)
completionHandler(contents: [], error: responseError ?? error)
}
task.resume()
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
let request = NSMutableURLRequest(URL: absoluteURL(path))
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let data = data {
let xresponse = self.parseXMLResponse(data)
if let attr = xresponse.first {
completionHandler(attributes: self.mapToFileObject(attr), error: error)
completionHandler(attributes: self.mapToFileObject(attr), error: responseError ?? error)
return
}
}
completionHandler(attributes: nil, error: error)
completionHandler(attributes: nil, error: responseError ?? error)
}
task.resume()
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
// Not all WebDAV clients implements RFC2518 which allows geting storage quota.
// In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
// and used space is zero.
guard let baseURL = baseURL else {
return
}
let request = NSMutableURLRequest(URL: baseURL)
request.HTTPMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let data = data {
let xresponse = self.parseXMLResponse(data)
if let attr = xresponse.first {
let totalSize = Int64(attr.prop["quota-available-bytes"] ?? "")
let usedSize = Int64(attr.prop["quota-used-bytes"] ?? "")
completionHandler(total: totalSize ?? -1, used: usedSize ?? 0)
return
}
}
completionHandler(total: -1, used: 0)
}
task.resume()
}
@@ -154,24 +150,32 @@ extension WebDAVFileProvider: FileProviderOperations {
let url = absoluteURL((atPath as NSString).stringByAppendingPathComponent(folderName) + "/")
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "MKCOL"
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse, let code = FileProviderWebDavErrorCode(rawValue: response.statusCode) where code != .OK {
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let response = response as? NSHTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) where code != .OK {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
return
}
completionHandler?(error: error)
self.delegateNotify(.Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"), error: error)
completionHandler?(error: responseError ?? error)
self.delegateNotify(.Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"), error: responseError ?? error)
}
task.resume()
}
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
let request = NSMutableURLRequest(URL: absoluteURL(path))
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
completionHandler?(error: error)
self.delegateNotify(.Create(path: (path as NSString).stringByAppendingPathComponent(fileAttribs.name)), error: error)
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
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.resume()
@@ -193,13 +197,12 @@ extension WebDAVFileProvider: FileProviderOperations {
} else {
request.HTTPMethod = "COPY"
}
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
request.setValue(absoluteURL(path).uw_absoluteString, forHTTPHeaderField: "Destination")
if !overwrite {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse, let code = FileProviderWebDavErrorCode(rawValue: response.statusCode) {
if let response = response as? NSHTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
defer {
let op = move ? FileOperation.Move(source: path, destination: toPath) : .Copy(source: path, destination: toPath)
self.delegateNotify(op, error: error)
@@ -223,9 +226,8 @@ extension WebDAVFileProvider: FileProviderOperations {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "DELETE"
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse, let code = FileProviderWebDavErrorCode(rawValue: response.statusCode) {
if let response = response as? NSHTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
defer {
self.delegateNotify(.Remove(path: path), error: error)
}
@@ -245,19 +247,29 @@ extension WebDAVFileProvider: FileProviderOperations {
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
let request = NSMutableURLRequest(URL: absoluteURL(toPath))
let url = absoluteURL(toPath)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
completionHandler?(error: error)
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: error)
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
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.resume()
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
let request = NSMutableURLRequest(URL: absoluteURL(path))
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
let task = session.downloadTaskWithRequest(request) { (sourceFileURL, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let sourceFileURL = sourceFileURL {
do {
try NSFileManager.defaultManager().copyItemAtURL(sourceFileURL, toURL: toLocalURL)
@@ -266,7 +278,7 @@ extension WebDAVFileProvider: FileProviderOperations {
return
}
}
completionHandler?(error: error)
completionHandler?(error: responseError ?? error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
task.resume()
@@ -279,7 +291,8 @@ extension WebDAVFileProvider: FileProviderReadWrite {
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
let request = NSMutableURLRequest(URL: absoluteURL(path))
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
@@ -287,7 +300,11 @@ extension WebDAVFileProvider: FileProviderReadWrite {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let task = session.dataTaskWithRequest(request) { (data, response, error) in
completionHandler(contents: data, error: error)
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler(contents: data, error: responseError ?? error)
}
task.resume()
}
@@ -298,16 +315,20 @@ extension WebDAVFileProvider: FileProviderReadWrite {
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: self.absoluteURL(path))
}
defer {
self.delegateNotify(.Modify(path: path), error: error)
self.delegateNotify(.Modify(path: path), error: responseError ?? error)
}
if let error = error {
completionHandler?(error: error)
return
}
if atomically {
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
}
if let error = error {
// If there is no error, completionHandler has been executed by move command
completionHandler?(error: error)
}
}
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
task.resume()
@@ -317,13 +338,16 @@ extension WebDAVFileProvider: FileProviderReadWrite {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
//request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
// FIXME: paginating results
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let data = data {
let xresponse = self.parseXMLResponse(data)
var fileObjects = [WebDavFileObject]()
@@ -335,10 +359,10 @@ extension WebDAVFileProvider: FileProviderReadWrite {
fileObjects.append(fileObject)
foundItemHandler?(fileObject)
}
completionHandler(files: fileObjects, error: error)
completionHandler(files: fileObjects, error: responseError ?? error)
return
}
completionHandler(files: [], error: error)
completionHandler(files: [], error: responseError ?? error)
}
task.resume()
}
@@ -357,6 +381,8 @@ extension WebDAVFileProvider: FileProviderReadWrite {
// TODO: implements methods for lock mechanism
}
extension WebDAVFileProvider: FileProvider {}
// MARK: WEBDAV XML response implementation
internal extension WebDAVFileProvider {
@@ -419,7 +445,7 @@ internal extension WebDAVFileProvider {
}
for propItemNode in propStatNode[proptag].children ?? [] {
propDic[propItemNode.name.componentsSeparatedByString(":").last!.lowercaseString] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xmlStringCompact.containsString("collection") {
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xmlString.containsString("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
}
@@ -506,10 +532,114 @@ extension WebDAVFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDele
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
}
public struct FileProviderWebDavError: ErrorType, CustomStringConvertible {
public let code: FileProviderHTTPErrorCode
public let url: NSURL
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"
}
}
}