diff --git a/.travis.yml b/.travis.yml index 5defceb..c33ceb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ env: - DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO" - DESTINATION="OS=10.1,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO" - - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO" + # - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO" - DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD="NO" CARTHAGEDEPLOY="NO" before_install: @@ -50,7 +50,7 @@ script: # Run `pod lib lint` if specified - if [ $POD == "YES" ]; then - pod lib lint; + pod lib lint --quick; fi after_success: diff --git a/FileProvider.podspec b/FileProvider.podspec index 2385cd2..b9613d6 100644 --- a/FileProvider.podspec +++ b/FileProvider.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "FileProvider" - s.version = "0.14.1" + s.version = "0.14.2" 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. diff --git a/FileProvider.xcodeproj/project.pbxproj b/FileProvider.xcodeproj/project.pbxproj index 435f2c7..2daf22e 100644 --- a/FileProvider.xcodeproj/project.pbxproj +++ b/FileProvider.xcodeproj/project.pbxproj @@ -597,7 +597,7 @@ 799396601D48B7BF00086753 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_VERSION_STRING = 0.14.1; + BUNDLE_VERSION_STRING = 0.14.2; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -627,7 +627,7 @@ 799396611D48B7BF00086753 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_VERSION_STRING = 0.14.1; + BUNDLE_VERSION_STRING = 0.14.2; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; diff --git a/Sources/ExtendedLocalFileProvider.swift b/Sources/ExtendedLocalFileProvider.swift index a5cc38f..3382a7d 100644 --- a/Sources/ExtendedLocalFileProvider.swift +++ b/Sources/ExtendedLocalFileProvider.swift @@ -247,7 +247,7 @@ public struct LocalFileInformationGenerator { var keys = [String]() func add(key: String, value: Any?) { - if let value = value { + if let value = value, !((value as? String)?.isEmpty ?? false) { keys.append(key) dic[key] = value } @@ -282,16 +282,15 @@ public struct LocalFileInformationGenerator { 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) - if let cr = tiffDict[kCGImagePropertyTIFFCopyright as String] as? String , !cr.isEmpty { - add(key: "Copyright", value: cr) - - } - if let date = tiffDict[kCGImagePropertyTIFFDateTime as String] as? String , !date.isEmpty { - add(key: "Date taken", value: date) - } + add(key: "Copyright", value: tiffDict[kCGImagePropertyTIFFCopyright as String] as? String) + add(key: "Date taken", value: tiffDict[kCGImagePropertyTIFFDateTime as String] as? String) + 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: "Altitude", value: tiffDict[kCGImagePropertyGPSAltitude as String] as? NSNumber) + add(key: "Area", value: tiffDict[kCGImagePropertyGPSAreaInformation as String] as? NSNumber) + add(key: "Color space", value: imageDict[kCGImagePropertyColorModel as String]) add(key: "Focal length", value: exifDict[kCGImagePropertyExifFocalLength as String]) add(key: "F number", value: exifDict[kCGImagePropertyExifFNumber as String]) @@ -398,7 +397,7 @@ public struct LocalFileInformationGenerator { var keys = [String]() func add(key: String, value: Any?) { - if let value = value { + if let value = value, !((value as? String)?.isEmpty ?? false) { keys.append(key) dic[key] = value } @@ -412,7 +411,8 @@ public struct LocalFileInformationGenerator { return nil } - func convertDate(_ date: String) -> Date? { + func convertDate(_ date: String?) -> Date? { + guard let date = date else { return nil } var dateStr = date if dateStr.hasPrefix("D:") { dateStr = date.substring(from: date.characters.index(date.startIndex, offsetBy: 2)) @@ -430,15 +430,9 @@ public struct LocalFileInformationGenerator { } if let data = try? Data(contentsOf: fileURL), let provider = CGDataProvider(data: data as CFData), let reference = CGPDFDocument(provider), let dict = reference.info { - if let title = getKey("Title", from: dict), !title.isEmpty { - add(key: "Title", value: title) - } - if let author = getKey("Author", from: dict), !author.isEmpty { - add(key: "Author", value: author) - } - if let subject = getKey("Subject", from: dict), !subject.isEmpty { - add(key: "Subject", value: subject) - } + add(key: "Title", value: getKey("Title", from: dict)) + add(key: "Author", value: getKey("Author", from: dict)) + add(key: "Subject", value: getKey("Subject", from: dict)) var majorVersion: Int32 = 0 var minorVersion: Int32 = 0 reference.getVersion(majorVersion: &majorVersion, minorVersion: &minorVersion) @@ -451,15 +445,9 @@ public struct LocalFileInformationGenerator { let size = pageRef.getBoxRect(CGPDFBox.mediaBox).size add(key: "Resolution", value: "\(Int(size.width))x\(Int(size.height))") } - if let creator = getKey("Creator", from: dict), !creator.isEmpty { - add(key: "Content creator", value: creator) - } - if let creationDateString = getKey("CreationDate", from: dict) { - add(key: "Creation date", value: convertDate(creationDateString)) - } - if let modifiedDateString = getKey("ModDate", from: dict) { - add(key: "Modified date", value: convertDate(modifiedDateString)) - } + add(key: "Content creator", value: getKey("Creator", from: dict)) + add(key: "Creation date", value: convertDate(getKey("CreationDate", from: dict))) + add(key: "Modified date", value: convertDate(getKey("ModDate", from: dict))) add(key: "Security", value: reference.isEncrypted ? "Present" : "None") add(key: "Allows printing", value: reference.allowsPrinting ? "Yes" : "No") add(key: "Allows copying", value: reference.allowsCopying ? "Yes" : "No") diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index 2cd57b6..caf37ae 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -95,7 +95,7 @@ public protocol FileProviderBasic: class { - Parameters: - path: location of directory to start search - recursive: Searching subdirectories of path - - query: Simple string that file name contains to be search, case-insensitive. + - query: Simple string that file name begins with to be search, case-insensitive. - 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. */ @@ -138,7 +138,7 @@ public protocol FileProviderBasic: class { extension FileProviderBasic { public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) { - let predicate = NSPredicate(format: "name CONTAINS[c] %@", query) + let predicate = NSPredicate(format: "name BEHINSWITH[c] %@", query) self.searchFiles(path: path, recursive: recursive, query: predicate, foundItemHandler: foundItemHandler, completionHandler: completionHandler) } @@ -948,6 +948,32 @@ public enum FileOperationType: CustomStringConvertible { return mirror.children.dropFirst().first?.value as? String } + init? (json: [String: AnyObject]) { + guard let type = json["type"] as? String, let source = json["source"] as? String else { + return nil + } + let dest = json["dest"] as? String + switch type { + case "Create": + self = .create(path: source) + case "Modify": + self = .modify(path: source) + case "Remove": + self = .remove(path: source) + case "Copy": + guard let dest = dest else { return nil } + self = .copy(source: source, destination: dest) + case "Move": + guard let dest = dest else { return nil } + self = .move(source: source, destination: dest) + case "Link": + guard let dest = dest else { return nil } + self = .link(link: source, target: dest) + default: + return nil + } + } + internal var json: String? { var dictionary: [String: AnyObject] = ["type": self.description as NSString] dictionary["source"] = source as NSString? diff --git a/Sources/LocalFileProvider.swift b/Sources/LocalFileProvider.swift index d54b461..54b04c8 100644 --- a/Sources/LocalFileProvider.swift +++ b/Sources/LocalFileProvider.swift @@ -42,15 +42,21 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo */ open var isCoorinating: Bool + /// **OBSOLETED**: Use FileProvider.init(for:in:) instead. + @available(*, obsoleted: 1.0, renamed: "init(for:in:)", message: "Use FileProvider.init(for:in:) instead.") + public convenience init (directory: FileManager.SearchPathDirectory = .documentDirectory, domainMask: FileManager.SearchPathDomainMask = .userDomainMask) { + self.init(baseURL: FileManager.default.urls(for: directory, in: domainMask).first!) + } + /** Initializes provider for the specified common directory in the requested domains. default values are `directory: .documentDirectory, domainMask: .userDomainMask`. - Parameters: - - directory: The search path directory. The supported values are described in `FileManager.SearchPathDirectory`. - - domainMask: The file system domain to search. The value for this parameter is one or more of the constants described in `FileManager.SearchPathDomainMask`. - */ - public convenience init (directory: FileManager.SearchPathDirectory = .documentDirectory, domainMask: FileManager.SearchPathDomainMask = .userDomainMask) { + - for: The search path directory. The supported values are described in `FileManager.SearchPathDirectory`. + - in: Base locations for directory to search. The value for this parameter is one or more of the constants described in `FileManager.SearchPathDomainMask`. + */ + public convenience init (for directory: FileManager.SearchPathDirectory = .documentDirectory, in domainMask: FileManager.SearchPathDomainMask = .userDomainMask) { self.init(baseURL: FileManager.default.urls(for: directory, in: domainMask).first!) } diff --git a/Sources/LocalHelper.swift b/Sources/LocalHelper.swift index 8aee057..c358028 100644 --- a/Sources/LocalHelper.swift +++ b/Sources/LocalHelper.swift @@ -299,18 +299,15 @@ open class LocalOperationHandle: OperationHandle { let fp = FileManager() let filesList = fp.enumerator(at: pathURL, includingPropertiesForKeys: keys, options: enumOpt, errorHandler: nil) while let fileURL = filesList?.nextObject() as? URL { - do { - let values = try fileURL.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey]) - let isdir = values.isDirectory ?? false - let size = Int64(values.fileSize ?? 0) - if isdir { - folders += 1 - } else { - files += 1 - } - totalsize += size - } catch _ { + guard let values = try? fileURL.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey]) else { continue } + let isdir = values.isDirectory ?? false + let size = Int64(values.fileSize ?? 0) + if isdir { + folders += 1 + } else { + files += 1 } + totalsize += size } return (folders, files, totalsize) diff --git a/Sources/OneDriveHelper.swift b/Sources/OneDriveHelper.swift index b174cd9..a798e76 100644 --- a/Sources/OneDriveHelper.swift +++ b/Sources/OneDriveHelper.swift @@ -134,7 +134,7 @@ internal extension OneDriveFileProvider { responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) } completionHandler?(responseError ?? error) - self.delegateNotify(.create(path: targetPath), error: responseError ?? error) + self.delegateNotify(operation, error: responseError ?? error) }) task.taskDescription = operation.json task.resume() @@ -161,7 +161,7 @@ internal extension OneDriveFileProvider { responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) } completionHandler?(responseError ?? error) - self.delegateNotify(.create(path: targetPath), error: responseError ?? error) + self.delegateNotify(operation, error: responseError ?? error) }) task.taskDescription = operation.json task.resume() diff --git a/Sources/RemoteSession.swift b/Sources/RemoteSession.swift index b8a03c3..b50de56 100644 --- a/Sources/RemoteSession.swift +++ b/Sources/RemoteSession.swift @@ -102,57 +102,44 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { self.didSendDataHandler?(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) - guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else { - return - } - guard let type = json["type"] as? String, let source = json["source"] as? String else { - return - } - let dest = json["dest"] as? String - let op : FileOperationType - switch type { - case "Create": - op = .create(path: source) - case "Copy": - guard let dest = dest else { return } - op = .copy(source: source, destination: dest) - case "Move": - guard let dest = dest else { return } - op = .move(source: source, destination: dest) - case "Modify": - op = .modify(path: source) - case "Remove": - op = .remove(path: source) - case "Link": - guard let dest = dest else { return } - op = .link(link: source, target: dest) - default: + guard let desc = task.taskDescription, let json = jsonToDictionary(desc), + let op = FileOperationType(json: json), let fileProvider = fileProvider else { return } let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend) - fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: op, progress: progress) + fileProvider.delegate?.fileproviderProgress(fileProvider, operation: op, progress: progress) } func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { self.didReceivedData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) - guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, let dest = json["dest"] as? String else { + guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), + let op = FileOperationType(json: json), let fileProvider = fileProvider else { return } - fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: .copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)) + fileProvider.delegate?.fileproviderProgress(fileProvider, operation: op, progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)) } func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - let deposition: Foundation.URLSession.AuthChallengeDisposition = credential != nil ? .useCredential : .performDefaultHandling - completionHandler(deposition, credential) + authenticate(didReceive: challenge, completionHandler: completionHandler) } func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - let deposition: Foundation.URLSession.AuthChallengeDisposition = credential != nil ? .useCredential : .performDefaultHandling - completionHandler(deposition, credential) + authenticate(didReceive: challenge, completionHandler: completionHandler) + } + + func authenticate(didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + switch (challenge.previousFailureCount, credential != nil) { + case (0...1, true): + completionHandler(.useCredential, credential) + case (0, false): + completionHandler(.useCredential, challenge.proposedCredential) + default: + completionHandler(.performDefaultHandling, nil) + } } } diff --git a/Sources/WebDAVFileProvider.swift b/Sources/WebDAVFileProvider.swift index 487a750..659e086 100644 --- a/Sources/WebDAVFileProvider.swift +++ b/Sources/WebDAVFileProvider.swift @@ -564,24 +564,21 @@ struct DavResponse { } static func parse(xmlResponse: Data, baseURL: URL?) -> [DavResponse] { + guard let xml = try? AEXMLDocument(xml: xmlResponse) else { return [] } var result = [DavResponse]() - do { - let xml = try AEXMLDocument(xml: xmlResponse) - var rootnode = xml.root - var responsetag = "response" - for node in rootnode.all ?? [] where node.name.lowercased().hasSuffix("multistatus") { - rootnode = node + var rootnode = xml.root + var responsetag = "response" + for node in rootnode.all ?? [] where node.name.lowercased().hasSuffix("multistatus") { + rootnode = node + } + for node in rootnode.children where node.name.lowercased().hasSuffix("response") { + responsetag = node.name + break + } + for responseNode in rootnode[responsetag].all ?? [] { + if let davResponse = DavResponse(responseNode, baseURL: baseURL) { + result.append(davResponse) } - for node in rootnode.children where node.name.lowercased().hasSuffix("response") { - responsetag = node.name - break - } - for responseNode in rootnode[responsetag].all ?? [] { - if let davResponse = DavResponse(responseNode, baseURL: baseURL) { - result.append(davResponse) - } - } - } catch _ { } return result }