Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96ce12f226 | |||
| 091fd14a88 | |||
| 10aac532f7 |
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.10.0"
|
||||
s.version = "0.10.3"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/OneDrive/SMB2) files on iOS and macOS."
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
|
||||
@@ -595,7 +595,7 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.10.0;
|
||||
BUNDLE_VERSION_STRING = 0.10.3;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -625,7 +625,7 @@
|
||||
799396611D48B7BF00086753 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.10.0;
|
||||
BUNDLE_VERSION_STRING = 0.10.3;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
|
||||
@@ -26,11 +26,17 @@ Local and WebDAV providers are fully tested and can be used in production enviro
|
||||
|
||||
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like searching and reading a portion of file.
|
||||
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, replaced FTP.
|
||||
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API. For now it has limitation in uploading files up to 150MB.
|
||||
- [x] **OneDriveFileProvider** A wrapper around OneDrive Web API, works with `onedrive.com` and compatible servers. For now it has limitation in uploading files up to 100MB.
|
||||
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!* SMB1/CIFS is depericated and very tricky to be implemented
|
||||
- [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 Web API, works with `onedrive.com` and compatible (business) servers.
|
||||
* For now it has limitation in uploading files up to 100MB.
|
||||
- [x] **CloudFileProvider** A wrapper around app's ubiquitous container to iCloud Drive in iOS 8+ 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.
|
||||
* Data types and some basic functions are implemented but *main interface is not implemented yet!*
|
||||
* SMB1/CIFS is depericated and very tricky to be implemented
|
||||
- [ ] **FTPFileProvider** while deprecated in 1990s, it's still in use on some Web hosts.
|
||||
- [ ] **AmazonS3FileProvider**
|
||||
- [ ] **GoogleDriveFileProvider**
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -97,18 +103,23 @@ For LocalFileProvider if you want to deal with `Documents` folder
|
||||
|
||||
``` swift
|
||||
let documentsProvider = LocalFileProvider()
|
||||
```
|
||||
|
||||
is equal to:
|
||||
// Equals with:
|
||||
let documentsProvider = LocalFileProvider(directory: .documentDirectory, domainMask: = .userDomainMask)
|
||||
|
||||
``` swift
|
||||
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
|
||||
let documentsURL = URL(fileURLWithPath: documentPath);
|
||||
// Equals with:
|
||||
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
|
||||
```
|
||||
|
||||
You can't change the base url later. and all paths are related to this base url by default.
|
||||
|
||||
To initialize an iCloud Container provider use below code, This will automatically manager creating Documents folder in container:
|
||||
|
||||
```swift
|
||||
let documentsProvider = CloudFileProvider(containerId: nil)
|
||||
```
|
||||
|
||||
For remote file providers authentication may be necessary:
|
||||
|
||||
``` swift
|
||||
|
||||
@@ -13,14 +13,14 @@ import CoreGraphics
|
||||
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
|
||||
// in case of using this class with unencrypted HTTP connection.
|
||||
|
||||
open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
open static let type: String = "DropBox"
|
||||
open let isPathRelative: Bool = true
|
||||
open let isPathRelative: Bool
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String = ""
|
||||
open var currentPath: String
|
||||
|
||||
open let apiURL = URL(string: "https://api.dropboxapi.com/2/")!
|
||||
open let contentURL = URL(string: "https://content.dropboxapi.com/2")!
|
||||
open let apiURL: URL
|
||||
open let contentURL: URL
|
||||
|
||||
open var dispatch_queue: DispatchQueue {
|
||||
willSet {
|
||||
@@ -30,8 +30,8 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential?
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool = false
|
||||
public var validatingCache: Bool = true
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
@@ -50,10 +50,17 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
|
||||
|
||||
public init? (credential: URLCredential?, cache: URLCache? = nil) {
|
||||
self.baseURL = nil
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(DropboxFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.credential = credential
|
||||
self.isPathRelative = true
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
|
||||
self.apiURL = URL(string: "https://api.dropboxapi.com/2/")!
|
||||
self.contentURL = URL(string: "https://content.dropboxapi.com/2")!
|
||||
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(DropboxFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
@@ -98,7 +98,12 @@ internal extension DropboxFileProvider {
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
assert(data.count < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
|
||||
if data.count > 150 * 1024 * 1024 {
|
||||
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
var requestDictionary = [String: Any]()
|
||||
let url: URL
|
||||
url = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
@@ -125,6 +130,13 @@ internal extension DropboxFileProvider {
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
|
||||
if size > 150 * 1024 * 1024 {
|
||||
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
var requestDictionary = [String: Any]()
|
||||
let url: URL
|
||||
url = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
|
||||
@@ -135,7 +135,7 @@ open class FileObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sorting FileObject array by given criteria, not thread-safe
|
||||
/// Sorting FileObject array by given criteria, **not thread-safe**
|
||||
public struct FileObjectSorting {
|
||||
|
||||
/// Determines sort kind by which item of File object
|
||||
@@ -222,6 +222,17 @@ public struct FileObjectSorting {
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element: FileObject {
|
||||
public func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) -> [Element] {
|
||||
let sorting = FileObjectSorting(type: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
|
||||
return sorting.sort(self) as! [Element]
|
||||
}
|
||||
|
||||
public mutating func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
|
||||
self = self.sorted(by: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
|
||||
}
|
||||
}
|
||||
|
||||
extension URLFileResourceType {
|
||||
public init(fileTypeValue: FileAttributeType) {
|
||||
switch fileTypeValue {
|
||||
|
||||
@@ -205,13 +205,14 @@ extension FileProviderBasic {
|
||||
} else {
|
||||
rpath = self.currentPath
|
||||
}
|
||||
if isPathRelative, let baseURL = baseURL {
|
||||
if rpath.hasPrefix("/") {
|
||||
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath
|
||||
if let baseURL = baseURL {
|
||||
if isPathRelative && rpath.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
}
|
||||
return URL(string: rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!, relativeTo: baseURL)!
|
||||
return URL(string: rpath, relativeTo: baseURL) ?? baseURL
|
||||
} else {
|
||||
return URL(string: rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!)!.standardizedFileURL
|
||||
return URL(string: rpath)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,37 +9,38 @@
|
||||
import Foundation
|
||||
|
||||
open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
open static let type = "Local"
|
||||
open var isPathRelative: Bool = true
|
||||
open private(set) var baseURL: URL? = LocalFileProvider.defaultBaseURL()
|
||||
open var currentPath: String = ""
|
||||
open static let type: String = "Local"
|
||||
open var isPathRelative: Bool
|
||||
open fileprivate(set) var baseURL: URL?
|
||||
open var currentPath: String
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: DispatchQueue
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential? = nil
|
||||
open fileprivate(set) var credential: URLCredential?
|
||||
|
||||
open private(set) var fileManager = FileManager()
|
||||
open private(set) var opFileManager = FileManager()
|
||||
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
|
||||
|
||||
public init () {
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
/// default values are `directory: .documentDirectory, domainMask: .userDomainMask`
|
||||
public convenience init (directory: FileManager.SearchPathDirectory = .documentDirectory, domainMask: FileManager.SearchPathDomainMask = .userDomainMask) {
|
||||
self.init(baseURL: FileManager.default.urls(for: directory, in: domainMask).first!)
|
||||
}
|
||||
|
||||
public init (baseURL: URL) {
|
||||
self.baseURL = baseURL
|
||||
self.isPathRelative = true
|
||||
self.currentPath = ""
|
||||
self.credential = nil
|
||||
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
}
|
||||
|
||||
open static func defaultBaseURL() -> URL {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true);
|
||||
return URL(fileURLWithPath: paths[0])
|
||||
open class func defaultBaseURL() -> URL {
|
||||
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
@@ -356,3 +357,33 @@ public extension LocalFileProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CloudFileProvider: LocalFileProvider {
|
||||
// FIXME: convert static var type to class var in next Swift version
|
||||
|
||||
open var type: String { return "iCloudDrive" }
|
||||
|
||||
public init? (containerId: String?) {
|
||||
assert(!Thread.isMainThread, "LocalFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
|
||||
guard FileManager.default.ubiquityIdentityToken == nil else {
|
||||
return nil
|
||||
}
|
||||
guard let ubiquityURL = FileManager.default.url(forUbiquityContainerIdentifier: containerId) else {
|
||||
return nil
|
||||
}
|
||||
super.init(baseURL: ubiquityURL.appendingPathComponent("Documents"))
|
||||
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(self.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = DispatchQueue(label: "FileProvider.\(self.type).Operation", attributes: [])
|
||||
|
||||
fileManager.url(forUbiquityContainerIdentifier: containerId)
|
||||
opFileManager.url(forUbiquityContainerIdentifier: containerId)
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
}
|
||||
|
||||
open override static func defaultBaseURL() -> URL {
|
||||
return FileManager.default.url(forUbiquityContainerIdentifier: nil) ?? super.defaultBaseURL()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,15 +13,16 @@ import CoreGraphics
|
||||
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
|
||||
// in case of using this class with unencrypted HTTP connection.
|
||||
|
||||
open class OneDriveFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
open static let type: String = "OneDrive"
|
||||
open let isPathRelative: Bool = true
|
||||
open let isPathRelative: Bool
|
||||
open let baseURL: URL?
|
||||
open var serverURL: URL { return baseURL! }
|
||||
open var drive: String
|
||||
open var driveURL: URL {
|
||||
return URL(string: "/drive/root:/", relativeTo: baseURL)!
|
||||
return URL(string: "/drive/\(drive):/", relativeTo: baseURL)!
|
||||
}
|
||||
open var currentPath: String = ""
|
||||
open var currentPath: String
|
||||
|
||||
open var dispatch_queue: DispatchQueue {
|
||||
willSet {
|
||||
@@ -31,8 +32,8 @@ open class OneDriveFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential?
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool = false
|
||||
public var validatingCache: Bool = true
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
@@ -49,13 +50,16 @@ open class OneDriveFileProvider: NSObject, FileProviderBasicRemote {
|
||||
return _session!
|
||||
}
|
||||
|
||||
public init? (baseURL: URL?, drive: String = "root", credential: URLCredential?, cache: URLCache? = nil) {
|
||||
self.baseURL = baseURL ?? URL(string: "https://api.onedrive.com")
|
||||
public init? (credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) {
|
||||
self.baseURL = serverURL ?? URL(string: "https://api.onedrive.com")
|
||||
self.drive = drive
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(OneDriveFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.credential = credential
|
||||
self.isPathRelative = true
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(OneDriveFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -341,7 +345,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
|
||||
|
||||
extension OneDriveFileProvider: FileProvider {
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = OneDriveFileProvider(baseURL: self.baseURL, drive: self.drive, credential: self.credential, cache: self.cache)!
|
||||
let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, drive: self.drive, cache: self.cache)!
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
|
||||
@@ -93,7 +93,12 @@ internal extension OneDriveFileProvider {
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
assert(data.count < 100*1024*1024, "Maximum size of allowed size to upload is 100MB")
|
||||
if data.count > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
@@ -115,6 +120,13 @@ internal extension OneDriveFileProvider {
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
|
||||
if size > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
|
||||
@@ -35,11 +35,11 @@ public final class WebDavFileObject: FileObject {
|
||||
/// Because this class uses NSURLSession, it's necessary to disable App Transport Security
|
||||
/// in case of using this class with unencrypted HTTP connection.
|
||||
|
||||
open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
open static let type: String = "WebDAV"
|
||||
open let isPathRelative: Bool = true
|
||||
open let isPathRelative: Bool
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String = ""
|
||||
open var currentPath: String
|
||||
public var dispatch_queue: DispatchQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
@@ -48,8 +48,8 @@ open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
|
||||
public weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential?
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool = false
|
||||
public var validatingCache: Bool = true
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
@@ -71,10 +71,13 @@ open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
|
||||
return nil
|
||||
}
|
||||
self.baseURL = baseURL
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(WebDAVFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.credential = credential
|
||||
self.isPathRelative = true
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(WebDAVFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
Reference in New Issue
Block a user