Skip to content

Commit

Permalink
Dropbox implementation completed and getting storage size, used
Browse files Browse the repository at this point in the history
  • Loading branch information
amosavian committed Aug 4, 2016
1 parent b4ace7e commit 940c7c1
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 40 deletions.
2 changes: 1 addition & 1 deletion FileProvider.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#

s.name = "FileProvider"
s.version = "0.3.4"
s.version = "0.4.0"
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.
Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Local and WebDAV providers are fully tested and can be used in production enviro

- [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.
- [x] **DropboxFileProvider** *implemented but not tested*
- [ ] **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** *almost completed. upload, thumbnail and search functions not implemented yet*
- [ ] **FTPFileProvider**
- [ ] **AmazonS3FileProvider**

Expand Down Expand Up @@ -155,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

Expand Down
155 changes: 121 additions & 34 deletions Sources/DropboxFileProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
}

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")
Expand All @@ -91,12 +91,9 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
}
let code = FileProviderHTTPErrorCode(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
}
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
Expand All @@ -106,6 +103,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?
}

Expand Down Expand Up @@ -182,15 +196,12 @@ 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 destURL: NSURL, completionHandler: SimpleCompletionHandler) {
Expand All @@ -215,6 +226,7 @@ extension DropboxFileProvider: FileProviderOperations {
completionHandler?(error: e)
}
})
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": destURL.uw_absoluteString])
task.resume()
}
}
Expand Down Expand Up @@ -256,28 +268,18 @@ extension DropboxFileProvider: FileProviderReadWrite {
}

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)) {
Expand All @@ -292,6 +294,8 @@ extension DropboxFileProvider: FileProviderReadWrite {
private func unregisterNotifcation(path: String) {
NotImplemented()
}

// TODO: Implement /copy_reference, /get_preview & /get_thumbnail, /get_temporary_link, /save_url, /get_account & /get_current_account
}

private extension DropboxFileProvider {
Expand All @@ -316,7 +320,6 @@ private extension DropboxFileProvider {
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: path)
}

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 {
Expand All @@ -340,6 +343,90 @@ private extension DropboxFileProvider {
}
task.resume()
}

private 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)
}
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, 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)
}
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 {
Expand Down
2 changes: 2 additions & 0 deletions Sources/FileProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,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 {
Expand Down
7 changes: 7 additions & 0 deletions Sources/LocalFileProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions Sources/SMBFileProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
28 changes: 28 additions & 0 deletions Sources/WebDAVFileProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,34 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
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()
}

public weak var fileOperationDelegate: FileOperationDelegate?
}

Expand Down

0 comments on commit 940c7c1

Please sign in to comment.