Merge pull request #28 from Isvvc/custom-caching-data

Custom data caching
This commit is contained in:
Isaac Lyons 2021-04-16 17:54:58 -06:00 committed by GitHub
commit 87b3d073ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 527 additions and 340 deletions

View File

@ -11,13 +11,12 @@ let package = Package(
targets: ["WebDAV"]),
],
dependencies: [
.package(url: "https://github.com/drmohundro/SWXMLHash.git", .upToNextMajor(from: "5.0.0")),
.package(url: "https://github.com/3lvis/Networking.git", .upToNextMajor(from: "5.1.0"))
.package(url: "https://github.com/drmohundro/SWXMLHash.git", .upToNextMajor(from: "5.0.0"))
],
targets: [
.target(
name: "WebDAV",
dependencies: ["SWXMLHash", "Networking"]),
dependencies: ["SWXMLHash"]),
.testTarget(
name: "WebDAVTests",
dependencies: ["WebDAV"]),

View File

@ -0,0 +1,70 @@
//
// Cache.swift
// WebDAV-Swift
//
// Created by Isaac Lyons on 4/8/21.
//
import Foundation
public final class Cache<Key: Hashable, Value> {
//MARK: Private
private let cache = NSCache<KeyWrapper, ContentWrapper>()
private final class KeyWrapper: NSObject {
let key: Key
init(_ key: Key) {
self.key = key
}
override var hash: Int {
key.hashValue
}
override func isEqual(_ object: Any?) -> Bool {
guard let value = object as? KeyWrapper else { return false }
return value.key == key
}
}
private final class ContentWrapper {
let value: Value
init(_ value: Value) {
self.value = value
}
}
//MARK: Public
internal func value(forKey key: Key) -> Value? {
guard let entry = cache.object(forKey: KeyWrapper(key)) else { return nil }
return entry.value
}
internal func set(_ value: Value, forKey key: Key) {
let entry = ContentWrapper(value)
cache.setObject(entry, forKey: KeyWrapper(key))
}
internal func removeValue(forKey key: Key) {
cache.removeObject(forKey: KeyWrapper(key))
}
internal func removeAllValues() {
cache.removeAllObjects()
}
internal subscript(key: Key) -> Value? {
get { value(forKey: key) }
set {
guard let value = newValue else {
return removeValue(forKey: key)
}
set(value, forKey: key)
}
}
}

View File

@ -0,0 +1,261 @@
//
// WebDAV+Images.swift
// WebDAV-Swift
//
// Created by Isaac Lyons on 4/9/21.
//
import UIKit
//MARK: ThumbnailProperties
public struct ThumbnailProperties: Hashable {
private var width: Int?
private var height: Int?
public var contentMode: ContentMode
public var size: (width: Int, height: Int)? {
get {
if let width = width,
let height = height {
return (width, height)
}
return nil
}
set {
width = newValue?.width
height = newValue?.height
}
}
/// Configurable default thumbnail properties. Initial value of content fill and server default dimensions.
public static var `default` = ThumbnailProperties(contentMode: .fill)
/// Content fill with the server's default dimensions.
public static let fill = ThumbnailProperties(contentMode: .fill)
/// Content fit with the server's default dimensions.
public static let fit = ThumbnailProperties(contentMode: .fit)
/// Constants that define how the thumbnail fills the dimensions.
public enum ContentMode: Hashable {
case fill
case fit
}
/// - Parameters:
/// - size: The size of the thumbnail. A nil value will use the server's default dimensions.
/// - contentMode: A flag that indicates whether the thumbnail view fits or fills the dimensions.
public init(_ size: (width: Int, height: Int)? = nil, contentMode: ThumbnailProperties.ContentMode) {
if let size = size {
width = size.width
height = size.height
}
self.contentMode = contentMode
}
/// - Parameters:
/// - size: The size of the thumbnail. Width and height will be trucated to integer pixel counts.
/// - contentMode: A flag that indicates whether the thumbnail view fits or fills the image of the given dimensions.
public init(size: CGSize, contentMode: ThumbnailProperties.ContentMode) {
width = Int(size.width)
height = Int(size.height)
self.contentMode = contentMode
}
}
//MARK: Public
public extension WebDAV {
//MARK: Images
/// Download and cache an image from the specified file path.
/// - Parameters:
/// - path: The path of the image to download.
/// - account: The WebDAV account.
/// - password: The WebDAV account's password.
/// - completion: If account properties are invalid, this will run immediately on the same thread.
/// Otherwise, it runs when the nextwork call finishes on a background thread.
/// - image: The image downloaded, if successful.
/// The cached image if it has balready been downloaded.
/// - cachedImageURL: The URL of the cached image.
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The request identifier.
@discardableResult
func downloadImage<A: WebDAVAccount>(path: String, account: A, password: String, caching options: WebDAVCachingOptions = [], completion: @escaping (_ image: UIImage?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
cachingDataTask(cache: imageCache, path: path, account: account, password: password, caching: options, valueFromData: { UIImage(data: $0) }, completion: completion)
}
//MARK: Thumbnails
/// Download and cache an image's thumbnail from the specified file path.
///
/// Only works with Nextcould or other instances that use Nextcloud's same thumbnail URL structure.
/// - Parameters:
/// - path: The path of the image to download the thumbnail of.
/// - account: The WebDAV account.
/// - password: The WebDAV account's password.
/// - dimensions: The dimensions of the thumbnail. A value of `nil` will use the server's default.
/// - aspectFill: Whether the thumbnail should fill the dimensions or fit within it.
/// - completion: If account properties are invalid, this will run immediately on the same thread.
/// Otherwise, it runs when the nextwork call finishes on a background thread.
/// - image: The thumbnail downloaded, if successful.
/// The cached thumbnail if it has balready been downloaded.
/// - cachedImageURL: The URL of the cached thumbnail.
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The request identifier.
@discardableResult
func downloadThumbnail<A: WebDAVAccount>(
path: String, account: A, password: String, with properties: ThumbnailProperties = .default,
caching options: WebDAVCachingOptions = [], completion: @escaping (_ image: UIImage?, _ error: WebDAVError?) -> Void
) -> URLSessionDataTask? {
// This function looks a lot like cachingDataTask and authorizedRequest,
// but generalizing both of those to support thumbnails would make them
// so much more complicated that it's better to just have similar code here.
// Check cache
var cachedThumbnail: UIImage?
let accountPath = AccountPath(account: account, path: path)
if !options.contains(.doNotReturnCachedResult) {
if let thumbnail = thumbnailCache[accountPath]?[properties] {
completion(thumbnail, nil)
if !options.contains(.requestEvenIfCached) {
if options.contains(.removeExistingCache) {
try? deleteCachedThumbnail(forItemAtPath: path, account: account, with: properties)
}
return nil
} else {
cachedThumbnail = thumbnail
}
}
}
if options.contains(.removeExistingCache) {
try? deleteCachedThumbnail(forItemAtPath: path, account: account, with: properties)
}
// Create Network request
guard let unwrappedAccount = UnwrappedAccount(account: account), let auth = self.auth(username: unwrappedAccount.username, password: password) else {
completion(nil, .invalidCredentials)
return nil
}
guard let url = nextcloudPreviewURL(for: unwrappedAccount.baseURL, at: path, with: properties) else {
completion(nil, .unsupported)
return nil
}
var request = URLRequest(url: url)
request.addValue("Basic \(auth)", forHTTPHeaderField: "Authorization")
// Perform the network request
let task = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil).dataTask(with: request) { [weak self] data, response, error in
let error = WebDAVError.getError(response: response, error: error)
if let data = data,
let thumbnail = UIImage(data: data) {
// Cache result
//TODO: Cache to disk
if !options.contains(.removeExistingCache),
!options.contains(.doNotCacheResult) {
var cachedThumbnails = self?.thumbnailCache[accountPath] ?? [:]
cachedThumbnails[properties] = thumbnail
self?.thumbnailCache[accountPath] = cachedThumbnails
}
if thumbnail != cachedThumbnail {
completion(thumbnail, error)
}
} else {
completion(nil, error)
}
}
task.resume()
return task
}
//MARK: Image Cache
func getCachedImage<A: WebDAVAccount>(forItemAtPath path: String, account: A) -> UIImage? {
getCachedValue(cache: imageCache, forItemAtPath: path, account: account)
}
//MARK: Thumbnail Cache
func getAllCachedThumbnails<A: WebDAVAccount>(forItemAtPath path: String, account: A) -> [ThumbnailProperties: UIImage]? {
getCachedValue(cache: thumbnailCache, forItemAtPath: path, account: account)
}
func getCachedThumbnail<A: WebDAVAccount>(forItemAtPath path: String, account: A, with properties: ThumbnailProperties) -> UIImage? {
getAllCachedThumbnails(forItemAtPath: path, account: account)?[properties]
}
func deleteCachedThumbnail<A: WebDAVAccount>(forItemAtPath path: String, account: A, with properties: ThumbnailProperties) throws {
let accountPath = AccountPath(account: account, path: path)
if var cachedThumbnails = thumbnailCache[accountPath] {
cachedThumbnails.removeValue(forKey: properties)
if cachedThumbnails.isEmpty {
thumbnailCache.removeValue(forKey: accountPath)
} else {
thumbnailCache[accountPath] = cachedThumbnails
}
}
}
func deleteAllCachedThumbnails<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws {
let accountPath = AccountPath(account: account, path: path)
thumbnailCache.removeValue(forKey: accountPath)
}
}
//MARK: Internal
extension WebDAV {
//MARK: Pathing
func nextcloudPreviewBaseURL(for baseURL: URL) -> URL? {
return nextcloudBaseURL(for: baseURL)?
.appendingPathComponent("index.php")
.appendingPathComponent("core")
.appendingPathComponent("preview.png")
}
func nextcloudPreviewQuery(at path: String, properties: ThumbnailProperties) -> [URLQueryItem]? {
var path = path
if path.hasPrefix("/") {
path.removeFirst()
}
var query = [
URLQueryItem(name: "file", value: path),
URLQueryItem(name: "mode", value: "cover")
]
if let size = properties.size {
query.append(URLQueryItem(name: "x", value: "\(size.width)"))
query.append(URLQueryItem(name: "y", value: "\(size.height)"))
}
if properties.contentMode == .fill {
query.append(URLQueryItem(name: "a", value: "1"))
}
return query
}
func nextcloudPreviewURL(for baseURL: URL, at path: String, with properties: ThumbnailProperties) -> URL? {
guard let thumbnailURL = nextcloudPreviewBaseURL(for: baseURL) else { return nil }
var components = URLComponents(string: thumbnailURL.absoluteString)
components?.queryItems = nextcloudPreviewQuery(at: path, properties: properties)
return components?.url
}
}

View File

@ -8,6 +8,8 @@
import Foundation
import SWXMLHash
//MARK: OCSTheme
/// Theming information from a WebDAV server that supports OCS.
public struct OCSTheme {
/// Name of the server.
@ -48,7 +50,9 @@ public struct OCSTheme {
}
}
extension WebDAV {
//MARK: Public
public extension WebDAV {
/// Get the theme information from a WebDAV server that supports OCS (including Nextcloud).
/// - Parameters:
@ -60,13 +64,16 @@ extension WebDAV {
/// - error: A WebDAVError if the call was unsuccessful.
/// - Returns: The data task for the request.
@discardableResult
public func getNextcloudTheme<A: WebDAVAccount>(account: A, password: String, completion: @escaping (_ theme: OCSTheme?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func getNextcloudTheme<A: WebDAVAccount>(account: A, password: String, completion: @escaping (_ theme: OCSTheme?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
guard let unwrappedAccount = UnwrappedAccount(account: account),
let auth = self.auth(username: unwrappedAccount.username, password: password),
let baseURL = nextcloudBaseURL(for: unwrappedAccount.baseURL) else {
let auth = self.auth(username: unwrappedAccount.username, password: password) else {
completion(nil, .invalidCredentials)
return nil
}
guard let baseURL = nextcloudBaseURL(for: unwrappedAccount.baseURL) else {
completion(nil, .unsupported)
return nil
}
let url = baseURL.appendingPathComponent("ocs/v1.php/cloud/capabilities")
var request = URLRequest(url: url)
@ -102,7 +109,7 @@ extension WebDAV {
/// - error: A WebDAVError if the call was unsuccessful.
/// - Returns: The data task for the request.
@discardableResult
public func getNextcloudColorHex<A: WebDAVAccount>(account: A, password: String, completion: @escaping (_ color: String?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func getNextcloudColorHex<A: WebDAVAccount>(account: A, password: String, completion: @escaping (_ color: String?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
getNextcloudTheme(account: account, password: password) { theme, error in
completion(theme?.colorHex, error)
}

View File

@ -7,7 +7,6 @@
import UIKit
import SWXMLHash
import Networking
public class WebDAV: NSObject, URLSessionDelegate {
static let domain = "app.lyons.webdav-swift"
@ -17,15 +16,34 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// The formatter used when rendering cache size in `getCacheSize`.
public var byteCountFormatter = ByteCountFormatter()
var networkings: [UnwrappedAccount: Networking] = [:]
var thumbnailNetworkings: [UnwrappedAccount: Networking] = [:]
public var filesCache: [AccountPath: [WebDAVFile]] = [:]
public var dataCache = Cache<AccountPath, Data>()
public var imageCache = Cache<AccountPath, UIImage>()
public var thumbnailCache = Cache<AccountPath, [ThumbnailProperties: UIImage]>()
public override init() {
super.init()
loadFilesCacheFromDisk()
}
//MARK: Static
public static func sortedFiles(_ files: [WebDAVFile], foldersFirst: Bool, includeSelf: Bool) -> [WebDAVFile] {
var files = files
if !includeSelf, !files.isEmpty {
files.removeFirst()
}
if foldersFirst {
files = files.filter { $0.isDirectory } + files.filter { !$0.isDirectory }
}
return files
}
}
//MARK: Public
public extension WebDAV {
//MARK: WebDAV Requests
/// List the files and directories at the specified path.
@ -44,7 +62,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful.
/// - Returns: The data task for the request.
@discardableResult
public func listFiles<A: WebDAVAccount>(atPath path: String, account: A, password: String, foldersFirst: Bool = true, includeSelf: Bool = false, caching options: WebDAVCacheOptions = [], completion: @escaping (_ files: [WebDAVFile]?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func listFiles<A: WebDAVAccount>(atPath path: String, account: A, password: String, foldersFirst: Bool = true, includeSelf: Bool = false, caching options: WebDAVCachingOptions = [], completion: @escaping (_ files: [WebDAVFile]?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
// Check the cache
var cachedResponse: [WebDAVFile]?
let accountPath = AccountPath(account: account, path: path)
@ -140,7 +158,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The upload task for the request.
@discardableResult
public func upload<A: WebDAVAccount>(data: Data, toPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionUploadTask? {
func upload<A: WebDAVAccount>(data: Data, toPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionUploadTask? {
guard let request = authorizedRequest(path: path, account: account, password: password, method: .put) else {
completion(.invalidCredentials)
return nil
@ -164,7 +182,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The upload task for the request.
@discardableResult
public func upload<A: WebDAVAccount>(file: URL, toPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionUploadTask? {
func upload<A: WebDAVAccount>(file: URL, toPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionUploadTask? {
guard let request = authorizedRequest(path: path, account: account, password: password, method: .put) else {
completion(.invalidCredentials)
return nil
@ -189,18 +207,8 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The data task for the request.
@discardableResult
public func download<A: WebDAVAccount>(fileAtPath path: String, account: A, password: String, completion: @escaping (_ data: Data?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
guard let request = authorizedRequest(path: path, account: account, password: password, method: .get) else {
completion(nil, .invalidCredentials)
return nil
}
let task = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil).dataTask(with: request) { data, response, error in
completion(data, WebDAVError.getError(response: response, error: error))
}
task.resume()
return task
func download<A: WebDAVAccount>(fileAtPath path: String, account: A, password: String, caching options: WebDAVCachingOptions = [], completion: @escaping (_ data: Data?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
cachingDataTask(cache: dataCache, path: path, account: account, password: password, caching: options, valueFromData: { $0 }, completion: completion)
}
/// Create a folder at the specified path
@ -213,7 +221,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The data task for the request.
@discardableResult
public func createFolder<A: WebDAVAccount>(atPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func createFolder<A: WebDAVAccount>(atPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
basicDataTask(path: path, account: account, password: password, method: .mkcol, completion: completion)
}
@ -227,7 +235,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The data task for the request.
@discardableResult
public func deleteFile<A: WebDAVAccount>(atPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func deleteFile<A: WebDAVAccount>(atPath path: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
basicDataTask(path: path, account: account, password: password, method: .delete, completion: completion)
}
@ -242,7 +250,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The data task for the request.
@discardableResult
public func moveFile<A: WebDAVAccount>(fromPath path: String, to destination: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func moveFile<A: WebDAVAccount>(fromPath path: String, to destination: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
basicDataTask(path: path, destination: destination, account: account, password: password, method: .move, completion: completion)
}
@ -257,84 +265,18 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The data task for the request.
@discardableResult
public func copyFile<A: WebDAVAccount>(fromPath path: String, to destination: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func copyFile<A: WebDAVAccount>(fromPath path: String, to destination: String, account: A, password: String, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
basicDataTask(path: path, destination: destination, account: account, password: password, method: .copy, completion: completion)
}
//MARK: Networking Requests
// Somewhat confusing header title, but this refers to requests made using the Networking library
//MARK: Cache
/// Download and cache an image from the specified file path.
/// - Parameters:
/// - path: The path of the image to download.
/// - account: The WebDAV account.
/// - password: The WebDAV account's password.
/// - completion: If account properties are invalid, this will run immediately on the same thread.
/// Otherwise, it runs when the nextwork call finishes on a background thread.
/// - image: The image downloaded, if successful.
/// The cached image if it has balready been downloaded.
/// - cachedImageURL: The URL of the cached image.
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The request identifier.
@discardableResult
public func downloadImage<A: WebDAVAccount>(path: String, account: A, password: String, completion: @escaping (_ image: UIImage?, _ cachedImageURL: URL?, _ error: WebDAVError?) -> Void) -> String? {
guard let networking = self.networking(for: account, password: password),
let path = networkingPath(path) else {
completion(nil, nil, .invalidCredentials)
return nil
}
let id = networking.downloadImage(path) { imageResult in
switch imageResult {
case .success(let imageResponse):
let path = try? networking.destinationURL(for: path)
completion(imageResponse.image, path, nil)
case .failure(let response):
completion(nil, nil, WebDAVError.getError(statusCode: response.statusCode, error: response.error))
}
}
return id
func getCachedData<A: WebDAVAccount>(forItemAtPath path: String, account: A) -> Data? {
getCachedValue(cache: dataCache, forItemAtPath: path, account: account)
}
/// Download and cache an image's thumbnail from the specified file path.
///
/// Only works with Nextcould or other instances that use Nextcloud's same thumbnail URL structure.
/// - Parameters:
/// - path: The path of the image to download the thumbnail of.
/// - account: The WebDAV account.
/// - password: The WebDAV account's password.
/// - dimensions: The dimensions of the thumbnail. A value of `nil` will use the server's default.
/// - aspectFill: Whether the thumbnail should fill the dimensions or fit within it.
/// - completion: If account properties are invalid, this will run immediately on the same thread.
/// Otherwise, it runs when the nextwork call finishes on a background thread.
/// - image: The thumbnail downloaded, if successful.
/// The cached thumbnail if it has balready been downloaded.
/// - cachedImageURL: The URL of the cached thumbnail.
/// - error: A WebDAVError if the call was unsuccessful. `nil` if it was.
/// - Returns: The request identifier.
@discardableResult
public func downloadThumbnail<A: WebDAVAccount>(
path: String, account: A, password: String, with dimensions: CGSize?, aspectFill: Bool = true,
completion: @escaping (_ image: UIImage?, _ cachedImageURL: URL?, _ error: WebDAVError?) -> Void
) -> String? {
guard let networking = thumbnailNetworking(for: account, password: password),
let path = nextcloudPreviewPath(at: path, with: dimensions, aspectFill: aspectFill) else {
completion(nil, nil, .invalidCredentials)
return nil
}
let id = networking.downloadImage(path) { imageResult in
switch imageResult {
case .success(let imageResponse):
let path = try? networking.destinationURL(for: path)
completion(imageResponse.image, path, nil)
case .failure(let response):
completion(nil, nil, WebDAVError.getError(statusCode: response.statusCode, error: response.error))
}
}
return id
func getCachedValue<A: WebDAVAccount, Value: Equatable>(cache: Cache<AccountPath, Value>, forItemAtPath path: String, account: A) -> Value? {
cache[AccountPath(account: account, path: path)]
}
/// Deletes the cached data for a certain path.
@ -342,15 +284,10 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - path: The path used to download the data.
/// - account: The WebDAV account used to download the data.
/// - Throws: An error if the cached object URL couldnt be created or the file can't be deleted.
public func deleteCachedData<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws {
// It's OK to leave the password blank here, because it gets set before every call
guard let networking = self.networking(for: account, password: ""),
let path = networkingPath(path) else { return }
let destinationURL = try networking.destinationURL(for: path)
if FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(atPath: destinationURL.path)
}
func deleteCachedData<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws {
let accountPath = AccountPath(account: account, path: path)
dataCache.removeValue(forKey: accountPath)
imageCache.removeValue(forKey: accountPath)
}
/// Get the URL used to store a resource for a certain path.
@ -360,128 +297,23 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - account: The WebDAV account used to download the data.
/// - Throws: An error if the URL couldnt be created.
/// - Returns: The URL where the resource is stored.
public func getCachedDataURL<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws -> URL? {
guard let path = networkingPath(path) else { return nil }
return try self.networking(for: account, password: "")?.destinationURL(for: path)
}
/// Get the image cached for a certain path.
/// - Parameters:
/// - path: The path used to download the image.
/// - account: The WebDAV account used to download the image.
/// - Returns: The image, if it is in the cache.
public func getCachedImage<A: WebDAVAccount>(forItemAtPath path: String, account: A) -> UIImage? {
guard let path = networkingPath(path) else { return nil }
return self.networking(for: account, password: "")?.imageFromCache(path)
}
/// Delete a specific cached thumbnail for a certain path and properties.
/// - Parameters:
/// - path: The path of the image to delete the thumbnail of.
/// - account: The WebDAV account used to download the data.
/// - dimensions: The dimensions of the thumbnail to delete.
/// - aspectFill: Whether the thumbnail was fetched with aspectFill.
/// - Throws: An error if the cached data URL couldnt be created or the file couldn't be deleted.
public func deleteCachedThumbnail<A: WebDAVAccount>(forItemAtPath path: String, account: A, with dimensions: CGSize?, aspectFill: Bool) throws {
guard let networking = thumbnailNetworking(for: account, password: ""),
let path = nextcloudPreviewPath(at: path, with: dimensions, aspectFill: aspectFill) else { return }
let destinationURL = try networking.destinationURL(for: path)
if FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(atPath: destinationURL.path)
}
}
/// Delete all cached thumbnails for a certain path.
/// - Parameters:
/// - path: The path of the image to delete the thumbnails of.
/// - account: The WebDAV account used to download the thumbnails.
/// - Throws: An error if the cached thumbnail URLs couldnt be created or the files couldn't be deleted.
public func deleteAllCachedThumbnails<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws {
try getAllCachedThumbnailURLs(forItemAtPath: path, account: account).forEach { url in
try FileManager.default.remove(at: url)
}
}
/// Get the URLs for the cached thumbnails for a certain path.
/// - Parameters:
/// - path: The path used to download the thumbnails.
/// - account: The WebDAV account used to download the thumbnails.
/// - Throws: An error if the cached thumbail URLs couldn't be created or the caches folder couldn't be accessed.
/// - Returns: An array of the URLs of cached thumbnails for the given path.
public func getAllCachedThumbnailURLs<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws -> [URL] {
// Getting the path with no dimensions and aspect fit will give the shortest form of the path with no extras added.
guard let networking = thumbnailNetworking(for: account, password: ""),
let path = nextcloudPreviewPath(at: path, with: nil, aspectFill: false),
let networkingCacheURL = networkingCacheURL else { return [] }
let destinationURL = try networking.destinationURL(for: path)
let name = destinationURL.deletingPathExtension().lastPathComponent
return try FileManager.default.contentsOfDirectory(at: networkingCacheURL, includingPropertiesForKeys: [], options: []).filter { url -> Bool in
// Any cached thumbnail is going to start with this name.
// It might also have dimensions and/or the aspect fill property after.
url.lastPathComponent.starts(with: name)
}
}
public func getAllCachedThumbnails<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws -> [UIImage] {
// We can't use imageFromCache(path) to get the images from a memory cache
// because we can't generate the path for every possible thumbnail. Instead
// we'll get the URLs from getAllCachedThumbnailURLs and get the data from those.
try getAllCachedThumbnailURLs(forItemAtPath: path, account: account).compactMap { url -> UIImage? in
UIImage(data: try Data(contentsOf: url))
}
}
/// Get the URL for the cached thumbnail for a certain path and properties.
/// Useful to find where a download thumbnail is located.
/// - Parameters:
/// - path: The path used to download the thumbnail.
/// - account: The WebDAV account used to download the thumbnail.
/// - dimensions: The dimensions of the thumbnail to get.
/// - aspectFill: Whether the thumbnail was fetched with aspectFill.
/// - Throws: An error if the cached data URL couldnt be created.
/// - Returns: The URL where the thumbnail has been stored.
public func getCachedThumbnailURL<A: WebDAVAccount>(forItemAtPath path: String, account: A, with dimensions: CGSize?, aspectFill: Bool) throws -> URL? {
guard let path = nextcloudPreviewPath(at: path, with: dimensions, aspectFill: aspectFill) else { return nil }
return try thumbnailNetworking(for: account, password: "")?.destinationURL(for: path)
}
/// Get the thumbnail cached for a certain path and properties.
/// - Parameters:
/// - path: The path used to download the thumbnail.
/// - account: The WebDAV account used to download the thumbnail.
/// - dimensions: The dimensions of the thumbnail to get.
/// - aspectFill: Whether the thumbnail was fetched with aspectFill.
/// - Returns: The thumbnail, if it is in the cache.
public func getCachedThumbnail<A: WebDAVAccount>(forItemAtPath path: String, account: A, with dimensions: CGSize?, aspectFill: Bool) -> UIImage? {
guard let path = nextcloudPreviewPath(at: path, with: dimensions, aspectFill: aspectFill) else { return nil }
return self.thumbnailNetworking(for: account, password: "")?.imageFromCache(path)
func getCachedDataURL<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws -> URL? {
//TODO
return nil
}
/// Deletes all downloaded data that has been cached.
/// - Throws: An error if the resources couldn't be deleted.
public func deleteAllCachedData() throws {
guard let caches = networkingCacheURL else { return }
try FileManager.default.remove(at: caches)
}
/// Cancel a request.
/// - Parameters:
/// - id: The identifier of the request.
/// - account: The WebDAV account the request was made on.
public func cancelRequest<A: WebDAVAccount>(id: String, account: A) {
guard let unwrappedAccount = UnwrappedAccount(account: account) else { return }
networkings[unwrappedAccount]?.cancel(id)
thumbnailNetworkings[unwrappedAccount]?.cancel(id)
func deleteAllCachedData() throws {
dataCache.removeAllValues()
imageCache.removeAllValues()
}
/// Get the total disk space for the contents of the image cache.
/// For a formatted string of the size, see `getCacheSize`.
/// - Returns: The total allocated space of the cache in bytes.
public func getCacheByteCount() -> Int {
guard let caches = networkingCacheURL,
func getCacheByteCount() -> Int {
guard let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first,
let urls = FileManager.default.enumerator(at: caches, includingPropertiesForKeys: nil)?.allObjects as? [URL] else { return 0 }
return urls.lazy.reduce(0) { total, url -> Int in
@ -494,29 +326,103 @@ public class WebDAV: NSObject, URLSessionDelegate {
///
/// This formats the size using this object's `byteCountFormatter` which can be modified.
/// - Returns: A localized string of the total allocated space of the cache.
public func getCacheSize() -> String {
func getCacheSize() -> String {
byteCountFormatter.string(fromByteCount: Int64(getCacheByteCount()))
}
/// The URL to the directory that contains the cached image data.
public var networkingCacheURL: URL? {
/// The URL to the directory of the depricated Networking image data cache.
var networkingCacheURL: URL? {
FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("com.3lvis.networking")
}
//MARK: Cache
/// The caching system has changed from WebDAV Swift v2 to v3.
/// Run this function if upgrading from v2 to v3 to clear the old cache.
/// - Throws: An error if the cache couldn't be deleted.
func clearV2Cache() throws {
guard let caches = networkingCacheURL,
FileManager.default.fileExists(atPath: caches.path) else { return }
try FileManager.default.removeItem(at: caches)
}
public func clearFilesMemoryCache() {
func clearFilesMemoryCache() {
filesCache.removeAll()
}
//MARK: Internal
}
//MARK: Internal
extension WebDAV {
//MARK: Standard Requests
func cachingDataTask<A: WebDAVAccount, Value: Equatable>(cache: Cache<AccountPath, Value>, path: String, account: A, password: String, caching options: WebDAVCachingOptions, valueFromData: @escaping (_ data: Data) -> Value?, completion: @escaping (_ value: Value?, _ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
// Check cache
var cachedValue: Value?
let accountPath = AccountPath(account: account, path: path)
if !options.contains(.doNotReturnCachedResult) {
if let value = cache[accountPath] {
completion(value, nil)
if !options.contains(.requestEvenIfCached) {
if options.contains(.removeExistingCache) {
cache.removeValue(forKey: accountPath)
}
return nil
} else {
// Remember the cached completion. If the fetched results
// are the same, don't bother completing again.
cachedValue = value
}
}
}
if options.contains(.removeExistingCache) {
cache.removeValue(forKey: accountPath)
}
// Create network request
guard let request = authorizedRequest(path: path, account: account, password: password, method: .get) else {
completion(nil, .invalidCredentials)
return nil
}
// Perform network request
let task = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil).dataTask(with: request) { data, response, error in
let error = WebDAVError.getError(response: response, error: error)
if let data = data,
let value = valueFromData(data) {
// Cache result
//TODO: Cache to disk
if !options.contains(.removeExistingCache),
!options.contains(.doNotCacheResult) {
cache.set(value, forKey: accountPath)
}
// Don't send a duplicate completion if the results are the same.
if value != cachedValue {
completion(value, error)
}
} else {
completion(nil, error)
}
}
task.resume()
return task
}
/// Creates a basic authentication credential.
/// - Parameters:
/// - username: The username
/// - password: The password
/// - Returns: A base-64 encoded credential if the provided credentials are valid (can be encoded as UTF-8).
internal func auth(username: String, password: String) -> String? {
func auth(username: String, password: String) -> String? {
let authString = username + ":" + password
let authData = authString.data(using: .utf8)
return authData?.base64EncodedString()
@ -529,7 +435,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
/// - password: The WebDAV password
/// - method: The HTTP Method for the request.
/// - Returns: The URL request if the credentials are valid (can be encoded as UTF-8).
internal func authorizedRequest<A: WebDAVAccount>(path: String, account: A, password: String, method: HTTPMethod) -> URLRequest? {
func authorizedRequest<A: WebDAVAccount>(path: String, account: A, password: String, method: HTTPMethod) -> URLRequest? {
guard let unwrappedAccount = UnwrappedAccount(account: account),
let auth = self.auth(username: unwrappedAccount.username, password: password) else { return nil }
@ -541,7 +447,7 @@ public class WebDAV: NSObject, URLSessionDelegate {
return request
}
internal func basicDataTask<A: WebDAVAccount>(path: String, destination: String? = nil, account: A, password: String, method: HTTPMethod, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
func basicDataTask<A: WebDAVAccount>(path: String, destination: String? = nil, account: A, password: String, method: HTTPMethod, completion: @escaping (_ error: WebDAVError?) -> Void) -> URLSessionDataTask? {
guard var request = authorizedRequest(path: path, account: account, password: password, method: method),
let unwrappedAccount = UnwrappedAccount(account: account) else {
completion(.invalidCredentials)
@ -561,18 +467,9 @@ public class WebDAV: NSObject, URLSessionDelegate {
return task
}
internal func networking<A: WebDAVAccount>(for account: A, password: String) -> Networking? {
guard let unwrappedAccount = UnwrappedAccount(account: account) else { return nil }
let networking = networkings[unwrappedAccount] ?? {
let networking = Networking(baseURL: unwrappedAccount.baseURL.absoluteString)
networkings[unwrappedAccount] = networking
return networking
}()
networking.setAuthorizationHeader(username: unwrappedAccount.username, password: password)
return networking
}
//MARK: Pathing
internal func nextcloudBaseURL(for baseURL: URL) -> URL? {
func nextcloudBaseURL(for baseURL: URL) -> URL? {
guard baseURL.absoluteString.lowercased().contains("remote.php/dav/files/"),
let index = baseURL.pathComponents.map({ $0.lowercased() }).firstIndex(of: "remote.php") else { return nil }
@ -586,63 +483,4 @@ public class WebDAV: NSObject, URLSessionDelegate {
return previewURL
}
internal func nextcloudPreviewBaseURL(for baseURL: URL) -> URL? {
return nextcloudBaseURL(for: baseURL)?
.appendingPathComponent("index.php")
.appendingPathComponent("core")
.appendingPathComponent("preview.png")
}
internal func thumbnailNetworking<A: WebDAVAccount>(for account: A, password: String) -> Networking? {
guard let unwrappedAccount = UnwrappedAccount(account: account),
let previewURL = nextcloudPreviewBaseURL(for: unwrappedAccount.baseURL) else { return nil }
let networking = thumbnailNetworkings[unwrappedAccount] ?? {
let networking = Networking(baseURL: previewURL.absoluteString)
thumbnailNetworkings[unwrappedAccount] = networking
return networking
}()
networking.setAuthorizationHeader(username: unwrappedAccount.username, password: password)
return networking
}
internal func networkingPath(_ path: String) -> String? {
let slashPath = path.first == "/" ? path : "/" + path
return slashPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
}
internal func nextcloudPreviewPath(at path: String, with dimensions: CGSize?, aspectFill: Bool = true) -> String? {
guard var encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { return nil }
if encodedPath.hasPrefix("/") {
encodedPath.removeFirst()
}
var thumbnailPath = "?file=\(encodedPath)&mode=cover"
if let dimensions = dimensions {
thumbnailPath += "&x=\(dimensions.width)&y=\(dimensions.height)"
}
if aspectFill {
thumbnailPath += "&a=1"
}
return thumbnailPath
}
//MARK: Static
public static func sortedFiles(_ files: [WebDAVFile], foldersFirst: Bool, includeSelf: Bool) -> [WebDAVFile] {
var files = files
if !includeSelf, !files.isEmpty {
files.removeFirst()
}
if foldersFirst {
files = files.filter { $0.isDirectory } + files.filter { !$0.isDirectory }
}
return files
}
}

View File

@ -10,7 +10,7 @@ import Foundation
/// **Default behavior** (empty set):
/// If there is a cached result, return that instead of making a request.
/// Otherwise, make a request and cache the result.
public struct WebDAVCacheOptions: OptionSet {
public struct WebDAVCachingOptions: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
@ -18,18 +18,18 @@ public struct WebDAVCacheOptions: OptionSet {
}
/// Do not cache the results of this request, if one is made.
public static let doNotCacheResult = WebDAVCacheOptions(rawValue: 1 << 0)
public static let doNotCacheResult = WebDAVCachingOptions(rawValue: 1 << 0)
/// Remove the cached value for this request.
public static let removeExistingCache = WebDAVCacheOptions(rawValue: 1 << 1)
public static let removeExistingCache = WebDAVCachingOptions(rawValue: 1 << 1)
/// If there is a cached result, ignore it and make a request.
public static let doNotReturnCachedResult = WebDAVCacheOptions(rawValue: 1 << 2)
public static let doNotReturnCachedResult = WebDAVCachingOptions(rawValue: 1 << 2)
/// If there is a cached result, return that, then make a request, returing that result again if it is different.
public static let requestEvenIfCached = WebDAVCacheOptions(rawValue: 1 << 3)
public static let requestEvenIfCached = WebDAVCachingOptions(rawValue: 1 << 3)
/// Disable all caching for this request including deleting any existing cache for it.
/// Same as `[.doNotCacheResult, .removeExistingCache, .doNotReturnCachedResult]`.
public static let disableCache: WebDAVCacheOptions = [.doNotCacheResult, .removeExistingCache, .doNotReturnCachedResult]
public static let disableCache: WebDAVCachingOptions = [.doNotCacheResult, .removeExistingCache, .doNotReturnCachedResult]
/// Ignore the cached result if there is one, and don't cache the new result.
/// Same as `[.doNotCacheResult, .doNotReturnCachedResult]`.
public static let ignoreCache: WebDAVCacheOptions = [.doNotCacheResult, .doNotReturnCachedResult]
public static let ignoreCache: WebDAVCachingOptions = [.doNotCacheResult, .doNotReturnCachedResult]
}

View File

@ -8,13 +8,15 @@
import Foundation
public enum WebDAVError: Error {
/// The DAVAccount was unable to be encoded to base 64.
/// The credentials or path were unable to be encoded.
/// No network request was called.
case invalidCredentials
/// The credentials were incorrect.
case unauthorized
/// The server was unable to store the data provided.
case insufficientStorage
/// The server does not support this feature.
case unsupported
/// Another unspecified Error occurred.
case nsError(Error)

View File

@ -1,6 +1,5 @@
import XCTest
@testable import WebDAV
import Networking
final class WebDAVTests: XCTestCase {
var webDAV = WebDAV()
@ -354,6 +353,9 @@ final class WebDAVTests: XCTestCase {
//MARK: Image Cache
// Commented out lines are lines that existed to test image cache in v2.x versions.
// They will be added again when disk cache is reimplemented in v3.0.
func testDownloadImage() {
guard let (account, password) = getAccount() else { return XCTFail() }
guard let imagePath = ProcessInfo.processInfo.environment["image_path"] else {
@ -373,11 +375,13 @@ final class WebDAVTests: XCTestCase {
downloadImage(imagePath: imagePath, account: account, password: password)
let cachedImageURL = try webDAV.getCachedDataURL(forItemAtPath: imagePath, account: account)!
XCTAssert(FileManager.default.fileExists(atPath: cachedImageURL.path))
// let cachedImageURL = try webDAV.getCachedDataURL(forItemAtPath: imagePath, account: account)!
// XCTAssert(FileManager.default.fileExists(atPath: cachedImageURL.path))
XCTAssertNotNil(webDAV.getCachedImage(forItemAtPath: imagePath, account: account))
try webDAV.deleteCachedData(forItemAtPath: imagePath, account: account)
XCTAssertFalse(FileManager.default.fileExists(atPath: cachedImageURL.path))
XCTAssertNil(webDAV.getCachedImage(forItemAtPath: imagePath, account: account))
// XCTAssertFalse(FileManager.default.fileExists(atPath: cachedImageURL.path))
}
func testDeleteAllCachedData() throws {
@ -388,11 +392,16 @@ final class WebDAVTests: XCTestCase {
downloadImage(imagePath: imagePath, account: account, password: password)
let cachedImageURL = try webDAV.getCachedDataURL(forItemAtPath: imagePath, account: account)!
let accountPath = AccountPath(account: account, path: imagePath)
// let cachedImageURL = try webDAV.getCachedDataURL(forItemAtPath: imagePath, account: account)!
XCTAssertNotNil(webDAV.imageCache[accountPath])
XCTAssertNoThrow(try webDAV.deleteAllCachedData())
XCTAssertFalse(FileManager.default.fileExists(atPath: cachedImageURL.path))
XCTAssertNil(webDAV.imageCache[accountPath])
// XCTAssertFalse(FileManager.default.fileExists(atPath: cachedImageURL.path))
}
//MARK: Thumbnails
func testDownloadThumbnail() {
@ -403,7 +412,7 @@ final class WebDAVTests: XCTestCase {
downloadThumbnail(imagePath: imagePath, account: account, password: password)
XCTAssertNoThrow(try webDAV.deleteCachedThumbnail(forItemAtPath: imagePath, account: account, with: nil, aspectFill: false))
XCTAssertNoThrow(try webDAV.deleteCachedThumbnail(forItemAtPath: imagePath, account: account, with: .fill))
}
func testSpecificThumbnailCache() throws {
@ -412,13 +421,15 @@ final class WebDAVTests: XCTestCase {
return XCTFail("You need to set the image_path in the environment.")
}
downloadThumbnail(imagePath: imagePath, account: account, password: password)
downloadThumbnail(imagePath: imagePath, account: account, password: password, with: .fill)
let cachedThumbnailURL = try webDAV.getCachedThumbnailURL(forItemAtPath: imagePath, account: account, with: nil, aspectFill: false)!
XCTAssertTrue(FileManager.default.fileExists(atPath: cachedThumbnailURL.path))
XCTAssertNotNil(webDAV.getCachedThumbnail(forItemAtPath: imagePath, account: account, with: nil, aspectFill: false))
try webDAV.deleteCachedThumbnail(forItemAtPath: imagePath, account: account, with: nil, aspectFill: false)
XCTAssertFalse(FileManager.default.fileExists(atPath: cachedThumbnailURL.path))
// let cachedThumbnailURL = try webDAV.getCachedThumbnailURL(forItemAtPath: imagePath, account: account, with: nil, aspectFill: false)!
// XCTAssertTrue(FileManager.default.fileExists(atPath: cachedThumbnailURL.path))
XCTAssertNotNil(webDAV.getCachedThumbnail(forItemAtPath: imagePath, account: account, with: .fill))
try webDAV.deleteCachedThumbnail(forItemAtPath: imagePath, account: account, with: .fill)
XCTAssertNil(webDAV.getCachedThumbnail(forItemAtPath: imagePath, account: account, with: .fill))
// XCTAssertFalse(FileManager.default.fileExists(atPath: cachedThumbnailURL.path))
}
func testGeneralThumbnailCache() throws {
@ -427,20 +438,21 @@ final class WebDAVTests: XCTestCase {
return XCTFail("You need to set the image_path in the environment.")
}
downloadThumbnail(imagePath: imagePath, account: account, password: password, with: nil, aspectFill: true)
downloadThumbnail(imagePath: imagePath, account: account, password: password, with: nil, aspectFill: false)
downloadThumbnail(imagePath: imagePath, account: account, password: password, with: .fill)
downloadThumbnail(imagePath: imagePath, account: account, password: password, with: .fit)
let cachedThumbnailFillURL = try webDAV.getCachedThumbnailURL(forItemAtPath: imagePath, account: account, with: nil, aspectFill: true)!
let cachedThumbnailFitURL = try webDAV.getCachedThumbnailURL(forItemAtPath: imagePath, account: account, with: nil, aspectFill: false)!
// let cachedThumbnailFillURL = try webDAV.getCachedThumbnailURL(forItemAtPath: imagePath, account: account, with: nil, aspectFill: true)!
// let cachedThumbnailFitURL = try webDAV.getCachedThumbnailURL(forItemAtPath: imagePath, account: account, with: nil, aspectFill: false)!
XCTAssert(FileManager.default.fileExists(atPath: cachedThumbnailFillURL.path))
XCTAssert(FileManager.default.fileExists(atPath: cachedThumbnailFitURL.path))
XCTAssertEqual(try webDAV.getAllCachedThumbnails(forItemAtPath: imagePath, account: account).count, 2)
// XCTAssert(FileManager.default.fileExists(atPath: cachedThumbnailFillURL.path))
// XCTAssert(FileManager.default.fileExists(atPath: cachedThumbnailFitURL.path))
XCTAssertEqual(webDAV.getAllCachedThumbnails(forItemAtPath: imagePath, account: account)?.count, 2)
// Delete all cached thumbnails and check that they're both gone
try webDAV.deleteAllCachedThumbnails(forItemAtPath: imagePath, account: account)
XCTAssertFalse(FileManager.default.fileExists(atPath: cachedThumbnailFillURL.path))
XCTAssertFalse(FileManager.default.fileExists(atPath: cachedThumbnailFitURL.path))
XCTAssertNil(webDAV.getAllCachedThumbnails(forItemAtPath: imagePath, account: account))
// XCTAssertFalse(FileManager.default.fileExists(atPath: cachedThumbnailFillURL.path))
// XCTAssertFalse(FileManager.default.fileExists(atPath: cachedThumbnailFitURL.path))
}
//MARK: OCS
@ -555,29 +567,25 @@ final class WebDAVTests: XCTestCase {
try? webDAV.deleteCachedData(forItemAtPath: imagePath, account: account)
webDAV.downloadImage(path: imagePath, account: account, password: password) { image, cachedImageURL, error in
webDAV.downloadImage(path: imagePath, account: account, password: password) { image, error in
XCTAssertNil(error)
XCTAssertNotNil(image)
XCTAssert(FileManager.default.fileExists(atPath: cachedImageURL!.path))
expectation.fulfill()
}
wait(for: [expectation], timeout: 10.0)
}
private func downloadThumbnail(imagePath: String, account: SimpleAccount, password: String, with dimensions: CGSize? = nil, aspectFill: Bool = false) {
private func downloadThumbnail(imagePath: String, account: SimpleAccount, password: String, with properties: ThumbnailProperties = .fill) {
let expectation = XCTestExpectation(description: "Download thumbnail from WebDAV")
try? webDAV.deleteCachedThumbnail(forItemAtPath: imagePath, account: account, with: dimensions, aspectFill: aspectFill)
try? webDAV.deleteCachedThumbnail(forItemAtPath: imagePath, account: account, with: properties)
webDAV.downloadThumbnail(path: imagePath, account: account, password: password, with: dimensions, aspectFill: aspectFill) { image, cachedImageURL, error in
webDAV.downloadThumbnail(path: imagePath, account: account, password: password, with: properties) { image, error in
XCTAssertNil(error)
XCTAssertNotNil(image)
XCTAssert(FileManager.default.fileExists(atPath: cachedImageURL!.path))
expectation.fulfill()
}
@ -605,12 +613,14 @@ final class WebDAVTests: XCTestCase {
("testFilesCacheDoubleRequest", testFilesCacheDoubleRequest),
// Image Cache
("testDownloadImage", testDownloadImage),
/*
("testImageCache", testImageCache),
("testDeleteAllCachedData", testDeleteAllCachedData),
// Thumbnails
("testDownloadThumbnail", testDownloadThumbnail),
("testSpecificThumbnailCache", testSpecificThumbnailCache),
("testGeneralThumbnailCache", testGeneralThumbnailCache),
*/
// OCS
("testTheme", testTheme),
("testColorHex", testColorHex)