Compare commits

..

17 Commits

Author SHA1 Message Date
Amir Abbas 28d01a9c59 Fix StreamTask read data bug
- Single stream upload file
- Removed redundant ftp helper functions
2017-12-29 19:46:46 +03:30
Amir Abbas 5d5e6811fd Fix Swift 3 compile error 2017-12-27 18:39:15 +03:30
Amir Abbas 1f2aa4888c Fixed FTP errors to bypass test cases and build error on macOS 2017-12-27 18:38:55 +03:30
Amir Abbas cf1b226417 Fix FTP provider crashes and errors on uploading and downloading 2017-12-27 17:36:35 +03:30
Amir Abbas 1ddb4b140d Remove testing on swift 3 2017-12-26 11:41:20 +03:30
Amir Abbas 86fe84351f Probable fix #76 , deinit to cleanup providers 2017-12-26 11:23:07 +03:30
Amir Abbas ca248f08dd Fix #72 (WebDAV result contains space) 2017-12-26 11:22:46 +03:30
Amir Abbas b17d92350c Fix. #71, Temp workaround for StreamTask issue 2017-12-26 11:22:05 +03:30
Amir Abbas d38748153d Fixed typo in WebDAV 2017-11-16 12:19:16 +03:30
Amir Abbas 17140f40d7 Fix #70 (WebDAV folder), Better URLRequest header interface 2017-11-14 21:32:41 +03:30
Amir Abbas 2820b4d3fe Fixed test scheme 2017-11-12 18:31:02 +03:30
Amir Abbas 7cd1528d75 Updated readme and travis for tests 2017-11-05 08:26:53 +03:30
Amir Abbas 14b4bcefe4 Fix Dropbox attributesOfItem, fix test cases 2017-11-04 16:23:44 +03:30
Amir Abbas a43640ecfd Fixed Test project settings, test for Dropbox and OneDrive 2017-11-03 01:30:23 +03:30
Amir Abbas f7f2d7f734 Fix error 2017-11-03 01:18:52 +03:30
Amir Abbas 5aaf8f2bf2 Added test 2017-11-03 01:10:05 +03:30
Amir Abbas 026e496512 Fixed macOS swift 4 convertToImage() compile issue 2017-11-03 01:09:26 +03:30
49 changed files with 1536 additions and 3402 deletions
+1 -1
View File
@@ -1 +1 @@
4.0
3.0
+6 -7
View File
@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode9
osx_image: xcode8.2
xcode_project: $PROJECTNAME.xcodeproj
env:
global:
@@ -8,16 +8,15 @@ env:
- IOS_FRAMEWORK_SCHEME="$PROJECTNAME iOS"
- MACOS_FRAMEWORK_SCHEME="$PROJECTNAME OSX"
- TVOS_FRAMEWORK_SCHEME="$PROJECTNAME tvOS"
- TEST_FRAMEWORK_SCHEME="$PROJECTNAMETests"
- IOS_SDK=iphonesimulator
- MACOS_SDK=macosx
- TVOS_SDK=appletvsimulator
matrix:
- DESTINATION="OS=11.0,name=iPad Pro (10.5-inch)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" CARTHAGEDEPLOY="YES"
- DESTINATION="OS=11.0,name=iPhone 8" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" POD="YES"
- DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK"
- DESTINATION="arch=x86_64" SCHEME="FilesProviderTests" SDK="$MACOS_SDK" RUN_TESTS="YES"
- DESTINATION="OS=11.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
- DESTINATION="OS=10.2,name=iPad Air 2" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" CARTHAGEDEPLOY="YES"
- DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" POD="YES"
- DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK"
# - DESTINATION="arch=x86_64" SCHEME="FilesProviderTests" SDK="$MACOS_SDK" RUN_TESTS="YES"
- DESTINATION="OS=10.1,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
before_install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
-273
View File
@@ -1,273 +0,0 @@
![File Provider](fileprovider.png)
# Concepts and Design
## Protocols and base classes
Every provider class conforms to some of the protocols each defines which operations are doable by a particular provider.
This allows developers to work with any provider class and to use a simple downcast to
intended protocol via `as?` optional keyword and call intended method.
### FileProviderBasic
This protocols consists basic variables necessary for all providers and functions to query contents and status of provider.
`type` static property should return a string which is usually provider's name.
Value can be usedfor display purposes and to compare two different instances' type.
`baseURL` determines root url of provider instance.
Some providers doesn't map url to files thus this variable is set to nil. `url(of:)` default implementation
uses this url to create a url which points to specified path.
`dispatch_queue` and `operation_queue` are dispatch and operation queues used to have async operation.
As a rule of thumb, file operations are done in operation queue and querying are done by dispatch queue.
Some objects and classes like `NSFileCoordinator` uses OperationQueue for async operations.
`delegate` is a used to inform controller about operation status/progress to update UI.
Avoid calling delegate methods directly and use `delegateNotify()` method in your implementation instead.
`credential` property stores user and password necessary to access provider.
Local provider would ignore it.
If listing query is paginated `contentsOfDirectory()` and `searchFiles()` may return both truncated
list array and error in case 2nd page can't be retrieved.
Thus it's safe to check `error` first to ensure list is complete. Truncated result is usable until full list is retrieved.
`storageProperties()` returns `VolumeObject` see below for more information.
When implementing `searchFiles()`, check `fileObject.mapPredicate()` using `query.evaluate(with:)`.
You may use `query` parameter to create a search string if provider supports search functionality.
Practically, `searchFiles(path: path, recursive: false, query: NSPredicate(format: "TRUEPREDICATE"))` should be equal with `contentsOfDirectory()` method.
Consequently, if provider enlisting and search backend are same (e.g. iCloud, OneDrive or Google)
implement `contentsOfDirectory()` as a wrapper around `searchFiles()`,
otherwise implement'em independently for optimization reason.
Avoid `isReachable()` to check connectivity and reachability.
Instead do operation and allow it o fail if there is a connection problem.
It may be deprecated at any time.
`url(of:)` and `relativePathOf(url:)` have default implementation which build a url using `baseURL` property.
In case that `baseURL` is `nil`, it will wrap path inside a `URL` instance.
You may override them if more functionality is needed (e.g. OneDrive) or appending path to baseURL can't be mapped to file url directly.
### FileProviderBasicRemote
Adds a `session` and `cache` object for providers that need internet connection and `URLSession` object.
If your provider is a HTTP based api, subclass `HTTPFileProvider` class otherwise
(e.g. FTP or SMB) conform it to this protocol.
### FileProviderOperations
This protocol encapsulates methods for copying, moving, renaming, removing and downloading/uploading files.
If your provider is a subclass of `HTTPFileProvider`, you may implement
`request(for:, overwrite:, attributes:)` abstract method which handles default implementation for these methods.
In this case, you need to create a `URLRequest` object based on `operation` parameter,
usually done a switch case statement.
See [`WebDAVFileProvder`](Sources/WebDAVFileProvider.swift) and [`OneDriveFileProvider`](Sources/OneDriveFileProvider) classes to see an example.
### FileProviderReadWrite
This protocol declares three methods to read and write files.
You must care about memory when using these functions. If you must handle big data,
write it to a temporary file and use `copyItem(localFile:, to:)` or `copyItem(path:, toLocalURL:)` methods accordingly.
### FileProviderMonitor
This protocol allows developer to update UI when file list is changed.
It's not implemented for all providers and some providers like FTP and WebDAV don't support such functionality.
If you are implementing it for a HTTP-based provider,
use `longpoolSession` to create a monitor request to avoid expiring requests frequently.
Implementation details vary based on provider specifications.
### FileProvideUndoable
Implementing this protocol is a little hard as you must save operation inside `undoManager` for any operation.
### FileProviderSharing
`publicLink(to:, completionHandler:)` method allows user to share file with other people.
Not all providers support this functionality and implementation details vary based on provider specification.
### ExtendedFileProvider
This protocol provides a way to fetch files' thumbnail and metadata.
Providers which have endpoint to get meta data (like Dropbox) implements this protocol.
Due to extensive network overload of fetching thumbnail,
It's recommended to check file using `thumbnailOfFileSupported(path)` method
to find out either provider supports thumbnail generation for specified file or not.
These methods only check file extension as a indicator and won't check file using provider.
A `true` result does not indicate that the file really has a thumbnail or not.
Implementation of `thumbnailOfFile(path:, dimension:)` must provide a NSImage/UIImage with size
according to `dimension` parameter. If server supports requesting thumbnail with specified size,
implementation would pass requested dimension to server,
otherwise implementation must resize image using `ExtendedFileProvider.scaleDown(image:, toSize:)` method to resize image.
`ExtendedFileProvider.convertToImage(pdfURL:, page:)` and `ExtendedFileProvider.convertToImage(pdfPage:)` methods can convert pdf file into an image.
Please note `pdfURL` parameter must be a local file url.
`propertiesOfFile()` method will extract meta data of specified file and return it as a dictionary orders by `keys` parameter.
Keys may vary according to file type, e.g. EXIF data for an image or ID3 tags for a music file.
To extend thumbnail generation behavior for local file to types which are not supported by Apple platform,
you may change `LocalFileInformationGenerator` struct static properties.
Some methods in this struct are unimplemented to allow developer to use third-party libraries
to provide meta data and thumbnail for other types of file.
### FileProvider
This protocol does not define any method, but indicates that class conforms to `FileProviderBasic `,
`FileProviderOperations`, `FileProviderReadWrite` and `NSCopying` protocols.
### FileOperationType
This enum holds operation type and associated information like source and destination path.
Developer is exposed to this enum in delegate methods.
Internally it's extensively used to refactor operation methods and to store operation info in
related `URLSessionTask` inside `taskDescription` property.
As a associated enum, it can not be bridged to Objective-C.
### FileObject
`FileObject` class stores file properties in a dictionary with `URLResourceKey` as key type.
All other properties are computed variables and simply cast value to a strict swift type.
Provider will create and return instance of `FileObject` class or its descendants in `contentsOfDirectory()`, `attributesOfItem()` and `searchFiles()` methods.
Provider **must** set `name` and `path` properties.
`path`'s value can be a unix-style hierarchal path or other ways to point a file supported by server.
Some providers like Dropbox and OneDrive define accessing to file by id or revision.
As a convention, these alternative paths are structured like `type:identifier` e.g. `rev:abcd1234` or `id:abcd1234`.
Google only allows to address file with `id:abcd1234` and does not provide unix-style path.
- **Important:** Never rely on `path` last component to extract file name, instead use `name` property.
Providers like `Google` have only file id in path thus using `path.lastPathComponent` to display file name may lead to confusion and improper result.
### VolumeObject
`VolumeObject` class is identical to `FileObject` structurally but only uses `URLResourceKey`'s keys which begin with `volume`.
An implementation of provider must return this enumerated in `storageProperties()` with properties like total size and free space of storage.
There is not correspondent key in storage for `usage` property,
indeed it's calculated by subtracting available space from total space.
## Progress handling
Almost all methods return a `Progress` instance which encapsulates progress fraction and a way to cancel entire operation.
It's upon your provider's implementation to update progress `totalUnitCount` and `completedUnitCount` properties and assign a cancellation handler to Progress object.
Cancellation handler may call `Operation`'s or `URLSessionTask`'s `cancel()` method to interrupt operation.
A typical progress handling in this library is like this:
```swift
// totalUnitCount must be set to file size for downloading/uploading operation.
// totalUnitCount must be set to 1 for a simple remote file operation.
let progress = Progress(totalUnitCount: size)
// allow updating progress inside URLSession's delegate methods
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
// kind must be set to .file for downloading/uploading operation.
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
// progres.cancel() will call task.cancel() method.
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
// Set .startingTimeKey to calculate estimated remaining time and speed in delegate.
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
```
Please note `.fileProvderOperationTypeKey` and `.startingTimeKey` are custom user info assigned to progress
to allow updating progress inside URLSession's delegate methods and calculating estimated remaining time and speed.
Your implementation must set these user info objects if you are using `SessionDelegate` object.
You may update cancellation handler if another task is added.
## Error handling
This library doesn't manipulate errors returned by Foundation methods and uses `URLError` and `CococaError` to report error as far as there is a corresponding defined error.
You can use `urlError(_ :, code:)` and `cocoaError(_:, code:)` convenience methods to create error object.
For HTTP providers, `FileProviderHTTPError` protocol defines a way to encapsulate HTTP status code and returned description by server.
You may declare a struct conforming to `FileProviderHTTPError` with an initializer to interpret server response.
`serverError(with:, path:, data:)` must be implemented in `HTTPFileProvider` subclasses and will be called by HTTP provider default implementation to digest error.
**NEVER** define a custom enum for errors. Instead use Foundation errors like `URLError` and `CococaError` as
they provide comprehensive localized description for error.
Alternatively use `FileProviderHTTPError` conforming struct for HTTP providers.
## Implementing HTTP-based Custom Provider
This library provide `HTTPFileProvider` abstract class to easily implement provider which connects to a cloud/server that
uses http protocol for connection.
That's almost all REST and web based providers like Dropbox, Box, Google Drive, etc.
`HTTPFileProvider` encapsulates much of downloading/uploading logic and provides `paginated` method
to allow enlisting/searching files in providers which return result in progressively. (e.g. Dropbox and OneDrive)
By subclassing `HTTPFileProvider` class, you must override half a dozen of methods, mainly querying methods.
Your implementation may cause a **crash** if you fail to override these methods.
### Methods and properties to override
`type` static property, which returns name of provider.
`init?(coder:)` decodes and initialize instance using `NSCoder`.
Your implementation must read `aDecoder` correspondent keys and initialize a new object using your provider's initilizer.
`copy(with:)` method must create a new instance and assign properties from source (`self`) to copied object.
`contentsOfDirectory()` and `searchFiles()` methods must send listing query to server and decode json/xml response into a `FileObject`.
Providers may subclass `FileObject` and implement an initializer to encapsulate decoding logic.
If server response is paginated, use `paginated()` method. See below and inline help to find how to use it.
`attributesOfItem()` must send file attribute fetching query to server and decode response into a `FileObject` or its descendants instance.
`storageProperties()` does querying account/cloud quota and encapsulates it into a `VolumeObject` instance.
You don't need to subclass `VolumeObject`.
If your server does not support such functionality, simply call completion handler with `nil` as result.
`request(for:, overwrite:, attributes:)` creates a `URLRequest` for requested operation.
It will be called by create, copy/move and remove functions.
You may set `httpMethod` and `httpBody` and header values of request regarding operation type and associated variables.
- **Important:** NEVER forget to call `urlrequest.setValue(authentication: credential, with:)` to set provider's credential
if server uses OAuth/OAuth2 authentication method.
You may need to set other http headers according to server specifications.
- **Important**: `copyItem(path:, toLocalURL:)` and `copyItem(localFile:, to:)` methods will call `request(for:)` method with
source/destination property set to a local file url, begins with `file://`.
You must handle these separately.
See `WebDAVProvider` [source](Sources/WebDAVFileProvider.swift) as an example.
`serverError(with:, path:, data:)` method will digest http status code and server response data to create an error conforming to `FileProviderHTTPError` protocol.
### Optional overridable methods
`isReachable()` default implementation tries to fetch storage properties and will return true if result is non-nil.
You may need to override this method if server does not support `storageProperties()` or there is a more optimized way to check reachability.
`copyItem(localFile:, to:)` and `writeContents(path:, contents:)` can be overrided if server requires upload session.
See `OneDriveProvider`'s [source](Sources/OneDriveProvider) to see how create and handle an upload session.
### Paginated enlisting
`paginated()` method defines an easy way to communicate to servers which list responses are paginated.
Here is method signature:
`pageHandler` closure gets server response as `Data`, decodes it and returns `FileObject` array or an error,
and if there is another sequel page, passes token of new page (a string tha can be a id or url) to `newToken`.
If there is no more sequel page, `newToken` must be nil.
This token will be delivered to `requestHandler` closure which returns a `URLRequest` according to token.
A nil token indicates it's the first page.
`completionHandler` is the closure which user passed to `contentsOfDirectory()` or `searchFiles()` methods.
`pageHandler` must update `progress` by adding the number of new files enlisted to `completedUnitCount` property.
This closure may filter results according to `query` parameter, using `query.evaluate(with:)`.
-83
View File
@@ -1,83 +0,0 @@
# Authentication
Dropbox and OneDrive are using OAuth2 authentication method. Here is sample codes to get Bearer token using [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift)
## Handling url scheme in App delegate
Add these lines to your application delegate:
```swift
extension AppDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
if url.host == "oauth-callback" {
OAuthSwift.handle(url: url)
}
// HANDLING OTHER PATERNS
}
}
```
## Dropbox
Your client id and secret must be given by Dropbox developer portal. Bearer tokens created by this method are permanent.
```swift
let appScheme = "YOUR_APP_SCHEME"
oauth = OAuth2Swift(consumerKey: "CLIENT_ID",
consumerSecret: "CLIENT_SECRET",
authorizeUrl: "https://www.dropbox.com/oauth2/authorize",
responseType: "token")!
oauth.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauth)
_ = oauth.authorize(withCallbackURL: URL(string: "\(appScheme)://oauth-callback/dropbox")!,
scope: "", state:"DROPBOX",
success: { credential, response, parameters in
let urlcredential = URLCredential(user: user ?? "anonymous", password: credential.oauthToken, persistence: .permanent)
// TODO: Save credential in keychain
// TODO: Create Dropbox provider using urlcredential
}, failure: { error in
print(error.localizedDescription)
}
)
```
## OneDrive
Your client id must be given by Microsoft developer portal. OneDrive doesn't need client secret for native apps, but will need to refresh token every one hour.
We must save refresh token in adition to bearer token and use it when we get `.unauthorized` 401 HTTP error in completion handlers to get a new bearer token.
```swift
let appScheme = "YOUR_APP_SCHEME"
oauth = OAuth2Swift.init(consumerKey: "CLIENT_ID",
consumerSecret: "",
authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
accessTokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
responseType: "code")!
oauth.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauth)
if let refreshToken = {SAVED_REFRESH_TOKEN} {
oauth.renewAccessToken(withRefreshToken: token,
success: { credential, response, parameters in
let urlcredential = URLCredential(user: user ?? "anonymous", password: credential.oauthToken, persistence: .permanent)
let refreshToken = credential.oauthRefreshToken
// TODO: Save refreshToken in keychain
// TODO: Save credential in keychain
// TODO: Create OneDrive provider using urlcredential
}, failure: { error in
print(error.localizedDescription)
// TODO: Clear saved refresh token and call this method again to get authorization token
})
} else {
_ = oauth.authorize(
withCallbackURL: URL(string: "\(appScheme)://oauth-callback/onedrive")!,
scope: "offline_access User.Read Files.ReadWrite.All", state: "ONEDRIVE",
success: { credential, response, parameters in
let credential = URLCredential(user: user ?? "anonymous", password: credential.oauthToken, persistence: .permanent)
// TODO: Save refreshToken in keychain
// TODO: Save credential in keychain
// TODO: Create OneDrive provider using credential
}, failure: { error in
print(error.localizedDescription)
})
}
```
-117
View File
@@ -1,117 +0,0 @@
//
// Sample-iOS.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2018 Mousavian. Distributed under MIT license.
//
import UIKit
import FilesProvider
class ViewController: UIViewController, FileProviderDelegate {
let server: URL = URL(string: "https://server-webdav.com")!
let username = "username"
let password = "password"
var webdav: WebDAVFileProvider?
@IBOutlet weak var uploadProgressView: UIProgressView
@IBOutlet weak var downloadProgressView: UIProgressView
override func viewDidLoad() {
super.viewDidLoad()
let credential = URLCredential(user: username, password: password, persistence: .permanent)
webdav = WebDAVFileProvider(baseURL: server, credential: credential)!
webdav?.delegate = self as FileProviderDelegate
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@IBAction func createFolder(_ sender: Any) {
webdav?.create(folder: "new folder", at: "/", completionHandler: nil)
}
@IBAction func createFile(_ sender: Any) {
let data = "Hello world from sample.txt!".data(encoding: .utf8)
webdav?.writeContents(path: "sample.txt", content: data, atomically: true, completionHandler: nil)
}
@IBAction func getData(_ sender: Any) {
webdav?.contents(path: "sample.txt", completionHandler: {
contents, error in
if let contents = contents {
print(String(data: contents, encoding: .utf8))
}
})
}
@IBAction func remove(_ sender: Any) {
webdav?.removeItem(path: "sample.txt", completionHandler: nil)
}
@IBAction func download(_ sender: Any) {
let localURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("fileprovider.png")
let remotePath = "fileprovider.png"
let progress = webdav?.copyItem(path: remotePath, toLocalURL: localURL, completionHandler: nil)
downloadProgressView.observedProgress = progress
}
@IBAction func upload(_ sender: Any) {
let localURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("fileprovider.png")
let remotePath = "/fileprovider.png"
let progress = webdav?.copyItem(localFile: localURL, to: remotePath, completionHandler: nil)
uploadProgressView.observedProgress = progress
}
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperationType) {
switch operation {
case .copy(source: let source, destination: let dest):
print("\(source) copied to \(dest).")
case .remove(path: let path):
print("\(path) has been deleted.")
default:
if let destination = operation.destination {
print("\(operation.actionDescription) from \(operation.source) to \(destination) succeed.")
} else {
print("\(operation.actionDescription) on \(operation.source) succeed.")
}
}
}
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperationType, error: Error) {
switch operation {
case .copy(source: let source, destination: let dest):
print("copying \(source) to \(dest) has been failed.")
case .remove:
print("file can't be deleted.")
default:
if let destination = operation.destination {
print("\(operation.actionDescription) from \(operation.source) to \(destination) failed.")
} else {
print("\(operation.actionDescription) on \(operation.source) failed.")
}
}
}
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperationType, progress: Float) {
switch operation {
case .copy(source: let source, destination: let dest) where dest.hasPrefix("file://"):
print("Downloading \(source) to \((dest as NSString).lastPathComponent): \(progress * 100) completed.")
case .copy(source: let source, destination: let dest) where source.hasPrefix("file://"):
print("Uploading \((source as NSString).lastPathComponent) to \(dest): \(progress * 100) completed.")
case .copy(source: let source, destination: let dest):
print("Copy \(source) to \(dest): \(progress * 100) completed.")
default:
break
}
}
}
+9 -8
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FilesProvider"
s.version = "0.25.0"
s.version = "0.21.0"
s.summary = "FileManager replacement for Local and Remote (WebDAV/FTP/Dropbox/OneDrive/SMB2) files on iOS and macOS."
# This description is used to generate tags and improve search results.
@@ -55,8 +55,9 @@ Pod::Spec.new do |s|
# profile URL.
#
s.author = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
# Or just: s.author = "Amir Abbas Mousavian"
s.authors = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
# s.authors = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
s.social_media_url = "https://twitter.com/amosavian"
# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
@@ -65,7 +66,10 @@ Pod::Spec.new do |s|
# the deployment target. You can optionally include the target after the platform.
#
s.swift_version = "4.1"
# s.platform = :ios
# s.platform = :ios, "8.0"
# When using multiple platforms
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.10"
# s.watchos.deployment_target = "2.0"
@@ -116,12 +120,9 @@ Pod::Spec.new do |s|
#
# s.framework = "SomeFramework"
s.frameworks = "AVFoundation", "ImageIO", "CoreGraphics"
s.ios.framework = "UIKit"
s.tvos.framework = "UIKit"
s.osx.framework = "AppKit"
# s.frameworks = "SomeFramework", "AnotherFramework"
s.library = "xml2"
# s.library = "iconv"
# s.libraries = "iconv", "xml2"
+109 -32
View File
@@ -72,6 +72,9 @@
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
@@ -495,12 +498,11 @@
7993965C1D48B7BF00086753 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0910;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 0810;
TargetAttributes = {
799396661D48B7F600086753 = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 0900;
LastSwiftMigration = 0800;
};
799396741D48B80D00086753 = {
CreatedOnToolsVersion = 7.3.1;
@@ -508,10 +510,6 @@
799396811D48B82700086753 = {
CreatedOnToolsVersion = 7.3.1;
};
79D903501FAB647400D61D31 = {
CreatedOnToolsVersion = 9.1;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 7993965F1D48B7BF00086753 /* Build configuration list for PBXProject "FilesProvider" */;
@@ -590,6 +588,7 @@
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 */,
7924B1961D89DAE000589DB7 /* Document.swift in Sources */,
@@ -634,6 +633,7 @@
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 */,
7924B1971D89DAE000589DB7 /* Document.swift in Sources */,
@@ -678,6 +678,7 @@
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 */,
7924B1981D89DAE000589DB7 /* Document.swift in Sources */,
@@ -720,21 +721,13 @@
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.25.0;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
BUNDLE_VERSION_STRING = 0.21.0;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@@ -746,36 +739,27 @@
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = FilesProvider;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.25.0;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
BUNDLE_VERSION_STRING = 0.21.0;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@@ -784,14 +768,14 @@
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PRODUCT_NAME = FilesProvider;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -803,6 +787,15 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -816,6 +809,12 @@
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@@ -824,6 +823,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-iOS";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -838,6 +838,15 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -846,6 +855,12 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@@ -871,6 +886,15 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -887,6 +911,12 @@
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-MacOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
@@ -895,6 +925,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-OSX";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -910,6 +941,15 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -921,6 +961,12 @@
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_VERSION = A;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-MacOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
@@ -944,6 +990,15 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -958,6 +1013,12 @@
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-tvOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -965,6 +1026,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-tvOS";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 10.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -982,6 +1044,15 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -991,6 +1062,12 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-tvOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "79D903501FAB647400D61D31"
BuildableName = "FilesProviderTests.xctest"
BlueprintName = "FilesProviderTests"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+1 -22
View File
@@ -1,26 +1,5 @@
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FilesProvider",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "FilesProvider",
targets: ["FilesProvider"]
)
],
dependencies: [],
targets: [
.target(name: "FilesProvider",
dependencies: [],
path: "Sources"
),
.testTarget(name: "FilesProviderTests",
dependencies: ["FilesProvider"],
path: "Tests"
),
]
name: "FilesProvider"
)
+15 -24
View File
@@ -1,6 +1,6 @@
![File Provider](Docs/fileprovider.png)
![File Provider](fileprovider.png)
> This Swift library provide a swifty way to deal with local and remote files and directories in a unified way.
>This Swift library provide a swifty way to deal with local and remote files and directories in a unified way.
<center>
@@ -36,6 +36,7 @@ All functions do async calls and it wont block your main thread.
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API.
* For now it has limitation in uploading files up to 150MB.
- [x] **OneDriveFileProvider** A wrapper around OneDrive REST API, works with `onedrive.com` and compatible (business) servers.
* For now it has limitation in uploading files up to 100MB.
- [ ] **AmazonS3FileProvider** Amazon storage backend. Used by many sites.
- [ ] **GoogleFileProvider** A wrapper around Goodle Drive REST API.
- [ ] **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.
@@ -44,11 +45,11 @@ All functions do async calls and it wont block your main thread.
## Requirements
- **Swift 4.0 or higher**
- **Swift 3.0 or higher**
- iOS 8.0 , OSX 10.10
- XCode 9.0
- XCode 8.0
Legacy version is available in swift-3 branch.
Legacy version is available in swift-2 branch.
## Installation
@@ -100,19 +101,13 @@ Then you can do either:
* Drop `FilesProvider.xcodeproj` to you Xcode workspace and add the framework to your Embeded Binaries in target.
## Design
To find design concepts and how to implement a custom provider, read [Concepts and Design document](Docs/DESIGN.md).
## Usage
Each provider has a specific class which conforms to FileProvider protocol and share same syntax.
Find a [sample code for iOS here](Docs/Sample-iOS.swift).
Each provider has a specific class which conforms to FileProvider protocol and share same syntax
### Initialization
For LocalFileProvider if you want to deal with `Documents` folder:
For LocalFileProvider if you want to deal with `Documents` folder
``` swift
import FilesProvider
@@ -158,11 +153,11 @@ let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example
* In case you want to connect non-secure servers for WebDAV (http) or FTP 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 & OneDrive, user is clientID and password is Token which both must be retrieved via OAuth2 API o. 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. The latter is easier to use and prefered. Please see [OAuth example for Dropbox and OneDrive](Docs/OAuth.md) for detailed instruction.
* For Dropbox & OneDrive, 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. The latter is easier to use and prefered.
For interaction with UI, set delegate property of `FileProvider` object.
For interaction with UI, set delegate variable of `FileProvider` object
You can use `url(of:)` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support and returns path simply wrapped in URL).
You can use `url(of:)` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support and returns path simply wrapped in URL)
### Delegates
@@ -470,8 +465,8 @@ We would love for you to contribute to **FileProvider**, check the `LICENSE` fil
Things you may consider to help us:
- [ ] Implement request/response stack for `SMBClient`
- [x] Implement Test-case (`XCTest`)
- [ ] Implement request/response stack for `SMBClient`
- [ ] Add Sample project for iOS
- [ ] Add Sample project for macOS
@@ -494,14 +489,14 @@ Distributed under the MIT license. See `LICENSE` for more information.
[cocoapods]: https://cocoapods.org/pods/FilesProvider
[cocoapods-old]: https://cocoapods.org/pods/FileProvider
[swift-image]: https://img.shields.io/badge/swift-4.0-orange.svg
[swift-image]: https://img.shields.io/badge/swift-3.0,%203.1-orange.svg
[swift-url]: https://swift.org/
[platform-image]: https://img.shields.io/cocoapods/p/FilesProvider.svg
[license-image]: https://img.shields.io/github/license/amosavian/FileProvider.svg
[license-url]: LICENSE
[codebeat-image]: https://codebeat.co/badges/7b359f48-78eb-4647-ab22-56262a827517
[codebeat-url]: https://codebeat.co/projects/github-com-amosavian-fileprovider
[travis-image]: https://travis-ci.org/amosavian/FileProvider.svg
[travis-image]: https://api.travis-ci.org/amosavian/FileProvider.svg?branch=swift-3
[travis-url]: https://travis-ci.org/amosavian/FileProvider
[release-url]: https://github.com/amosavian/FileProvider/releases
[release-image]: https://img.shields.io/github/release/amosavian/FileProvider.svg
@@ -511,8 +506,4 @@ Distributed under the MIT license. See `LICENSE` for more information.
[cocoapods-downloads]: https://img.shields.io/cocoapods/dt/FilesProvider.svg
[cocoapods-apps]: https://img.shields.io/cocoapods/at/FilesProvider.svg
[docs-image]: https://img.shields.io/cocoapods/metrics/doc-percent/FilesProvider.svg
[docs-url]: http://cocoadocs.org/docsets/FilesProvider/
## Support on Beerpay
Hey dude! Help me out for a couple of :beers:!
[![Beerpay](https://beerpay.io/amosavian/FileProvider/badge.svg?style=beer-square)](https://beerpay.io/amosavian/FileProvider) [![Beerpay](https://beerpay.io/amosavian/FileProvider/make-wish.svg?style=flat-square)](https://beerpay.io/amosavian/FileProvider?focus=wish)
[docs-url]: http://cocoadocs.org/docsets/FilesProvider/
+124 -133
View File
@@ -48,7 +48,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
- Parameter containerId: The fully-qualified container identifier for an iCloud container directory. The string you specify must not contain wildcards and must be of the form `<TEAMID>.<CONTAINER>`, where `<TEAMID>` is your development team ID and `<CONTAINER>` is the bundle identifier of the container you want to access.\
The container identifiers for your app must be declared in the `com.apple.developer.ubiquity-container-identifiers` array of the `.entitlements` property list file in your Xcode project.\
If you specify nil for this parameter, this method uses the first container listed in the `com.apple.developer.ubiquity-container-identifiers` entitlement array.
- Parameter scope: Use `.documents` (default) to put documents that the user is allowed to access inside a `Documents` subdirectory. Otherwise use `.data` to store user-related data files that your app needs to share but that are not files you want the user to manipulate directly.
- Parameter scope: Use `.documents` (default) to put documents that the user is allowed to access inside a Documents subdirectory. Otherwise use `.data` to store user-related data files that your app needs to share but that are not files you want the user to manipulate directly.
*/
public convenience init? (containerId: String?, scope: UbiquitousScope = .documents) {
assert(!(CloudFileProvider.asserting && Thread.isMainThread), "CloudFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
@@ -93,11 +93,11 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
}
public required convenience init?(coder aDecoder: NSCoder) {
if let containerId = aDecoder.decodeObject(of: NSString.self, forKey: "containerId") as String?,
let scopeString = aDecoder.decodeObject(of: NSString.self, forKey: "scope") as String?,
if let containerId = aDecoder.decodeObject(forKey: "containerId") as? String,
let scopeString = aDecoder.decodeObject(forKey: "scope") as? String,
let scope = UbiquitousScope(rawValue: scopeString) {
self.init(containerId: containerId, scope: scope)
} else if let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? {
} else if let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL {
self.init(baseURL: baseURL)
} else {
return nil
@@ -141,7 +141,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
// FIXME: create runloop for dispatch_queue, start query on it
let query = NSPredicate(format: "TRUEPREDICATE")
_ = searchFiles(path: path, recursive: false, query: query, completionHandler: completionHandler)
_ = searchFiles(path: path, recursive: false, query: query, foundItemHandler: nil, completionHandler: completionHandler)
}
/// Please don't rely this function to get iCloud drive total and remaining capacity
@@ -162,10 +162,48 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
- error: Error returned by system.
*/
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
let query = NSPredicate(format: "%K LIKE[CD] %@", NSMetadataItemPathKey, path)
_ = searchFiles(path: path, recursive: false, query: query, completionHandler: { (files, error) in
completionHandler(files.first, error)
})
dispatch_queue.async {
let pathURL = self.url(of: path)
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K LIKE[CD] %@", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]
query.searchScopes = [self.scope.rawValue]
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
defer {
query.stop()
NotificationCenter.default.removeObserver(finishObserver!)
}
query.disableUpdates()
guard let result = (query.results as? [NSMetadataItem])?.first, let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
let error = self.cocoaError(path, code: .fileNoSuchFile)
self.dispatch_queue.async {
completionHandler(nil, error)
}
return
}
if let file = self.mapFileObject(attributes: attribs) {
self.dispatch_queue.async {
completionHandler(file, nil)
}
} else {
let noFileError = self.cocoaError(path, code: .fileNoSuchFile)
self.dispatch_queue.async {
completionHandler(nil, noFileError)
}
}
})
DispatchQueue.main.async {
if !query.start() {
self.dispatch_queue.async {
completionHandler(nil, self.cocoaError(path, code: .fileReadNoPermission))
}
}
}
}
}
/**
@@ -192,7 +230,6 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
@discardableResult
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
@@ -283,9 +320,9 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
return progress
}
open override func isReachable(completionHandler: @escaping (_ success: Bool, _ error: Error?) -> Void) {
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
dispatch_queue.async {
completionHandler(self.fileManager.ubiquityIdentityToken != nil, nil)
completionHandler(self.fileManager.ubiquityIdentityToken != nil)
}
}
@@ -327,8 +364,8 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
progress.isCancellable = false
progress.setUserInfoObject(localFile, forKey: .fileURLKey)
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let moveblock: () -> Void = {
monitorFile(path: toPath, operation: operation, progress: progress)
operation_queue.addOperation {
let tempFolder: URL
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) {
tempFolder = FileManager.default.temporaryDirectory
@@ -346,33 +383,13 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
completionHandler?(nil)
self.delegateNotify(operation)
} catch {
if tmpFile.fileExists {
if self.opFileManager.fileExists(atPath: tmpFile.path) {
try? self.opFileManager.removeItem(at: tmpFile)
}
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
}
let dest = self.url(of: toPath)
if /* fileExists */ ((try? dest.checkResourceIsReachable()) ?? false) ||
((try? dest.checkPromisedItemIsReachable()) ?? false) {
if overwrite {
self.removeItem(path: toPath, completionHandler: { _ in
self.operation_queue.addOperation(moveblock)
})
} else {
let e = self.cocoaError(dest.path, code: .fileWriteFileExists)
dispatch_queue.async {
completionHandler?(e)
}
self.delegateNotify(operation, error: e)
return nil
}
} else {
self.operation_queue.addOperation(moveblock)
}
return progress
}
@@ -583,84 +600,16 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
return file
}
lazy fileprivate var observer: KVOObserver = KVOObserver()
fileprivate func monitorFile(path: String, operation: FileOperationType, progress: Progress?) {
var isDownloadingOperation: Bool
let isUploadingOperation: Bool
switch operation {
case .copy(_, destination: let dest) where dest.hasPrefix("file://"), .move(_, destination: let dest) where dest.hasPrefix("file://"):
fallthrough
case .fetch:
isDownloadingOperation = true
isUploadingOperation = false
case .copy(source: let source, _) where source.hasPrefix("file://"), .move(source: let source, _) where source.hasPrefix("file://"):
fallthrough
case .modify, .create:
isDownloadingOperation = false
isUploadingOperation = true
default:
return
}
let pathURL = self.url(of: path).standardizedFileURL
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K LIKE[CD] %@)", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataUbiquitousItemPercentDownloadedKey,
NSMetadataUbiquitousItemPercentUploadedKey,
NSMetadataUbiquitousItemIsUploadedKey,
NSMetadataUbiquitousItemDownloadingStatusKey,
NSMetadataItemFSSizeKey]
query.predicate = NSPredicate(format: "%K LIKE[CD] %@", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataUbiquitousItemPercentDownloadedKey, NSMetadataUbiquitousItemPercentUploadedKey, NSMetadataUbiquitousItemDownloadingStatusKey, NSMetadataItemFSSizeKey]
query.searchScopes = [self.scope.rawValue]
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: .main) { [weak self] (notification) in
guard let items = notification.userInfo?[NSMetadataQueryUpdateChangedItemsKey] as? NSArray,
let item = items.firstObject as? NSMetadataItem else {
return
}
func terminateAndRemoveObserver() {
guard observer != nil else { return }
query.stop()
observer.flatMap(NotificationCenter.default.removeObserver)
observer = nil
}
func updateProgress(_ percent: NSNumber) {
let fraction = percent.doubleValue / 100
self?.delegateNotify(operation, progress: fraction)
if let progress = progress {
if progress.totalUnitCount < 1, let size = item.value(forAttribute: NSMetadataItemFSSizeKey) as? NSNumber {
progress.totalUnitCount = size.int64Value
}
progress.completedUnitCount = progress.totalUnitCount > 0 ? Int64(Double(progress.totalUnitCount) * fraction) : 0
}
if percent.doubleValue == 100.0 {
terminateAndRemoveObserver()
}
}
for attrName in item.attributes {
switch attrName {
case NSMetadataUbiquitousItemPercentDownloadedKey:
guard isDownloadingOperation, let percent = item.value(forAttribute: attrName) as? NSNumber else { break }
updateProgress(percent)
case NSMetadataUbiquitousItemPercentUploadedKey:
guard isUploadingOperation, let percent = item.value(forAttribute: attrName) as? NSNumber else { break }
updateProgress(percent)
case NSMetadataUbiquitousItemDownloadingStatusKey:
if isDownloadingOperation, let value = item.value(forAttribute: attrName) as? String,
value == NSMetadataUbiquitousItemDownloadingStatusDownloaded {
terminateAndRemoveObserver()
}
case NSMetadataUbiquitousItemIsUploadedKey:
if isUploadingOperation, let value = item.value(forAttribute: attrName) as? NSNumber, value.boolValue {
terminateAndRemoveObserver()
}
default:
break
}
}
}
var context = QueryProgressWrapper(provider: self, progress: progress, operation: operation)
query.addObserver(self.observer, forKeyPath: "results", options: [.initial, .new, .old], context: &context)
DispatchQueue.main.async {
query.start()
@@ -684,11 +633,9 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
}
}
/**
Removes local copy of file, but spares cloud copy.
- Parameter path: Path of file or directory to be removed from local
- Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
/// Removes local copy of file, but spares cloud copy.
/// - Parameter path: Path of file or directory to be removed from local
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
operation_queue.addOperation {
do {
@@ -700,11 +647,9 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
}
}
/**
Returns current version of file on this device and all versions of files in user devices.
- Parameter path: Path of file or directory.
- Parameter completionHandler: Retrieve current version on this device and all versions available. `currentVersion` will be nil if file doesn't exist. If an error parameter was provided, a presentable `Error` will be returned.
*/
/// Returns current version of file on this device and all versions of files in user devices.
/// - Parameter path: Path of file or directory.
/// - Parameter completionHandler: Retrieve current version on this device and all versions available. `currentVersion` will be nil if file doesn't exist. If an error parameter was provided, a presentable `Error` will be returned.
func versionsOfItem(path: String, completionHandler: @escaping ((_ currentVersion: NSFileVersion?, _ versions: [NSFileVersion], _ error: Error?) -> Void)) {
NotImplemented()
}
@@ -720,21 +665,16 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
/// Scope of iCloud, wrapper for NSMetadataQueryUbiquitous...Scope constants
public enum UbiquitousScope: RawRepresentable {
/**
Search all files not in the Documents directories of the apps iCloud container directories.
Use this scope to store user-related data files that your app needs to share
but that are not files you want the user to manipulate directly.
Raw value is equivalent to `NSMetadataQueryUbiquitousDataScope`
*/
/// Search all files not in the Documents directories of the apps iCloud container directories.
/// Use this scope to store user-related data files that your app needs to share
/// but that are not files you want the user to manipulate directly.
///
/// Raw value is equivalent to `NSMetadataQueryUbiquitousDataScope`
case data
/**
Search all files in the Documents directories of the apps iCloud container directories.
Put documents that the user is allowed to access inside a Documents subdirectory.
Raw value is equivalent to `NSMetadataQueryUbiquitousDocumentsScope`
*/
/// Search all files in the Documents directories of the apps iCloud container directories.
/// Put documents that the user is allowed to access inside a Documents subdirectory.
///
/// Raw value is equivalent to `NSMetadataQueryUbiquitousDocumentsScope`
case documents
public typealias RawValue = String
@@ -759,6 +699,57 @@ public enum UbiquitousScope: RawRepresentable {
}
}
}
struct QueryProgressWrapper {
weak var provider: CloudFileProvider?
weak var progress: Progress?
let operation: FileOperationType
}
fileprivate class KVOObserver: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let query = object as? NSMetadataQuery else {
return
}
guard let wrapper = context?.load(as: QueryProgressWrapper.self) else {
query.stop()
query.removeObserver(self, forKeyPath: "results")
return
}
let provider = wrapper.provider
let progress = wrapper.progress
let operation = wrapper.operation
guard let results = change?[.newKey], let item = (results as? [NSMetadataItem])?.first else {
return
}
query.disableUpdates()
var size = progress?.totalUnitCount ?? -1
if size < 0, let size_d = item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 {
size = size_d
progress?.totalUnitCount = size
}
let downloadStatus = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? String ?? ""
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
progress?.completedUnitCount = Int64(uploaded / 100 * Double(size))
provider?.delegateNotify(operation, progress: uploaded / 100)
} else if (uploaded == 0 || uploaded == 100) && downloadStatus != NSMetadataUbiquitousItemDownloadingStatusCurrent {
progress?.completedUnitCount = Int64(downloaded / 100 * Double(size))
provider?.delegateNotify(operation, progress: downloaded / 100)
} else if uploaded == 100 || downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent {
progress?.completedUnitCount = size
query.stop()
query.removeObserver(self, forKeyPath: "results")
provider?.delegateNotify(operation)
}
query.enableUpdates()
}
}
/*
func getMetadataItem(url: URL) -> NSMetadataItem? {
let query = NSMetadataQuery()
+59 -58
View File
@@ -8,9 +8,7 @@
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS)
import CoreGraphics
#endif
/**
Allows accessing to Dropbox stored files. This provider doesn't cache or save files internally, however you can
@@ -44,7 +42,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
}
public required convenience init?(coder aDecoder: NSCoder) {
self.init(credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"))
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
}
@@ -134,7 +132,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (fileSize >= 10000)")
NSPredicate(format: "(name CONTAINS[c] 'hello') && (filesize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
@@ -153,7 +151,6 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
@discardableResult
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let queryStr: String?
if query.predicateFormat == "TRUEPREDICATE" {
@@ -271,7 +268,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
} else {
errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
}
return FileProviderDropboxError(code: code, path: path ?? "", serverDescription: errorDesc)
return FileProviderDropboxError(code: code, path: path ?? "", errorDescription: errorDesc)
}
override var maxUploadSimpleSupported: Int64 {
@@ -293,7 +290,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
}
*/
// TODO: Implement /get_account & /get_current_account
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)!
var request = URLRequest(url: url)
@@ -310,8 +307,12 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON() {
link = (json["link"] as? String).flatMap(URL.init(string:))
fileObject = (json["metadata"] as? [String: AnyObject]).flatMap(DropboxFileObject.init(json:))
if let linkStr = json["link"] as? String {
link = URL(string: linkStr)
}
if let attribDic = json["metadata"] as? [String: AnyObject] {
fileObject = DropboxFileObject(json: attribDic)
}
}
}
@@ -353,7 +354,9 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
serverError = code.flatMap { self.serverError(with: $0, path: toPath, data: data) }
if let json = data?.deserializeJSON() {
jobId = json["async_job_id"] as? String
fileObject = (json["metadata"] as? [String: AnyObject]).flatMap(DropboxFileObject.init(json:))
if let attribDic = json["metadata"] as? [String: AnyObject] {
fileObject = DropboxFileObject(json: attribDic)
}
}
}
completionHandler(jobId, fileObject, serverError ?? error)
@@ -390,47 +393,6 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
}
extension DropboxFileProvider: ExtendedFileProvider {
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
/*case "mp3", "aac", "m4a":
return true*/
case "mp4", "mpg", "3gp", "mov", "avi":
return true
default:
return false
}
}
@discardableResult
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) -> Progress? {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var dic = [String: Any]()
var keys = [String]()
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON(), let properties = (json["media_info"] as? [String: Any])?["metadata"] as? [String: Any] {
(dic, keys) = self.mapMediaInfo(properties)
}
}
completionHandler(dic, keys, serverError ?? error)
})
task.resume()
return nil
}
#if os(macOS) || os(iOS) || os(tvOS)
open func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
@@ -446,9 +408,22 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
}
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
/*case "mp3", "aac", "m4a":
return true*/
case "mp4", "mpg", "3gp", "mov", "avi":
return true
default:
return false
}
}
/// Default value for dimension is 64x64, according to Dropbox documentation
@discardableResult
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) -> Progress? {
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
let thumbAPI: Bool
switch (path as NSString).pathExtension.lowercased() {
@@ -463,7 +438,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
url = URL(string: "files/get_preview", relativeTo: contentURL)!
thumbAPI = false
default:
return nil
return
}
var request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
@@ -493,14 +468,40 @@ extension DropboxFileProvider: ExtendedFileProvider {
image = pageImage
} else if let contentType = (response as? HTTPURLResponse)?.allHeaderFields["Content-Type"] as? String, contentType.contains("text/html") {
// TODO: Implement converting html returned type of get_preview to image
} else if let fetchedimage = ImageClass(data: data) {
image = dimension.map({ DropboxFileProvider.scaleDown(image: fetchedimage, toSize: $0) }) ?? fetchedimage
} else if let fetchedimage = ImageClass(data: data){
if let dimension = dimension {
image = DropboxFileProvider.scaleDown(image: fetchedimage, toSize: dimension)
} else {
image = fetchedimage
}
}
}
completionHandler(image, error)
})
task.resume()
return nil
}
#endif
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var dic = [String: Any]()
var keys = [String]()
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON(), let properties = (json["media_info"] as? [String: Any])?["metadata"] as? [String: Any] {
(dic, keys) = self.mapMediaInfo(properties)
}
}
completionHandler(dic, keys, serverError ?? error)
})
task.resume()
}
}
+2 -2
View File
@@ -12,7 +12,7 @@ import Foundation
public struct FileProviderDropboxError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let serverDescription: String?
public let errorDescription: String?
}
/// Containts path, url and attributes of a Dropbox file or resource.
@@ -94,7 +94,7 @@ internal extension DropboxFileProvider {
request.httpMethod = "POST"
request.setValue(authentication: self.credential, with: .oAuth2)
request.setValue(contentType: .json)
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path)! as NSString]
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path) as NSString!]
requestDictionary["query"] = queryStr as NSString
requestDictionary["start"] = NSNumber(value: (token.flatMap( { Int($0) } ) ?? 0))
request.httpBody = Data(jsonDictionary: requestDictionary)
+30 -54
View File
@@ -6,7 +6,6 @@
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
#if os(macOS) || os(iOS) || os(tvOS)
import Foundation
import ImageIO
import CoreGraphics
@@ -55,8 +54,7 @@ extension LocalFileProvider: ExtendedFileProvider {
}
}
@discardableResult
open func thumbnailOfFile(path: String, dimension: CGSize? = nil, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) -> Progress? {
open func thumbnailOfFile(path: String, dimension: CGSize? = nil, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let dimension = dimension ?? CGSize(width: 64, height: 64)
(dispatch_queue).async {
var thumbnailImage: ImageClass? = nil
@@ -86,11 +84,9 @@ extension LocalFileProvider: ExtendedFileProvider {
completionHandler(scaledImage, nil)
}
}
return nil
}
@discardableResult
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)) -> Progress? {
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)) {
(dispatch_queue).async {
let fileExt = (path as NSString).pathExtension.lowercased()
var getter: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))?
@@ -121,7 +117,6 @@ extension LocalFileProvider: ExtendedFileProvider {
completionHandler(dic, keys, nil)
}
return nil
}
}
@@ -222,7 +217,7 @@ public struct LocalFileInformationGenerator {
let asset = AVAsset(url: fileURL)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
let time = CMTime(value: asset.duration.value / 3, timescale: asset.duration.timescale)
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
if let cgImage = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
#if os(macOS)
return ImageClass(cgImage: cgImage, size: .zero)
@@ -282,31 +277,26 @@ public struct LocalFileInformationGenerator {
let imageDict = cfImageDict as NSDictionary
let tiffDict = imageDict[kCGImagePropertyTIFFDictionary as String] as? NSDictionary ?? [:]
let exifDict = imageDict[kCGImagePropertyExifDictionary as String] as? NSDictionary ?? [:]
let gpsDict = imageDict[kCGImagePropertyGPSDictionary as String] as? NSDictionary ?? [:]
if let pixelWidth = imageDict.object(forKey: kCGImagePropertyPixelWidth) as? NSNumber, let pixelHeight = imageDict.object(forKey: kCGImagePropertyPixelHeight) as? NSNumber {
add(key: "Dimensions", value: "\(pixelWidth)x\(pixelHeight)")
}
add(key: "DPI", value: imageDict[kCGImagePropertyDPIWidth as String])
add(key: "Device maker", value: tiffDict[kCGImagePropertyTIFFMake as String])
add(key: "Device make", value: tiffDict[kCGImagePropertyTIFFMake as String])
add(key: "Device model", value: tiffDict[kCGImagePropertyTIFFModel as String])
add(key: "Lens model", value: exifDict[kCGImagePropertyExifLensModel as String])
add(key: "Artist", value: tiffDict[kCGImagePropertyTIFFArtist as String] as? String)
add(key: "Copyright", value: tiffDict[kCGImagePropertyTIFFCopyright as String] as? String)
add(key: "Date taken", value: tiffDict[kCGImagePropertyTIFFDateTime as String] as? String)
if let latitude = gpsDict[kCGImagePropertyGPSLatitude as String] as? NSNumber,
let longitude = gpsDict[kCGImagePropertyGPSLongitude as String] as? NSNumber {
let altitudeDesc = (gpsDict[kCGImagePropertyGPSAltitude as String] as? NSNumber).map({ " at \($0.format(precision: 0))m" }) ?? ""
add(key: "Location", value: "\(latitude.format()), \(longitude.format())\(altitudeDesc)")
if let latitude = tiffDict[kCGImagePropertyGPSLatitude as String] as? NSNumber, let longitude = tiffDict[kCGImagePropertyGPSLongitude as String] as? NSNumber {
add(key: "Location", value: "\(latitude), \(longitude)")
}
add(key: "Area", value: gpsDict[kCGImagePropertyGPSAreaInformation as String])
add(key: "Altitude", value: tiffDict[kCGImagePropertyGPSAltitude as String] as? NSNumber)
add(key: "Area", value: tiffDict[kCGImagePropertyGPSAreaInformation as String])
add(key: "Color space", value: imageDict[kCGImagePropertyColorModel as String])
add(key: "Color depth", value: (imageDict[kCGImagePropertyDepth as String] as? NSNumber).map({ "\($0) bits" }))
add(key: "Color profile", value: imageDict[kCGImagePropertyProfileName as String])
add(key: "Focal length", value: exifDict[kCGImagePropertyExifFocalLength as String])
add(key: "White banance", value: exifDict[kCGImagePropertyExifWhiteBalance as String])
add(key: "F number", value: exifDict[kCGImagePropertyExifFNumber as String])
add(key: "Exposure program", value: exifDict[kCGImagePropertyExifExposureProgram as String])
@@ -330,7 +320,7 @@ public struct LocalFileInformationGenerator {
}
}
func makeKeyDescription(_ key: String?) -> String? {
func makeDescription(_ key: String?) -> String? {
guard let key = key else {
return nil
}
@@ -341,19 +331,7 @@ public struct LocalFileInformationGenerator {
return newKey.capitalized
}
func parseLocationData(_ value: String) -> (latitude: Double, longitude: Double, height: Double?)? {
let scanner = Scanner.init(string: value)
var latitude: Double = 0.0, longitude: Double = 0.0, height: Double = 0
if scanner.scanDouble(&latitude), scanner.scanDouble(&longitude) {
scanner.scanDouble(&height)
return (latitude, longitude, height)
} else {
return nil
}
}
guard fileURL.fileExists else {
guard FileManager.default.fileExists(atPath: fileURL.path) else {
return (dic, keys)
}
let playerItem = AVPlayerItem(url: fileURL)
@@ -364,20 +342,10 @@ public struct LocalFileInformationGenerator {
#else
let commonKey = item.commonKey
#endif
if let key = makeKeyDescription(commonKey) {
if commonKey == "location", let value = item.stringValue, let loc = parseLocationData(value) {
keys.append(key)
let heightStr: String = (loc.height as NSNumber?).map({ ", \($0.format(precision: 0))m" }) ?? ""
dic[key] = "\((loc.latitude as NSNumber).format())°, \((loc.longitude as NSNumber).format())°\(heightStr)"
} else if let value = item.dateValue {
keys.append(key)
dic[key] = value
} else if let value = item.numberValue {
keys.append(key)
dic[key] = value
} else if let value = item.stringValue {
keys.append(key)
dic[key] = value
if let description = makeDescription(commonKey) {
if let value = item.stringValue {
keys.append(description)
dic[description] = value
}
}
}
@@ -476,12 +444,21 @@ public struct LocalFileInformationGenerator {
guard let date = date else { return nil }
let dateStr = date.replacingOccurrences(of: "'", with: "").replacingOccurrences(of: "D:", with: "", options: .anchored)
let dateFormatter = DateFormatter()
let formats: [String] = ["yyyyMMddHHmmssTZ", "yyyyMMddHHmmssZZZZZ", "yyyyMMddHHmmssZ", "yyyyMMddHHmmss"]
for format in formats {
dateFormatter.dateFormat = format
if let result = dateFormatter.date(from: dateStr) {
return result
}
dateFormatter.dateFormat = "yyyyMMddHHmmssTZ"
if let result = dateFormatter.date(from: dateStr) {
return result
}
dateFormatter.dateFormat = "yyyyMMddHHmmssZZZZZ"
if let result = dateFormatter.date(from: dateStr) {
return result
}
dateFormatter.dateFormat = "yyyyMMddHHmmssZ"
if let result = dateFormatter.date(from: dateStr) {
return result
}
dateFormatter.dateFormat = "yyyyMMddHHmmss"
if let result = dateFormatter.date(from: dateStr) {
return result
}
return nil
}
@@ -527,4 +504,3 @@ public struct LocalFileInformationGenerator {
/// - Note: No default implementation is avaiable
static public var customProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
}
#endif
+36 -93
View File
@@ -80,15 +80,8 @@ internal extension URL {
}
var fileExists: Bool {
return (try? self.checkResourceIsReachable()) ?? false
return self.isFileURL && FileManager.default.fileExists(atPath: self.path)
}
#if os(macOS) || os(iOS) || os(tvOS)
#else
func checkPromisedItemIsReachable() throws -> Bool {
return false
}
#endif
}
public extension URLRequest {
@@ -260,7 +253,7 @@ internal extension URLRequest {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(acceptCharset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(charsetString);q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
self.setValue("\(charsetString); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
} else {
self.setValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
@@ -270,7 +263,7 @@ internal extension URLRequest {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(acceptCharset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(charsetString);q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
self.addValue("\(charsetString); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
} else {
self.addValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
@@ -286,7 +279,7 @@ internal extension URLRequest {
mutating func setValue(acceptEncoding: Encoding, quality: Double? = nil) {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(acceptEncoding.rawValue);q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
self.setValue("\(acceptEncoding.rawValue); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
} else {
self.setValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
@@ -294,7 +287,7 @@ internal extension URLRequest {
mutating func addValue(acceptEncoding: Encoding, quality: Double? = nil) {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(acceptEncoding.rawValue);q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
self.addValue("\(acceptEncoding.rawValue); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
} else {
self.addValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
@@ -303,7 +296,7 @@ internal extension URLRequest {
mutating func setValue(acceptLanguage: Locale, quality: Double? = nil) {
let langCode = acceptLanguage.identifier.replacingOccurrences(of: "_", with: "-")
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(langCode);q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
self.setValue("\(langCode); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
} else {
self.setValue(langCode, forHTTPHeaderField: "Accept-Language")
}
@@ -312,7 +305,7 @@ internal extension URLRequest {
mutating func addValue(acceptLanguage: Locale, quality: Double? = nil) {
let langCode = acceptLanguage.identifier.replacingOccurrences(of: "_", with: "-")
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(langCode);q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
self.addValue("\(langCode); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
} else {
self.addValue(langCode, forHTTPHeaderField: "Accept-Language")
}
@@ -335,17 +328,6 @@ internal extension URLRequest {
}
}
mutating func setValue(contentRange range: Range<Int64>, totalBytes: Int64) {
let range = max(0, range.lowerBound)..<range.upperBound
if range.upperBound < Int.max && range.count > 0 {
self.setValue("bytes \(range.lowerBound)-\(range.upperBound - 1)/\(totalBytes)", forHTTPHeaderField: "Content-Range")
} else if range.lowerBound > 0 {
self.setValue("bytes \(range.lowerBound)-/\(totalBytes)", forHTTPHeaderField: "Content-Range")
} else {
self.setValue("bytes 0-/\(totalBytes)", forHTTPHeaderField: "Content-Range")
}
}
mutating func setValue(contentType: ContentMIMEType, charset: String.Encoding? = nil) {
var parameter = ""
if let charset = charset {
@@ -379,7 +361,6 @@ internal extension Data {
}
init? (jsonDictionary dictionary: [String: AnyObject]) {
guard JSONSerialization.isValidJSONObject(dictionary) else { return nil }
guard let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []) else {
return nil
}
@@ -387,7 +368,10 @@ internal extension Data {
}
func deserializeJSON() -> [String: AnyObject]? {
return (try? JSONSerialization.jsonObject(with: self, options: [])) as? [String: AnyObject]
if let dic = try? JSONSerialization.jsonObject(with: self, options: []) as? [String: AnyObject] {
return dic
}
return nil
}
init<T>(value: T) {
@@ -410,6 +394,12 @@ internal extension Data {
guard self.count >= start + length else { return nil }
return String(data: self.subdata(in: start..<start+length), encoding: encoding)
}
static func mapMemory<T, U>(from: T) -> U? {
guard MemoryLayout<T>.size >= MemoryLayout<U>.size else { return nil }
let data = Data(value: from)
return data.scanValue()
}
}
internal extension String {
@@ -441,14 +431,14 @@ internal extension String {
}
}
internal extension NSNumber {
internal func format(precision: Int = 2, style: NumberFormatter.Style = .decimal) -> String {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = precision
formatter.numberStyle = style
return formatter.string(from: self)!
#if swift(>=4.0)
#else
extension String {
var count: Int {
return self.characters.count
}
}
#endif
internal extension TimeInterval {
internal var formatshort: String {
@@ -479,50 +469,32 @@ internal extension TimeInterval {
public extension Date {
/// Date formats used commonly in internet messaging defined by various RFCs.
public enum RFCStandards: String {
/// Obsolete (2-digit year) date format defined by RFC 822 for http.
case rfc822 = "EEE',' dd' 'MMM' 'yy HH':'mm':'ss z"
/// Obsolete (2-digit year) date format defined by RFC 850 for usenet.
/// Date format defined by usenet, commonly used in old implementations.
case rfc850 = "EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z"
/// Date format defined by RFC 1123 for http.
/// Date format defined by RFC 1132 for http.
case rfc1123 = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss z"
/// Date format defined by RFC 3339, as a profile of ISO 8601.
case rfc3339 = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZZZZZ"
/// Date format defined RFC 3339 as rare case with milliseconds.
case rfc3339Extended = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSSZZZZZ"
/// Date format defined by ISO 8601, also defined in RFC 3339. Used by Dropbox.
case iso8601 = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
/// Date string returned by asctime() function.
case asctime = "EEE MMM d HH':'mm':'ss yyyy"
// Defining a http alias allows changing default time format if a new RFC becomes standard.
/// Equivalent to and defined by RFC 1123.
public static let http = RFCStandards.rfc1123
/// Equivalent to and defined by ISO 8610.
public static let rfc3339 = RFCStandards.iso8601
/// Equivalent to and defined by RFC 850.
public static let usenet = RFCStandards.rfc850
/* re. [RFC7231 section-7.1.1.1](https://tools.ietf.org/html/rfc7231#section-7.1.1.1)
"HTTP servers and client MUST accept all three HTTP-date formats" which are IMF-fixdate,
obsolete RFC 850 format and ANSI C's asctime() format.
ISO 8601 format is common in JSON and XML fields, defined by RFC 3339 as a timestamp format.
Though not mandated, we check string against them to allow using Date(rfcString:) in
wider and more general sitations.
We use RFC 822 instead of RFC 1123 to convert from string because NSDateFormatter can parse
both 2-digit and 4-digit year correctly when `dateFormat` year is 2-digit.
These values are sorted by frequency.
*/
fileprivate static let parsingCases: [RFCStandards] = [.rfc822, .rfc850, .asctime, .rfc3339, .rfc3339Extended]
// Sorted by commonness
fileprivate static let allValues: [RFCStandards] = [.rfc1123, .rfc850, .iso8601, .asctime]
}
private static let posixLocale = Locale(identifier: "en_US_POSIX")
private static let utcTimezone = TimeZone(identifier: "UTC")
/// Checks date string against various RFC standards and returns `Date`.
public init?(rfcString: String) {
let dateFor: DateFormatter = DateFormatter()
dateFor.locale = Date.posixLocale
dateFor.locale = Locale(identifier: "en_US")
for standard in RFCStandards.parsingCases {
for standard in RFCStandards.allValues {
dateFor.dateFormat = standard.rawValue
if let date = dateFor.date(from: rfcString) {
self = date
@@ -534,12 +506,11 @@ public extension Date {
}
/// Formats date according to RFCs standard.
/// - Note: local and timezone paramters should be nil for `.http` standard
internal func format(with standard: RFCStandards, locale: Locale? = nil, timeZone: TimeZone? = nil) -> String {
public func format(with standard: RFCStandards, locale: Locale? = nil, timeZone: TimeZone? = nil) -> String {
let fm = DateFormatter()
fm.dateFormat = standard.rawValue
fm.timeZone = timeZone ?? Date.utcTimezone
fm.locale = locale ?? Date.posixLocale
fm.timeZone = timeZone ?? TimeZone(identifier: "UTC")
fm.locale = locale ?? Locale(identifier: "en_US_POSIX")
return fm.string(from: self)
}
}
@@ -586,31 +557,3 @@ func hasSuffix(_ suffix: String) -> (_ value: String) -> Bool {
value.hasSuffix(suffix)
}
}
// Legacy Swift versions support
#if swift(>=4.0)
#else
extension String {
var count: Int {
return self.characters.count
}
}
#endif
#if swift(>=4.1)
#else
extension Array {
func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try self.flatMap(transform)
}
}
extension ArraySlice {
func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try self.flatMap(transform)
}
}
#endif
+2 -5
View File
@@ -410,12 +410,9 @@ fileprivate func arrayOfBytes<T>(_ value:T, length:Int? = nil) -> [UInt8] {
bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
}
valuePointer.deinitialize(count: 1)
#if swift(>=4.1)
valuePointer.deallocate()
#else
valuePointer.deinitialize()
valuePointer.deallocate(capacity: 1)
#endif
return bytes
}
+34 -180
View File
@@ -8,67 +8,14 @@
import Foundation
private var _lasttaskIdAssociated = 1_000_000_000
let _lasttaskIdAssociated_lock: NSLock = NSLock()
var lasttaskIdAssociated: Int {
get {
_lasttaskIdAssociated_lock.try()
defer {
_lasttaskIdAssociated_lock.unlock()
}
return _lasttaskIdAssociated
}
set {
_lasttaskIdAssociated_lock.try()
defer {
_lasttaskIdAssociated_lock.unlock()
}
_lasttaskIdAssociated = newValue
}
}
private var lasttaskIdAssociated = 1_000_000_000
// codebeat:disable[TOTAL_LOC,TOO_MANY_IVARS]
/// This class is a replica of NSURLSessionStreamTask with same api for iOS 7/8
/// while it can actually fallback to NSURLSessionStreamTask in iOS 9.
public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
fileprivate var _inputStream: InputStream?
fileprivate var _outputStream: OutputStream?
let _inputStream_lock: NSLock = NSLock()
var inputStream: InputStream? {
get {
_inputStream_lock.try()
defer {
_inputStream_lock.unlock()
}
return _inputStream
}
set {
_inputStream_lock.try()
defer {
_inputStream_lock.unlock()
}
_inputStream = newValue
}
}
let _outputStream_lock: NSLock = NSLock()
var outputStream: OutputStream? {
get {
_outputStream_lock.try()
defer {
_outputStream_lock.unlock()
}
return _outputStream
}
set {
_outputStream_lock.try()
defer {
_outputStream_lock.unlock()
}
_outputStream = newValue
}
}
fileprivate var inputStream: InputStream?
fileprivate var outputStream: OutputStream?
fileprivate var operation_queue: OperationQueue!
internal var _underlyingSession: URLSession
@@ -360,12 +307,6 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
self.operation_queue.maxConcurrentOperationCount = 1
}
deinit {
if !self.useURLSession {
self.cancel()
}
}
/**
* Cancels the task.
*
@@ -384,11 +325,9 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
}
}
self.operation_queue.isSuspended = true
self._state = .canceling
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
//inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
//outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
self.inputStream?.close()
self.outputStream?.close()
@@ -396,6 +335,9 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
self.inputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.outputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream = nil
self.outputStream = nil
@@ -404,25 +346,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
self._countOfBytesRecieved = 0
}
var __error: Error? = nil
let _error_lock: NSLock = NSLock()
var _error: Error? {
get {
_error_lock.try()
defer {
_error_lock.unlock()
}
return __error
}
set {
_error_lock.try()
defer {
_error_lock.unlock()
}
__error = newValue
}
}
var _error: Error? = nil
/**
* An error object that indicates why the task failed.
@@ -470,74 +394,43 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
if self.inputStream == nil || self.outputStream == nil {
if let host = self.host {
if inputStream == nil || outputStream == nil {
if let host = host {
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host.hostname as CFString, UInt32(host.port), &readStream, &writeStream)
} else if let service = self.service {
} else if let service = service {
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain as CFString, service.type as CFString, service.name as CFString, Int32(service.port))
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
}
self.inputStream = readStream?.takeRetainedValue()
self.outputStream = writeStream?.takeRetainedValue()
guard let inputStream = self.inputStream, let outputStream = self.outputStream else {
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
self.streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
}
guard let inputStream = self.inputStream, let outputStream = self.outputStream else {
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
if self.isSecure {
inputStream.setProperty(self.securityLevel.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(self.securityLevel.rawValue, forKey: .socketSecurityLevelKey)
} else {
inputStream.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
}
inputStream.delegate = self
outputStream.delegate = self
inputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
outputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
operation_queue.addOperation {
inputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
outputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
}
inputStream.open()
outputStream.open()
self.operation_queue.isSuspended = false
self._state = .running
operation_queue.isSuspended = false
_state = .running
}
fileprivate var dataToBeSent: Data = Data()
fileprivate var _dataReceived: Data = Data()
// We are updating `dataReceived` from main thread while reading it from operation_queue.
let _dataReceived_lock: NSLock = NSLock()
var dataReceived: Data {
get {
if !_dataReceived_lock.try() {
print("not locked")
}
defer {
_dataReceived_lock.unlock()
}
return _dataReceived
}
set {
if !_dataReceived_lock.try() {
print("not locked")
}
defer {
_dataReceived_lock.unlock()
}
_dataReceived = newValue
}
}
fileprivate var dataReceived: Data = Data()
/**
* Asynchronously reads a number of bytes from the stream, and calls a handler upon completion.
@@ -568,11 +461,6 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
operation_queue.addOperation {
var timedOut: Bool = false
while (self.dataReceived.count == 0 || self.dataReceived.count < minBytes) && !timedOut {
if let error = inputStream.streamError {
completionHandler(nil, inputStream.streamStatus == .atEnd, error)
return
}
Thread.sleep(forTimeInterval: 0.1)
timedOut = expireDate < Date()
}
@@ -686,10 +574,6 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
var byteSent: Int = 0
let expireDate = Date(timeIntervalSinceNow: timeout)
while self.dataToBeSent.count > 0 && (timeout == 0 || expireDate > Date()) {
if let _ = outputStream.streamError {
return byteSent == 0 ? -1 : byteSent
}
let bytesWritten = self.dataToBeSent.withUnsafeBytes {
outputStream.write($0, maxLength: self.dataToBeSent.count)
}
@@ -741,9 +625,6 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
}
}
fileprivate var isSecure = false
public var securityLevel: StreamSocketSecurityLevel = .negotiatedSSL
/**
* Completes any enqueued reads and writes, and establishes a secure connection.
*
@@ -758,13 +639,9 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
}
}
isSecure = true
operation_queue.addOperation {
if let inputStream = self.inputStream, let outputStream = self.outputStream,
inputStream.property(forKey: .socketSecurityLevelKey) as? String == StreamSocketSecurityLevel.none.rawValue {
inputStream.setProperty(self.securityLevel.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(self.securityLevel.rawValue, forKey: .socketSecurityLevelKey)
}
self.inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
self.outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
}
}
@@ -778,47 +655,24 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
return
}
}
isSecure = false
operation_queue.addOperation {
if let inputStream = self.inputStream, let outputStream = self.outputStream,
inputStream.property(forKey: .socketSecurityLevelKey) as? String != StreamSocketSecurityLevel.none.rawValue {
inputStream.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
}
self.inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
self.outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
}
}
private var _retriesForInputStream: Int = 0
private var _retriesForOutputStream: Int = 0
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if eventCode.contains(.errorOccurred) {
let retries: Int
if aStream == self.inputStream {
retries = _retriesForInputStream
_retriesForInputStream += 1
} else if aStream == self.outputStream {
retries = _retriesForOutputStream
_retriesForOutputStream += 1
} else {
return
}
if retries < 3 {
aStream.open()
} else {
self._error = aStream.streamError
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
}
self._error = aStream.streamError
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
}
if aStream == self.inputStream && eventCode.contains(.hasBytesAvailable) {
while (self.inputStream!.hasBytesAvailable) {
if aStream == inputStream && eventCode.contains(.hasBytesAvailable) {
while (inputStream!.hasBytesAvailable) {
var buffer = [UInt8](repeating: 0, count: 2048)
let len = self.inputStream!.read(&buffer, maxLength: buffer.count)
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
self.dataReceived.append(&buffer, count: len)
dataReceived.append(&buffer, count: len)
self._countOfBytesRecieved += Int64(len)
}
}
+29 -143
View File
@@ -12,22 +12,12 @@ import Foundation
Allows accessing to FTP files and directories. This provider doesn't cache or save files internally.
It's a complete reimplementation and doesn't use CFNetwork deprecated API.
*/
open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite, FileProviderReadWriteProgressive {
/// FTP data connection mode.
public enum Mode: String {
/// Passive mode for FTP and Extended Passive mode for FTP over TLS.
case `default`
/// Data connection would establish by client to determined server host/port.
case passive
/// Data connection would establish by server to determined client's port.
case active
/// Data connection would establish by client to determined server host/port, with IPv6 support. (RFC 2428)
case extendedPassive
}
open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class var type: String { return "FTP" }
open let baseURL: URL?
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
@available(*, obsoleted: 0.22, message: "This property is redundant with almost no use internally.")
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue {
@@ -47,7 +37,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
public var validatingCache: Bool
/// Determine either FTP session is in passive or active mode.
public let mode: Mode
public let passiveMode: Bool
fileprivate var _session: URLSession!
internal var sessionDelegate: SessionDelegate?
@@ -80,26 +70,22 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
- Note: `passive` value should be set according to server settings and firewall presence.
- Parameter baseURL: a url with `ftp://hostaddress/` format.
- Parameter mode: FTP server data connection type.
- Parameter passive: FTP server data connection, `true` means passive connection (data connection created by client)
and `false` means active connection (data connection created by server). Default is `true` (passive mode).
- Parameter credential: a `URLCredential` object contains user and password.
- Parameter cache: A URLCache to cache downloaded files and contents. (unimplemented for FTP and should be nil)
- Important: Extended Passive or Active modes will fallback to normal Passive or Active modes if your server
does not support extended modes.
*/
public init? (baseURL: URL, mode: Mode = .default, credential: URLCredential? = nil, cache: URLCache? = nil) {
guard ["ftp", "ftps", "ftpes"].contains(baseURL.uw_scheme.lowercased()) else {
return nil
}
public init? (baseURL: URL, passive: Bool = true, credential: URLCredential? = nil, cache: URLCache? = nil) {
guard (baseURL.scheme ?? "ftp").lowercased().hasPrefix("ftp") else { return nil }
guard baseURL.host != nil else { return nil }
var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: true)!
let defaultPort: Int = baseURL.scheme?.lowercased() == "ftps" ? 990 : 21
let defaultPort: Int = baseURL.scheme == "ftps" ? 990 : 21
urlComponents.port = urlComponents.port ?? defaultPort
urlComponents.scheme = urlComponents.scheme ?? "ftp"
urlComponents.path = urlComponents.path.hasSuffix("/") ? urlComponents.path : urlComponents.path + "/"
self.baseURL = urlComponents.url!.absoluteURL
self.mode = mode
self.passiveMode = passive
self.useCache = false
self.validatingCache = true
self.cache = cache
@@ -114,40 +100,14 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
super.init()
}
/**
**DEPRECATED** Initializer for FTP provider with given username and password.
- Note: `passive` value should be set according to server settings and firewall presence.
- Parameter baseURL: a url with `ftp://hostaddress/` format.
- Parameter passive: FTP server data connection, `true` means passive connection (data connection created by client)
and `false` means active connection (data connection created by server). Default is `true` (passive mode).
- Parameter credential: a `URLCredential` object contains user and password.
- Parameter cache: A URLCache to cache downloaded files and contents. (unimplemented for FTP and should be nil)
*/
@available(*, deprecated, renamed: "init(baseURL:mode:credential:cache:)")
public convenience init? (baseURL: URL, passive: Bool, credential: URLCredential? = nil, cache: URLCache? = nil) {
self.init(baseURL: baseURL, mode: passive ? .passive : .active, credential: credential, cache: cache)
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else { return nil }
let mode: Mode
if let modeStr = aDecoder.decodeObject(of: NSString.self, forKey: "mode") as String?, let mode_v = Mode(rawValue: modeStr) {
mode = mode_v
} else {
let passiveMode = aDecoder.decodeBool(forKey: "passiveMode")
mode = passiveMode ? .passive : .active
}
self.init(baseURL: baseURL, mode: mode, credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"))
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
self.supportsRFC3659 = aDecoder.decodeBool(forKey: "supportsRFC3659")
self.securedDataConnection = aDecoder.decodeBool(forKey: "securedDataConnection")
self.init(baseURL: baseURL, passive: aDecoder.decodeBool(forKey: "passiveMode"), credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
self.supportsRFC3659 = aDecoder.decodeBool(forKey: "supportsRFC3659")
}
public func encode(with aCoder: NSCoder) {
@@ -155,9 +115,8 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
aCoder.encode(self.credential, forKey: "credential")
aCoder.encode(self.useCache, forKey: "useCache")
aCoder.encode(self.validatingCache, forKey: "validatingCache")
aCoder.encode(self.mode.rawValue, forKey: "mode")
aCoder.encode(self.passiveMode, forKey: "passiveMode")
aCoder.encode(self.supportsRFC3659, forKey: "supportsRFC3659")
aCoder.encode(self.securedDataConnection, forKey: "securedDataConnection")
}
public static var supportsSecureCoding: Bool {
@@ -165,12 +124,11 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
open func copy(with zone: NSZone? = nil) -> Any {
let copy = FTPFileProvider(baseURL: self.baseURL!, mode: self.mode, credential: self.credential, cache: self.cache)!
let copy = FTPFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.useCache = self.useCache
copy.validatingCache = self.validatingCache
copy.securedDataConnection = self.securedDataConnection
copy.supportsRFC3659 = self.supportsRFC3659
return copy
}
@@ -192,7 +150,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
/**
Uploads files in chunk if `true`, Otherwise It will uploads entire file/data as single stream.
- Note: Due to an internal bug in `NSURLSessionStreamTask`, it must be `true` when using Apple's stream task,
- Note: Due to an internal bug in `NSURLSessionStreamTask`, it must be true when using Apple's stream task,
otherwise it will occasionally throw `Assertion failed: (_writeBufferAlreadyWrittenForNextWrite == 0)`
fatal error. My implementation of `FileProviderStreamTask` doesn't have this bug.
@@ -200,12 +158,6 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
*/
public var uploadByREST: Bool = FileProviderStreamTask.defaultUseURLSession
/**
Determines data connection must TLS or not. `false` value indicates to use `PROT C` and
`true` value indicates to use `PROT P`. Default is `true`.
*/
public var securedDataConnection: Bool = true
open func contentsOfDirectory(path: String, completionHandler: @escaping ([FileObject], Error?) -> Void) {
self.contentsOfDirectory(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler)
}
@@ -238,7 +190,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
self.ftpQuit(task)
}
if let error = error {
if let uerror = error as? URLError, uerror.code == .unsupportedURL {
if ((error as NSError).domain == URLError.errorDomain && (error as NSError).code == URLError.unsupportedURL.rawValue) {
self.contentsOfDirectory(path: path, rfc3659enabled: false, completionHandler: completionHandler)
return
}
@@ -250,8 +202,8 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
let files: [FileObject] = contents.compactMap {
rfc3659enabled ? self.parseMLST($0, in: path) : (self.parseUnixList($0, in: path) ?? self.parseDOSList($0, in: path))
let files: [FileObject] = contents.flatMap {
rfc3659enabled ? self.parseMLST($0, in: path) : self.parseUnixList($0, in: path)
}
self.dispatch_queue.async {
@@ -307,14 +259,11 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
self.attributesOfItem(path: path, rfc3659enabled: false, completionHandler: completionHandler)
}
let lines = response.components(separatedBy: "\n").compactMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) }
let lines = response.components(separatedBy: "\n").flatMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) }
guard lines.count > 2 else {
throw self.urlError(path, code: .badServerResponse)
}
let dirPath = (path as NSString).deletingLastPathComponent
let file: FileObject? = rfc3659enabled ?
self.parseMLST(lines[1], in: dirPath) :
(self.parseUnixList(lines[1], in: dirPath) ?? self.parseDOSList(lines[1], in: dirPath))
let file: FileObject? = rfc3659enabled ? self.parseMLST(lines[1], in: path) : self.parseUnixList(lines[1], in: path)
self.dispatch_queue.async {
completionHandler(file, nil)
}
@@ -333,7 +282,6 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
}
@discardableResult
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
if recursive {
@@ -395,36 +343,31 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return relativePath.replacingOccurrences(of: "/", with: "", options: .anchored)
}
open func isReachable(completionHandler: @escaping (_ success: Bool, _ error: Error?) -> Void) {
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
self.attributesOfItem(path: "/") { (file, error) in
completionHandler(file != nil, error)
completionHandler(file != nil)
}
}
open weak var fileOperationDelegate: FileOperationDelegate?
@discardableResult
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
@discardableResult
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
}
@discardableResult
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
}
@discardableResult
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
@discardableResult
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
@@ -479,7 +422,6 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return progress
}
@discardableResult
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: destURL.absoluteString)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
@@ -531,7 +473,6 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return progress
}
@discardableResult
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
if length == 0 || offset < 0 {
@@ -561,7 +502,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { _, recevied, totalReceived, totalSize in
}, onProgress: { recevied, totalReceived, totalSize in
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
@@ -587,7 +528,6 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return progress
}
@discardableResult
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
@@ -645,60 +585,6 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return progress
}
public func contents(path: String, offset: Int64, length: Int, responseHandler: ((URLResponse) -> Void)?, progressHandler: @escaping (Int64, Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.fetch(path: path)
if length == 0 || offset < 0 {
dispatch_queue.async {
completionHandler?(nil)
self.delegateNotify(operation)
}
return nil
}
let progress = Progress(totalUnitCount: 0)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
}
return
}
self.ftpFileData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { data, recevied, totalReceived, totalSize in
progressHandler(totalReceived - recevied, data)
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (data, error) in
if let error = error {
progress.cancel()
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
self.dispatch_queue.async {
completionHandler?(nil)
self.delegateNotify(operation)
}
}
}
return progress
}
/**
Creates a symbolic link at the specified path that points to an item at the given path.
This method does not traverse symbolic links contained in destination path, making it possible
@@ -761,9 +647,9 @@ extension FTPFileProvider {
return
}
let codes: [Int] = response.components(separatedBy: .newlines).compactMap({ $0.isEmpty ? nil : $0})
.compactMap {
let code = $0.components(separatedBy: .whitespaces).compactMap({ $0.isEmpty ? nil : $0}).first
let codes: [Int] = response.components(separatedBy: .newlines).flatMap({ $0.isEmpty ? nil : $0})
.flatMap {
let code = $0.components(separatedBy: .whitespaces).flatMap({ $0.isEmpty ? nil : $0}).first
return code != nil ? Int(code!) : nil
}
+34 -187
View File
@@ -60,13 +60,12 @@ internal extension FTPFileProvider {
}
// needs password
if response.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("33") {
if FileProviderFTPError(message: response).code == 331 {
self.execute(command: "PASS \(self.credential?.password ?? "fileprovider@")", on: task) { (response, error) in
if response?.hasPrefix("23") ?? false {
completionHandler(nil)
} else {
let error: Error = response.flatMap(FileProviderFTPError.init(message:)) ?? self.urlError("", code: .userAuthenticationRequired)
completionHandler(error)
completionHandler(self.urlError("", code: .userAuthenticationRequired))
}
}
return
@@ -77,25 +76,6 @@ internal extension FTPFileProvider {
}
}
fileprivate func ftpEstablishSecureDataConnection(_ task: FileProviderStreamTask, completionHandler: @escaping (_ error: Error?) -> Void) {
self.execute(command: "PBSZ 0", on: task, completionHandler: { (response, error) in
if let error = error {
completionHandler(error)
return
}
let prot = self.securedDataConnection ? "PROT P" : "PROT C"
self.execute(command: prot, on: task, completionHandler: { (response, error) in
if let error = error {
completionHandler(error)
return
}
self.ftpUserPass(task, completionHandler: completionHandler)
})
})
}
func ftpLogin(_ task: FileProviderStreamTask, completionHandler: @escaping (_ error: Error?) -> Void) {
let timeout = session.configuration.timeoutIntervalForRequest
@@ -138,25 +118,25 @@ internal extension FTPFileProvider {
if let response = response, response.hasPrefix("23") {
task.startSecureConnection()
isSecure = true
self.ftpEstablishSecureDataConnection(task) { error in
self.execute(command: "PBSZ 0\r\nPROT P", on: task, completionHandler: { (response, error) in
if let error = error {
completionHandler(error)
return
}
self.ftpUserPass(task, completionHandler: completionHandler)
}
})
}
})
} else if isSecure {
self.ftpEstablishSecureDataConnection(task) { error in
self.execute(command: "PBSZ 0\r\nPROT P", on: task, completionHandler: { (response, error) in
if let error = error {
completionHandler(error)
return
}
self.ftpUserPass(task, completionHandler: completionHandler)
}
})
} else {
self.ftpUserPass(task, completionHandler: completionHandler)
}
@@ -178,13 +158,13 @@ internal extension FTPFileProvider {
throw self.urlError("", code: .badServerResponse)
}
let destArray = destString.components(separatedBy: ",").compactMap({ UInt32(trimmedNumber($0)) })
let destArray = destString.components(separatedBy: ",").flatMap({ UInt32(trimmedNumber($0)) })
guard destArray.count == 6 else {
throw self.urlError("", code: .badServerResponse)
}
// first 4 elements are ip, 2 next are port, as byte
var host = destArray.prefix(4).compactMap(String.init).joined(separator: ".")
var host = destArray.prefix(4).flatMap(String.init).joined(separator: ".")
let portHi = Int(destArray[4]) << 8
let portLo = Int(destArray[5])
let port = portHi + portLo
@@ -194,54 +174,10 @@ internal extension FTPFileProvider {
}
let passiveTask = self.session.fpstreamTask(withHostName: host, port: port)
passiveTask.resume()
if self.baseURL?.scheme == "ftps" || self.baseURL?.scheme == "ftpes" || self.baseURL?.port == 990 {
passiveTask.startSecureConnection()
}
passiveTask.securityLevel = .tlSv1
passiveTask.resume()
completionHandler(passiveTask, nil)
} catch {
completionHandler(nil, error)
return
}
}
}
func ftpExtendedPassive(_ task: FileProviderStreamTask, completionHandler: @escaping (_ dataTask: FileProviderStreamTask?, _ error: Error?) -> Void) {
func trimmedNumber(_ s : String) -> String {
return s.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
self.execute(command: "EPSV", on: task) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response, let destString = response.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: " ").last else {
throw self.urlError("", code: .badServerResponse)
}
if response.trimmingCharacters(in: .whitespaces).hasPrefix("50") {
self.ftpPassive(task, completionHandler: completionHandler)
}
let destArray = destString.components(separatedBy: "|")
guard destArray.count >= 4, let port = Int(trimmedNumber(destArray[3])) else {
throw self.urlError("", code: .badServerResponse)
}
var host = destArray[2]
if host.isEmpty {
host = self.baseURL?.host ?? ""
}
let passiveTask = self.session.fpstreamTask(withHostName: host, port: port)
if self.baseURL?.scheme == "ftps" || self.baseURL?.scheme == "ftpes" || self.baseURL?.port == 990 {
passiveTask.startSecureConnection()
}
passiveTask.securityLevel = .tlSv1
passiveTask.resume()
completionHandler(passiveTask, nil)
} catch {
completionHandler(nil, error)
@@ -259,10 +195,10 @@ internal extension FTPFileProvider {
usleep(100_000)
}
let activeTask = self.session.fpstreamTask(withNetService: service)
activeTask.resume()
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
activeTask.startSecureConnection()
}
activeTask.resume()
self.execute(command: "PORT \(service.port)", on: task) { (response, error) in
do {
@@ -287,18 +223,9 @@ internal extension FTPFileProvider {
}
func ftpDataConnect(_ task: FileProviderStreamTask, completionHandler: @escaping (_ dataTask: FileProviderStreamTask?, _ error: Error?) -> Void) {
switch self.mode {
case .default:
if self.baseURL?.port == 990 || self.baseURL?.scheme == "ftps" || self.baseURL?.scheme == "ftpes" {
self.ftpExtendedPassive(task, completionHandler: completionHandler)
} else {
self.ftpPassive(task, completionHandler: completionHandler)
}
case .passive:
if self.passiveMode {
self.ftpPassive(task, completionHandler: completionHandler)
case .extendedPassive:
self.ftpExtendedPassive(task, completionHandler: completionHandler)
case .active:
} else {
dispatch_queue.async {
self.ftpActive(task, completionHandler: completionHandler)
}
@@ -319,7 +246,6 @@ internal extension FTPFileProvider {
return
}
let success_lock = NSLock()
var success = false
let command = useMLST ? "MLSD \(path)" : "LIST \(path)"
self.execute(command: command, on: task, minLength: 20, afterSend: { error in
@@ -327,7 +253,6 @@ internal extension FTPFileProvider {
let timeout = self.session.configuration.timeoutIntervalForRequest
var finalData = Data()
var eof = false
let error_lock = NSLock()
var error: Error?
do {
@@ -339,22 +264,18 @@ internal extension FTPFileProvider {
finalData.append(data)
}
eof = seof
error_lock.try()
error = serror
error_lock.unlock()
group.leave()
}
let waitResult = group.wait(timeout: .now() + timeout)
error_lock.try()
if let error = error {
error_lock.unlock()
if (error as? URLError)?.code != .cancelled {
if !((error as NSError).domain == URLError.errorDomain
&& (error as NSError).code == URLError.cancelled.rawValue) {
throw error
}
return
}
error_lock.unlock()
if waitResult == .timedOut {
throw self.urlError(path, code: .timedOut)
@@ -366,10 +287,8 @@ internal extension FTPFileProvider {
}
let contents: [String] = response.components(separatedBy: "\n")
.compactMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
success_lock.try()
.flatMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
success = true
success_lock.unlock()
completionHandler(contents, nil)
} catch {
completionHandler([], error)
@@ -391,12 +310,8 @@ internal extension FTPFileProvider {
throw self.urlError(path, code: .unsupportedURL)
}
success_lock.try()
if !success && !(response.hasPrefix("25") || response.hasPrefix("15")) {
success_lock.unlock()
throw FileProviderFTPError(message: response, path: path)
} else {
success_lock.unlock()
}
} catch {
self.dispatch_queue.async {
@@ -411,16 +326,16 @@ internal extension FTPFileProvider {
completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
let queue = DispatchQueue(label: "\(self.type).recursiveList")
let group = DispatchGroup()
queue.async {
let group = DispatchGroup()
var result = [FileObject]()
var success = true
group.enter()
self.contentsOfDirectory(path: path, completionHandler: { (files, error) in
success = success && (error == nil)
if let error = error {
group.leave()
completionHandler([], error)
group.leave()
return
}
@@ -490,7 +405,6 @@ internal extension FTPFileProvider {
DispatchQueue.global().async {
var totalReceived: Int64 = 0
var eof = false
let error_lock = NSLock()
var error: Error?
while !eof {
let group = DispatchGroup()
@@ -505,20 +419,15 @@ internal extension FTPFileProvider {
onProgress(data, totalReceived, totalSize)
}
eof = segeof || (length > 0 && totalReceived >= Int64(length))
error_lock.try()
error = segerror
error_lock.unlock()
group.leave()
}
let waitResult = group.wait(timeout: .now() + timeout)
error_lock.try()
if let error = error {
error_lock.unlock()
completionHandler(error)
return
}
error_lock.unlock()
if waitResult == .timedOut {
completionHandler(self.urlError(filePath, code: .timedOut))
@@ -557,7 +466,7 @@ internal extension FTPFileProvider {
func ftpFileData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ data: Data, _ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?,
onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) {
// Check cache
@@ -571,7 +480,7 @@ internal extension FTPFileProvider {
var finalData = Data()
self.ftpRetrieve(task, filePath: filePath, from: position, length: length, onTask: onTask, onProgress: { (data, total, expected) in
finalData.append(data)
onProgress?(data, Int64(data.count), total, expected)
onProgress?(Int64(data.count), total, expected)
}) { (error) in
if let error = error {
completionHandler(nil, error)
@@ -656,7 +565,6 @@ internal extension FTPFileProvider {
return
}
let len = 19 /* TYPE response */ + 44 + filePath.count /* STOR open response */ + 10 /* RETR Transfer complete message. */
let success_lock = NSLock()
var success = false
self.execute(command: "TYPE I" + "\r\n" + "STOR \(filePath)", on: task, minLength: len, afterSend: { error in
onTask?(dataTask)
@@ -675,7 +583,6 @@ internal extension FTPFileProvider {
}
let chunkSize = 4096
let lock = NSLock()
var eof = false
var sent: Int64 = 0
repeat {
@@ -694,49 +601,33 @@ internal extension FTPFileProvider {
let group = DispatchGroup()
group.enter()
dataTask.write(subdata, timeout: timeout, completionHandler: { (serror) in
lock.try()
error = serror
sent += Int64(subdata.count)
let totalsent = sent
let sentbytes = Int64(subdata.count)
lock.unlock()
group.leave()
onProgress?(sentbytes, totalsent, size)
onProgress?(Int64(subdata.count), sent, size)
//print("ftp \(filePath): \(subdata.count), \(sent), \(size)")
})
let waitResult = group.wait(timeout: .now() + timeout)
lock.try()
if waitResult == .timedOut {
error = self.urlError(filePath, code: .timedOut)
}
if let error = error {
lock.unlock()
completionHandler(error)
return
}
if let data = fromData {
lock.try()
let endIndex = min(data.count, Int(sent) + chunkSize)
eof = endIndex == data.count
lock.unlock()
} else if let fileHandle = fileHandle {
eof = Int64(fileHandle.offsetInFile) == size
}
} while !eof
success_lock.try()
success = true
success_lock.unlock()
}) { (response, error) in
success_lock.try()
guard success else {
success_lock.unlock()
return
}
success_lock.unlock()
guard success else { return }
do {
if let error = error {
@@ -859,7 +750,6 @@ internal extension FTPFileProvider {
}
// Send retreive command
let success_lock = NSLock()
var success = false
let len = 19 /* TYPE response */ + 65 + String(position).count /* REST Response */ + 44 + filePath.count /* STOR open response */ + 10 /* RETR Transfer complete message. */
self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "STOR \(filePath)", on: task, minLength: len, afterSend: { error in
@@ -874,20 +764,13 @@ internal extension FTPFileProvider {
completionHandler(error)
return
}
success_lock.try()
success = true
success_lock.unlock()
dataTask.closeRead()
dataTask.closeWrite()
})
}) { (response, error) in
success_lock.try()
guard success else {
success_lock.unlock()
return
}
success_lock.unlock()
guard success else { return }
do {
if let error = error {
@@ -921,7 +804,6 @@ internal extension FTPFileProvider {
func ftpPath(_ apath: String) -> String {
// path of base url should be concreted into file path! And remove final slash
let apath = apath.replacingOccurrences(of: "/", with: "", options: [.anchored])
var path = baseURL!.appendingPathComponent(apath).path.replacingOccurrences(of: "/", with: "", options: [.anchored, .backwards])
// Fixing slashes
@@ -937,14 +819,14 @@ internal extension FTPFileProvider {
let nearDateFormatter = DateFormatter()
nearDateFormatter.calendar = gregorian
nearDateFormatter.locale = Locale(identifier: "en_US_POSIX")
nearDateFormatter.dateFormat = "MMM dd hh:mm yyyy"
nearDateFormatter.dateFormat = "MMM dd hh:ss yyyy"
let farDateFormatter = DateFormatter()
farDateFormatter.calendar = gregorian
farDateFormatter.locale = Locale(identifier: "en_US_POSIX")
farDateFormatter.dateFormat = "MMM dd yyyy"
let thisYear = gregorian.component(.year, from: Date())
let components = text.components(separatedBy: " ").compactMap { $0.isEmpty ? nil : $0 }
let components = text.components(separatedBy: " ").flatMap { $0.isEmpty ? nil : $0 }
guard components.count >= 9 else { return nil }
let posixPermission = components[0]
let linksCount = Int(components[1]) ?? 0
@@ -985,35 +867,8 @@ internal extension FTPFileProvider {
return file
}
func parseDOSList(_ text: String, in path: String) -> FileObject? {
let gregorian = Calendar(identifier: .gregorian)
let dateFormatter = DateFormatter()
dateFormatter.calendar = gregorian
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "M-d-y hh:mma"
let components = text.components(separatedBy: " ").compactMap { $0.isEmpty ? nil : $0 }
guard components.count >= 4 else { return nil }
let size = Int64(components[2]) ?? -1
let date = components[0..<2].joined(separator: " ")
let name = components[3..<components.count].joined(separator: " ")
guard name != "." && name != ".." else { return nil }
let path = (path as NSString).appendingPathComponent(name).replacingOccurrences(of: "/", with: "", options: .anchored)
let file = FileObject(url: url(of: path), name: name, path: "/" + path)
file.type = components[2] == "<DIR>" ? .directory : .regular
file.size = size
if let parsedDate = dateFormatter.date(from: date) {
file.modifiedDate = parsedDate
}
return file
}
func parseMLST(_ text: String, in path: String) -> FileObject? {
var components = text.components(separatedBy: ";").compactMap { $0.isEmpty ? nil : $0 }
var components = text.components(separatedBy: ";").flatMap { $0.isEmpty ? nil : $0 }
guard components.count > 1 else { return nil }
let nameOrPath = components.removeLast().trimmingCharacters(in: .whitespacesAndNewlines)
@@ -1030,7 +885,7 @@ internal extension FTPFileProvider {
var attributes = [String: String]()
for component in components {
let keyValue = component.components(separatedBy: "=").compactMap { $0.isEmpty ? nil : $0 }
let keyValue = component.components(separatedBy: "=") .flatMap { $0.isEmpty ? nil : $0 }
guard keyValue.count >= 2, !keyValue[0].isEmpty else { continue }
attributes[keyValue[0].lowercased()] = keyValue.dropFirst().joined(separator: "=")
}
@@ -1081,25 +936,21 @@ internal extension FTPFileProvider {
}
/// Contains error code and description returned by FTP/S provider.
public struct FileProviderFTPError: LocalizedError {
public struct FileProviderFTPError: Error {
/// HTTP status code returned for error by server.
public let code: Int
/// Path of file/folder casued that error
public let path: String
/// Contents returned by server as error description
public let serverDescription: String?
public let errorDescription: String?
init(code: Int, path: String, serverDescription: String?) {
init(code: Int, path: String, errorDescription: String?) {
self.code = code
self.path = path
self.serverDescription = serverDescription
self.errorDescription = errorDescription
}
init(message response: String) {
self.init(message: response, path: "")
}
init(message response: String, path: String) {
init(message response: String, path: String = "") {
let message = response.components(separatedBy: .newlines).last ?? "No Response"
#if swift(>=4.0)
let startIndex = (message.index(of: "-") ?? message.index(of: " ")) ?? message.startIndex
@@ -1111,16 +962,12 @@ public struct FileProviderFTPError: LocalizedError {
self.path = path
if code > 0 {
#if swift(>=4.0)
self.serverDescription = message[startIndex...].trimmingCharacters(in: .whitespacesAndNewlines)
self.errorDescription = message[startIndex...].trimmingCharacters(in: .whitespacesAndNewlines)
#else
self.serverDescription = message.substring(from: startIndex).trimmingCharacters(in: .whitespacesAndNewlines)
self.errorDescription = message.substring(from: startIndex).trimmingCharacters(in: .whitespacesAndNewlines)
#endif
} else {
self.serverDescription = message
self.errorDescription = message
}
}
public var errorDescription: String? {
return serverDescription
}
}
+6 -36
View File
@@ -9,7 +9,7 @@
import Foundation
/// Containts path, url and attributes of a file or resource.
open class FileObject: NSObject {
open class FileObject: Equatable {
/// A `Dictionary` contains file information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
@@ -19,7 +19,6 @@ open class FileObject: NSObject {
internal init(url: URL?, name: String, path: String) {
self.allValues = [URLResourceKey: Any]()
super.init()
if let url = url {
self.url = url
}
@@ -148,18 +147,6 @@ open class FileObject: NSObject {
open var isSymLink: Bool {
return self.type == .symbolicLink
}
}
extension FileObject {
open override var hashValue: Int {
let hashURL = self.url.hashValue
let hashSize = self.size.hashValue
return (hashURL << 7) &+ hashURL &+ hashSize
}
open override var hash: Int {
return self.hashValue
}
/// Check `FileObject` equality
public static func ==(lhs: FileObject, rhs: FileObject) -> Bool {
@@ -172,26 +159,19 @@ extension FileObject {
}
#else
if type(of: lhs) != type(of: rhs) {
return false
return false
}
#endif
if let rurl = rhs.allValues[.fileURLKey] as? URL, let lurl = lhs.allValues[.fileURLKey] as? URL {
return rurl == lurl && rhs.size == lhs.size
return rurl == lurl
}
return rhs.path == lhs.path && rhs.size == lhs.size && rhs.modifiedDate == lhs.modifiedDate
}
open override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? FileObject else { return false }
return self == object
}
}
extension FileObject {
internal func mapPredicate() -> [String: Any] {
let mapDict: [URLResourceKey: String] = [.fileURLKey: "url", .nameKey: "name", .pathKey: "path",
.fileSizeKey: "fileSize", .creationDateKey: "creationDate",
.fileSizeKey: "filesize", .creationDateKey: "creationDate",
.contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden",
.isWritableKey: "isWritable", .serverDateKey: "serverDate",
.entryTagKey: "entryTag", .mimeTypeKey: "mimeType"]
@@ -204,7 +184,6 @@ extension FileObject {
}
}
result["eTag"] = result["entryTag"]
result["filesize"] = result["fileSize"]
result["isReadOnly"] = self.isReadOnly
result["isDirectory"] = self.isDirectory
result["isRegularFile"] = self.isRegularFile
@@ -245,7 +224,7 @@ extension FileObject {
}
/// Containts attributes of a provider.
open class VolumeObject: NSObject {
open class VolumeObject {
/// A `Dictionary` contains volume information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
@@ -272,16 +251,7 @@ open class VolumeObject: NSObject {
allValues[.volumeNameKey] = newValue
}
}
/// The root directory of the resources volume, returned as an `URL` object.
open internal(set) var uuid: String? {
get {
return allValues[.volumeUUIDStringKey] as? String
}
set {
allValues[.volumeUUIDStringKey] = newValue
}
}
/// the volumes capacity in bytes, return -1 if is undetermined.
open internal(set) var totalCapacity: Int64 {
+68 -165
View File
@@ -109,7 +109,7 @@ public protocol FileProviderBasic: class, NSSecureCoding {
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (fileSize >= 10000)")
NSPredicate(format: "(name CONTAINS[c] 'hello') && (filesize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
@@ -140,7 +140,7 @@ public protocol FileProviderBasic: class, NSSecureCoding {
func url(of path: String) -> URL
/// Returns the relative path of url, without percent encoding. Even if url is absolute or
/// Returns the relative path of url, wothout percent encoding. Even if url is absolute or
/// retrieved from another provider, it will try to resolve the url against `baseURL` of
/// current provider. It's highly recomended to use this method for displaying purposes.
///
@@ -153,8 +153,7 @@ public protocol FileProviderBasic: class, NSSecureCoding {
/// - Note: To prevent race condition, use this method wisely and avoid it as far possible.
///
/// - Parameter success: indicated server is reachable or not.
/// - Parameter error: `Error` returned by server if occured.
func isReachable(completionHandler: @escaping(_ success: Bool, _ error: Error?) -> Void)
func isReachable(completionHandler: @escaping(_ success: Bool) -> Void)
}
extension FileProviderBasic {
@@ -163,33 +162,6 @@ extension FileProviderBasic {
return self.searchFiles(path: path, recursive: recursive, query: predicate, foundItemHandler: foundItemHandler, completionHandler: completionHandler)
}
/**
Search files inside directory using query asynchronously.
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (filesize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
- Note: Don't pass Spotlight predicates to this method directly, use `FileProvider.convertSpotlightPredicateTo()` method to get usable predicate.
- Important: A file name criteria should be provided for Dropbox.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
- files: all files meat the `query` criteria.
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
func searchFiles(path: String, recursive: Bool, query: NSPredicate, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
return searchFiles(path: path, recursive: recursive, query: query, foundItemHandler: nil, completionHandler: completionHandler)
}
/// The maximum number of queued operations that can execute at the same time.
///
/// The default value of this property is `OperationQueue.defaultMaxConcurrentOperationCount`.
@@ -241,6 +213,12 @@ public protocol FileProviderBasicRemote: FileProviderBasic {
var validatingCache: Bool { get set }
}
internal protocol FileProviderBasicRemoteInternal: FileProviderBasic {
var completionHandlersForTasks: [Int: SimpleCompletionHandler] { get set }
var downloadCompletionHandlersForTasks: [Int: (URL) -> Void] { get set }
var dataCompletionHandlersForTasks: [Int: (Data) -> Void] { get set }
}
internal extension FileProviderBasicRemote {
func returnCachedDate(with request: URLRequest, validatingCache: Bool, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> Bool {
guard let cache = self.cache else { return false }
@@ -424,6 +402,14 @@ public protocol FileProviderOperations: FileProviderBasic {
}
public extension FileProviderOperations {
/// *OBSOLETED:* Use Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.
@available(*, obsoleted: 0.23, message: "Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.")
@discardableResult
public func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (at as NSString).appendingPathComponent(file)
return (self as? FileProviderReadWrite)?.writeContents(path: path, contents: data, completionHandler: completionHandler)
}
@discardableResult
public func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> Progress? {
return self.moveItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
@@ -569,72 +555,6 @@ extension FileProviderReadWrite {
}
}
/// Defines method for fetching file contents progressivly
public protocol FileProviderReadWriteProgressive {
/**
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- progressHandler: a closure which will be called every time a new data received from server.
- position: Start position of returned data, indexed from zero.
- data: returned `Data` from server.
- completionHandler: a closure which will be called after receiving is completed or an error occureed.
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
func contents(path: String, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress?
/**
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- responseHandler: a closure which will be called after fetching server response.
- response: `URLResponse` returned from server.
- progressHandler: a closure which will be called every time a new data received from server.
- position: Start position of returned data, indexed from zero.
- data: returned `Data` from server.
- completionHandler: a closure which will be called after receiving is completed or an error occureed.
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
func contents(path: String, responseHandler: ((_ response: URLResponse) -> Void)?, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress?
/**
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- offset: First byte index which should be read. **Starts from 0.**
- length: Bytes count of data. Pass `-1` to read until the end of file.
- responseHandler: a closure which will be called after fetching server response.
- response: `URLResponse` returned from server.
- progressHandler: a closure which will be called every time a new data received from server.
- position: Start position of returned data, indexed from offset.
- data: returned `Data` from server.
- completionHandler: a closure which will be called after receiving is completed or an error occureed.
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
func contents(path: String, offset: Int64, length: Int, responseHandler: ((_ response: URLResponse) -> Void)?, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress?
}
public extension FileProviderReadWriteProgressive {
@discardableResult
public func contents(path: String, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
return contents(path: path, offset: 0, length: -1, responseHandler: nil, progressHandler: progressHandler, completionHandler: completionHandler)
}
@discardableResult
public func contents(path: String, responseHandler: ((_ response: URLResponse) -> Void)?, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
return contents(path: path, offset: 0, length: -1, responseHandler: responseHandler, progressHandler: progressHandler, completionHandler: completionHandler)
}
}
/// Allows a file provider to notify changes occured
public protocol FileProviderMonitor: FileProviderBasic {
@@ -667,7 +587,6 @@ public protocol FileProviderMonitor: FileProviderBasic {
func isRegisteredForNotification(path: String) -> Bool
}
#if os(macOS) || os(iOS) || os(tvOS)
/// Allows undo file operations done by provider
public protocol FileProvideUndoable: FileProviderOperations {
/// To initialize undo manager either call `setupUndoManager()` or set it manually.
@@ -723,7 +642,6 @@ public extension FileProvideUndoable {
self.undoManager?.levelsOfUndo = 10
}
}
#endif
/// This protocol defines method to share a public link with other users
public protocol FileProviderSharing {
@@ -749,7 +667,7 @@ public protocol FileProvider: FileProviderOperations, FileProviderReadWrite, NSC
}
internal let pathTrimSet = CharacterSet(charactersIn: " /")
public extension FileProviderBasic {
extension FileProviderBasic {
public var type: String {
#if swift(>=3.1)
return Swift.type(of: self).type
@@ -779,12 +697,20 @@ public extension FileProviderBasic {
}
// resolve url string against baseurl
guard let baseURL = self.baseURL else { return url.absoluteString }
let standardRelativePath = url.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").replacingOccurrences(of: "/", with: "", options: .anchored)
if URLComponents(string: standardRelativePath)?.host?.isEmpty ?? true {
if baseURL?.isFileURL ?? false {
guard let baseURL = self.baseURL?.standardizedFileURL else { return url.absoluteString }
let standardPath = url.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardBase = baseURL.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardRelativePath = standardPath.replacingOccurrences(of: standardBase, with: "/").replacingOccurrences(of: "/", with: "", options: .anchored)
return standardRelativePath.removingPercentEncoding ?? standardRelativePath
} else {
return relativePath.replacingOccurrences(of: "/", with: "", options: .anchored)
guard let baseURL = self.baseURL else { return url.absoluteString }
let standardRelativePath = url.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").replacingOccurrences(of: "/", with: "", options: .anchored)
if URLComponents(string: standardRelativePath)?.host?.isEmpty ?? true {
return standardRelativePath.removingPercentEncoding ?? standardRelativePath
} else {
return relativePath.replacingOccurrences(of: "/", with: "", options: .anchored)
}
}
}
@@ -830,19 +756,12 @@ public extension FileProviderBasic {
internal func urlError(_ path: String, code: URLError.Code) -> Error {
let fileURL = self.url(of: path)
let userInfo: [String: Any] = [NSURLErrorKey: fileURL,
NSURLErrorFailingURLErrorKey: fileURL,
NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString,
]
return URLError(code, userInfo: userInfo)
return URLError(code, userInfo: [NSURLErrorKey: fileURL, NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString])
}
internal func cocoaError(_ path: String, code: CocoaError.Code) -> Error {
let fileURL = self.url(of: path)
let userInfo: [String: Any] = [NSFilePathErrorKey: path,
NSURLErrorKey: fileURL,
]
return CocoaError(code, userInfo: userInfo)
return CocoaError(code, userInfo: [NSFilePathErrorKey: path, NSURLErrorKey: fileURL])
}
internal func NotImplemented(_ fn: String = #function, file: StaticString = #file) {
@@ -852,6 +771,12 @@ public extension FileProviderBasic {
/// Define methods to get preview and thumbnail for files or folders
public protocol ExtendedFileProvider: FileProviderBasic {
/// Returuns true if thumbnail preview is supported by provider and file type accordingly.
///
/// - Parameter path: path of file.
/// - Returns: A `Bool` idicates path can have thumbnail.
func thumbnailOfFileSupported(path: String) -> Bool
/// Returns true if provider supports fetching properties of file like dimensions, duration, etc.
/// Usually media or document files support these meta-infotmations.
///
@@ -859,30 +784,6 @@ public protocol ExtendedFileProvider: FileProviderBasic {
/// - Returns: A `Bool` idicates path can have properties.
func propertiesOfFileSupported(path: String) -> Bool
/**
Fetching properties of file like dimensions, duration, etc. It's variant depending on file type.
Images, videos and audio files meta-information will be returned.
- Note: `LocalFileInformationGenerator` variables can be set to change default behavior of
thumbnail and properties generator of `LocalFileProvider`.
- Parameters:
- path: path of file.
- completionHandler: a closure with result of preview image or error.
- propertiesDictionary: A `Dictionary` of proprty keys and values.
- keys: An `Array` contains ordering of keys.
- error: Error returned by system.
*/
@discardableResult
func propertiesOfFile(path: String, completionHandler: @escaping (_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void) -> Progress?
#if os(macOS) || os(iOS) || os(tvOS)
/// Returuns true if thumbnail preview is supported by provider and file type accordingly.
///
/// - Parameter path: path of file.
/// - Returns: A `Bool` idicates path can have thumbnail.
func thumbnailOfFileSupported(path: String) -> Bool
/**
Generates and returns a thumbnail preview of document asynchronously. The defualt dimension of returned image is different
regarding provider type, usually 64x64 pixels.
@@ -893,8 +794,7 @@ public protocol ExtendedFileProvider: FileProviderBasic {
- image: `NSImage`/`UIImage` object contains preview.
- error: `Error` returned by system.
*/
@discardableResult
func thumbnailOfFile(path: String, completionHandler: @escaping (_ image: ImageClass?, _ error: Error?) -> Void) -> Progress?
func thumbnailOfFile(path: String, completionHandler: @escaping (_ image: ImageClass?, _ error: Error?) -> Void)
/**
Generates and returns a thumbnail preview of document asynchronously. The defualt dimension of returned image is different
@@ -910,16 +810,28 @@ public protocol ExtendedFileProvider: FileProviderBasic {
- image: `NSImage`/`UIImage` object contains preview.
- error: `Error` returned by system.
*/
@discardableResult
func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping (_ image: ImageClass?, _ error: Error?) -> Void) -> Progress?
#endif
func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping (_ image: ImageClass?, _ error: Error?) -> Void)
/**
Fetching properties of file like dimensions, duration, etc. It's variant depending on file type.
Images, videos and audio files meta-information will be returned.
- Note: `LocalFileInformationGenerator` variables can be set to change default behavior of
thumbnail and properties generator of `LocalFileProvider`.
- Parameters:
- path: path of file.
- completionHandler: a closure with result of preview image or error.
- propertiesDictionary: A `Dictionary` of proprty keys and values.
- keys: An `Array` contains ordering of keys.
- error: Error returned by system.
*/
func propertiesOfFile(path: String, completionHandler: @escaping (_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)
}
#if os(macOS) || os(iOS) || os(tvOS)
extension ExtendedFileProvider {
@discardableResult
public func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) -> Progress? {
return self.thumbnailOfFile(path: path, dimension: nil, completionHandler: completionHandler)
public func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
self.thumbnailOfFile(path: path, dimension: nil, completionHandler: completionHandler)
}
internal static func convertToImage(pdfData: Data?, page: Int = 1) -> ImageClass? {
@@ -948,11 +860,7 @@ extension ExtendedFileProvider {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
#if os(macOS)
#if swift(>=4.0)
let ppp = Int(NSScreen.main?.backingScaleFactor ?? 1) // fetch device is retina or not
#elseif swift(>=3.3)
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
#elseif swift(>=3.2)
#if swift(>=3.2)
let ppp = Int(NSScreen.main?.backingScaleFactor ?? 1) // fetch device is retina or not
#else
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
@@ -960,23 +868,15 @@ extension ExtendedFileProvider {
size.width *= CGFloat(ppp)
size.height *= CGFloat(ppp)
#if swift(>=4.0)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB,
bytesPerRow: 0, bitsPerPixel: 0)
#elseif swift(>=3.3)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
bytesPerRow: 0, bitsPerPixel: 0)
#elseif swift(>=3.2)
#if swift(>=3.2)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB,
bytesPerRow: 0, bitsPerPixel: 0)
#else
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
bytesPerRow: 0, bitsPerPixel: 0)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
bytesPerRow: 0, bitsPerPixel: 0)
#endif
guard let context = NSGraphicsContext(bitmapImageRep: rep!) else {
@@ -1052,7 +952,6 @@ extension ExtendedFileProvider {
#endif
}
}
#endif
/// Operation type description of file operation, included files path in associated values.
public enum FileOperationType: CustomStringConvertible {
@@ -1143,6 +1042,10 @@ public enum FileOperationType: CustomStringConvertible {
}
}
/// Allows to get progress or cancel an in-progress operation, useful for remote providers
@available(*, obsoleted: 1.0, message: "Use Foudation.Progress class instead.")
public protocol OperationHandle {}
/// Delegate methods for reporting provider's operation result and progress, when it's ready to update
/// user interface.
/// All methods are called in main thread to avoids UI bugs.
+94 -180
View File
@@ -14,9 +14,14 @@ import Foundation
No instance of this class should (and can) be created. Use derived classes instead. It leads to a crash with `fatalError()`.
*/
open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite, FileProviderReadWriteProgressive {
open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class var type: String { fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.") }
open let baseURL: URL?
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
@available(*, obsoleted: 0.22, message: "This property is redundant with almost no use internally.")
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue {
willSet {
@@ -76,7 +81,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
This is parent initializer for subclasses. Using this method on `HTTPFileProvider` will fail as `type` is not implemented.
- Parameters:
- baseURL: Location of server.
- baseURL: Location of WebDAV server.
- credential: An `URLCredential` object with `user` and `password`.
- cache: A URLCache to cache downloaded files and contents.
*/
@@ -97,8 +102,6 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
super.init()
}
public required convenience init?(coder aDecoder: NSCoder) {
@@ -115,7 +118,6 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
} else {
_session?.finishTasksAndInvalidate()
}
longpollSession.invalidateAndCancel()
}
public func encode(with aCoder: NSCoder) {
@@ -145,79 +147,35 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
@discardableResult
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func isReachable(completionHandler: @escaping (_ success: Bool, _ error: Error?) -> Void) {
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
self.storageProperties { volume in
if volume != nil {
completionHandler(volume != nil, nil)
return
} else {
self.contentsOfDirectory(path: "", completionHandler: { (files, error) in
completionHandler(false, error)
})
}
}
}
// Nothing special for these two funcs, just reimplemented to workaround a bug in swift to allow override in subclasses!
open func url(of path: String) -> URL {
var rpath: String = path
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? rpath
if let baseURL = baseURL {
if rpath.hasPrefix("/") {
rpath.remove(at: rpath.startIndex)
}
return URL(string: rpath, relativeTo: baseURL) ?? baseURL
} else {
return URL(string: rpath) ?? URL(string: "/")!
}
}
open func relativePathOf(url: URL) -> String {
// check if url derieved from current base url
let relativePath = url.relativePath
if !relativePath.isEmpty, url.baseURL == self.baseURL {
return (relativePath.removingPercentEncoding ?? relativePath).replacingOccurrences(of: "/", with: "", options: .anchored)
}
// resolve url string against baseurl
guard let baseURL = self.baseURL else { return url.absoluteString }
let standardRelativePath = url.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").replacingOccurrences(of: "/", with: "", options: .anchored)
if URLComponents(string: standardRelativePath)?.host?.isEmpty ?? true {
return standardRelativePath.removingPercentEncoding ?? standardRelativePath
} else {
return relativePath.replacingOccurrences(of: "/", with: "", options: .anchored)
completionHandler(volume != nil)
}
}
open weak var fileOperationDelegate: FileOperationDelegate?
@discardableResult
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
@discardableResult
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.move(source: path, destination: toPath), overwrite: overwrite, completionHandler: completionHandler)
}
@discardableResult
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.copy(source: path, destination: toPath), overwrite: overwrite, completionHandler: completionHandler)
}
@discardableResult
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
@discardableResult
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
@@ -232,10 +190,9 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
return nil
}
let request = self.request(for: operation, overwrite: overwrite)
return upload_file(toPath, request: request, localFile: localFile, operation: operation, completionHandler: completionHandler)
return upload_simple(toPath, request: request, localFile: localFile, operation: operation, completionHandler: completionHandler)
}
@discardableResult
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: destURL.absoluteString)
let request = self.request(for: operation)
@@ -286,11 +243,12 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
- error: `Error` returned by system if occured.
- Returns: An `Progress` to get progress or cancel progress.
*/
@discardableResult
open func contents(path: String, offset: Int64 = 0, length: Int = -1, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
open func contents(path: String, offset: Int64 = 0, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.fetch(path: path)
var request = self.request(for: operation)
request.setValue(rangeWithOffset: offset, length: length)
if offset > 0 {
request.addValue("bytes \(offset)-", forHTTPHeaderField: "Range")
}
var position: Int64 = offset
return download_progressive(path: path, request: request, operation: operation, responseHandler: responseHandler, progressHandler: { data in
progressHandler(position, data)
@@ -298,7 +256,6 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
}, completionHandler: (completionHandler ?? { _ in return }))
}
@discardableResult
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
if length == 0 || offset < 0 {
dispatch_queue.async {
@@ -329,14 +286,13 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
})
}
@discardableResult
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
public func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let request = self.request(for: operation, overwrite: overwrite, attributes: [.contentModificationDateKey: Date()])
return upload_data(path, request: request, data: data ?? Data(), operation: operation, completionHandler: completionHandler)
return upload_simple(path, request: request, data: data ?? Data(), operation: operation, completionHandler: completionHandler)
}
internal func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey: Any] = [:]) -> URLRequest {
@@ -351,22 +307,12 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
// WebDAV will override this function
}
/**
This is the main function to init urlsession task for specified file operation.
You won't need to override this function unless another network request must be done before intended operation,
such as retrieving file id from file path. Then you must call `super.doOperation()`
In case you have to call super method asyncronously, create a `Progress` object and pass ot to `progress` parameter.
*/
@discardableResult
internal func doOperation(_ operation: FileOperationType, overwrite: Bool = false, progress: Progress? = nil,
completionHandler: SimpleCompletionHandler) -> Progress? {
fileprivate func doOperation(_ operation: FileOperationType, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let progress = progress ?? Progress(totalUnitCount: 1)
let progress = Progress(totalUnitCount: 1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
@@ -375,14 +321,12 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
if let response = response as? HTTPURLResponse {
if response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
serverError = self.serverError(with: code, path: operation.source, data: data)
}
if FileProviderHTTPErrorCode(rawValue: response.statusCode) == .multiStatus, let data = data {
self.multiStatusHandler(source: operation.source, data: data, completionHandler: completionHandler)
}
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
serverError = self.serverError(with: code, path: operation.source, data: data)
}
if let response = response as? HTTPURLResponse, FileProviderHTTPErrorCode(rawValue: response.statusCode) == .multiStatus, let data = data {
self.multiStatusHandler(source: operation.source, data: data, completionHandler: completionHandler)
}
if serverError == nil && error == nil {
@@ -390,7 +334,6 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
} else {
progress.cancel()
}
completionHandler?(serverError ?? error)
self.delegateNotify(operation, error: serverError ?? error)
})
@@ -403,43 +346,35 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
return progress
}
// codebeat:disable[ARITY]
/**
This method should be used in subclasses to fetch directory content from servers which support paginated results.
Almost all HTTP based provider, except WebDAV, supports this method.
- Important: Please use `[weak self]` when implementing handlers to prevent retain cycles. In these cases,
return `nil` as the result of handler as the operation will be aborted.
- Parameters:
- path: path of directory which enqueued for listing, for informational use like errpr reporting.
- requestHandler: Get token of next page and returns appropriate `URLRequest` to be sent to server.
handler can return `nil` to cancel entire operation.
- token: Token of the page which `URLRequest` is needed, token will be `nil` for initial page.
- pageHandler: Handler which is called after fetching results of a page to parse data. will return parse result as
array of `FileObject` or error if data is nil or parsing is failed. Method will not continue to next page if
`error` is returned, otherwise `nextToken` will be used for next page. `nil` value for `newToken` will indicate
last page of directory contents.
- data: Raw data returned from server. Handler should parse them and return files.
- progress: `Progress` object that `completedUnits` will be increased when a new `FileObject` is parsed in method.
- completionHandler: All file objects returned by `pageHandler` will be passed to this handler, or error if occured.
This handler will be called when `pageHandler` returns `nil for `newToken`.
- contents: all files parsed via `pageHandler` will be return aggregated.
- error: `Error` returned by server. `nil` means success. If exists, it means `contents` are incomplete.
*/
internal func paginated(_ path: String, requestHandler: @escaping (_ token: String?) -> URLRequest?,
pageHandler: @escaping (_ data: Data?, _ progress: Progress) -> (files: [FileObject], error: Error?, newToken: String?),
completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress {
/// This method should be used in subclasses to fetch directory content from servers which support paginated results.
/// Almost all HTTP based provider, except WebDAV, supports this method.
///
/// - Important: Please use `[weak self]` when implementing handlers to prevent retain cycles. In these cases,
/// return `nil` as the result of handler as the operation will be aborted.
///
/// - Parameters:
/// - path: path of directory which enqueued for listing, for informational use like errpr reporting.
/// - requestHandler: Get token of next page and returns appropriate `URLRequest` to be sent to server.
/// handler can return `nil` to cancel entire operation.
/// - token: Token of the page which `URLRequest` is needed, token will be `nil` for initial page. .
/// - pageHandler: Handler which is called after fetching results of a page to parse data. will return parse result as
/// array of `FileObject` or error if data is nil or parsing is failed. Method will not continue to next page if
/// `error` is returned, otherwise `nextToken` will be used for next page. `nil` value for `newToken` will indicate
/// last page of directory contents.
/// - data: Raw data returned from server. Handler should parse them and return files.
/// - progress: `Progress` object that `completedUnits` will be increased when a new `FileObject` is parsed in method.
/// - completionHandler: All file objects returned by `pageHandler` will be passed to this handler, or error if occured.
/// This handler will be called when `pageHandler` returns `nil for `newToken`.
/// - contents: all files parsed via `pageHandler` will be return aggregated.
/// - error: `Error` returned by server. `nil` means success. If exists, it means `contents` are incomplete.
internal func paginated(_ path: String, requestHandler: @escaping (_ token: String?) -> URLRequest?, pageHandler: @escaping (_ data: Data?, _ progress: Progress) -> (files: [FileObject], error: Error?, newToken: String?), completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress {
let progress = Progress(totalUnitCount: -1)
self.paginated(path, startToken: nil, currentProgress: progress, previousResult: [], requestHandler: requestHandler, pageHandler: pageHandler, completionHandler: completionHandler)
return progress
}
private func paginated(_ path: String, startToken: String?, currentProgress progress: Progress,
previousResult: [FileObject], requestHandler: @escaping (_ token: String?) -> URLRequest?,
pageHandler: @escaping (_ data: Data?, _ progress: Progress) -> (files: [FileObject], error: Error?, newToken: String?),
completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
// codebeat:disable[ARITY]
private func paginated(_ path: String, startToken: String?, currentProgress progress: Progress, previousResult: [FileObject], requestHandler: @escaping (_ token: String?) -> URLRequest?, pageHandler: @escaping (_ data: Data?, _ progress: Progress) -> (files: [FileObject], error: Error?, newToken: String?), completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
guard !progress.isCancelled, let request = requestHandler(startToken) else {
return
}
@@ -478,38 +413,16 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
internal var maxUploadSimpleSupported: Int64 { return Int64.max }
func upload_task(_ targetPath: String, progress: Progress, task: URLSessionTask, operation: FileOperationType,
completionHandler: SimpleCompletionHandler) -> Void {
var progress = progress
var allData = Data()
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { data in
allData.append(data)
internal func upload_simple(_ targetPath: String, request: URLRequest, data: Data? = nil, localFile: URL? = nil, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
let size: Int64
if let data = data {
size = Int64(data.count)
} else if let localFile = localFile {
let fSize = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize
size = Int64(fSize ?? -1)
} else {
return nil
}
completionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
var responseError: FileProviderHTTPError?
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = self?.serverError(with: rCode, path: targetPath, data: allData)
}
if !(responseError == nil && error == nil) {
progress.cancel()
}
completionHandler?(responseError ?? error)
self?.delegateNotify(operation, error: responseError ?? error)
}
task.taskDescription = operation.json
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
}
func upload_data(_ targetPath: String, request: URLRequest, data: Data, operation: FileOperationType,
completionHandler: SimpleCompletionHandler) -> Progress? {
let size: Int64 = Int64(data.count)
if size > maxUploadSimpleSupported {
let error = self.serverError(with: .payloadTooLarge, path: targetPath, data: nil)
completionHandler?(error)
@@ -517,49 +430,52 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
return nil
}
let progress = Progress(totalUnitCount: size)
var progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
progress.totalUnitCount = size
let task = session.uploadTask(with: request, from: data)
self.upload_task(targetPath, progress: progress, task: task, operation: operation, completionHandler: completionHandler)
return progress
}
func upload_file(_ targetPath: String, request: URLRequest, localFile: URL, operation: FileOperationType,
completionHandler: SimpleCompletionHandler) -> Progress? {
let fSize = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize
let size = Int64(fSize ?? -1)
if size > maxUploadSimpleSupported {
let error = self.serverError(with: .payloadTooLarge, path: targetPath, data: nil)
completionHandler?(error)
self.delegateNotify(operation, error: error)
return nil
let taskHandler = { (task: URLSessionTask) -> Void in
completionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
var responseError: FileProviderHTTPError?
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
// We can't fetch server result from delegate!
responseError = self?.serverError(with: rCode, path: targetPath, data: nil)
}
if !(responseError == nil && error == nil) {
progress.cancel()
}
completionHandler?(responseError ?? error)
self?.delegateNotify(operation, error: responseError ?? error)
}
task.taskDescription = operation.json
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
}
let progress = Progress(totalUnitCount: size)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: localFile, options: .forUploading, error: &error, byAccessor: { (url) in
let task = self.session.uploadTask(with: request, fromFile: localFile)
self.upload_task(targetPath, progress: progress, task: task, operation: operation, completionHandler: completionHandler)
})
if let error = error {
completionHandler?(error)
if let data = data {
let task = session.uploadTask(with: request, from: data)
taskHandler(task)
} else if let localFile = localFile {
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: localFile, options: .forUploading, error: &error, byAccessor: { (url) in
let task = self.session.uploadTask(with: request, fromFile: localFile)
taskHandler(task)
})
if let error = error {
completionHandler?(error)
}
}
return progress
}
internal func download_progressive(path: String, request: URLRequest, operation: FileOperationType,
responseHandler: ((_ response: URLResponse) -> Void)? = nil,
progressHandler: @escaping (_ data: Data) -> Void,
completionHandler: @escaping (_ error: Error?) -> Void) -> Progress? {
internal func download_progressive(path: String, request: URLRequest, operation: FileOperationType, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ data: Data) -> Void, completionHandler: @escaping (_ error: Error?) -> Void) -> Progress? {
var progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
@@ -572,8 +488,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
}
}
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak task, weak self] data in
task.flatMap { self?.delegateNotify(operation, progress: Double($0.countOfBytesReceived) / Double($0.countOfBytesExpectedToReceive)) }
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { data in
progressHandler(data)
}
@@ -596,8 +511,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
return progress
}
internal func download_simple(path: String, request: URLRequest, operation: FileOperationType,
completionHandler: @escaping ((_ tempURL: URL?, _ error: Error?) -> Void)) -> Progress? {
internal func download_simple(path: String, request: URLRequest, operation: FileOperationType, completionHandler: @escaping ((_ tempURL: URL?, _ error: Error?) -> Void)) -> Progress? {
var progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
+63 -85
View File
@@ -14,9 +14,12 @@ import Foundation
it uses `FileManager` foundation class with some additions like searching and reading a portion of file.
*/
open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndoable {
open class var type: String { return "Local" }
open fileprivate(set) var baseURL: URL?
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
@available(*, obsoleted: 0.21, message: "This property is redundant with almost no use internally.")
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
@@ -28,9 +31,8 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
open private(set) var opFileManager = FileManager()
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
#if os(macOS) || os(iOS) || os(tvOS)
open var undoManager: UndoManager? = nil
/**
Forces file operations to use `NSFileCoordinating`, should be set `true` if:
- Files are on ubiquity (iCloud) container.
@@ -41,7 +43,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
otherwise it's `false` to accelerate operations.
*/
open var isCoorinating: Bool
#endif
/**
Initializes provider for the specified common directory in the requested domains.
@@ -55,7 +56,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
self.init(baseURL: FileManager.default.urls(for: directory, in: domainMask).first!)
}
#if os(macOS) || os(iOS) || os(tvOS)
/**
Failable initializer for the specified shared container directory, allows data and files to be shared among app
and extensions regarding sandbox requirements. Container ID is same with app group specified in project `Capabilities`
@@ -92,7 +92,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
try? fileManager.createDirectory(at: finalBaseURL, withIntermediateDirectories: true)
}
#endif
/// Initializes provider for the specified local URL.
///
@@ -114,14 +113,12 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
super.init()
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
return nil
}
self.init(baseURL: baseURL)
@@ -137,7 +134,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
open func encode(with aCoder: NSCoder) {
aCoder.encode(self.baseURL, forKey: "baseURL")
aCoder.encode(self.baseURL, forKey: "currentPath")
aCoder.encode(self.isCoorinating, forKey: "isCoorinating")
}
@@ -148,9 +145,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
public func copy(with zone: NSZone? = nil) -> Any {
let copy = LocalFileProvider(baseURL: self.baseURL!)
copy.undoManager = self.undoManager
#if os(macOS) || os(iOS) || os(tvOS)
copy.isCoorinating = self.isCoorinating
#endif
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
return copy
@@ -160,7 +155,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
dispatch_queue.async {
do {
let contents = try self.fileManager.contentsOfDirectory(at: self.url(of: path), includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants)
let filesAttributes = contents.compactMap({ (fileURL) -> LocalFileObject? in
let filesAttributes = contents.flatMap({ (fileURL) -> LocalFileObject? in
let path = self.relativePathOf(url: fileURL)
return LocalFileObject(fileWithPath: path, relativeTo: self.baseURL)
})
@@ -188,7 +183,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
}
@discardableResult
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
@@ -217,31 +211,12 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
return progress
}
open func isReachable(completionHandler: @escaping (_ success: Bool, _ error: Error?) -> Void) {
open func isReachable(completionHandler: @escaping (_ success: Bool) -> Void) {
dispatch_queue.async {
do {
let isReachable = try self.baseURL!.checkResourceIsReachable()
completionHandler(isReachable, nil)
} catch {
completionHandler(false, error)
}
completionHandler(self.fileManager.isReadableFile(atPath: self.baseURL!.path))
}
}
open func relativePathOf(url: URL) -> String {
// check if url derieved from current base url
let relativePath = url.relativePath
if !relativePath.isEmpty, url.baseURL == self.baseURL {
return (relativePath.removingPercentEncoding ?? relativePath).replacingOccurrences(of: "/", with: "", options: .anchored)
}
guard let baseURL = self.baseURL?.standardizedFileURL else { return url.absoluteString }
let standardPath = url.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardBase = baseURL.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardRelativePath = standardPath.replacingOccurrences(of: standardBase, with: "/").replacingOccurrences(of: "/", with: "", options: .anchored)
return standardRelativePath.removingPercentEncoding ?? standardRelativePath
}
open weak var fileOperationDelegate : FileOperationDelegate?
@discardableResult
@@ -253,13 +228,37 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
@discardableResult
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.move(source: path, destination: toPath)
return self.doOperation(operation, overwrite: overwrite, completionHandler: completionHandler)
let fileExists = ((try? self.url(of: toPath).checkResourceIsReachable()) ?? false) ||
((try? self.url(of: toPath).checkPromisedItemIsReachable()) ?? false)
if !overwrite && fileExists {
let e = self.cocoaError(toPath, code: .fileWriteFileExists)
dispatch_queue.async {
completionHandler?(e)
}
self.delegateNotify(operation, error: e)
return nil
}
return self.doOperation(operation, completionHandler: completionHandler)
}
@discardableResult
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: toPath)
return self.doOperation(operation, overwrite: overwrite, completionHandler: completionHandler)
let fileExists = ((try? self.url(of: toPath).checkResourceIsReachable()) ?? false) ||
((try? self.url(of: toPath).checkPromisedItemIsReachable()) ?? false)
if !overwrite && fileExists {
let e = self.cocoaError(toPath, code: .fileWriteFileExists)
dispatch_queue.async {
completionHandler?(e)
}
self.delegateNotify(operation, error: e)
return nil
}
return self.doOperation(operation, completionHandler: completionHandler)
}
@discardableResult
@@ -271,7 +270,18 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
@discardableResult
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
return self.doOperation(operation, overwrite: overwrite, forUploading: true, completionHandler: completionHandler)
let fileExists = ((try? self.url(of: toPath).checkResourceIsReachable()) ?? false) ||
((try? self.url(of: toPath).checkPromisedItemIsReachable()) ?? false)
if !overwrite && fileExists {
let e = self.cocoaError(toPath, code: .fileWriteFileExists)
dispatch_queue.async {
completionHandler?(e)
}
self.delegateNotify(operation, error: e)
return nil
}
return self.doOperation(operation, forUploading: true, completionHandler: completionHandler)
}
@discardableResult
@@ -280,17 +290,15 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
return self.doOperation(operation, completionHandler: completionHandler)
}
#if os(macOS) || os(iOS) || os(tvOS)
@objc dynamic func doSimpleOperation(_ box: UndoBox) {
guard let _ = self.undoManager else { return }
_ = self.doOperation(box.undoOperation) { (_) in
return
}
}
#endif
@discardableResult
fileprivate func doOperation(_ operation: FileOperationType, data: Data? = nil, overwrite: Bool = true, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
fileprivate func doOperation(_ operation: FileOperationType, data: Data? = nil, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
@@ -312,19 +320,13 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
let source: URL = urlofpath(path: sourcePath)
progress.setUserInfoObject(source, forKey: .fileURLKey)
let dest = destPath.map(urlofpath(path:))
if !overwrite, let dest = dest, /* fileExists */ ((try? dest.checkResourceIsReachable()) ?? false) ||
((try? dest.checkPromisedItemIsReachable()) ?? false) {
let e = self.cocoaError(destPath!, code: .fileWriteFileExists)
dispatch_queue.async {
completionHandler?(e)
}
self.delegateNotify(operation, error: e)
return nil
let dest: URL?
if let destPath = destPath {
dest = urlofpath(path: destPath)
} else {
dest = nil
}
#if os(macOS) || os(iOS) || os(tvOS)
if let undoManager = self.undoManager, let undoOp = self.undoOperation(for: operation) {
let undoBox = UndoBox(provider: self, operation: operation, undoOperation: undoOp)
undoManager.beginUndoGrouping()
@@ -334,7 +336,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
var successfulSecurityScopedResourceAccess = false
#endif
let operationHandler: (URL, URL?) -> Void = { source, dest in
do {
@@ -367,11 +368,9 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
default:
return
}
#if os(macOS) || os(iOS) || os(tvOS)
if successfulSecurityScopedResourceAccess {
source.stopAccessingSecurityScopedResource()
}
#endif
progress.completedUnitCount = progress.totalUnitCount
self.dispatch_queue.async {
@@ -379,11 +378,9 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
self.delegateNotify(operation)
} catch {
#if os(macOS) || os(iOS) || os(tvOS)
if successfulSecurityScopedResourceAccess {
source.stopAccessingSecurityScopedResource()
}
#endif
progress.cancel()
self.dispatch_queue.async {
completionHandler?(error)
@@ -392,7 +389,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
}
#if os(macOS) || os(iOS) || os(tvOS)
if isCoorinating {
successfulSecurityScopedResourceAccess = source.startAccessingSecurityScopedResource()
var intents = [NSFileAccessIntent]()
@@ -423,11 +419,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
operationHandler(source, dest)
}
}
#else
operation_queue.addOperation {
operationHandler(source, dest)
}
#endif
return progress
}
@@ -461,7 +452,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
}
#if os(macOS) || os(iOS) || os(tvOS)
if isCoorinating {
let intent = NSFileAccessIntent.readingIntent(with: url, options: .withoutChanges)
coordinated(intents: [intent], operationHandler: operationHandler, errorHandler: { error in
@@ -475,11 +465,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
operationHandler(url)
}
}
#else
dispatch_queue.async {
operationHandler(url)
}
#endif
return progress
}
@@ -545,7 +530,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
}
#if os(macOS) || os(iOS) || os(tvOS)
if isCoorinating {
let intent = NSFileAccessIntent.readingIntent(with: url, options: .withoutChanges)
coordinated(intents: [intent], operationHandler: operationHandler, errorHandler: { error in
@@ -557,11 +541,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
operationHandler(url)
}
}
#else
dispatch_queue.async {
operationHandler(url)
}
#endif
return progress
}
@@ -582,12 +562,16 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
return self.doOperation(operation, data: data ?? Data(), atomically: atomically, completionHandler: completionHandler)
}
fileprivate var monitors = [LocalFileMonitor]()
fileprivate var monitors = [LocalFolderMonitor]()
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
let url = self.url(of: path)
let monitor = LocalFileMonitor(url: url) {
let dirurl = self.url(of: path)
let isdir = (try? dirurl.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false
if !isdir {
return
}
let monitor = LocalFolderMonitor(url: dirurl) {
eventHandler()
}
monitor.start()
@@ -595,7 +579,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
open func unregisterNotifcation(path: String) {
var removedMonitor: LocalFileMonitor?
var removedMonitor: LocalFolderMonitor?
for (i, monitor) in monitors.enumerated() {
if self.relativePathOf(url: monitor.url) == path {
removedMonitor = monitors.remove(at: i)
@@ -652,10 +636,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
}
#if os(macOS) || os(iOS) || os(tvOS)
extension LocalFileProvider: FileProvideUndoable { }
internal extension LocalFileProvider {
func coordinated(intents: [NSFileAccessIntent], operationHandler: @escaping (_ url: URL) -> Void, errorHandler: ((_ error: Error) -> Void)? = nil) {
let coordinator = NSFileCoordinator(filePresenter: nil)
@@ -687,5 +667,3 @@ internal extension LocalFileProvider {
}
}
}
#endif
+7 -11
View File
@@ -89,21 +89,19 @@ public final class LocalFileObject: FileObject {
}
}
public final class LocalFileMonitor {
internal final class LocalFolderMonitor {
fileprivate let source: DispatchSourceFileSystemObject
fileprivate let descriptor: CInt
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: .default)
fileprivate var state: Bool = false
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
public var url: URL
var url: URL
/// Creates a folder monitor object with monitoring enabled.
public init(url: URL, handler: @escaping ()->Void) {
init(url: URL, handler: @escaping ()->Void) {
self.url = url
descriptor = open((url.absoluteURL as NSURL).fileSystemRepresentation, O_EVTONLY)
let event: DispatchSource.FileSystemEvent = url.fileIsDirectory ? [.write] : .all
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: event, queue: qq)
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: .write, queue: qq)
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
@@ -128,7 +126,7 @@ public final class LocalFileMonitor {
}
/// Starts sending notifications if currently stopped
public func start() {
func start() {
if !state {
state = true
source.resume()
@@ -136,7 +134,7 @@ public final class LocalFileMonitor {
}
/// Stops sending notifications if currently enabled
public func stop() {
func stop() {
if state {
state = false
source.suspend()
@@ -226,7 +224,6 @@ internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
}
}
#if os(macOS) || os(iOS) || os(tvOS)
class UndoBox: NSObject {
weak var provider: FileProvideUndoable?
let operation: FileOperationType
@@ -238,4 +235,3 @@ class UndoBox: NSObject {
self.undoOperation = undoOperation
}
}
#endif
+126 -256
View File
@@ -1,3 +1,4 @@
//
// OneDriveFileProvider.swift
// FileProvider
@@ -7,10 +8,8 @@
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS)
import CoreGraphics
#endif
/**
Allows accessing to OneDrive stored files, either hosted on Microsoft servers or business coprporate one.
This provider doesn't cache or save files internally, however you can set `useCache` and `cache` properties
@@ -93,13 +92,6 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
}
}
}
/// Microsoft Graph URL
public static var graphURL = URL(string: "https://graph.microsoft.com/")!
/// Microsoft Graph URL
public static var graphVersion = "v1.0"
/// Route for container, default is `.me`.
open let route: Route
@@ -117,9 +109,11 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
- cache: A URLCache to cache downloaded files and contents.
*/
@available(*, deprecated, message: "use init(credential:, serverURL:, route:, cache:) instead.")
public convenience init(credential: URLCredential?, serverURL: URL? = nil, drive: String?, cache: URLCache? = nil) {
let route: Route = drive.flatMap({ UUID(uuidString: $0) }).flatMap({ Route.drive(uuid: $0) }) ?? .me
self.init(credential: credential, serverURL: serverURL, route: route, cache: cache)
public init(credential: URLCredential?, serverURL: URL? = nil, drive: String?, cache: URLCache? = nil) {
let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")!
let refinedBaseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.route = drive.flatMap({ UUID(uuidString: $0) }).flatMap({ Route.drive(uuid: $0) }) ?? .me
super.init(baseURL: refinedBaseURL, credential: credential, cache: cache)
}
/**
@@ -137,8 +131,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
- cache: A URLCache to cache downloaded files and contents.
*/
public init(credential: URLCredential?, serverURL: URL? = nil, route: Route = .me, cache: URLCache? = nil) {
let baseURL = (serverURL?.absoluteURL ?? OneDriveFileProvider.graphURL)
.appendingPathComponent(OneDriveFileProvider.graphVersion, isDirectory: true)
let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")!
let refinedBaseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.route = route
super.init(baseURL: refinedBaseURL, credential: credential, cache: cache)
@@ -146,13 +139,13 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
public required convenience init?(coder aDecoder: NSCoder) {
let route: Route
if let driveId = aDecoder.decodeObject(of: NSString.self, forKey: "drive") as String?, let uuid = UUID(uuidString: driveId) {
if let driveId = aDecoder.decodeObject(forKey: "drive") as? String, let uuid = UUID(uuidString: driveId) {
route = .drive(uuid: uuid)
} else {
route = (aDecoder.decodeObject(forKey: "route") as? String).flatMap({ Route(rawValue: $0) }) ?? .me
}
self.init(credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"),
serverURL: aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL?,
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential,
serverURL: aDecoder.decodeObject(forKey: "baseURL") as? URL,
route: route)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
@@ -172,17 +165,6 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
return copy
}
/**
Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty array.
- Parameters:
- path: path to target directory. If empty, root will be iterated.
- completionHandler: a closure with result of directory entries or error.
- contents: An array of `FileObject` identifying the the directory entries.
- error: Error returned by system.
*/
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
_ = paginated(path, requestHandler: { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
@@ -209,17 +191,6 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
}, completionHandler: completionHandler)
}
/**
Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
- Parameters:
- path: path to target directory. If empty, attributes of root will be returned.
- completionHandler: a closure with result of directory entries or error.
- attributes: A `FileObject` containing the attributes of the item.
- error: Error returned by system.
*/
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
@@ -227,23 +198,20 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var fileObject: OneDriveFileObject?
if let response = response as? HTTPURLResponse, response.statusCode >= 400 {
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
}
if let json = data?.deserializeJSON(), let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: json) {
fileObject = file
if let json = data?.deserializeJSON(), let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: json) {
fileObject = file
}
}
completionHandler(fileObject, serverError ?? error)
})
task.resume()
}
/// Returns volume/provider information asynchronously.
/// - Parameter volumeInfo: Information of filesystem/Provider returned by system/server.
open override func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
let url = URL(string: route.drivePath, relativeTo: baseURL)!
var request = URLRequest(url: url)
var request = URLRequest(url: url(of: ""))
request.httpMethod = "GET"
request.setValue(authentication: self.credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -254,7 +222,6 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
let volume = VolumeObject(allValues: [:])
volume.url = request.url
volume.uuid = json["id"] as? String
volume.name = json["name"] as? String
volume.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
volume.totalCapacity = (json["quota"]?["total"] as? NSNumber)?.int64Value ?? -1
@@ -264,33 +231,8 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
task.resume()
}
/**
Search files inside directory using query asynchronously.
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (fileSize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
- Note: Don't pass Spotlight predicates to this method directly, use `FileProvider.convertSpotlightPredicateTo()` method to get usable predicate.
- Important: A file name criteria should be provided for Dropbox.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
- files: all files meat the `query` criteria.
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
@discardableResult
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).compactMap { $0.value as? String }.first
let queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).flatMap { $0.value as? String }.first
return paginated(path, requestHandler: { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
@@ -331,103 +273,50 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
}, completionHandler: completionHandler)
}
/**
Returns an independent url to access the file. Some providers like `Dropbox` due to their nature.
don't return an absolute url to be used to access file directly.
- Parameter path: Relative path of file or directory.
- Returns: An url, can be used to access to file directly.
*/
open override func url(of path: String) -> URL {
return OneDriveFileObject.url(of: path, modifier: nil, baseURL: baseURL!, route: route)
}
/**
Returns an independent url to access the file. Some providers like `Dropbox` due to their nature.
don't return an absolute url to be used to access file directly.
- Parameter path: Relative path of file or directory.
- Parameter modifier: Added to end of url to indicate what it can used for, e.g. `contents` to fetch data.
- Returns: An url, can be used to access to file directly.
*/
open func url(of path: String, modifier: String? = nil) -> URL {
return OneDriveFileObject.url(of: path, modifier: modifier, baseURL: baseURL!, route: route)
var url: URL = baseURL!
var rpath: String = path
let isId = path.hasPrefix("id:")
url.appendPathComponent(route.drivePath)
if isId {
url.appendPathComponent("root:")
} else {
url.appendPathComponent("items")
}
rpath = rpath.trimmingCharacters(in: pathTrimSet)
switch (modifier == nil, rpath.isEmpty, isId) {
case (true, false, _):
url.appendPathComponent(rpath)
case (true, true, _):
break
case (false, true, _):
url.appendPathComponent(modifier!)
case (false, false, true):
url.appendPathComponent(rpath)
url.appendPathComponent(modifier!)
case (false, false, false):
url.appendPathComponent(rpath + ":")
url.appendPathComponent(modifier!)
}
return url
}
open override func relativePathOf(url: URL) -> String {
return OneDriveFileObject.relativePathOf(url: url, baseURL: baseURL, route: route)
}
/// Checks the connection to server or permission on local
///
/// - Note: To prevent race condition, use this method wisely and avoid it as far possible.
///
/// - Parameter success: indicated server is reachable or not.
open override func isReachable(completionHandler: @escaping (_ success: Bool, _ error: Error?) -> Void) {
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
var request = URLRequest(url: url(of: ""))
request.httpMethod = "HEAD"
request.setValue(authentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
let status = (response as? HTTPURLResponse)?.statusCode ?? 400
if status >= 400, let code = FileProviderHTTPErrorCode(rawValue: status) {
let errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
let error = FileProviderOneDriveError(code: code, path: "", serverDescription: errorDesc)
completionHandler(false, error)
return
}
completionHandler(status == 200, error)
completionHandler(status == 200)
})
task.resume()
}
/**
Uploads a file from local file url to designated path asynchronously.
Method will fail if source is not a local url with `file://` scheme.
- Note: It's safe to assume that this method only works on individual files and **won't** copy folders recursively.
- Parameters:
- localFile: a file url to file.
- to: destination path of file, including file/directory name.
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
- Returns: An `Progress` to get progress or cancel progress.
*/
@discardableResult
open override func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
dispatch_queue.async {
completionHandler?(self.urlError(localFile.path, code: .fileIsDirectory))
}
return nil
}
let operation = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
return self.upload_multipart_file(toPath, file: localFile, operation: operation, overwrite: overwrite, completionHandler: completionHandler)
}
/**
Write the contents of the `Data` to a location asynchronously.
It will return error if file is already exists.
Not attomically by default, unless the provider enforces it.
- Parameters:
- path: Path of target file.
- contents: Data to be written into file, pass nil to create empty file.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
open override func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
return upload_multipart_data(path, data: data ?? Data(), operation: operation, overwrite: overwrite, completionHandler: completionHandler)
}
override func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey : Any] = [:]) -> URLRequest {
func correctPath(_ path: String) -> String {
@@ -447,24 +336,23 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
case .fetch(path: let path):
method = "GET"
url = self.url(of: path, modifier: "content")
case .create(path: let path) where path.hasSuffix("/"):
method = "POST"
let parent = (path as NSString).deletingLastPathComponent
url = self.url(of: parent, modifier: "children")
case .modify(path: let path):
method = "PUT"
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
url = URL(string: self.url(of: path, modifier: "content").absoluteString + queryStr)!
case .copy(source: let source, destination: let dest) where source.hasPrefix("file://"):
url = self.url(of: path, modifier: "content\(queryStr)")
case .create(path: let path):
method = "CREATE"
url = self.url(of: path)
case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"):
method = "POST"
url = self.url(of: source)
case .copy(let source, let dest) where source.hasPrefix("file://"):
method = "PUT"
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
url = URL(string: self.url(of: dest, modifier: "content").absoluteString + queryStr)!
case .copy(source: let source, destination: let dest) where dest.hasPrefix("file://"):
url = self.url(of: dest, modifier: "content\(queryStr)")
case .copy(let source, let dest) where dest.hasPrefix("file://"):
method = "GET"
url = self.url(of: source, modifier: "content")
case .copy(source: let source, destination: _):
method = "POST"
url = self.url(of: source, modifier: "copy")
case .move(source: let source, destination: _):
method = "PATCH"
url = self.url(of: source)
@@ -477,40 +365,29 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue(authentication: self.credential, with: .oAuth2)
// Remove gzip to fix availability of progress re. (Oleg Marchik)[https://github.com/evilutioner] PR (#61)
if method == "GET" {
request.setValue(acceptEncoding: .deflate)
request.addValue(acceptEncoding: .identity)
}
// Remove gzip to fix availability of progress per (Oleg Marchik)[https://github.com/evilutioner] PR (#61)
request.setValue(acceptEncoding: .deflate)
request.addValue(acceptEncoding: .identity)
switch operation {
case .create(path: let path) where path.hasSuffix("/"):
request.setValue(contentType: .json)
var requestDictionary = [String: AnyObject]()
let name = (path as NSString).lastPathComponent
requestDictionary["name"] = name as NSString
requestDictionary["folder"] = NSDictionary()
requestDictionary["@microsoft.graph.conflictBehavior"] = "fail" as NSString
request.httpBody = Data(jsonDictionary: requestDictionary)
case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"),
.move(source: let source, destination: let dest):
request.setValue(contentType: .json, charset: .utf8)
request.setValue(contentType: .json)
let cdest = correctPath(dest) as NSString
var parentReference: [String: AnyObject] = [:]
var parentRefrence: [String: AnyObject] = [:]
if cdest.hasPrefix("id:") {
parentReference["id"] = cdest.components(separatedBy: "/").first?.replacingOccurrences(of: "id:", with: "", options: .anchored) as NSString?
parentRefrence["id"] = cdest.components(separatedBy: "/").first as NSString?
switch self.route {
case .drive(uuid: let uuid):
parentRefrence["driveId"] = uuid.uuidString as NSString
default:
break
}
} else {
parentReference["path"] = ("/drive/root:" as NSString).appendingPathComponent(cdest.deletingLastPathComponent) as NSString
}
switch self.route {
case .drive(uuid: let uuid):
parentReference["driveId"] = uuid.uuidString as NSString
default:
//parentReference["driveId"] = cachedDriveID as NSString? ?? ""
break
parentRefrence["path"] = cdest.deletingLastPathComponent as NSString
}
var requestDictionary = [String: AnyObject]()
requestDictionary["parentReference"] = parentReference as NSDictionary
requestDictionary["parentReference"] = parentRefrence as NSDictionary
requestDictionary["name"] = (cdest as NSString).lastPathComponent as NSString
request.httpBody = Data(jsonDictionary: requestDictionary)
default:
@@ -527,11 +404,11 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
} else {
errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
}
return FileProviderOneDriveError(code: code, path: path ?? "", serverDescription: errorDesc)
return FileProviderOneDriveError(code: code, path: path ?? "", errorDescription: errorDesc)
}
override var maxUploadSimpleSupported: Int64 {
return 4_194_304 // 4MB!
return 104_857_600 // 100MB
}
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
@@ -548,22 +425,23 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
}
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
var request = URLRequest(url: self.url(of: path, modifier: "createLink"))
var request = URLRequest(url: self.url(of: path, modifier: "action.createLink"))
request.httpMethod = "POST"
let requestDictionary: [String: AnyObject] = ["type": "view" as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var link: URL?
if let response = response as? HTTPURLResponse, response.statusCode >= 400 {
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
}
if let json = data?.deserializeJSON() {
if let linkDic = json["link"] as? NSDictionary, let linkStr = linkDic["webUrl"] as? String {
link = URL(string: linkStr)
if let json = data?.deserializeJSON() {
if let linkDic = json["link"] as? NSDictionary, let linkStr = linkDic["webUrl"] as? String {
link = URL(string: linkStr)
}
}
}
completionHandler(link, nil, nil, serverError ?? error)
})
task.resume()
@@ -572,12 +450,50 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
extension OneDriveFileProvider: ExtendedFileProvider {
open func propertiesOfFileSupported(path: String) -> Bool {
open func thumbnailOfFileSupported(path: String) -> Bool {
return true
}
@discardableResult
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) -> Progress? {
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
case "mp3", "aac", "m4a", "wma":
return true
case "mp4", "mpg", "3gp", "mov", "avi", "wmv":
return true
default:
return false
}
}
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
if let dimension = dimension {
url = self.url(of: path, modifier: "thumbnails/0/=c\(dimension.width)x\(dimension.height)/content")
} else {
url = self.url(of: path, modifier: "thumbnails/0/small/content")
}
var request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
let responseError = self.serverError(with: rCode, path: path, data: data)
completionHandler(nil, responseError)
return
}
if let data = data {
image = ImageClass(data: data)
}
completionHandler(image, error)
})
task.resume()
}
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.setValue(authentication: credential, with: .oAuth2)
@@ -588,58 +504,12 @@ extension OneDriveFileProvider: ExtendedFileProvider {
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
}
if let json = data?.deserializeJSON() {
(dic, keys) = self.mapMediaInfo(json)
if let json = data?.deserializeJSON() {
(dic, keys) = self.mapMediaInfo(json)
}
}
completionHandler(dic, keys, serverError ?? error)
})
task.resume()
return nil
}
#if os(macOS) || os(iOS) || os(tvOS)
open func thumbnailOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
case "mp3", "aac", "m4a", "wma":
return true
case "mp4", "mpg", "3gp", "mov", "avi", "wmv":
return true
case "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf":
return true
default:
return false
}
}
@discardableResult
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) -> Progress? {
let thumbQuery: String
switch dimension.map( {max($0.width, $0.height) } ) ?? 0 {
case 0...96: thumbQuery = "small"
case 97...176: thumbQuery = "medium"
default: thumbQuery = "large"
}
let url = self.url(of: path, modifier: "thumbnails")
.appendingPathComponent("0").appendingPathComponent(thumbQuery)
.appendingPathComponent("content")
var request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 400, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
let responseError = self.serverError(with: rCode, path: path, data: data)
completionHandler(nil, responseError)
return
}
image = data.flatMap(ImageClass.init(data:))
completionHandler(image, error)
})
task.resume()
return nil
}
#endif
}
+17 -249
View File
@@ -12,48 +12,41 @@ import Foundation
public struct FileProviderOneDriveError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let serverDescription: String?
public let errorDescription: String?
}
/// Containts path, url and attributes of a OneDrive file or resource.
public final class OneDriveFileObject: FileObject {
internal init(baseURL: URL?, name: String, path: String) {
let rpath = (URL(string:path)?.appendingPathComponent(name).absoluteString)!.replacingOccurrences(of: "/", with: "", options: .anchored)
let url = URL(string: rpath, relativeTo: baseURL) ?? URL(string: rpath)!
super.init(url: url, name: name, path: rpath.removingPercentEncoding ?? path)
}
internal convenience init? (baseURL: URL?, route: OneDriveFileProvider.Route, jsonStr: String) {
guard let json = jsonStr.deserializeJSON() else { return nil }
self.init(baseURL: baseURL, route: route, json: json)
}
internal init? (baseURL: URL?, route: OneDriveFileProvider.Route, json: [String: AnyObject]) {
internal convenience init? (baseURL: URL?, route: OneDriveFileProvider.Route, json: [String: AnyObject]) {
guard let name = json["name"] as? String else { return nil }
guard let id = json["id"] as? String else { return nil }
let path: String
if let refpath = json["parentReference"]?["path"] as? String {
let parentPath: String
if let colonIndex = refpath.index(of: ":") {
#if swift(>=4.0)
parentPath = String(refpath[refpath.index(after: colonIndex)...])
#else
parentPath = refpath.substring(from: refpath.index(after: colonIndex))
#endif
} else {
parentPath = refpath
}
path = (parentPath as NSString).appendingPathComponent(name)
} else {
path = "id:\(id)"
}
let url = baseURL.map { OneDriveFileObject.url(of: path, modifier: nil, baseURL: $0, route: route) }
super.init(url: url, name: name, path: path)
self.id = id
guard let path = json["parentReference"]?["path"] as? String else { return nil }
var lPath = path.replacingOccurrences(of: route.drivePath, with: "", options: .anchored, range: nil)
lPath = lPath.replacingOccurrences(of: "/:", with: "", options: .anchored)
lPath = lPath.replacingOccurrences(of: "//", with: "", options: .anchored)
self.init(baseURL: baseURL, name: name, path: lPath)
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
self.childrensCount = json["folder"]?["childCount"] as? Int
self.modifiedDate = (json["lastModifiedDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.type = json["folder"] != nil ? .directory : .regular
self.contentType = (json["file"]?["mimeType"] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
self.id = json["id"] as? String
self.entryTag = json["eTag"] as? String
let hashes = json["file"]?["hashes"] as? NSDictionary
// checks for both sha1 or quickXor. First is available in personal drives, second in business one.
self.fileHash = (hashes?["sha1Hash"] as? String) ?? (hashes?["quickXorHash"] as? String)
self.hash = (hashes?["sha1Hash"] as? String) ?? (hashes?["quickXorHash"] as? String)
}
/// The document identifier is a value assigned by the OneDrive to a file.
@@ -88,7 +81,7 @@ public final class OneDriveFileObject: FileObject {
}
/// Calculated hash from OneDrive server. Hex string SHA1 in personal or Base65 string [QuickXOR](https://dev.onedrive.com/snippets/quickxorhash.htm) in business drives.
open internal(set) var fileHash: String? {
open internal(set) var hash: String? {
get {
return allValues[.documentIdentifierKey] as? String
}
@@ -96,225 +89,9 @@ public final class OneDriveFileObject: FileObject {
allValues[.documentIdentifierKey] = newValue
}
}
static func url(of path: String, modifier: String?, baseURL: URL, route: OneDriveFileProvider.Route) -> URL {
var url: URL = baseURL
let isId = path.hasPrefix("id:")
var rpath: String = path.replacingOccurrences(of: "id:", with: "", options: .anchored)
//url.appendPathComponent("v1.0")
url.appendPathComponent(route.drivePath)
if rpath.isEmpty {
url.appendPathComponent("root")
} else if isId {
url.appendPathComponent("items")
} else {
url.appendPathComponent("root:")
}
rpath = rpath.trimmingCharacters(in: pathTrimSet)
switch (modifier == nil, rpath.isEmpty, isId) {
case (true, false, _):
url.appendPathComponent(rpath)
case (true, true, _):
break
case (false, true, _):
url.appendPathComponent(modifier!)
case (false, false, true):
url.appendPathComponent(rpath)
url.appendPathComponent(modifier!)
case (false, false, false):
url.appendPathComponent(rpath + ":")
url.appendPathComponent(modifier!)
}
return url
}
static func relativePathOf(url: URL, baseURL: URL?, route: OneDriveFileProvider.Route) -> String {
let base = baseURL?.appendingPathComponent(route.drivePath).path ?? ""
let crudePath = url.path.replacingOccurrences(of: base, with: "", options: .anchored)
.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
switch crudePath {
case hasPrefix("items/"):
let components = (crudePath as NSString).pathComponents
return components.dropFirst().first.map { "id:\($0)" } ?? ""
case hasPrefix("root:"):
return crudePath.components(separatedBy: ":").dropFirst().first ?? ""
default:
return ""
}
}
}
internal extension OneDriveFileProvider {
internal func upload_multipart_data(_ targetPath: String, data: Data, operation: FileOperationType,
overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return self.upload_multipart(targetPath, operation: operation, size: Int64(data.count), overwrite: overwrite, dataProvider: {
let range = $0.clamped(to: 0..<Int64(data.count))
return data[range]
}, completionHandler: completionHandler)
}
internal func upload_multipart_file(_ targetPath: String, file: URL, operation: FileOperationType,
overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// upload task can't handle uploading file
return self.upload_multipart(targetPath, operation: operation, size: file.fileSize, overwrite: overwrite, dataProvider: { range in
guard let handle = FileHandle(forReadingAtPath: file.path) else {
throw self.cocoaError(targetPath, code: .fileNoSuchFile)
}
defer {
handle.closeFile()
}
let offset = range.lowerBound
handle.seek(toFileOffset: UInt64(offset))
guard Int64(handle.offsetInFile) == offset else {
throw self.cocoaError(targetPath, code: .fileReadTooLarge)
}
return handle.readData(ofLength: range.count)
}, completionHandler: completionHandler)
}
private func upload_multipart(_ targetPath: String, operation: FileOperationType, size: Int64, overwrite: Bool,
dataProvider: @escaping (Range<Int64>) throws -> Data, completionHandler: SimpleCompletionHandler) -> Progress? {
guard size > 0 else { return nil }
let progress = Progress(totalUnitCount: size)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let createURL = self.url(of: targetPath, modifier: "createUploadSession")
var createRequest = URLRequest(url: createURL)
createRequest.httpMethod = "POST"
createRequest.setValue(authentication: self.credential, with: .oAuth2)
createRequest.setValue(contentType: .json)
if overwrite {
createRequest.httpBody = Data(jsonDictionary: ["item": ["@microsoft.graph.conflictBehavior": "replace"] as NSDictionary])
} else {
createRequest.httpBody = Data(jsonDictionary: ["item": ["@microsoft.graph.conflictBehavior": "fail"] as NSDictionary])
}
let createSessionTask = session.dataTask(with: createRequest) { (data, response, error) in
if let error = error {
completionHandler?(error)
return
}
if let data = data, let json = data.deserializeJSON(),
let uploadURL = (json["uploadUrl"] as? String).flatMap(URL.init(string:)) {
self.upload_multipart(url: uploadURL, operation: operation, size: size, progress: progress, dataProvider: dataProvider, completionHandler: completionHandler)
}
}
createSessionTask.resume()
return progress
}
private func upload_multipart(url: URL, operation: FileOperationType, size: Int64, range: Range<Int64>? = nil, uploadedSoFar: Int64 = 0,
progress: Progress, dataProvider: @escaping (Range<Int64>) throws -> Data, completionHandler: SimpleCompletionHandler) {
guard !progress.isCancelled else { return }
var progress = progress
let maximumSize: Int64 = 10_485_760 // Recommended by OneDrive documentations and divides evenly by 320 KiB, max 60MiB.
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue(authentication: self.credential, with: .oAuth2)
let finalRange: Range<Int64>
if let range = range {
if range.count > maximumSize {
finalRange = range.lowerBound..<(range.upperBound + maximumSize)
} else {
finalRange = range
}
} else {
finalRange = 0..<min(maximumSize, size)
}
request.setValue(contentRange: finalRange, totalBytes: size)
let data: Data
do {
data = try dataProvider(finalRange)
} catch {
dispatch_queue.async {
completionHandler?(error)
}
self.delegateNotify(operation, error: error)
return
}
let task = session.uploadTask(with: request, from: data)
var dictionary: [String: AnyObject] = ["type": operation.description as NSString]
dictionary["source"] = operation.source as NSString?
dictionary["dest"] = operation.destination as NSString?
dictionary["uploadedBytes"] = uploadedSoFar as NSNumber
dictionary["totalBytes"] = data.count as NSNumber
task.taskDescription = String(jsonDictionary: dictionary)
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
progress.cancellationHandler = { [weak task, weak self] in
task?.cancel()
var deleteRequest = URLRequest(url: url)
deleteRequest.httpMethod = "DELETE"
self?.session.dataTask(with: deleteRequest).resume()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
var allData = Data()
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { data in
allData.append(data)
}
// We retain self here intentionally to allow resuming upload, This behavior may change anytime!
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak task] error in
if let error = error {
progress.cancel()
completionHandler?(error)
self.delegateNotify(operation, error: error)
return
}
guard let json = allData.deserializeJSON() else {
let error = URLError(.badServerResponse, userInfo: [NSURLErrorKey: url, NSURLErrorFailingURLErrorKey: url, NSURLErrorFailingURLStringErrorKey: url.absoluteString])
completionHandler?(error)
self.delegateNotify(operation, error: error)
return
}
if let _ = json["error"] {
let code = ((task?.response as? HTTPURLResponse)?.statusCode).flatMap(FileProviderHTTPErrorCode.init(rawValue:)) ?? .badRequest
let error = self.serverError(with: code, path: self.relativePathOf(url: url), data: allData)
completionHandler?(error)
self.delegateNotify(operation, error: error)
return
}
if let ranges = json["nextExpectedRanges"] as? [String], let firstRange = ranges.first {
let uploaded = uploadedSoFar + Int64(finalRange.count)
let comp = firstRange.components(separatedBy: "-")
let lower = comp.first.flatMap(Int64.init) ?? uploaded
let upper = comp.dropFirst().first.flatMap(Int64.init) ?? Int64.max
let range = Range<Int64>(uncheckedBounds: (lower: lower, upper: upper))
self.upload_multipart(url: url, operation: operation, size: size, range: range, uploadedSoFar: uploaded, progress: progress,
dataProvider: dataProvider, completionHandler: completionHandler)
return
}
if let _ = json["id"] as? String {
completionHandler?(nil)
self.delegateNotify(operation)
}
}
task.resume()
}
static let dateFormatter = DateFormatter()
static let decimalFormatter = NumberFormatter()
@@ -376,17 +153,8 @@ internal extension OneDriveFileProvider {
if let audio = json["audio"] as? [String: Any] {
for (key, value) in audio {
var value = value
if key == "bitrate" || key == "isVariableBitrate" { continue }
let casedKey = spaceCamelCase(key)
switch casedKey {
case "Duration":
value = (value as? Int64).map { (TimeInterval($0) / 1000).formatshort } as Any
case "Bitrate":
value = (value as? Int64).map { "\($0)kbps" } as Any
default:
break
}
add(key: casedKey, value: value)
}
}
+11 -24
View File
@@ -10,7 +10,7 @@ import Foundation
/// A protocol defines properties for errors returned by HTTP/S based providers.
/// Including Dropbox, OneDrive and WebDAV.
public protocol FileProviderHTTPError: LocalizedError, CustomStringConvertible {
public protocol FileProviderHTTPError: Error, CustomStringConvertible {
/// HTTP status codes as an enum.
typealias Code = FileProviderHTTPErrorCode
/// HTTP status code returned for error by server.
@@ -18,16 +18,16 @@ public protocol FileProviderHTTPError: LocalizedError, CustomStringConvertible {
/// Path of file/folder casued that error
var path: String { get }
/// Contents returned by server as error description
var serverDescription: String? { get }
var errorDescription: String? { get }
}
extension FileProviderHTTPError {
public var description: String {
return "Status \(code.rawValue): \(code.description)"
return code.description
}
public var errorDescription: String? {
return "Status \(code.rawValue): \(code.description)"
public var localizedDescription: String {
return description
}
}
@@ -40,14 +40,12 @@ internal func initEmptySessionHandler(_ uuid: String) {
completionHandlersForTasks[uuid] = [:]
downloadCompletionHandlersForTasks[uuid] = [:]
dataCompletionHandlersForTasks[uuid] = [:]
responseCompletionHandlersForTasks[uuid] = [:]
}
internal func removeSessionHandler(for uuid: String) {
_ = completionHandlersForTasks.removeValue(forKey: uuid)
_ = downloadCompletionHandlersForTasks.removeValue(forKey: uuid)
_ = dataCompletionHandlersForTasks.removeValue(forKey: uuid)
_ = responseCompletionHandlersForTasks.removeValue(forKey: uuid)
}
/// All objects set to `FileProviderRemote.session` must be an instance of this class
@@ -77,23 +75,16 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
}
}
case #keyPath(URLSessionTask.countOfBytesSent):
progress.completedUnitCount = newVal
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
let elapsed = Date().timeIntervalSince(startTime)
let throughput = Double(newVal) / elapsed
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
// wokaround for multipart uploading
let json = task.taskDescription?.deserializeJSON()
let uploadedBytes = ((json?["uploadedBytes"] as? Int64) ?? 0) + newVal
let totalBytes = (json?["totalBytes"] as? Int64) ?? task.countOfBytesExpectedToSend
progress.completedUnitCount = uploadedBytes
if totalBytes > 0 {
let remain = totalBytes - uploadedBytes
if task.countOfBytesExpectedToSend > 0 {
let remain = task.countOfBytesExpectedToSend - task.countOfBytesSent
let estimatedTimeRemaining = Double(remain) / elapsed
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
}
} else {
progress.completedUnitCount = newVal
}
case #keyPath(URLSessionTask.countOfBytesExpectedToReceive), #keyPath(URLSessionTask.countOfBytesExpectedToSend):
progress.totalUnitCount = newVal
@@ -163,10 +154,10 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let completionHandler = dataCompletionHandlersForTasks[session.sessionDescription!]?[dataTask.taskIdentifier] {
/*if let json = dataTask.taskDescription?.deserializeJSON(),
if let json = dataTask.taskDescription?.deserializeJSON(),
let op = FileOperationType(json: json), let fileProvider = fileProvider {
fileProvider.delegateNotify(op, progress: Double(dataTask.countOfBytesReceived) / Double(dataTask.countOfBytesExpectedToReceive))
}*/
}
completionHandler(data)
}
@@ -190,11 +181,7 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
return
}
// wokaround for multipart uploading
let uploadedBytes = (json["uploadedBytes"] as? Int64) ?? 0
let totalBytes = (json["totalBytes"] as? Int64) ?? totalBytesExpectedToSend
fileProvider.delegateNotify(op, progress: Double(uploadedBytes + totalBytesSent) / Double(totalBytes))
fileProvider.delegateNotify(op, progress: Double(totalBytesSent) / Double(totalBytesExpectedToSend))
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
+119 -198
View File
@@ -11,200 +11,152 @@ import Foundation
// This client implementation is for little-endian platform, namely x86, x64 & arm
// For big-endian platforms like PowerPC, there must be a huge overhaul
enum SMBClientError: Error {
case streamNotOpened
case timedOut
protocol FileProviderSMBTaskDelegate: class {
func receivedResponse(client: FileProviderSMBTask, response: SMBResponse, for: SMBRequest)
}
@objcMembers
class SMBClient: NSObject, StreamDelegate {
fileprivate var inputStream: InputStream?
fileprivate var outputStream: OutputStream?
fileprivate var operation_queue: OperationQueue!
fileprivate var host: (hostname: String, port: Int)?
fileprivate var service: NetService?
public var timeout: TimeInterval = 30
internal private(set) var messageId: UInt64 = 0
fileprivate func createMessageId() -> UInt64 {
defer {
messageId += 1
}
return messageId
}
internal private(set) var credit: UInt16 = 0
fileprivate func consumeCredit() -> UInt16 {
if credit > 0 {
credit -= 1
return credit
} else {
return 0
}
}
class FileProviderSMBTask: FileProviderStreamTask {
var timeout: TimeInterval = 30
private(set) var lastMessageID: UInt64 = 0
private(set) var sessionId: UInt64 = 0
private func messageId() -> UInt64 {
defer {
lastMessageID += 1
}
return lastMessageID
}
private(set) var establishedTrees = Array<SMB2.TreeConnectResponse>()
private(set) var requestStack = [Int: SMBRequest]()
private(set) var responseStack = [Int: SMBResponse]()
init(host: String, port: Int) {
self.host = (host, port)
self.operation_queue = OperationQueue()
self.operation_queue.name = "FileProviderStreamTask"
self.operation_queue.maxConcurrentOperationCount = 1
super.init()
weak var delegate: FileProviderSMBTaskDelegate?
func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: UInt16(126), messageId: mId, treeId: UInt32(0), sessionId: UInt64(0))
let msg = SMB2.NegotiateRequest()
let data = createSMB2Message(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
deinit {
close()
}
fileprivate func open(secure: Bool = false) {
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
if inputStream == nil || outputStream == nil {
if let host = host {
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host.hostname as CFString, UInt32(host.port), &readStream, &writeStream)
} else if let service = service {
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain as CFString, service.type as CFString, service.name as CFString, Int32(service.port))
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
func sendSessionSetup(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let credit = UInt16(sessionId > 0 ? 124 : 125)
let smbHeader = SMB2.Header(command: SMB2.Command.SESSION_SETUP, creditRequestResponse: credit, messageId: mId, treeId: UInt32(0), sessionId: sessionId)
let msg = SMB2.SessionSetupRequest(singing: [])
let data = createSMB2Message(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
if self.sessionId == 0 {
self.readData(ofMinLength: 64, maxLength: 65536, timeout: self.timeout, completionHandler: { (data, eof, e2) in
// TODO: set session id
completionHandler?(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)
let cmp = url.pathComponents
let share = cmp.first ?? ""
let tcHeader = SMB2.TreeConnectRequest.Header(flags: [])
let msg = SMB2.TreeConnectRequest(header: tcHeader, host: host, share: share)
let data = createSMB2Message(header: smbHeader, message: msg!)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
}
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
if secure {
inputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
outputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
}
inputStream.delegate = self
outputStream.delegate = self
inputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
outputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
inputStream.open()
outputStream.open()
operation_queue.isSuspended = false
})
return mId
}
fileprivate func close() {
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.outputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream = nil
self.outputStream = nil
func sendTreeDisconnect(id 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(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
@discardableResult
fileprivate func write(data: Data) throws -> Int {
guard let outputStream = self.outputStream else {
throw SMBClientError.streamNotOpened
}
let expireDate = Date(timeIntervalSinceNow: timeout)
var data = data
var byteSent: Int = 0
while data.count > 0 {
let bytesWritten = data.withUnsafeBytes {
outputStream.write($0, maxLength: data.count)
}
if bytesWritten > 0 {
let range = 0..<bytesWritten
data.replaceSubrange(range, with: Data())
byteSent += bytesWritten
} else if bytesWritten < 0 {
if let error = outputStream.streamError {
throw error
}
return bytesWritten
}
if data.count == 0 {
break
}
if expireDate < Date() {
throw SMBClientError.timedOut
}
}
return byteSent
func sendLogoff(id 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(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
var currentHandlingData: Data = Data()
var expectedBytes = 0
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if eventCode.contains(.errorOccurred) {
/*self._error = aStream.streamError
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)*/
}
func reset() {
if aStream == inputStream && eventCode.contains(.hasBytesAvailable) {
while (inputStream!.hasBytesAvailable) {
var buffer = [UInt8](repeating: 0, count: 65536)
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
/*dataReceived.append(&buffer, count: len)
self._countOfBytesRecieved += Int64(len)*/
}
}
}
}
}
// MARK: create and analyse messages
extension SMBClient {
internal func sendMessage(_ message: SMBRequestBody, toTree treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = createMessageId()
let credit = consumeCredit()
let smbHeader = SMB2.Header(command: message.command, creditRequestResponse: credit, messageId: mId, treeId: treeId, sessionId: sessionId)
let data = createRequest(header: smbHeader, message: message)
operation_queue.addOperation {
do {
try self.write(data: data)
completionHandler?(nil)
} catch {
completionHandler?(error)
}
}
return mId
}
}
fileprivate extension SMBClient {
extension FileProviderSMBTask {
func determineSMBVersion(_ data: Data) -> Float {
let smbverChar: Int8 = Int8(bitPattern: data.first ?? 0)
let version = 0 - smbverChar
return Float(version)
}
func createRequest(header: SMB2.Header, message: SMBRequestBody) -> Data {
var result = Data(value: header)
result.append(message.data())
return result
func digestSMBMessage(_ data: Data) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: Data?)]) {
guard data.count > 30 else {
throw URLError(.badServerResponse)
}
var buffer = [UInt8](repeating: 0, count: data.count)
guard determineSMBVersion(data) == 1 else {
throw SMBFileProviderError.incompatibleHeader
}
let headersize = MemoryLayout<SMB1.Header>.size
let header: SMB1.Header = data.scanValue()!
var blocks = [(params: [UInt16], message: Data?)]()
var offset = headersize
while offset < data.count {
let paramWords: [UInt16]
let paramWordsCount = Int(buffer[offset])
guard data.count > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.incorrectParamsLength
}
offset += MemoryLayout<UInt8>.size
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
let paramData = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&rawParamWords), count: rawParamWords.count, deallocator: .free)
paramWords = paramData.scanValue()!
offset += paramWordsCount * 2
let messageBytesCountHi = Int(buffer[1]) << 8
let messageBytesCount = Int(buffer[0]) + messageBytesCountHi
offset += MemoryLayout<UInt16>.size
guard data.count >= (offset + messageBytesCount) else {
throw SMBFileProviderError.incorrectMessageLength
}
let rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
offset += messageBytesCount
let message = Data(bytes: rawMessage)
blocks.append((params: paramWords, message: message))
}
return (header, blocks)
}
func responseOf(_ data: Data) throws -> SMBResponse? {
func digestSMB2Message(_ data: Data) throws -> SMBResponse? {
guard data.count > 65 else {
throw URLError(.badServerResponse)
}
guard determineSMBVersion(data) >= 2 else {
guard determineSMBVersion(data) == 2 else {
throw SMBFileProviderError.incompatibleHeader
}
let headersize = MemoryLayout<SMB2.Header>.size
@@ -251,12 +203,12 @@ fileprivate extension SMBClient {
return (header, SMB2.SetInfoResponse(data: messageData))
case .OPLOCK_BREAK:
return (header, nil) // FIXME:
default:
case .INVALID:
throw SMBFileProviderError.invalidCommand
}
}
/*func createSMBMessage(header: SMB1.Header, blocks: [(params: Data?, message: Data?)]) -> Data {
func createSMBMessage(header: SMB1.Header, blocks: [(params: Data?, message: Data?)]) -> Data {
var result = Data(value: header)
for block in blocks {
var paramWordsCount = UInt8(block.params?.count ?? 0)
@@ -272,42 +224,11 @@ fileprivate extension SMBClient {
}
}
return result
}*/
}
/*func digestSMBMessage(_ data: Data) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: Data?)]) {
guard data.count > 30 else {
throw URLError(.badServerResponse)
}
var buffer = [UInt8](repeating: 0, count: data.count)
guard determineSMBVersion(data) == 1 else {
throw SMBFileProviderError.incompatibleHeader
}
let headersize = MemoryLayout<SMB1.Header>.size
let header: SMB1.Header = data.scanValue()!
var blocks = [(params: [UInt16], message: Data?)]()
var offset = headersize
while offset < data.count {
let paramWords: [UInt16]
let paramWordsCount = Int(buffer[offset])
guard data.count > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.incorrectParamsLength
}
offset += MemoryLayout<UInt8>.size
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
let paramData = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&rawParamWords), count: rawParamWords.count, deallocator: .free)
paramWords = paramData.scanValue()!
offset += paramWordsCount * 2
let messageBytesCountHi = Int(buffer[1]) << 8
let messageBytesCount = Int(buffer[0]) + messageBytesCountHi
offset += MemoryLayout<UInt16>.size
guard data.count >= (offset + messageBytesCount) else {
throw SMBFileProviderError.incorrectMessageLength
}
let rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
offset += messageBytesCount
let message = Data(bytes: rawMessage)
blocks.append((params: paramWords, message: message))
}
return (header, blocks)
}*/
func createSMB2Message(header: SMB2.Header, message: SMBRequestBody) -> Data {
var result = Data(value: header)
result.append(message.data())
return result
}
}
+1 -1
View File
@@ -68,7 +68,7 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
NotImplemented()
}
func isReachable(completionHandler: @escaping (_ success: Bool, _ error: Error?) -> Void) {
func isReachable(completionHandler: @escaping (Bool) -> Void) {
NotImplemented()
}
-25
View File
@@ -8,36 +8,11 @@
import Foundation
protocol Option: RawRepresentable, Hashable {
}
extension Option where RawValue: Hashable {
var hashValue: Int {
return rawValue.hashValue
}
}
extension Option where RawValue: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.rawValue == rhs.rawValue
}
}
protocol SMBRequestBody {
static var command: SMB2.Command { get }
func data() -> Data
}
extension SMBRequestBody {
var command: SMB2.Command {
#if swift(>=3.1)
return Swift.type(of: self).command
#else
return type(of: self).command
#endif
}
func data() -> Data {
return Data(value: self)
}
+63 -66
View File
@@ -12,8 +12,6 @@ extension SMB2 {
// MARK: SMB2 Create
struct CreateRequest: SMBRequestBody {
static var command: SMB2.Command = .CREATE
let header: CreateRequest.Header
let name: String?
let contexts: [CreateContext]
@@ -50,14 +48,38 @@ extension SMB2 {
struct Header {
let size: UInt16
fileprivate let securityFlags: UInt8
var requestedOplockLevel: OplockLevel
var impersonationLevel: ImpersonationLevel
fileprivate var _requestedOplockLevel: UInt8
var requestedOplockLevel: OplockLevel {
get {
return OplockLevel(rawValue: _requestedOplockLevel)!
}
set {
_requestedOplockLevel = newValue.rawValue
}
}
fileprivate var _impersonationLevel: UInt32
var impersonationLevel: ImpersonationLevel {
get {
return ImpersonationLevel(rawValue: _impersonationLevel)!
}
set {
_impersonationLevel = newValue.rawValue
}
}
fileprivate let flags: UInt64
fileprivate let reserved: UInt64
let access: FileAccessMask
let fileAttributes: FileAttributes
let shareAccess: ShareAccess
var desposition: CreateDisposition
fileprivate var _desposition: UInt32
var desposition: CreateDisposition {
get {
return CreateDisposition(rawValue: _desposition)!
}
set {
_desposition = newValue.rawValue
}
}
let options: CreateOptions
var nameOffset: UInt16
var nameLength: UInt16
@@ -67,14 +89,14 @@ extension SMB2 {
init(requestedOplockLevel: OplockLevel = .NONE, impersonationLevel: ImpersonationLevel = .anonymous, access: FileAccessMask = [.GENERIC_ALL], fileAttributes: FileAttributes = [], shareAccess: ShareAccess = [.READ], desposition: CreateDisposition = .OPEN_IF, options: CreateOptions = []) {
self.size = 57
self.securityFlags = 0
self.requestedOplockLevel = requestedOplockLevel
self.impersonationLevel = impersonationLevel
self._requestedOplockLevel = requestedOplockLevel.rawValue
self._impersonationLevel = impersonationLevel.rawValue
self.flags = 0
self.reserved = 0
self.access = access
self.fileAttributes = fileAttributes
self.shareAccess = shareAccess
self.desposition = desposition
self._desposition = desposition.rawValue
self.options = options
self.nameOffset = 0
self.nameLength = 0
@@ -113,44 +135,36 @@ extension SMB2 {
fileprivate static let RESERVE_OPFILTER = CreateOptions(rawValue: 0x00100000)
}
struct CreateDisposition: Option {
init(rawValue: UInt32) {
self.rawValue = rawValue
}
var rawValue: UInt32
enum CreateDisposition: UInt32 {
/// If the file already exists, supersede it. Otherwise, create the file.
public static let SUPERSEDE = CreateDisposition(rawValue: 0x00000000)
case SUPERSEDE = 0x00000000
/// If the file already exists, return success; otherwise, fail the operation.
public static let OPEN = CreateDisposition(rawValue: 0x00000001)
case OPEN = 0x00000001
/// If the file already exists, fail the operation; otherwise, create the file.
public static let CREATE = CreateDisposition(rawValue: 0x00000002)
case CREATE = 0x00000002
/// Open the file if it already exists; otherwise, create the file.
public static let OPEN_IF = CreateDisposition(rawValue: 0x00000003)
case OPEN_IF = 0x00000003
/// Overwrite the file if it already exists; otherwise, fail the operation.
public static let OVERWRITE = CreateDisposition(rawValue: 0x00000004)
case OVERWRITE = 0x00000004
/// Overwrite the file if it already exists; otherwise, create the file.
public static let OVERWRITE_IF = CreateDisposition(rawValue: 0x00000005)
case OVERWRITE_IF = 0x00000005
}
struct ImpersonationLevel {
init(rawValue: UInt32) {
self.rawValue = rawValue
}
var rawValue: UInt32
public static let anonymous = ImpersonationLevel(rawValue: 0x00000000)
public static let identification = ImpersonationLevel(rawValue: 0x00000001)
public static let impersonation = ImpersonationLevel(rawValue: 0x00000002)
public static let delegate = ImpersonationLevel(rawValue: 0x00000003)
enum ImpersonationLevel: UInt32 {
case anonymous = 0x00000000
case identification = 0x00000001
case impersonation = 0x00000002
case delegate = 0x00000003
}
}
struct CreateResponse: SMBResponseBody {
struct Header {
let size: UInt16
let oplockLevel: OplockLevel
fileprivate let _oplockLevel: UInt8
var oplockLevel: OplockLevel {
return OplockLevel(rawValue: _oplockLevel)!
}
fileprivate let reserved: UInt32
let creationTime: SMBTime
let lastAccessTime: SMBTime
@@ -234,47 +248,34 @@ extension SMB2 {
return result
}
struct ContextNames: Option {
init(rawValue: String) {
self.rawValue = rawValue
}
let rawValue: String
enum ContextNames: String {
/// Request Create Context: Extended attributes
public static let EA_BUFFER = ContextNames(rawValue: "ExtA")
case EA_BUFFER = "ExtA"
/// Request Create Context: Security descriptor
public static let SD_BUFFER = ContextNames(rawValue: "SecD")
case SD_BUFFER = "SecD"
/// Request & Response Create Context: Open to be durable
public static let DURABLE_HANDLE = ContextNames(rawValue: "DHnQ")
/// Request & Response Create Context: Open to be durable
public static let DURABLE_HANDLE_RESPONSE_V2 = ContextNames(rawValue: "DH2Q")
case DURABLE_HANDLE = "DHnQ"
case DURABLE_HANDLE_RESPONSE_V2 = "DH2Q"
/// Request Create Context: Reconnect to a durable open after being disconnected
public static let DURABLE_HANDLE_RECONNECT = ContextNames(rawValue: "DHnC")
case DURABLE_HANDLE_RECONNECT = "DHnC"
/// Request Create Context: Required allocation size of the newly created file
public static let ALLOCATION_SIZE = ContextNames(rawValue: "AISi")
case ALLOCATION_SIZE = "AISi"
/// Request & Response Create Context: Maximal access information
public static let QUERY_MAXIMAL_ACCESS = ContextNames(rawValue: "MxAc")
public static let TIMEWARP_TOKEN = ContextNames(rawValue: "TWrp")
case QUERY_MAXIMAL_ACCESS = "MxAc"
case TIMEWARP_TOKEN = "TWrp"
/// Response Create Context: DiskID of the open file in a volume.
public static let QUERY_ON_DISK_ID = ContextNames(rawValue: "QFid")
case QUERY_ON_DISK_ID = "QFid"
/// Response Create Context: A lease. This value is only supported for the SMB 2.1 and 3.x dialect family.
public static let LEASE = ContextNames(rawValue: "RqLs")
case LEASE = "RqLs"
}
}
struct OplockLevel {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
public static let NONE = OplockLevel(rawValue: 0x00)
public static let LEVEL_II = OplockLevel(rawValue: 0x01)
public static let EXCLUSIVE = OplockLevel(rawValue: 0x08)
public static let BATCH = OplockLevel(rawValue: 0x09)
public static let LEASE = OplockLevel(rawValue: 0xFF)
enum OplockLevel: UInt8 {
case NONE = 0x00
case LEVEL_II = 0x01
case EXCLUSIVE = 0x08
case BATCH = 0x09
case LEASE = 0xFF
}
struct ShareAccess: OptionSet {
@@ -357,8 +358,6 @@ extension SMB2 {
// MARK: SMB2 Close
struct CloseRequest: SMBRequestBody {
static var command: SMB2.Command = .CLOSE
let size: UInt16
let flags: CloseFlags
fileprivate let reserved2: UInt32
@@ -400,8 +399,6 @@ extension SMB2 {
// MARK: SMB2 Flush
struct FlushRequest: SMBRequestBody {
static var command: SMB2.Command = .FLUSH
let size: UInt16
fileprivate let reserved: UInt16
fileprivate let reserved2: UInt32
+14 -26
View File
@@ -12,8 +12,6 @@ extension SMB2 {
// MARK: SMB2 Read
struct ReadRequest: SMBRequestBody {
static var command: SMB2.Command = .READ
let size: UInt16
fileprivate let padding: UInt8
let flags: ReadRequest.Flags
@@ -21,7 +19,10 @@ extension SMB2 {
let offset: UInt64
let fileId: FileId
let minimumLength: UInt32
let channel: Channel
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
let remainingBytes: UInt32
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
@@ -35,7 +36,7 @@ extension SMB2 {
self.offset = offset
self.fileId = fileId
self.minimumLength = minimumLength
self.channel = channel
self._channel = channel.rawValue
self.remainingBytes = remainingBytes
self.channelInfoOffset = 0
self.channelInfoLength = 0
@@ -76,23 +77,15 @@ extension SMB2 {
}
}
struct Channel: Option {
init(rawValue: UInt32) {
self.rawValue = rawValue
}
let rawValue: UInt32
public static let NONE = Channel(rawValue: 0x00000000)
public static let RDMA_V1 = Channel(rawValue: 0x00000001)
public static let RDMA_V1_INVALIDATE = Channel(rawValue: 0x00000002)
enum Channel: UInt32 {
case NONE = 0x00000000
case RDMA_V1 = 0x00000001
case RDMA_V1_INVALIDATE = 0x00000002
}
// MARK: SMB2 Write
struct WriteRequest: SMBRequestBody {
static var command: SMB2.Command = .WRITE
let header: WriteRequest.Header
let channelInfo: ChannelInfo?
let fileData: Data
@@ -103,7 +96,10 @@ extension SMB2 {
let length: UInt32
let offset: UInt64
let fileId: FileId
let channel: Channel
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
let remainingBytes: UInt32
let channelInfoOffset: UInt16
let channelInfoLength: UInt16
@@ -119,7 +115,7 @@ extension SMB2 {
channelInfoLength = UInt16(MemoryLayout<SMB2.ChannelInfo>.size)
}
let dataOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<WriteRequest.Header>.size) + channelInfoLength
self.header = WriteRequest.Header(size: UInt16(49), dataOffset: dataOffset, length: UInt32(data.count), offset: offset, fileId: fileId, channel: channel, remainingBytes: remainingBytes, channelInfoOffset: channelInfoOffset, channelInfoLength: channelInfoLength, flags: flags)
self.header = WriteRequest.Header(size: UInt16(49), dataOffset: dataOffset, length: UInt32(data.count), offset: offset, fileId: fileId, _channel: channel.rawValue, remainingBytes: remainingBytes, channelInfoOffset: channelInfoOffset, channelInfoLength: channelInfoLength, flags: flags)
self.channelInfo = channelInfo
self.fileData = data
}
@@ -156,8 +152,6 @@ extension SMB2 {
}
struct ChannelInfo: SMBRequestBody {
static var command: SMB2.Command = .WRITE
let offset: UInt64
let token: UInt32
let length: UInt32
@@ -166,8 +160,6 @@ extension SMB2 {
// MARK: SMB2 Lock
struct LockElement: SMBRequestBody {
static var command: SMB2.Command = .LOCK
let offset: UInt64
let length: UInt64
let flags: LockElement.Flags
@@ -188,8 +180,6 @@ extension SMB2 {
}
struct LockRequest: SMBRequestBody {
static var command: SMB2.Command = .LOCK
let header: LockRequest.Header
let locks: [LockElement]
@@ -227,8 +217,6 @@ extension SMB2 {
// MARK: SMB2 Cancel
struct CancelRequest: SMBRequestBody {
static var command: SMB2.Command = .CANCEL
let size: UInt16
let reserved: UInt16
+50 -69
View File
@@ -16,14 +16,12 @@ extension SMB2 {
*/
struct IOCtlRequest: SMBRequestBody {
static var command: SMB2.Command = .IOCTL
let header: Header
let requestData: IOCtlRequestProtocol?
init(fileId: FileId ,ctlCode: IOCtlCode, requestData: IOCtlRequestProtocol?, flags: IOCtlRequest.Flags = []) {
let offset = requestData != nil ? UInt32(MemoryLayout<SMB2.Header>.size + MemoryLayout<IOCtlRequest.Header>.size) : 0
self.header = Header(size: 57, reserved: 0, ctlCode: ctlCode, fileId: fileId, inputOffset: offset, inputCount: UInt32((requestData?.data().count ?? 0)), maxInputResponse: 0, outputOffset: offset, outputCount: 0, maxOutputResponse: UInt32(Int32.max), flags: flags, reserved2: 0)
self.header = Header(size: 57, reserved: 0, _ctlCode: ctlCode.rawValue, fileId: fileId, inputOffset: offset, inputCount: UInt32((requestData?.data().count ?? 0)), maxInputResponse: 0, outputOffset: offset, outputCount: 0, maxOutputResponse: UInt32(Int32.max), flags: flags, reserved2: 0)
self.requestData = requestData
}
@@ -38,7 +36,10 @@ extension SMB2 {
struct Header {
let size: UInt16
fileprivate let reserved: UInt16
let ctlCode: IOCtlCode
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
let fileId: FileId
let inputOffset: UInt32
let inputCount: UInt32
@@ -91,7 +92,10 @@ extension SMB2 {
struct Header {
let size: UInt16
fileprivate let reserved: UInt16
let ctlCode: IOCtlCode
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
let fileId: FileId
let inputOffset: UInt32
let inputCount: UInt32
@@ -102,43 +106,35 @@ extension SMB2 {
}
}
struct IOCtlCode: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let DFS_GET_REFERRALS = IOCtlCode(rawValue: 0x00060194)
public static let DFS_GET_REFERRALS_EX = IOCtlCode(rawValue: 0x000601B0)
public static let SET_REPARSE_POINT = IOCtlCode(rawValue: 0x000900A4)
public static let FILE_LEVEL_TRIM = IOCtlCode(rawValue: 0x00098208)
public static let PIPE_PEEK = IOCtlCode(rawValue: 0x0011400C)
public static let PIPE_WAIT = IOCtlCode(rawValue: 0x00110018)
enum IOCtlCode: UInt32 {
case DFS_GET_REFERRALS = 0x00060194
case DFS_GET_REFERRALS_EX = 0x000601B0
case SET_REPARSE_POINT = 0x000900A4
case FILE_LEVEL_TRIM = 0x00098208
case PIPE_PEEK = 0x0011400C
case PIPE_WAIT = 0x00110018
/// PIPE_TRANSCEIVE is valid only on a named pipe with mode set to FILE_PIPE_MESSAGE_MODE.
public static let PIPE_TRANSCEIVE = IOCtlCode(rawValue: 0x0011C017)
case PIPE_TRANSCEIVE = 0x0011C017
/// Get ResumeKey used by the client to uniquely identify the source file in an FSCTL_SRV_COPYCHUNK or FSCTL_SRV_COPYCHUNK_WRITE request.
public static let SRV_REQUEST_RESUME_KEY = IOCtlCode(rawValue: 0x00140078)
case SRV_REQUEST_RESUME_KEY = 0x00140078
/// Get all the revision time-stamps that are associated with the Tree Connect share in which the open resides
public static let SRV_ENUMERATE_SNAPSHOTS = IOCtlCode(rawValue: 0x00144064)
case SRV_ENUMERATE_SNAPSHOTS = 0x00144064
/// Reads a chunk of file for performing server side copy operations.
public static let SRV_COPYCHUNK = IOCtlCode(rawValue: 0x001440F2)
case SRV_COPYCHUNK = 0x001440F2
/// Retrieve data from the Content Information File associated with a specified file, not valid for the SMB 2.0.2 dialect.
public static let SRV_READ_HASH = IOCtlCode(rawValue: 0x001441BB)
case SRV_READ_HASH = 0x001441BB
/// Writes the chunk of file for performing server side copy operations.
public static let SRV_COPYCHUNK_WRITE = IOCtlCode(rawValue: 0x001480F2)
case SRV_COPYCHUNK_WRITE = 0x001480F2
/// Request resiliency for a specified open file, not valid for the SMB 2.0.2 dialect.
public static let LMR_REQUEST_RESILIENCY = IOCtlCode(rawValue: 0x001401D4)
case LMR_REQUEST_RESILIENCY = 0x001401D4
/// Get server network interface info e.g. link speed and socket address information
public static let QUERY_NETWORK_INTERFACE_INFO = IOCtlCode(rawValue: 0x001401FC)
case QUERY_NETWORK_INTERFACE_INFO = 0x001401FC
/// Request validation of a previous SMB 2 NEGOTIATE, valid for SMB 3.0 and SMB 3.0.2 dialects.
public static let VALIDATE_NEGOTIATE_INFO = IOCtlCode(rawValue: 0x00140204)
case VALIDATE_NEGOTIATE_INFO = 0x00140204
}
struct IOCtlRequestData {
struct CopyChunk: IOCtlRequestProtocol {
static var command: SMB2.Command = .IOCTL
let sourceKey: (UInt64, UInt64, UInt64)
let chunkCount: UInt32
let chunks: [Chunk]
@@ -160,26 +156,31 @@ extension SMB2 {
}
struct ReadHash: IOCtlRequestProtocol {
static var command: SMB2.Command = .IOCTL
let _hashType: IOCtlHashType
let _hashVersion: IOCtlHashVersion
let _hashRetrievalType: IOCtlHashRetrievalType
let _hashType: UInt32
var hashType: IOCtlHashType {
return IOCtlHashType(rawValue: _hashType) ?? .PEER_DIST
}
let _hashVersion: UInt32
var hashVersion: IOCtlHashVersion {
return IOCtlHashVersion(rawValue: _hashVersion) ?? .VER_1
}
let _hashRetrievalType: UInt32
var hashRetrievalType: IOCtlHashRetrievalType {
return IOCtlHashRetrievalType(rawValue: _hashRetrievalType) ?? .FILE_BASED
}
let length: UInt32
let offset: UInt64
init(offset: UInt64, length: UInt32, hashType: IOCtlHashType = .PEER_DIST, hashVersion: IOCtlHashVersion = .VER_1, hashRetrievalType: IOCtlHashRetrievalType = .FILE_BASED) {
self._hashType = hashType
self._hashVersion = hashVersion
self._hashRetrievalType = hashRetrievalType
self._hashType = hashType.rawValue
self._hashVersion = hashVersion.rawValue
self._hashRetrievalType = hashRetrievalType.rawValue
self.length = length
self.offset = offset
}
}
struct ResilencyRequest: IOCtlRequestProtocol {
static var command: SMB2.Command = .IOCTL
let timeout: UInt32
fileprivate let reserved: UInt32
@@ -191,8 +192,6 @@ extension SMB2 {
}
struct ValidateNegotiateInfo: IOCtlRequestProtocol {
static var command: SMB2.Command = .IOCTL
let header: ValidateNegotiateInfo.Header
let dialects: [UInt16]
@@ -310,11 +309,11 @@ extension SMB2 {
static let ipv6: sa_family_t = 0x17
var sockaddr: sockaddr_in {
return unsafeBitCast(self.sockaddrStorage, to: sockaddr_in.self)
return Data.mapMemory(from: self.sockaddrStorage)!
}
var sockaddr6: sockaddr_in6 {
return unsafeBitCast(self.sockaddrStorage, to: sockaddr_in6.self)
return Data.mapMemory(from: self.sockaddrStorage)!
}
}
}
@@ -341,35 +340,17 @@ extension SMB2 {
static let RDMA_CAPABLE = IOCtlCapabilities(rawValue: 0x00000002)
}
struct IOCtlHashType: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let PEER_DIST = IOCtlHashType(rawValue: 0x00000001)
enum IOCtlHashType: UInt32 {
case PEER_DIST = 0x00000001
}
struct IOCtlHashVersion: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let VER_1 = IOCtlHashVersion(rawValue: 0x00000001)
public static let VER_2 = IOCtlHashVersion(rawValue: 0x00000002)
enum IOCtlHashVersion: UInt32 {
case VER_1 = 0x00000001
case VER_2 = 0x00000002
}
struct IOCtlHashRetrievalType: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let HASH_BASED = IOCtlHashRetrievalType(rawValue: 0x00000001)
public static let FILE_BASED = IOCtlHashRetrievalType(rawValue: 0x00000002)
enum IOCtlHashRetrievalType: UInt32 {
case HASH_BASED = 0x00000001
case FILE_BASED = 0x00000002
}
}
+15 -25
View File
@@ -12,8 +12,6 @@ extension SMB2 {
// MARK: SMB2 Change Notify
struct ChangeNotifyRequest: SMBRequestBody {
static var command: SMB2.Command = .CHANGE_NOTIFY
let size: UInt16
let flags: ChangeNotifyRequest.Flags
let outputBufferLength: UInt32
@@ -89,7 +87,9 @@ extension SMB2 {
while i < maxLoop {
let nextOffset: UInt32 = data.scanValue(start: offset) ?? 0
let actionValue: UInt32 = data.scanValue(start: offset + 4) ?? 0
let action = FileNotifyAction(rawValue: actionValue)
guard let action = FileNotifyAction(rawValue: actionValue) else {
continue
}
let fileNameLen = Int(data.scanValue(start: offset + 8) as UInt32? ?? 0)
let fileName = data.scanString(start: offset + 12, length: fileNameLen, using: .utf16) ?? ""
@@ -106,38 +106,28 @@ extension SMB2 {
}
}
struct FileNotifyAction: Option {
init(rawValue: UInt32) {
self.rawValue = rawValue
}
init(_ rawValue: UInt32) {
self.rawValue = rawValue
}
let rawValue: UInt32
enum FileNotifyAction: UInt32 {
/// The file was added to the directory.
public static let ADDED = FileNotifyAction(0x00000001)
case ADDED = 0x00000001
/// The file was removed from the directory.
public static let REMOVED = FileNotifyAction(0x00000002)
case REMOVED = 0x00000002
/// The file was modified. This can be a change to the data or attributes of the file.
public static let MODIFIED = FileNotifyAction(0x00000003)
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.
public static let RENAMED_OLD_NAME = FileNotifyAction(0x00000004)
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.
public static let RENAMED_NEW_NAME = FileNotifyAction(0x00000005)
case RENAMED_NEW_NAME = 0x00000005
/// The file was added to a named stream.
public static let ADDED_STREAM = FileNotifyAction(0x00000006)
case ADDED_STREAM = 0x00000006
/// The file was removed from the named stream.
public static let REMOVED_STREAM = FileNotifyAction(0x00000007)
case REMOVED_STREAM = 0x00000007
/// The file was modified. This can be a change to the data or attributes of the file.
public static let MODIFIED_STREAM = FileNotifyAction(0x00000008)
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".
public static let REMOVED_BY_DELETE = FileNotifyAction(0x00000009)
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".
public static let NOT_TUNNELLED = FileNotifyAction(0x0000000A)
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".
public static let TUNNELLED_ID_COLLISION = FileNotifyAction(0x0000000B)
case TUNNELLED_ID_COLLISION = 0x0000000B
}
}
+9 -22
View File
@@ -12,8 +12,6 @@ extension SMB2 {
// MARK: SMB2 Query Directory
struct QueryDirectoryRequest: SMBRequestBody {
static let command: SMB2.Command = .QUERY_DIRECTORY
let header: QueryDirectoryRequest.Header
let searchPattern: String?
@@ -72,23 +70,17 @@ extension SMB2 {
let header: SMB2FilesInformationHeader
switch type {
case .fileDirectoryInformation:
let tHeader: FileDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
header = buffer.scanValue(start: offset) as FileDirectoryInformationHeader!
case .fileFullDirectoryInformation:
let tHeader: FileFullDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
header = buffer.scanValue(start: offset) as FileFullDirectoryInformationHeader!
case .fileIdFullDirectoryInformation:
let tHeader: FileIdFullDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
header = buffer.scanValue(start: offset) as FileIdFullDirectoryInformationHeader!
case .fileBothDirectoryInformation:
let tHeader: FileBothDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
header = buffer.scanValue(start: offset) as FileBothDirectoryInformationHeader!
case .fileIdBothDirectoryInformation:
let tHeader: FileIdBothDirectoryInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
header = buffer.scanValue(start: offset) as FileIdBothDirectoryInformationHeader!
case .fileNamesInformation:
let tHeader: FileNamesInformationHeader = buffer.scanValue(start: offset)!
header = tHeader
header = buffer.scanValue(start: offset) as FileNamesInformationHeader!
default:
return []
}
@@ -104,10 +96,8 @@ extension SMB2 {
}
init? (data: Data) {
let tOffset: UInt16 = data.scanValue(start: 2)!
let offset = Int(tOffset)
let tLength: UInt32 = data.scanValue(start: 4)!
let length = Int(tLength)
let offset = Int(data.scanValue(start: 2) as UInt16!)
let length = Int(data.scanValue(start: 4) as UInt32!)
guard data.count > offset + length else {
return nil
}
@@ -118,8 +108,6 @@ extension SMB2 {
// MARK: SMB2 Query Info
struct QueryInfoRequest: SMBRequestBody {
static var command: SMB2.Command = .QUERY_INFO
let header: Header
let buffer: Data?
@@ -208,8 +196,7 @@ extension SMB2 {
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
let offset: UInt16 = decode(offsetData)*/
let tLength: UInt32 = data.scanValue(start: 4)!
let length = Int(tLength)
let length = Int(data.scanValue(start: 4) as UInt32!)
guard data.count >= 8 + length else {
return nil
+131 -164
View File
@@ -15,100 +15,88 @@ protocol SMB2FilesInformationHeader: SMBResponseBody {
}
extension SMB2 {
struct FileInformationEnum: Option {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
public static let none = 0x00
public static let fileDirectoryInformation = FileInformationEnum(rawValue: 0x01)
public static let fileFullDirectoryInformation = FileInformationEnum(rawValue: 0x02)
public static let fileBothDirectoryInformation = FileInformationEnum(rawValue: 0x03)
public static let fileBasicInformation = FileInformationEnum(rawValue: 0x04)
public static let fileStandardInformation = FileInformationEnum(rawValue: 0x05)
public static let fileInternalInformation = FileInformationEnum(rawValue: 0x06)
public static let fileEaInformation = FileInformationEnum(rawValue: 0x07)
public static let fileAccessInformation = FileInformationEnum(rawValue: 0x08)
public static let fileNameInformation = FileInformationEnum(rawValue: 0x09)
public static let fileRenameInformation = FileInformationEnum(rawValue: 0x0A)
public static let fileLinkInformation = FileInformationEnum(rawValue: 0x0B)
public static let fileNamesInformation = FileInformationEnum(rawValue: 0x0C)
public static let fileDispositionInformation = FileInformationEnum(rawValue: 0x0D)
public static let filePositionInformation = FileInformationEnum(rawValue: 0x0E)
public static let fileFullEaInformation = FileInformationEnum(rawValue: 0x0F)
public static let fileModeInformation = FileInformationEnum(rawValue: 0x10)
public static let fileAlignmentInformation = FileInformationEnum(rawValue: 0x11)
public static let fileAllInformation = FileInformationEnum(rawValue: 0x12)
public static let fileAllocationInformation = FileInformationEnum(rawValue: 0x13)
public static let fileEndOfFileInformation = FileInformationEnum(rawValue: 0x14)
public static let fileAlternateNameInformation = FileInformationEnum(rawValue: 0x15)
public static let fileStreamInformation = FileInformationEnum(rawValue: 0x16)
public static let filePipeInformation = FileInformationEnum(rawValue: 0x17)
public static let filePipeLocalInformation = FileInformationEnum(rawValue: 0x18)
public static let filePipeRemoteInformation = FileInformationEnum(rawValue: 0x19)
public static let fileMailslotQueryInformation = FileInformationEnum(rawValue: 0x1A)
public static let fileMailslotSetInformation = FileInformationEnum(rawValue: 0x1B)
public static let fileCompressionInformation = FileInformationEnum(rawValue: 0x1C)
public static let fileObjectIdInformation = FileInformationEnum(rawValue: 0x1D)
public static let fileCompletionInformation = FileInformationEnum(rawValue: 0x1E)
public static let fileMoveClusterInformation = FileInformationEnum(rawValue: 0x1F)
public static let fileQuotaInformation = FileInformationEnum(rawValue: 0x20)
public static let fileReparsePointInformation = FileInformationEnum(rawValue: 0x21)
public static let fileNetworkOpenInformation = FileInformationEnum(rawValue: 0x22)
public static let fileAttributeTagInformation = FileInformationEnum(rawValue: 0x23)
public static let fileTrackingInformation = FileInformationEnum(rawValue: 0x24)
public static let fileIdBothDirectoryInformation = FileInformationEnum(rawValue: 0x25)
public static let fileIdFullDirectoryInformation = FileInformationEnum(rawValue: 0x26)
public static let fileValidDataLengthInformation = FileInformationEnum(rawValue: 0x27)
public static let fileShortNameInformation = FileInformationEnum(rawValue: 0x28)
public static let fileIoCompletionNotificationInformation = FileInformationEnum(rawValue: 0x29)
public static let fileIoStatusBlockRangeInformation = FileInformationEnum(rawValue: 0x2A)
public static let fileIoPriorityHintInformation = FileInformationEnum(rawValue: 0x2B)
public static let fileSfioReserveInformation = FileInformationEnum(rawValue: 0x2C)
public static let fileSfioVolumeInformation = FileInformationEnum(rawValue: 0x2D)
public static let fileHardLinkInformation = FileInformationEnum(rawValue: 0x2E)
public static let fileProcessIdsUsingFileInformation = FileInformationEnum(rawValue: 0x2F)
public static let fileNormalizedNameInformation = FileInformationEnum(rawValue: 0x30)
public static let fileNetworkPhysicalNameInformation = FileInformationEnum(rawValue: 0x31)
public static let fileIdGlobalTxDirectoryInformation = FileInformationEnum(rawValue: 0x32)
public static let fileIsRemoteDeviceInformation = FileInformationEnum(rawValue: 0x33)
public static let fileUnusedInformation = FileInformationEnum(rawValue: 0x34)
public static let fileNumaNodeInformation = FileInformationEnum(rawValue: 0x35)
public static let fileStandardLinkInformation = FileInformationEnum(rawValue: 0x36)
public static let fileRemoteProtocolInformation = FileInformationEnum(rawValue: 0x37)
public static let fileRenameInformationBypassAccessCheck = FileInformationEnum(rawValue: 0x38)
public static let fileLinkInformationBypassAccessCheck = FileInformationEnum(rawValue: 0x39)
public static let fileVolumeNameInformation = FileInformationEnum(rawValue: 0x3A)
public static let fileIdInformation = FileInformationEnum(rawValue: 0x3B)
public static let fileIdExtdDirectoryInformation = FileInformationEnum(rawValue: 0x3C)
public static let fileReplaceCompletionInformation = FileInformationEnum(rawValue: 0x3D)
public static let fileHardLinkFullIdInformation = FileInformationEnum(rawValue: 0x3E)
public static let fileIdExtdBothDirectoryInformation = FileInformationEnum(rawValue: 0x3F)
public static let fileMaximumInformation = FileInformationEnum(rawValue: 0x40)
enum FileInformationEnum: UInt8 {
case none = 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]
}
struct FileSystemInformationEnum: Option {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
public static let none = FileSystemInformationEnum(rawValue: 0x00)
public static let fileFsAttributeInformation = FileSystemInformationEnum(rawValue: 0x01)
public static let fileFsControlInformation = FileSystemInformationEnum(rawValue: 0x02)
public static let fileFsDeviceInformation = FileSystemInformationEnum(rawValue: 0x03)
public static let fileFsFullSizeInformation = FileSystemInformationEnum(rawValue: 0x04)
public static let fileFsObjectIdInformation = FileSystemInformationEnum(rawValue: 0x05)
public static let fileFsSectorSizeInformation = FileSystemInformationEnum(rawValue: 0x06)
public static let fileFsSizeInformation = FileSystemInformationEnum(rawValue: 0x07)
public static let fileFsVolumeInformation = FileSystemInformationEnum(rawValue: 0x08)
enum FileSystemInformationEnum: UInt8 {
case none = 0
case fileFsAttributeInformation
case fileFsControlInformation
case fileFsDeviceInformation
case fileFsFullSizeInformation
case fileFsObjectIdInformation
case fileFsSectorSizeInformation
case fileFsSizeInformation
case fileFsVolumeInformation
}
struct FileSecurityInfo: OptionSet {
@@ -315,89 +303,71 @@ extension SMB2 {
}
struct FilePipeInformation {
let readMode: ReadMode
fileprivate let completionMode: CompletionMode
struct ReadMode: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let BYTE_STREAM_MODE = ReadMode(rawValue: 0x00000000)
public static let MESSAGE_MODE = ReadMode(rawValue: 0x00000001)
fileprivate let _readMode: UInt32
var readMode: ReadMode {
return ReadMode(rawValue: _readMode) ?? .BYTE_STREAM_MODE
}
fileprivate let _completionMode: UInt32
var completionMode: CompletionMode {
return CompletionMode(rawValue: _completionMode) ?? .QUEUE_OPERATION
}
struct CompletionMode: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let QUEUE_OPERATION = CompletionMode(rawValue: 0x00000000)
public static let COMPLETE_OPERATION = CompletionMode(rawValue: 0x00000001)
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 {
let namedPipeType: Type
let namedPipeConfiguration: Configuration
fileprivate let _namedPipeType: UInt32
var namedPipeType: Type {
return Type(rawValue: _namedPipeType) ?? .BYTE_STREAM_TYPE
}
fileprivate 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
let namedPipeState: State
let namedPipeEnd: End
struct `Type`: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let BYTE_STREAM_TYPE = `Type`(rawValue: 0x00000000)
public static let MESSAGE_TYPE = `Type`(rawValue: 0x00000001)
fileprivate let _namedPipeState: UInt32
var namedPipeState: State {
return State(rawValue: _namedPipeState) ?? .DISCONNECTED_STATE
}
fileprivate let _namedPipeEnd: UInt32
var namedPipeEnd: End {
return End(rawValue: _namedPipeEnd) ?? .CLIENT_END
}
struct Configuration: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let INBOUND = Configuration(rawValue: 0x00000000)
public static let OUTBOUND = Configuration(rawValue: 0x00000001)
public static let FULL_DUPLEX = Configuration(rawValue: 0x00000002)
enum `Type`: UInt32 {
case BYTE_STREAM_TYPE = 0x00000000
case MESSAGE_TYPE = 0x00000001
}
struct State: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let DISCONNECTED_STATE = State(rawValue: 0x00000001)
public static let LISTENING_STATE = State(rawValue: 0x00000002)
public static let CONNECTED_STATE = State(rawValue: 0x00000003)
public static let CLOSING_STATE = State(rawValue: 0x00000004)
enum Configuration: UInt32 {
case INBOUND = 0x00000000
case OUTBOUND = 0x00000001
case FULL_DUPLEX = 0x00000002
}
struct End: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let CLIENT_END = End(rawValue: 0x00000000)
public static let SERVER_END = End(rawValue: 0x00000001)
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
}
}
@@ -442,18 +412,15 @@ extension SMB2 {
}
struct FileFsDeviceInformation {
let deviceType: DeviceType
fileprivate let _deviceType: UInt32
var deviceType: DeviceType {
return DeviceType(rawValue: _deviceType) ?? .DISK
}
let charactristics: Charactristics
struct DeviceType: Option {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let CD_ROM = DeviceType(rawValue: 0x00000002)
public static let DISK = DeviceType(rawValue: 0x00000007)
enum DeviceType: UInt32 {
case CD_ROM = 0x00000002
case DISK = 0x00000007
}
struct Charactristics: OptionSet {
@@ -504,7 +471,7 @@ extension SMB2 {
/// 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 public static let of file names when it places a name on disk.
/// 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)
-8
View File
@@ -12,8 +12,6 @@ extension SMB2 {
// MARK: SMB2 Negotiating
struct NegotiateRequest: SMBRequestBody {
static var command: SMB2.Command = .NEGOTIATE
let header: NegotiateRequest.Header
let dialects: [UInt16]
let contexts: [(type: NegotiateContextType, data: Data)]
@@ -180,8 +178,6 @@ extension SMB2 {
// MARK: SMB2 Session Setup
struct SessionSetupRequest: SMBRequestBody {
static var command: SMB2.Command = .SESSION_SETUP
let header: SessionSetupRequest.Header
let buffer: Data?
@@ -295,8 +291,6 @@ extension SMB2 {
// MARK: SMB2 Log off
struct LogOff: SMBRequestBody, SMBResponseBody {
static var command: SMB2.Command = .LOGOFF
let size: UInt16
let reserved: UInt16
@@ -309,8 +303,6 @@ extension SMB2 {
// MARK: SMB2 Echo
struct Echo: SMBRequestBody, SMBResponseBody {
static var command: SMB2.Command = .ECHO
let size: UInt16
let reserved: UInt16
-2
View File
@@ -11,8 +11,6 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Set Info
struct SetInfoRequest: SMBRequestBody {
static var command: SMB2.Command = .SET_INFO
let header: Header
let buffer: Data?
+2 -6
View File
@@ -12,15 +12,13 @@ extension SMB2 {
// MARK: SMB2 Tree Connect
struct TreeConnectRequest: SMBRequestBody {
static var command: SMB2.Command = .TREE_CONNECT
let header: TreeConnectRequest.Header
let buffer: Data?
var path: String {
return buffer.flatMap({ String.init(data: $0, encoding: .utf16) }) ?? ""
return ""
}
var share: String {
return path.split(separator: "/", omittingEmptySubsequences: true).first.map(String.init) ?? ""
return ""
}
init? (header: TreeConnectRequest.Header, host: String, share: String) {
@@ -127,8 +125,6 @@ extension SMB2 {
// MARK: SMB2 Tree Disconnect
struct TreeDisconnect: SMBRequestBody, SMBResponseBody {
static var command: SMB2.Command = .TREE_DISCONNECT
let size: UInt16
let reserved: UInt16
+34 -37
View File
@@ -22,17 +22,20 @@ struct SMB2 {
let size: UInt16
let creditCharge: UInt16
// error messages from the server to the client
let status: NTStatus
let status: UInt32
enum StatusSeverity: UInt8 {
case success = 0, information, warning, error
}
var statusDetails: (severity: StatusSeverity, customer: Bool, facility: UInt16, code: UInt16) {
let severity = StatusSeverity(rawValue: UInt8(status.rawValue >> 30))!
return (severity, status.rawValue & 0x20000000 != 0,
UInt16((status.rawValue & 0x0FFF0000) >> 16),
UInt16(status.rawValue & 0x0000FFFF))
let severity = StatusSeverity(rawValue: UInt8(status >> 30))!
return (severity, status & 0x20000000 != 0, UInt16((status & 0x0FFF0000) >> 16), UInt16(status & 0x0000FFFF))
}
fileprivate let _command: UInt16
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
}
}
let command: Command
let creditRequestResponse: UInt16
let flags: Flags
var nextCommand: UInt32
@@ -51,8 +54,8 @@ struct SMB2 {
init(command: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [], nextCommand: UInt32 = 0, messageId: UInt64, treeId: UInt32, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status
self.command = command
self.status = status.rawValue
self._command = command.rawValue
self.creditCharge = creditCharge
self.creditRequestResponse = creditRequestResponse
self.flags = flags
@@ -67,8 +70,8 @@ struct SMB2 {
init(asyncCommand: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [.ASYNC_COMMAND], nextCommand: UInt32 = 0, messageId: UInt64, asyncId: UInt64, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status
self.command = asyncCommand
self.status = status.rawValue
self._command = asyncCommand.rawValue
self.creditCharge = creditCharge
self.creditRequestResponse = creditRequestResponse
self.flags = flags.union([Flags.ASYNC_COMMAND])
@@ -107,33 +110,27 @@ struct SMB2 {
static let REPLAY_OPERATION = Flags(rawValue: 0x20000000)
}
struct Command: Option {
init(rawValue: UInt16) {
self.rawValue = rawValue
}
let rawValue: UInt16
public static let NEGOTIATE = Command(rawValue: 0x0000)
public static let SESSION_SETUP = Command(rawValue: 0x0001)
public static let LOGOFF = Command(rawValue: 0x0002)
public static let TREE_CONNECT = Command(rawValue: 0x0003)
public static let TREE_DISCONNECT = Command(rawValue: 0x0004)
public static let CREATE = Command(rawValue: 0x0005)
public static let CLOSE = Command(rawValue: 0x0006)
public static let FLUSH = Command(rawValue: 0x0007)
public static let READ = Command(rawValue: 0x0008)
public static let WRITE = Command(rawValue: 0x0009)
public static let LOCK = Command(rawValue: 0x000A)
public static let IOCTL = Command(rawValue: 0x000B)
public static let CANCEL = Command(rawValue: 0x000C)
public static let ECHO = Command(rawValue: 0x000D)
public static let QUERY_DIRECTORY = Command(rawValue: 0x000E)
public static let CHANGE_NOTIFY = Command(rawValue: 0x000F)
public static let QUERY_INFO = Command(rawValue: 0x0010)
public static let SET_INFO = Command(rawValue: 0x0011)
public static let OPLOCK_BREAK = Command(rawValue: 0x0012)
public static let INVALID = Command(rawValue: 0xFFFF)
enum Command: UInt16 {
case NEGOTIATE = 0x0000
case SESSION_SETUP = 0x0001
case LOGOFF = 0x0002
case TREE_CONNECT = 0x0003
case TREE_DISCONNECT = 0x0004
case CREATE = 0x0005
case CLOSE = 0x0006
case FLUSH = 0x0007
case READ = 0x0008
case WRITE = 0x0009
case LOCK = 0x000A
case IOCTL = 0x000B
case CANCEL = 0x000C
case ECHO = 0x000D
case QUERY_DIRECTORY = 0x000E
case CHANGE_NOTIFY = 0x000F
case QUERY_INFO = 0x0010
case SET_INFO = 0x0011
case OPLOCK_BREAK = 0x0012
case INVALID = 0xFFFF
}
// MARK: SMB2 Oplock Break
+120 -126
View File
@@ -10,132 +10,126 @@ import Foundation
/// Error Types and Description
struct NTStatus: Option, Error, CustomStringConvertible {
init(rawValue: UInt32) {
self.rawValue = rawValue
}
var rawValue: UInt32
public static let SUCCESS = NTStatus(rawValue: 0x00000000)
public static let NOT_IMPLEMENTED = NTStatus(rawValue: 0xC0000002)
public static let INVALID_DEVICE_REQUEST = NTStatus(rawValue: 0xC0000010)
public static let ILLEGAL_FUNCTION = NTStatus(rawValue: 0xC00000AF)
public static let NO_SUCH_FILE = NTStatus(rawValue: 0xC000000F)
public static let NO_SUCH_DEVICE = NTStatus(rawValue: 0xC000000E)
public static let OBJECT_NAME_NOT_FOUND = NTStatus(rawValue: 0xC0000034)
public static let OBJECT_PATH_INVALID = NTStatus(rawValue: 0xC0000039)
public static let OBJECT_PATH_NOT_FOUND = NTStatus(rawValue: 0xC000003A)
public static let OBJECT_PATH_SYNTAX_BAD = NTStatus(rawValue: 0xC000003B)
public static let DFS_EXIT_PATH_FOUND = NTStatus(rawValue: 0xC000009B)
public static let REDIRECTOR_NOT_STARTED = NTStatus(rawValue: 0xC00000FB)
public static let TOO_MANY_OPENED_FILES = NTStatus(rawValue: 0xC000011F)
public static let ACCESS_DENIED = NTStatus(rawValue: 0xC0000022)
public static let INVALID_LOCK_SEQUENCE = NTStatus(rawValue: 0xC000001E)
public static let INVALID_VIEW_SIZE = NTStatus(rawValue: 0xC000001F)
public static let ALREADY_COMMITTED = NTStatus(rawValue: 0xC0000021)
public static let PORT_CONNECTION_REFUSED = NTStatus(rawValue: 0xC0000041)
public static let THREAD_IS_TERMINATING = NTStatus(rawValue: 0xC000004B)
public static let DELETE_PENDING = NTStatus(rawValue: 0xC0000056)
public static let PRIVILEGE_NOT_HELD = NTStatus(rawValue: 0xC0000061)
public static let LOGON_FAILURE = NTStatus(rawValue: 0xC000006D)
public static let FILE_IS_A_DIRECTORY = NTStatus(rawValue: 0xC00000BA)
public static let FILE_RENAMED = NTStatus(rawValue: 0xC00000D5)
public static let PROCESS_IS_TERMINATING = NTStatus(rawValue: 0xC000010A)
public static let DIRECTORY_NOT_EMPTY = NTStatus(rawValue: 0xC0000101)
public static let CANNOT_DELETE = NTStatus(rawValue: 0xC0000121)
public static let FILE_NOT_AVAILABLE = NTStatus(rawValue: 0xC0000467)
public static let FILE_DELETED = NTStatus(rawValue: 0xC0000123)
public static let SMB_BAD_FID = NTStatus(rawValue: 0x00060001)
public static let INVALID_HANDLE = NTStatus(rawValue: 0xC0000008)
public static let OBJECT_TYPE_MISMATCH = NTStatus(rawValue: 0xC0000024)
public static let PORT_DISCONNECTED = NTStatus(rawValue: 0xC0000037)
public static let INVALID_PORT_HANDLE = NTStatus(rawValue: 0xC0000042)
public static let FILE_CLOSED = NTStatus(rawValue: 0xC0000128)
public static let HANDLE_NOT_CLOSABLE = NTStatus(rawValue: 0xC0000235)
public static let SECTION_TOO_BIG = NTStatus(rawValue: 0xC0000040)
public static let TOO_MANY_PAGING_FILES = NTStatus(rawValue: 0xC0000097)
public static let INSUFF_SERVER_RESOURCES = NTStatus(rawValue: 0xC0000205)
public static let OS2_INVALID_ACCESS = NTStatus(rawValue: 0x000C0001)
public static let ACCESS_DENIED_2 = NTStatus(rawValue: 0xC00000CA)
public static let DATA_ERROR = NTStatus(rawValue: 0xC000009C)
public static let NOT_SAME_DEVICE = NTStatus(rawValue: 0xC00000D4)
public static let NO_MORE_FILES = NTStatus(rawValue: 0x80000006)
public static let NO_MORE_ENTRIES = NTStatus(rawValue: 0x8000001A)
public static let UNSUCCESSFUL = NTStatus(rawValue: 0xC0000001)
public static let SHARING_VIOLATION = NTStatus(rawValue: 0xC0000043)
public static let FILE_LOCK_CONFLICT = NTStatus(rawValue: 0xC0000054)
public static let LOCK_NOT_GRANTED = NTStatus(rawValue: 0xC0000055)
public static let END_OF_FILE = NTStatus(rawValue: 0xC0000011)
public static let NOT_SUPPORTED = NTStatus(rawValue: 0xC00000BB)
public static let OBJECT_NAME_COLLISION = NTStatus(rawValue: 0xC0000035)
public static let INVALID_PARAMETER = NTStatus(rawValue: 0xC000000D)
public static let OS2_INVALID_LEVEL = NTStatus(rawValue: 0x007C0001)
public static let OS2_NEGATIVE_SEEK = NTStatus(rawValue: 0x00830001)
public static let RANGE_NOT_LOCKED = NTStatus(rawValue: 0xC000007E)
public static let OS2_NO_MORE_SIDS = NTStatus(rawValue: 0x00710001)
public static let OS2_CANCEL_VIOLATION = NTStatus(rawValue: 0x00AD0001)
public static let OS2_ATOMIC_LOCKS_NOT_SUPPORTED = NTStatus(rawValue: 0x00AE0001)
public static let INVALID_INFO_CLASS = NTStatus(rawValue: 0xC0000003)
public static let INVALID_PIPE_STATE = NTStatus(rawValue: 0xC00000AD)
public static let INVALID_READ_MODE = NTStatus(rawValue: 0xC00000B4)
public static let OS2_CANNOT_COPY = NTStatus(rawValue: 0x010A0001)
public static let STOPPED_ON_SYMLINK = NTStatus(rawValue: 0x8000002D)
public static let INSTANCE_NOT_AVAILABLE = NTStatus(rawValue: 0xC00000AB)
public static let PIPE_NOT_AVAILABLE = NTStatus(rawValue: 0xC00000AC)
public static let PIPE_BUSY = NTStatus(rawValue: 0xC00000AE)
public static let PIPE_CLOSING = NTStatus(rawValue: 0xC00000B1)
public static let PIPE_EMPTY = NTStatus(rawValue: 0xC00000D9)
public static let PIPE_DISCONNECTED = NTStatus(rawValue: 0xC00000B0)
public static let BUFFER_OVERFLOW = NTStatus(rawValue: 0x80000005)
public static let MORE_PROCESSING_REQUIRED = NTStatus(rawValue: 0xC0000016)
public static let EA_TOO_LARGE = NTStatus(rawValue: 0xC0000050)
public static let OS2_EAS_DIDNT_FIT = NTStatus(rawValue: 0x01130001)
public static let EAS_NOT_SUPPORTED = NTStatus(rawValue: 0xC000004F)
public static let EA_LIST_INCONSISTENT = NTStatus(rawValue: 0x80000014)
public static let OS2_EA_ACCESS_DENIED = NTStatus(rawValue: 0x03E20001)
public static let NOTIFY_ENUM_DIR = NTStatus(rawValue: 0x0000010C)
public static let INVALID_SMB = NTStatus(rawValue: 0x00010002)
public static let WRONG_PASSWORD = NTStatus(rawValue: 0xC000006A)
public static let PATH_NOT_COVERED = NTStatus(rawValue: 0xC0000257)
public static let NETWORK_NAME_DELETED = NTStatus(rawValue: 0xC00000C9)
public static let SMB_BAD_TID = NTStatus(rawValue: 0x00050002)
public static let BAD_NETWORK_NAME = NTStatus(rawValue: 0xC00000CC)
public static let BAD_DEVICE_TYPE = NTStatus(rawValue: 0xC00000CB)
public static let SMB_BAD_COMMAND = NTStatus(rawValue: 0x00160002)
public static let PRINT_QUEUE_FULL = NTStatus(rawValue: 0xC00000C6)
public static let NO_SPOOL_SPACE = NTStatus(rawValue: 0xC00000C7)
public static let PRINT_CANCELLED = NTStatus(rawValue: 0xC00000C8)
public static let UNEXPECTED_NETWORK_ERROR = NTStatus(rawValue: 0xC00000C4)
public static let IO_TIMEOUT = NTStatus(rawValue: 0xC00000B5)
public static let REQUEST_NOT_ACCEPTED = NTStatus(rawValue: 0xC00000D0)
public static let TOO_MANY_SESSIONS = NTStatus(rawValue: 0xC00000CE)
public static let SMB_BAD_UID = NTStatus(rawValue: 0x005B0002)
public static let SMB_USE_MPX = NTStatus(rawValue: 0x00FA0002)
public static let SMB_USE_STANDARD = NTStatus(rawValue: 0x00FB0002)
public static let SMB_CONTINUE_MPX = NTStatus(rawValue: 0x00FC0002)
public static let ACCOUNT_DISABLED = NTStatus(rawValue: 0xC0000072)
public static let ACCOUNT_EXPIRED = NTStatus(rawValue: 0xC0000193)
public static let INVALID_WORKSTATION = NTStatus(rawValue: 0xC0000070)
public static let INVALID_LOGON_HOURS = NTStatus(rawValue: 0xC000006F)
public static let PASSWORD_EXPIRED = NTStatus(rawValue: 0xC0000071)
public static let PASSWORD_MUST_CHANGE = NTStatus(rawValue: 0xC0000224)
public static let SMB_NO_SUPPORT = NTStatus(rawValue: 0xFFFF0002)
public static let MEDIA_WRITE_PROTECTED = NTStatus(rawValue: 0xC00000A2)
public static let NO_MEDIA_IN_DEVICE = NTStatus(rawValue: 0xC0000013)
public static let INVALID_DEVICE_STATE = NTStatus(rawValue: 0xC0000184)
public static let DATA_ERROR_2 = NTStatus(rawValue: 0xC000003E)
public static let CRC_ERROR = NTStatus(rawValue: 0xC000003F)
public static let DISK_CORRUPT_ERROR = NTStatus(rawValue: 0xC0000032)
public static let NONEXISTENT_SECTOR = NTStatus(rawValue: 0xC0000015)
public static let DEVICE_PAPER_EMPTY = NTStatus(rawValue: 0x8000000E)
public static let WRONG_VOLUME = NTStatus(rawValue: 0xC0000012)
public static let DISK_FULL = NTStatus(rawValue: 0xC000007F)
public static let BUFFER_TOO_SMALL = NTStatus(rawValue: 0xC0000023)
public static let BAD_IMPERSONATION_LEVEL = NTStatus(rawValue: 0xC00000A5)
public static let USER_SESSION_DELETED = NTStatus(rawValue: 0xC0000203)
public static let NETWORK_SESSION_EXPIRED = NTStatus(rawValue: 0xC000035C)
public static let SMB_TOO_MANY_UIDS = NTStatus(rawValue: 0xC000205A)
enum NTStatus: UInt32, Error, CustomStringConvertible {
case SUCCESS = 0x00000000
case NOT_IMPLEMENTED = 0xC0000002
case INVALID_DEVICE_REQUEST = 0xC0000010
case ILLEGAL_FUNCTION = 0xC00000AF
case NO_SUCH_FILE = 0xC000000F
case NO_SUCH_DEVICE = 0xC000000E
case OBJECT_NAME_NOT_FOUND = 0xC0000034
case OBJECT_PATH_INVALID = 0xC0000039
case OBJECT_PATH_NOT_FOUND = 0xC000003A
case OBJECT_PATH_SYNTAX_BAD = 0xC000003B
case DFS_EXIT_PATH_FOUND = 0xC000009B
case REDIRECTOR_NOT_STARTED = 0xC00000FB
case TOO_MANY_OPENED_FILES = 0xC000011F
case ACCESS_DENIED = 0xC0000022
case INVALID_LOCK_SEQUENCE = 0xC000001E
case INVALID_VIEW_SIZE = 0xC000001F
case ALREADY_COMMITTED = 0xC0000021
case PORT_CONNECTION_REFUSED = 0xC0000041
case THREAD_IS_TERMINATING = 0xC000004B
case DELETE_PENDING = 0xC0000056
case PRIVILEGE_NOT_HELD = 0xC0000061
case LOGON_FAILURE = 0xC000006D
case FILE_IS_A_DIRECTORY = 0xC00000BA
case FILE_RENAMED = 0xC00000D5
case PROCESS_IS_TERMINATING = 0xC000010A
case DIRECTORY_NOT_EMPTY = 0xC0000101
case CANNOT_DELETE = 0xC0000121
case FILE_NOT_AVAILABLE = 0xC0000467
case FILE_DELETED = 0xC0000123
case SMB_BAD_FID = 0x00060001
case INVALID_HANDLE = 0xC0000008
case OBJECT_TYPE_MISMATCH = 0xC0000024
case PORT_DISCONNECTED = 0xC0000037
case INVALID_PORT_HANDLE = 0xC0000042
case FILE_CLOSED = 0xC0000128
case HANDLE_NOT_CLOSABLE = 0xC0000235
case SECTION_TOO_BIG = 0xC0000040
case TOO_MANY_PAGING_FILES = 0xC0000097
case INSUFF_SERVER_RESOURCES = 0xC0000205
case OS2_INVALID_ACCESS = 0x000C0001
case ACCESS_DENIED_2 = 0xC00000CA
case DATA_ERROR = 0xC000009C
case NOT_SAME_DEVICE = 0xC00000D4
case NO_MORE_FILES = 0x80000006
case NO_MORE_ENTRIES = 0x8000001A
case UNSUCCESSFUL = 0xC0000001
case SHARING_VIOLATION = 0xC0000043
case FILE_LOCK_CONFLICT = 0xC0000054
case LOCK_NOT_GRANTED = 0xC0000055
case END_OF_FILE = 0xC0000011
case NOT_SUPPORTED = 0xC00000BB
case OBJECT_NAME_COLLISION = 0xC0000035
case INVALID_PARAMETER = 0xC000000D
case OS2_INVALID_LEVEL = 0x007C0001
case OS2_NEGATIVE_SEEK = 0x00830001
case RANGE_NOT_LOCKED = 0xC000007E
case OS2_NO_MORE_SIDS = 0x00710001
case OS2_CANCEL_VIOLATION = 0x00AD0001
case OS2_ATOMIC_LOCKS_NOT_SUPPORTED = 0x00AE0001
case INVALID_INFO_CLASS = 0xC0000003
case INVALID_PIPE_STATE = 0xC00000AD
case INVALID_READ_MODE = 0xC00000B4
case OS2_CANNOT_COPY = 0x010A0001
case STOPPED_ON_SYMLINK = 0x8000002D
case INSTANCE_NOT_AVAILABLE = 0xC00000AB
case PIPE_NOT_AVAILABLE = 0xC00000AC
case PIPE_BUSY = 0xC00000AE
case PIPE_CLOSING = 0xC00000B1
case PIPE_EMPTY = 0xC00000D9
case PIPE_DISCONNECTED = 0xC00000B0
case BUFFER_OVERFLOW = 0x80000005
case MORE_PROCESSING_REQUIRED = 0xC0000016
case EA_TOO_LARGE = 0xC0000050
case OS2_EAS_DIDNT_FIT = 0x01130001
case EAS_NOT_SUPPORTED = 0xC000004F
case EA_LIST_INCONSISTENT = 0x80000014
case OS2_EA_ACCESS_DENIED = 0x03E20001
case NOTIFY_ENUM_DIR = 0x0000010C
case INVALID_SMB = 0x00010002
case WRONG_PASSWORD = 0xC000006A
case PATH_NOT_COVERED = 0xC0000257
case NETWORK_NAME_DELETED = 0xC00000C9
case SMB_BAD_TID = 0x00050002
case BAD_NETWORK_NAME = 0xC00000CC
case BAD_DEVICE_TYPE = 0xC00000CB
case SMB_BAD_COMMAND = 0x00160002
case PRINT_QUEUE_FULL = 0xC00000C6
case NO_SPOOL_SPACE = 0xC00000C7
case PRINT_CANCELLED = 0xC00000C8
case UNEXPECTED_NETWORK_ERROR = 0xC00000C4
case IO_TIMEOUT = 0xC00000B5
case REQUEST_NOT_ACCEPTED = 0xC00000D0
case TOO_MANY_SESSIONS = 0xC00000CE
case SMB_BAD_UID = 0x005B0002
case SMB_USE_MPX = 0x00FA0002
case SMB_USE_STANDARD = 0x00FB0002
case SMB_CONTINUE_MPX = 0x00FC0002
case ACCOUNT_DISABLED = 0xC0000072
case ACCOUNT_EXPIRED = 0xC0000193
case INVALID_WORKSTATION = 0xC0000070
case INVALID_LOGON_HOURS = 0xC000006F
case PASSWORD_EXPIRED = 0xC0000071
case PASSWORD_MUST_CHANGE = 0xC0000224
case SMB_NO_SUPPORT = 0xFFFF0002
case MEDIA_WRITE_PROTECTED = 0xC00000A2
case NO_MEDIA_IN_DEVICE = 0xC0000013
case INVALID_DEVICE_STATE = 0xC0000184
case DATA_ERROR_2 = 0xC000003E
case CRC_ERROR = 0xC000003F
case DISK_CORRUPT_ERROR = 0xC0000032
case NONEXISTENT_SECTOR = 0xC0000015
case DEVICE_PAPER_EMPTY = 0x8000000E
case WRONG_VOLUME = 0xC0000012
case DISK_FULL = 0xC000007F
case BUFFER_TOO_SMALL = 0xC0000023
case BAD_IMPERSONATION_LEVEL = 0xC00000A5
case USER_SESSION_DELETED = 0xC0000203
case NETWORK_SESSION_EXPIRED = 0xC000035C
case SMB_TOO_MANY_UIDS = 0xC000205A
public var description: String {
switch self {
+17 -80
View File
@@ -7,9 +7,7 @@
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS)
import CoreGraphics
#endif
/**
Allows accessing to WebDAV server files. This provider doesn't cache or save files internally, however you can
@@ -46,11 +44,11 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
return nil
}
self.init(baseURL: baseURL,
credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"))
credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
}
@@ -64,17 +62,6 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
return copy
}
/**
Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty array.
- Parameters:
- path: path to target directory. If empty, root will be iterated.
- completionHandler: a closure with result of directory entries or error.
- contents: An array of `FileObject` identifying the the directory entries.
- error: Error returned by system.
*/
override open func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
let query = NSPredicate(format: "TRUEPREDICATE")
_ = searchFiles(path: path, recursive: false, query: query, including: [], foundItemHandler: nil, completionHandler: completionHandler)
@@ -105,11 +92,11 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
- Parameters:
- path: path to target directory. If empty, attributes of root will be returned.
- completionHandler: a closure with result of directory entries or error.
- attributes: A `FileObject` containing the attributes of the item.
- error: Error returned by system.
- Parameter path: path to target directory. If empty, attributes of root will be returned.
- Parameter including: An array which determines which file properties should be considered to fetch.
- Parameter completionHandler: a closure with result of directory entries or error.
- Parameter attributes: A `FileObject` containing the attributes of the item.
- Parameter error: Error returned by system.
*/
open func attributesOfItem(path: String, including: [URLResourceKey], completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
let url = self.url(of: path)
@@ -135,8 +122,6 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
})
}
/// Returns volume/provider information asynchronously.
/// - Parameter volumeInfo: Information of filesystem/Provider returned by system/server.
override open func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
// Not all WebDAV clients implements RFC2518 which allows geting storage quota.
// In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
@@ -166,31 +151,6 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
})
}
/**
Search files inside directory using query asynchronously.
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (fileSize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
- Note: Don't pass Spotlight predicates to this method directly, use `FileProvider.convertSpotlightPredicateTo()` method to get usable predicate.
- Important: A file name criteria should be provided for Dropbox.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
- files: all files meat the `query` criteria.
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
@discardableResult
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ([FileObject], Error?) -> Void) -> Progress? {
return searchFiles(path: path, recursive: recursive, query: query, including: [], foundItemHandler: foundItemHandler, completionHandler: completionHandler)
}
@@ -198,29 +158,18 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
/**
Search files inside directory using query asynchronously.
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (fileSize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
- Note: Don't pass Spotlight predicates to this method directly, use `FileProvider.convertSpotlightPredicateTo()` method to get usable predicate.
- Important: A file name criteria should be provided for Dropbox.
- Note: Query string is limited to file name, to search based on other file attributes, use NSPredicate version.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
- query: Simple string that file name begins with to be search, case-insensitive.
- including: An array which determines which file properties should be considered to fetch.
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
- files: all files meat the `query` criteria.
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
@discardableResult
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, including: [URLResourceKey], foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let url = self.url(of: path)
var request = URLRequest(url: url)
@@ -229,7 +178,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.setValue(recursive ? "infinity" : "1", forHTTPHeaderField: "Depth")
request.setValue(authentication: credential, with: credentialType)
request.setValue(contentType: .xml, charset: .utf8)
request.httpBody = WebDavFileObject.xmlProp(including)
request.httpBody = WebDavFileObject.xmlProp([])
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(url, forKey: .fileURLKey)
@@ -267,7 +216,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
return progress
}
override open func isReachable(completionHandler: @escaping (_ success: Bool, _ error: Error?) -> Void) {
override open func isReachable(completionHandler: @escaping (Bool) -> Void) {
var request = URLRequest(url: baseURL!)
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
@@ -276,13 +225,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.httpBody = WebDavFileObject.xmlProp([.volumeTotalCapacityKey, .volumeAvailableCapacityKey])
runDataTask(with: request, completionHandler: { (data, response, error) in
let status = (response as? HTTPURLResponse)?.statusCode ?? 400
if status >= 400, let code = FileProviderHTTPErrorCode(rawValue: status) {
let errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
let error = FileProviderWebDavError(code: code, path: "", serverDescription: errorDesc, url: self.baseURL!)
completionHandler(false, error)
return
}
completionHandler(status < 300, error)
completionHandler(status < 300)
})
}
@@ -370,7 +313,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
}
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
return FileProviderWebDavError(code: code, path: path ?? "", serverDescription: data.flatMap({ String(data: $0, encoding: .utf8) }), url: self.url(of: path ?? ""))
return FileProviderWebDavError(code: code, path: path ?? "", errorDescription: data.flatMap({ String(data: $0, encoding: .utf8) }), url: self.url(of: path ?? ""))
}
override func multiStatusHandler(source: String, data: Data, completionHandler: SimpleCompletionHandler) {
@@ -399,7 +342,6 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
}
extension WebDAVFileProvider: ExtendedFileProvider {
#if os(macOS) || os(iOS) || os(tvOS)
open func thumbnailOfFileSupported(path: String) -> Bool {
guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
return false
@@ -408,13 +350,12 @@ extension WebDAVFileProvider: ExtendedFileProvider {
return supportedExt.contains((path as NSString).pathExtension)
}
@discardableResult
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((ImageClass?, Error?) -> Void)) -> Progress? {
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((ImageClass?, Error?) -> Void)) {
guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
dispatch_queue.async {
completionHandler(nil, self.urlError(path, code: .resourceUnavailable))
}
return nil
return
}
let dimension = dimension ?? CGSize(width: 64, height: 64)
@@ -433,20 +374,16 @@ extension WebDAVFileProvider: ExtendedFileProvider {
completionHandler(data.flatMap({ ImageClass(data: $0) }), nil)
})
task.resume()
return nil
}
#endif
open func propertiesOfFileSupported(path: String) -> Bool {
return false
}
@discardableResult
open func propertiesOfFile(path: String, completionHandler: @escaping (([String : Any], [String], Error?) -> Void)) -> Progress? {
open func propertiesOfFile(path: String, completionHandler: @escaping (([String : Any], [String], Error?) -> Void)) {
dispatch_queue.async {
completionHandler([:], [], self.urlError(path, code: .resourceUnavailable))
}
return nil
}
}
@@ -645,7 +582,7 @@ public final class WebDavFileObject: FileObject {
public struct FileProviderWebDavError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let serverDescription: String?
public let errorDescription: String?
/// URL of resource caused error.
public let url: URL
}
+11 -103
View File
@@ -8,7 +8,7 @@
import XCTest
import FilesProvider
class FilesProviderTests: XCTestCase, FileProviderDelegate {
class FilesProviderTests: XCTestCase {
override func setUp() {
super.setUp()
@@ -18,7 +18,6 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
try? FileManager.default.removeItem(at: dummyFile())
}
func testLocal() {
@@ -27,7 +26,6 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testBasic(provider)
testArchiving(provider)
testOperations(provider)
}
@@ -41,8 +39,6 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
cred = nil
}
let provider = WebDAVFileProvider(baseURL: url, credential: cred)!
provider.delegate = self
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
@@ -55,27 +51,6 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = DropboxFileProvider(credential: cred)
provider.delegate = self
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testBasic(provider)
testOperations(provider)
}
func testFTPPassive() {
guard let urlStr = ProcessInfo.processInfo.environment["ftp_url"] else { return }
let url = URL(string: urlStr)!
let cred: URLCredential?
if let user = ProcessInfo.processInfo.environment["ftp_user"], let pass = ProcessInfo.processInfo.environment["ftp_password"] {
cred = URLCredential(user: user, password: pass, persistence: .forSession)
} else {
cred = nil
}
let provider = FTPFileProvider(baseURL: url, mode: .extendedPassive, credential: cred)!
provider.delegate = self
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
@@ -88,9 +63,6 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = OneDriveFileProvider(credential: cred)
provider.delegate = self
testBasic(provider)
testArchiving(provider)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
@@ -106,7 +78,7 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
*/
let timeout: Double = 60.0
let timeout: Double = 20.0
let testFolderName = "Test"
let textFilePath = "/Test/file.txt"
let renamedFilePath = "/Test/renamed.txt"
@@ -115,19 +87,16 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
fileprivate func testCreateFolder(_ provider: FileProvider, folderName: String) {
let desc = "Creating folder at root in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.create(folder: folderName, at: "/") { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
fileprivate func testContentsOfDirectory(_ provider: FileProvider) {
let desc = "Enumerating files list in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.contentsOfDirectory(path: "/") { (files, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
@@ -140,12 +109,10 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
fileprivate func testAttributesOfFile(_ provider: FileProvider, filePath: String) {
let desc = "Attrubutes of file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.attributesOfItem(path: filePath) { (fileObject, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
@@ -158,25 +125,21 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
fileprivate func testCreateFile(_ provider: FileProvider, filePath: String) {
let desc = "Creating file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
let data = sampleText.data(using: .ascii)
provider.writeContents(path: filePath, contents: data, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
print("Test fulfilled: \(desc).")
wait(for: [expectation], timeout: timeout)
}
fileprivate func testContentsFile(_ provider: FileProvider, filePath: String, hasSampleText: Bool = true) {
let desc = "Reading file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.contents(path: filePath) { (data, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
@@ -188,49 +151,37 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
print("Test fulfilled: \(desc).")
wait(for: [expectation], timeout: timeout)
}
fileprivate func testRenameFile(_ provider: FileProvider, filePath: String, to toPath: String) {
let desc = "Renaming file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.moveItem(path: filePath, to: toPath, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
fileprivate func testCopyFile(_ provider: FileProvider, filePath: String, to toPath: String) {
let desc = "Copying file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.copyItem(path: filePath, to: toPath, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
if provider is FTPFileProvider {
// FTP will need to download and upload file again.
wait(for: [expectation], timeout: timeout * 6)
} else {
wait(for: [expectation], timeout: timeout)
}
print("Test fulfilled: \(desc).")
wait(for: [expectation], timeout: timeout)
}
fileprivate func testRemoveFile(_ provider: FileProvider, filePath: String) {
let desc = "Deleting file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.removeItem(path: filePath) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
private func randomData(size: Int = 262144) -> Data {
@@ -257,39 +208,36 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
fileprivate func testUploadFile(_ provider: FileProvider, filePath: String) {
// test Upload/Download
let url = dummyFile()
let desc = "Uploading file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
let dummy = dummyFile()
provider.copyItem(localFile: dummy, to: filePath) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
// TODO: check file existance of server
try? FileManager.default.removeItem(at: url)
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
print("Test fulfilled: \(desc).")
}
fileprivate func testDownloadFile(_ provider: FileProvider, filePath: String) {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("downloadedfile.dat")
let desc = "Downloading file in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.copyItem(path: filePath, toLocalURL: url) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertTrue(FileManager.default.fileExists(atPath: url.path), "downloaded file doesn't exist")
let size = (try? FileManager.default.attributesOfItem(atPath: url.path))?[FileAttributeKey.size] as? Int64
XCTAssertEqual(size, 262144, "downloaded file size is unexpected")
XCTAssert(FileManager.default.contentsEqual(atPath: self.dummyFile().path, andPath: url.path), "downloaded data is corrupted")
try? FileManager.default.removeItem(at: url)
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
print("Test fulfilled: \(desc).")
}
fileprivate func testStorageProperties(_ provider: FileProvider, isExpected: Bool) {
let desc = "Querying volume in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.storageProperties { (volume) in
if !isExpected {
@@ -304,34 +252,17 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
fileprivate func testReachability(_ provider: FileProvider) {
// Test file operations
let desc = "Reachability of \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.isReachable { (status, error) in
XCTAssertTrue(status, "\(provider.type) not reachable: \(error?.localizedDescription ?? "no error desc")")
provider.isReachable { (status) in
XCTAssertTrue(status, "\(provider.type) not reachable")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
fileprivate func testArchiving(_ provider: FileProvider) {
let archivedData = NSKeyedArchiver.archivedData(withRootObject: provider)
let unarchived = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as? FileProvider
XCTAssertNotNil(unarchived)
XCTAssertEqual(unarchived?.baseURL, provider.baseURL, "archived provider is not same with original")
XCTAssertEqual(unarchived?.credential, provider.credential, "archived provider is not same with original")
if let provider = provider as? FileProviderBasicRemote, let unarchived_r = unarchived as? FileProviderBasicRemote {
XCTAssertEqual(unarchived_r.useCache, provider.useCache, "archived provider is not same with original")
XCTAssertEqual(unarchived_r.validatingCache, provider.validatingCache, "archived provider is not same with original")
}
if let provider = provider as? OneDriveFileProvider, let unarchived_o = unarchived as? OneDriveFileProvider {
XCTAssertEqual(unarchived_o.route.rawValue, provider.route.rawValue, "archived provider is not same with original")
}
}
fileprivate func testBasic(_ provider: FileProvider) {
@@ -351,7 +282,6 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
fileprivate func testOperations(_ provider: FileProvider) {
// Test file operations
testReachability(provider)
testCreateFolder(provider, folderName: testFolderName)
testContentsOfDirectory(provider)
@@ -369,26 +299,4 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
testUploadFile(provider, filePath: uploadFilePath)
testDownloadFile(provider, filePath: uploadFilePath)
}
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperationType) {
return
}
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperationType, error: Error) {
return
}
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperationType, progress: Float) {
switch operation {
case .copy(source: let source, destination: let dest) where dest.hasPrefix("file://"):
print("Downloading \(source) to \((dest as NSString).lastPathComponent): \(progress * 100) completed.")
case .copy(source: let source, destination: let dest) where source.hasPrefix("file://"):
print("Uploading \((source as NSString).lastPathComponent) to \(dest): \(progress * 100) completed.")
case .copy(source: let source, destination: let dest):
print("Copy \(source) to \(dest): \(progress * 100) completed.")
default:
break
}
return
}
}
-5
View File
@@ -18,10 +18,5 @@
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB