diff --git a/README.md b/README.md index 6bef62c..34427f2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This Swift library provide a swifty way to deal with local and remote files and directories in same way. This library provides implementaion of WebDav and SMB/CIFS (incomplete) and local files. -All functions are async recalls and it wont block your main thread. +All functions are async calls and it wont block your main thread. ## Installation @@ -16,11 +16,11 @@ Each provider has a specific class which conforms to FileProvider protocol and s For now this providers are supported: -**LocalFileProvider :** a wrapper for NSFileManager with some additions like searching and reading a portion of file +**LocalFileProvider :** a wrapper for `NSFileManager` with some additions like searching and reading a portion of file **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 Windows and SMB2/3 is now replacing AFP protocol on MacOS too. I Implemented data types and some basic functions but main interface is not implemented yet! +**SMBFileProvider :** SMB/CIFS and SMB2/3 are file and printer sharing protocol which is originated from Windows 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 :** not implemented yet! @@ -51,6 +51,8 @@ For interaction with UI, set delegate variable of `FileProvider` object For updating User interface please consider using delegate method instead of completion handlers. Delegate methods are guaranteed to run in main thread to avoid bugs. +Your class should conforms `FileProviderDelegate` class: + override func viewDidLoad() { documentsFileProvider.delegate = self } @@ -126,7 +128,7 @@ Creating new file from data stream: documentsFileProvider.removeItemAtPath(path: "new.txt", completionHandler: nil) -Caution: This method will not delete directories with content. +***Caution:*** This method will not delete directories with content. ### Retrieve Content of File @@ -142,7 +144,7 @@ THere is two method for this purpose, one of them loads entire file into NSData If you want to retrieve a portion of file you should can `contentsAtPath` method with offset and length arguments. Please note first byte of file has offset: 0. - documentsFileProvider.contentsAtPath(path: "old.txt:, offset: 2, length: 5, completionHandler: { + documentsFileProvider.contentsAtPath(path: "old.txt", offset: 2, length: 5, completionHandler: { (contents: NSData?, error: ErrorType?) -> Void if let contents = contents { print(String(data: contents, encoding: NSUTF8StringEncoding)) // "llo w" @@ -152,6 +154,6 @@ If you want to retrieve a portion of file you should can `contentsAtPath` method ### Write Data To Files let data = "What's up Newyork!".dataUsingEncoding(NSUTF8StringEncoding) - documentsFileProvider.writeContentsAtPath(path: "old, contents data: data, atomically: true, completionHandler: nil) + documentsFileProvider.writeContentsAtPath(path: "old.txt", contents data: data, atomically: true, completionHandler: nil) diff --git a/Source/FileProvider.swift b/Source/FileProvider.swift index 3527f1d..3aa7b18 100644 --- a/Source/FileProvider.swift +++ b/Source/FileProvider.swift @@ -158,7 +158,7 @@ extension FileProvider { } func NotImplemented() { - assert(false, "method not implemented") + assertionFailure("method not implemented") } } diff --git a/Source/SMBFileProvider.swift b/Source/SMBFileProvider.swift index 3381e02..561d707 100644 --- a/Source/SMBFileProvider.swift +++ b/Source/SMBFileProvider.swift @@ -21,7 +21,7 @@ func decode(data: NSData) -> T { return pointer.move() } -class SMBFileProvider: NSObject, FileProvider, NSStreamDelegate { +class SMBFileProvider: FileProvider { var type: String = "Samba" var isPathRelative: Bool = true var baseURL: NSURL? @@ -32,9 +32,6 @@ class SMBFileProvider: NSObject, FileProvider, NSStreamDelegate { typealias FileObjectClass = FileObject - var inputStream: NSInputStream? - var outputStream: NSOutputStream? - init? (baseURL: NSURL, credential: NSURLCredential, afterInitialized: SimpleCompletionHandler) { guard baseURL.scheme.lowercaseString == "smb" else { return nil @@ -43,79 +40,12 @@ class SMBFileProvider: NSObject, FileProvider, NSStreamDelegate { dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT) //let url = baseURL.absoluteString self.credential = credential - super.init() - - connect() - } - - func connect() -> Bool { - guard let hostStr = self.baseURL?.host else { - return false - } - var readStream : Unmanaged? - var writeStream : Unmanaged? - let host : CFString = NSString(string: hostStr) - let port : UInt32 = self.baseURL?.port?.unsignedIntValue ?? 80 - - CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, 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) - - inputStream.open() - outputStream.open() - return true - } - - func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { - switch (eventCode) { - case NSStreamEvent.ErrorOccurred: - NSLog("ErrorOccurred") - case NSStreamEvent.EndEncountered: - NSLog("EndEncountered") - case NSStreamEvent.None: - NSLog("None") - case NSStreamEvent.HasBytesAvailable: - NSLog("HasBytesAvaible") - var buffer = [UInt8](count: 4096, repeatedValue: 0) - if ( aStream == inputStream) { - while (inputStream!.hasBytesAvailable ?? false) { - let len = inputStream!.read(&buffer, maxLength: buffer.count) - if(len > 0) { - let output = NSString(bytes: &buffer, length: buffer.count, encoding: NSUTF8StringEncoding) - if (output != ""){ - NSLog("server said: %@", output!) - } - } - } - } - case NSStreamEvent.OpenCompleted: - NSLog("OpenCompleted") - case NSStreamEvent.HasSpaceAvailable: - NSLog("HasSpaceAvailable") - default: - NSLog("allZeros") - } - } - - deinit { - inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String) - outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String) } func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObjectClass], error: ErrorType?) -> Void)) { + NotImplemented() dispatch_async(dispatch_queue) { - self.NotImplemented() + } } @@ -257,6 +187,21 @@ extension SMBFileProvider { return result } + func negotiateAndConnectV2() -> UInt64? { + guard let sockt = TCPSocketTransmitter(baseURL: self.baseURL!) else { + return nil + } + sockt.connect() + let negHeader = HeaderV2(command: .NEGOTIATE, creditRequestResponse: 0, messageId: 0, treeId: 0, sessionId: 0) + let negMessage = NSData() + self.createSMBMessage(negHeader, blocks: [(params: nil, message: negMessage)]) + _ = try? sockt.send(data: nil) + sockt.waitForResponse() + print(sockt.dataRecieved) + + return 0 + } + private func smbTimeToUnix(smbTime: UInt64) -> UInt { return UInt(smbTime / 10000000) - 11644473600 } @@ -538,7 +483,7 @@ extension SMBFileProvider { var signatureLo: UInt64 var signatureHi: UInt64 - init(command: CommandsV2, status: NTStatus = .SUCCESS, creditCharge: UInt16, creditRequestResponse: UInt16, flags: FlagsV2, nextCommand: UInt32 = 0, messageId: UInt64, treeId: UInt32, sessionId: UInt64,signatureLo: UInt64, signatureHi: UInt64) { + init(command: CommandsV2, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: FlagsV2 = [], nextCommand: UInt32 = 0, messageId: UInt64, treeId: UInt32, sessionId: UInt64,signatureLo: UInt64 = 0, signatureHi: UInt64 = 0) { self.protocolID = self.dynamicType.protocolConst self.structSize = 64 self.status = status.rawValue diff --git a/Source/SocketTransmitter.swift b/Source/SocketTransmitter.swift new file mode 100644 index 0000000..f42885b --- /dev/null +++ b/Source/SocketTransmitter.swift @@ -0,0 +1,198 @@ +// +// SocketTransmitter.swift +// ExtDownloader +// +// Created by Amir Abbas Mousavian on 4/9/95. +// Copyright © 1395 Mousavian. All rights reserved. +// + +import Foundation + +class TCPSocketTransmitter: NSObject, NSStreamDelegate { + static let ports = ["http": 80, + "https": 443, + "smb": 445, + "ftp": 21, + "sftp": 22, + "sftp": 2121, + "telnet": 23, + "pop": 110, + "smtp": 25, + "imap": 143] + 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() + let dataRecieved: NSMutableData = NSMutableData() + + let baseURL: NSURL + let secureConnection: Bool + private let port: UInt32 + private var connected = false + + init?(baseURL: NSURL, secure: Bool = false) { + self.baseURL = baseURL + self.secureConnection = secure + let scheme = baseURL.scheme.lowercaseString + let defaultPort = secure ? UInt32(TCPSocketTransmitter.securePorts[scheme] ?? 0) : UInt32(TCPSocketTransmitter.ports[scheme] ?? 0) + self.port = baseURL.port?.unsignedIntValue ?? defaultPort + if self.port == 0 { + return nil + } + } + + deinit { + disconnect() + } + + func connect() -> Bool { + guard let hostStr = baseURL.host else { + return false + } + var readStream : Unmanaged? + var writeStream : Unmanaged? + 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 + } + + 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 + } + + func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { + switch (eventCode) { + case NSStreamEvent.ErrorOccurred: + break + case NSStreamEvent.EndEncountered: + connected = false + case NSStreamEvent.None: + break + case NSStreamEvent.OpenCompleted: + let activeStatus: [NSStreamStatus] = [.Open, .Reading, .Writing, .AtEnd] + connected = 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 { + dataRecieved.appendBytes(&buffer, length: len) + } + } + } + case NSStreamEvent.HasSpaceAvailable: + if aStream == outputStream { + do { + try send(data: nil) + } catch _ { + NSLog("Sending error") + } + + } + default: + break + } + } + + func send(data data: NSData?) throws { + if self.outputStream?.hasSpaceAvailable ?? false { + if let data = data { + dataToBeSent.appendData(data) + } + + if dataToBeSent.length > 0 { + let bytesWritten = self.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) + } + } + } + + func waitForSendDataPurge() { + if NSThread.isMainThread() { + assertionFailure("waitForSendDataPurge() 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) + } + } + + func waitForResponse() -> Bool { + if NSThread.isMainThread() { + assertionFailure("waitForResponse() 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 + } +} \ No newline at end of file