diff --git a/Riot/Managers/URLPreviews/ClosedURLPreview.swift b/Riot/Managers/URLPreviews/ClosedURLPreview.swift index c47434550..37f778d79 100644 --- a/Riot/Managers/URLPreviews/ClosedURLPreview.swift +++ b/Riot/Managers/URLPreviews/ClosedURLPreview.swift @@ -14,9 +14,12 @@ // limitations under the License. // -import Foundation +import CoreData extension ClosedURLPreview { - // Nothing to extend, however having this file stops Xcode - // complaining that it can't find ClosedURLPreview. + convenience init(context: NSManagedObjectContext, eventID: String, roomID: String) { + self.init(context: context) + self.eventID = eventID + self.roomID = roomID + } } diff --git a/Riot/Managers/URLPreviews/PreviewManager.swift b/Riot/Managers/URLPreviews/PreviewManager.swift deleted file mode 100644 index 29236f988..000000000 --- a/Riot/Managers/URLPreviews/PreviewManager.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// Copyright 2021 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -@objcMembers -class PreviewManager: NSObject { - private let restClient: MXRestClient - private let mediaManager: MXMediaManager - - // Core Data cache to reduce network requests - private let cache = URLPreviewCache() - - init(restClient: MXRestClient, mediaManager: MXMediaManager) { - self.restClient = restClient - self.mediaManager = mediaManager - } - - func preview(for url: URL, and event: MXEvent, success: @escaping (URLPreviewViewData) -> Void, failure: @escaping (Error?) -> Void) { - // Sanitize the URL before checking cache or performing lookup - let sanitizedURL = sanitize(url) - - if let preview = cache.preview(for: sanitizedURL, and: event) { - MXLog.debug("[PreviewManager] Using preview from cache") - success(preview) - return - } - - restClient.preview(for: sanitizedURL, success: { preview in - MXLog.debug("[PreviewManager] Preview not found in cache. Requesting from homeserver.") - - if let preview = preview { - self.makePreviewData(for: sanitizedURL, and: event, from: preview) { previewData in - self.cache.store(previewData) - success(previewData) - } - } - - }, failure: failure) - } - - func makePreviewData(for url: URL, and event: MXEvent, from preview: MXURLPreview, completion: @escaping (URLPreviewViewData) -> Void) { - let previewData = URLPreviewViewData(url: url, - eventID: event.eventId, - roomID: event.roomId, - siteName: preview.siteName, - title: preview.title, - text: preview.text) - - guard let imageURL = preview.imageURL else { - completion(previewData) - return - } - - if let cachePath = MXMediaManager.cachePath(forMatrixContentURI: imageURL, andType: preview.imageType, inFolder: nil), - let image = MXMediaManager.loadThroughCache(withFilePath: cachePath) { - previewData.image = image - completion(previewData) - return - } - - // Don't de-dupe image downloads as the manager should de-dupe preview generation. - - mediaManager.downloadMedia(fromMatrixContentURI: imageURL, withType: preview.imageType, inFolder: nil) { path in - guard let image = MXMediaManager.loadThroughCache(withFilePath: path) else { - completion(previewData) - return - } - previewData.image = image - completion(previewData) - } failure: { error in - completion(previewData) - } - } - - func removeExpiredItemsFromCache() { - cache.removeExpiredItems() - } - - func clearCache() { - cache.clear() - } - - func closePreview(for eventID: String, in roomID: String) { - cache.closePreview(for: eventID, in: roomID) - } - - func hasClosedPreview(from event: MXEvent) -> Bool { - cache.hasClosedPreview(for: event.eventId, in: event.roomId) - } - - private func sanitize(_ url: URL) -> URL { - // Remove the fragment from the URL. - var components = URLComponents(url: url, resolvingAgainstBaseURL: false) - components?.fragment = nil - - return components?.url ?? url - } -} diff --git a/Riot/Managers/URLPreviews/URLPreviewCacheData.swift b/Riot/Managers/URLPreviews/URLPreviewCacheData.swift index f32d276b5..099f1ebb1 100644 --- a/Riot/Managers/URLPreviews/URLPreviewCacheData.swift +++ b/Riot/Managers/URLPreviews/URLPreviewCacheData.swift @@ -17,12 +17,12 @@ import CoreData extension URLPreviewCacheData { - convenience init(context: NSManagedObjectContext, preview: URLPreviewViewData, creationDate: Date) { + convenience init(context: NSManagedObjectContext, preview: URLPreviewData, creationDate: Date) { self.init(context: context) update(from: preview, on: creationDate) } - func update(from preview: URLPreviewViewData, on date: Date) { + func update(from preview: URLPreviewData, on date: Date) { url = preview.url siteName = preview.siteName title = preview.title @@ -32,15 +32,15 @@ extension URLPreviewCacheData { creationDate = date } - func preview(for event: MXEvent) -> URLPreviewViewData? { + func preview(for event: MXEvent) -> URLPreviewData? { guard let url = url else { return nil } - let viewData = URLPreviewViewData(url: url, - eventID: event.eventId, - roomID: event.roomId, - siteName: siteName, - title: title, - text: text) + let viewData = URLPreviewData(url: url, + eventID: event.eventId, + roomID: event.roomId, + siteName: siteName, + title: title, + text: text) viewData.image = image as? UIImage return viewData diff --git a/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewData.swift b/Riot/Managers/URLPreviews/URLPreviewData.swift similarity index 97% rename from Riot/Modules/Room/Views/URLPreviews/URLPreviewViewData.swift rename to Riot/Managers/URLPreviews/URLPreviewData.swift index bcf2a38f3..74e5c5125 100644 --- a/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewData.swift +++ b/Riot/Managers/URLPreviews/URLPreviewData.swift @@ -17,7 +17,7 @@ import Foundation @objcMembers -class URLPreviewViewData: NSObject { +class URLPreviewData: NSObject { /// The URL that's represented by the preview data. This may have been sanitized. /// Note: The original URL, can be found in the bubble components with `eventID` and `roomID`. let url: URL diff --git a/Riot/Managers/URLPreviews/URLPreviewManager.swift b/Riot/Managers/URLPreviews/URLPreviewManager.swift new file mode 100644 index 000000000..6d46e2fe4 --- /dev/null +++ b/Riot/Managers/URLPreviews/URLPreviewManager.swift @@ -0,0 +1,116 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objcMembers +class URLPreviewManager: NSObject { + static let shared = URLPreviewManager() + + // Core Data store to reduce network requests + private let store = URLPreviewStore() + + private override init() { } + + func preview(for url: URL, + and event: MXEvent, + with session: MXSession, + success: @escaping (URLPreviewData) -> Void, + failure: @escaping (Error?) -> Void) { + // Sanitize the URL before checking the store or performing lookup + let sanitizedURL = sanitize(url) + + if let preview = store.preview(for: sanitizedURL, and: event) { + MXLog.debug("[URLPreviewManager] Using cached preview.") + success(preview) + return + } + + session.matrixRestClient.preview(for: sanitizedURL, success: { previewResponse in + MXLog.debug("[URLPreviewManager] Cached preview not found. Requesting from homeserver.") + + if let previewResponse = previewResponse { + self.makePreviewData(from: previewResponse, for: sanitizedURL, and: event, with: session) { previewData in + self.store.store(previewData) + success(previewData) + } + } + + }, failure: failure) + } + + func makePreviewData(from previewResponse: MXURLPreview, + for url: URL, + and event: MXEvent, + with session: MXSession, + completion: @escaping (URLPreviewData) -> Void) { + let previewData = URLPreviewData(url: url, + eventID: event.eventId, + roomID: event.roomId, + siteName: previewResponse.siteName, + title: previewResponse.title, + text: previewResponse.text) + + guard let imageURL = previewResponse.imageURL else { + completion(previewData) + return + } + + if let cachePath = MXMediaManager.cachePath(forMatrixContentURI: imageURL, andType: previewResponse.imageType, inFolder: nil), + let image = MXMediaManager.loadThroughCache(withFilePath: cachePath) { + previewData.image = image + completion(previewData) + return + } + + // Don't de-dupe image downloads as the manager should de-dupe preview generation. + + session.mediaManager.downloadMedia(fromMatrixContentURI: imageURL, withType: previewResponse.imageType, inFolder: nil) { path in + guard let image = MXMediaManager.loadThroughCache(withFilePath: path) else { + completion(previewData) + return + } + previewData.image = image + completion(previewData) + } failure: { error in + completion(previewData) + } + } + + func removeExpiredCacheData() { + store.removeExpiredItems() + } + + func clearStore() { + store.deleteAll() + } + + func closePreview(for eventID: String, in roomID: String) { + store.closePreview(for: eventID, in: roomID) + } + + func hasClosedPreview(from event: MXEvent) -> Bool { + store.hasClosedPreview(for: event.eventId, in: event.roomId) + } + + private func sanitize(_ url: URL) -> URL { + // Remove the fragment from the URL. + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) + components?.fragment = nil + + return components?.url ?? url + } +} diff --git a/Riot/Managers/URLPreviews/URLPreviewCache.swift b/Riot/Managers/URLPreviews/URLPreviewStore.swift similarity index 89% rename from Riot/Managers/URLPreviews/URLPreviewCache.swift rename to Riot/Managers/URLPreviews/URLPreviewStore.swift index 33711e5f2..5748844f8 100644 --- a/Riot/Managers/URLPreviews/URLPreviewCache.swift +++ b/Riot/Managers/URLPreviews/URLPreviewStore.swift @@ -17,7 +17,7 @@ import CoreData /// A cache for URL previews backed by Core Data. -class URLPreviewCache { +class URLPreviewStore { // MARK: - Properties @@ -46,7 +46,7 @@ class URLPreviewCache { ValueTransformer.setValueTransformer(URLPreviewImageTransformer(), forName: .urlPreviewImageTransformer) // Create the container, updating it's path if storing the data in memory. - container = NSPersistentContainer(name: "URLPreviewCache") + container = NSPersistentContainer(name: "URLPreviewStore") if inMemory { container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null") @@ -55,7 +55,7 @@ class URLPreviewCache { // Load the persistent stores into the container container.loadPersistentStores { storeDescription, error in if let error = error { - MXLog.error("[URLPreviewCache] Core Data container error: \(error.localizedDescription)") + MXLog.error("[URLPreviewStore] Core Data container error: \(error.localizedDescription)") } } } @@ -65,7 +65,7 @@ class URLPreviewCache { /// Store a preview in the cache. If a preview already exists with the same URL it will be updated from the new preview. /// - Parameter preview: The preview to add to the cache. /// - Parameter date: Optional: The date the preview was generated. - func store(_ preview: URLPreviewViewData, generatedOn generationDate: Date? = nil) { + func store(_ preview: URLPreviewData, generatedOn generationDate: Date? = nil) { // Create a fetch request for an existing preview. let request: NSFetchRequest = URLPreviewCacheData.fetchRequest() request.predicate = NSPredicate(format: "url == %@", preview.url as NSURL) @@ -87,7 +87,7 @@ class URLPreviewCache { /// if the preview is older than the ``dataValidityTime`` the returned value will be nil. /// - Parameter url: The URL to fetch the preview for. /// - Returns: The preview if found, otherwise nil. - func preview(for url: URL, and event: MXEvent) -> URLPreviewViewData? { + func preview(for url: URL, and event: MXEvent) -> URLPreviewData? { // Create a request for the url excluding any expired items let request: NSFetchRequest = URLPreviewCacheData.fetchRequest() request.predicate = NSCompoundPredicate(type: .and, subpredicates: [ @@ -104,11 +104,13 @@ class URLPreviewCache { return cachedPreview.preview(for: event) } - func count() -> Int { + /// Returns the number of URL previews cached in the store. + func cacheCount() -> Int { let request: NSFetchRequest = URLPreviewCacheData.fetchRequest() return (try? context.count(for: request)) ?? 0 } + /// Removes any expired cache data from the store. func removeExpiredItems() { let request: NSFetchRequest = URLPreviewCacheData.fetchRequest() request.predicate = NSPredicate(format: "creationDate < %@", expiryDate as NSDate) @@ -116,23 +118,22 @@ class URLPreviewCache { do { try context.execute(NSBatchDeleteRequest(fetchRequest: request)) } catch { - MXLog.error("[URLPreviewCache] Error executing batch delete request: \(error.localizedDescription)") + MXLog.error("[URLPreviewStore] Error executing batch delete request: \(error.localizedDescription)") } } - func clear() { + /// Deletes all cache data and all closed previews from the store. + func deleteAll() { do { _ = try context.execute(NSBatchDeleteRequest(fetchRequest: URLPreviewCacheData.fetchRequest())) _ = try context.execute(NSBatchDeleteRequest(fetchRequest: ClosedURLPreview.fetchRequest())) } catch { - MXLog.error("[URLPreviewCache] Error executing batch delete request: \(error.localizedDescription)") + MXLog.error("[URLPreviewStore] Error executing batch delete request: \(error.localizedDescription)") } } func closePreview(for eventID: String, in roomID: String) { - let closedPreview = ClosedURLPreview(context: context) - closedPreview.eventID = eventID - closedPreview.roomID = roomID + _ = ClosedURLPreview(context: context, eventID: eventID, roomID: roomID) save() } diff --git a/Riot/Managers/URLPreviews/URLPreviewCache.xcdatamodeld/URLPreviewCache.xcdatamodel/contents b/Riot/Managers/URLPreviews/URLPreviewStore.xcdatamodeld/URLPreviewStore.xcdatamodel/contents similarity index 100% rename from Riot/Managers/URLPreviews/URLPreviewCache.xcdatamodeld/URLPreviewCache.xcdatamodel/contents rename to Riot/Managers/URLPreviews/URLPreviewStore.xcdatamodeld/URLPreviewStore.xcdatamodel/contents diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h index 555c1ace4..df279fc14 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.h +++ b/Riot/Modules/Application/LegacyAppDelegate.h @@ -31,7 +31,7 @@ @protocol LegacyAppDelegateDelegate; @class CallBar; @class CallPresenter; -@class PreviewManager; +@class URLPreviewManager; #pragma mark - Notifications /** @@ -105,9 +105,6 @@ UINavigationControllerDelegate // Associated matrix sessions (empty by default). @property (nonatomic, readonly) NSArray *mxSessions; -#warning Move this elsewhere. -@property (nonatomic, readonly) PreviewManager *previewManager; - // Current selected room id. nil if no room is presently visible. @property (strong, nonatomic) NSString *visibleRoomId; diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 1133de1a5..c16d43bef 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -548,7 +548,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [MXMediaManager reduceCacheSizeToInsert:0]; // Remove expired URL previews from the cache - [self.previewManager removeExpiredItemsFromCache]; + [URLPreviewManager.shared removeExpiredCacheData]; // Hide potential notification if (self.mxInAppNotification) @@ -1983,10 +1983,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self checkDeviceId:mxSession]; [self.delegate legacyAppDelegate:self didAddMatrixSession:mxSession]; - - #warning Move this elsewhere - self->_previewManager = [[PreviewManager alloc] initWithRestClient:mxSession.matrixRestClient - mediaManager:mxSession.mediaManager]; } } diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index 9af568e2c..f4f7e8078 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -15,7 +15,7 @@ */ #import -@class URLPreviewViewData; +@class URLPreviewData; extern NSString *const URLPreviewDidUpdateNotification; @@ -84,7 +84,7 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) /** A link if the textMessage contains one, otherwise nil. */ -@property (nonatomic) URLPreviewViewData *urlPreviewData; +@property (nonatomic) URLPreviewData *urlPreviewData; /** MXKeyVerification object associated to key verification event when using key verification by direct message. diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index aba1eb55a..5ddb7c4e8 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -1071,7 +1071,7 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat } // Check that the preview hasn't been dismissed already. - if ([LegacyAppDelegate.theDelegate.previewManager hasClosedPreviewFrom:lastComponent.event]) + if ([URLPreviewManager.shared hasClosedPreviewFrom:lastComponent.event]) { return; } @@ -1079,7 +1079,10 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat // Set the preview data. MXWeakify(self); - [LegacyAppDelegate.theDelegate.previewManager previewFor:lastComponent.link and:lastComponent.event success:^(URLPreviewViewData * _Nonnull urlPreviewData) { + [URLPreviewManager.shared previewFor:lastComponent.link + and:lastComponent.event + with:self.mxSession + success:^(URLPreviewData * _Nonnull urlPreviewData) { MXStrongifyAndReturnIfNil(self); // Update the preview data and send a notification for refresh diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 1f4ab111b..b379830c9 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -1259,7 +1259,7 @@ const CGFloat kTypingCellHeight = 24; } // Remember that the user closed the preview so it isn't shown again. - [LegacyAppDelegate.theDelegate.previewManager closePreviewFor:eventID in:roomID]; + [URLPreviewManager.shared closePreviewFor:eventID in:roomID]; // Remove the preview data and refresh the cells. cellData.urlPreviewData = nil; diff --git a/Riot/Modules/Room/Views/URLPreviews/URLPreviewView.swift b/Riot/Modules/Room/Views/URLPreviews/URLPreviewView.swift index 910855b6c..63d5d4c49 100644 --- a/Riot/Modules/Room/Views/URLPreviews/URLPreviewView.swift +++ b/Riot/Modules/Room/Views/URLPreviews/URLPreviewView.swift @@ -17,6 +17,12 @@ import UIKit import Reusable +@objc +protocol URLPreviewViewDelegate: AnyObject { + func didOpenURLFromPreviewView(_ previewView: URLPreviewView, for eventID: String, in roomID: String) + func didCloseURLPreviewView(_ previewView: URLPreviewView, for eventID: String, in roomID: String) +} + @objcMembers class URLPreviewView: UIView, NibLoadable, Themable { // MARK: - Constants @@ -32,7 +38,7 @@ class URLPreviewView: UIView, NibLoadable, Themable { // MARK: - Properties - var preview: URLPreviewViewData? { + var preview: URLPreviewData? { didSet { guard let preview = preview else { return } renderLoaded(preview) @@ -100,7 +106,7 @@ class URLPreviewView: UIView, NibLoadable, Themable { closeButton.setImage(closeButtonAsset.image, for: .normal) } - static func contentViewHeight(for preview: URLPreviewViewData) -> CGFloat { + static func contentViewHeight(for preview: URLPreviewData) -> CGFloat { sizingView.renderLoaded(preview) return sizingView.systemLayoutSizeFitting(sizingView.intrinsicContentSize).height @@ -116,7 +122,7 @@ class URLPreviewView: UIView, NibLoadable, Themable { descriptionLabel.text = "" } - private func renderLoaded(_ preview: URLPreviewViewData) { + private func renderLoaded(_ preview: URLPreviewData) { if let image = preview.image { imageView.image = image showImageContainer() diff --git a/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewDelegate.swift b/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewDelegate.swift deleted file mode 100644 index 629c28367..000000000 --- a/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewDelegate.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright 2021 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -@objc -protocol URLPreviewViewDelegate: AnyObject { - func didOpenURLFromPreviewView(_ previewView: URLPreviewView, for eventID: String, in roomID: String) - func didCloseURLPreviewView(_ previewView: URLPreviewView, for eventID: String, in roomID: String) -} diff --git a/RiotTests/URLPreviewCacheTests.swift b/RiotTests/URLPreviewStoreTests.swift similarity index 54% rename from RiotTests/URLPreviewCacheTests.swift rename to RiotTests/URLPreviewStoreTests.swift index 8d89ef02f..606b2d5c3 100644 --- a/RiotTests/URLPreviewCacheTests.swift +++ b/RiotTests/URLPreviewStoreTests.swift @@ -17,29 +17,44 @@ import XCTest @testable import Riot -class URLPreviewCacheTests: XCTestCase { - var cache: URLPreviewCache! +class URLPreviewStoreTests: XCTestCase { + var store: URLPreviewStore! - func matrixPreview() -> URLPreviewViewData { - let preview = URLPreviewViewData(url: URL(string: "https://www.matrix.org/")!, - siteName: "Matrix", - title: "Home", - text: "An open network for secure, decentralized communication") + /// Creates mock URL preview data for matrix.org + func matrixPreview() -> URLPreviewData { + let preview = URLPreviewData(url: URL(string: "https://www.matrix.org/")!, + eventID: "", + roomID: "", + siteName: "Matrix", + title: "Home", + text: "An open network for secure, decentralized communication") preview.image = Asset.Images.appSymbol.image return preview } - func elementPreview() -> URLPreviewViewData { - URLPreviewViewData(url: URL(string: "https://element.io/")!, - siteName: "Element", - title: "Home", - text: "Secure and independent communication, connected via Matrix") + /// Creates mock URL preview data for element.io + func elementPreview() -> URLPreviewData { + URLPreviewData(url: URL(string: "https://element.io/")!, + eventID: "", + roomID: "", + siteName: "Element", + title: "Home", + text: "Secure and independent communication, connected via Matrix") + } + + + /// Creates a fake `MXEvent` object to be passed to the store as needed. + func fakeEvent() -> MXEvent { + let event = MXEvent() + event.eventId = "" + event.roomId = "" + return event } override func setUpWithError() throws { // Create a fresh in-memory cache for each test. - cache = URLPreviewCache(inMemory: true) + store = URLPreviewStore(inMemory: true) } func testStoreAndRetrieve() { @@ -47,9 +62,9 @@ class URLPreviewCacheTests: XCTestCase { let preview = matrixPreview() // When storing and retrieving that preview. - cache.store(preview) + store.store(preview) - guard let cachedPreview = cache.preview(for: preview.url) else { + guard let cachedPreview = store.preview(for: preview.url, and: fakeEvent()) else { XCTFail("The cache should return a preview after storing one with the same URL.") return } @@ -65,35 +80,40 @@ class URLPreviewCacheTests: XCTestCase { func testUpdating() { // Given a preview stored in the cache. let preview = matrixPreview() - cache.store(preview) + store.store(preview) - guard let cachedPreview = cache.preview(for: preview.url) else { + guard let cachedPreview = store.preview(for: preview.url, and: fakeEvent()) else { XCTFail("The cache should return a preview after storing one with the same URL.") return } XCTAssertEqual(cachedPreview.text, preview.text, "The text should match the original preview's text.") - XCTAssertEqual(cache.count(), 1, "There should be 1 item in the cache.") + XCTAssertEqual(store.cacheCount(), 1, "There should be 1 item in the cache.") // When storing an updated version of that preview. - let updatedPreview = URLPreviewViewData(url: preview.url, siteName: "Matrix", title: "Home", text: "We updated our website.") - cache.store(updatedPreview) + let updatedPreview = URLPreviewData(url: preview.url, + eventID: "", + roomID: "", + siteName: "Matrix", + title: "Home", + text: "We updated our website.") + store.store(updatedPreview) // Then the store should update the original preview. - guard let updatedCachedPreview = cache.preview(for: preview.url) else { + guard let updatedCachedPreview = store.preview(for: preview.url, and: fakeEvent()) else { XCTFail("The cache should return a preview after storing one with the same URL.") return } XCTAssertEqual(updatedCachedPreview.text, updatedPreview.text, "The text should match the updated preview's text.") - XCTAssertEqual(cache.count(), 1, "There should still only be 1 item in the cache.") + XCTAssertEqual(store.cacheCount(), 1, "There should still only be 1 item in the cache.") } func testPreviewExpiry() { // Given a preview generated 30 days ago. let preview = matrixPreview() - cache.store(preview, generatedOn: Date().addingTimeInterval(-60 * 60 * 24 * 30)) + store.store(preview, generatedOn: Date().addingTimeInterval(-60 * 60 * 24 * 30)) // When retrieving that today. - let cachedPreview = cache.preview(for: preview.url) + let cachedPreview = store.preview(for: preview.url, and: fakeEvent()) // Then no preview should be returned. XCTAssertNil(cachedPreview, "The expired preview should not be returned.") @@ -103,15 +123,15 @@ class URLPreviewCacheTests: XCTestCase { // Given a cache with 2 items, one of which has expired. testPreviewExpiry() let preview = elementPreview() - cache.store(preview) - XCTAssertEqual(cache.count(), 2, "There should be 2 items in the cache.") + store.store(preview) + XCTAssertEqual(store.cacheCount(), 2, "There should be 2 items in the cache.") // When removing expired items. - cache.removeExpiredItems() + store.removeExpiredItems() // Then only the expired item should have been removed. - XCTAssertEqual(cache.count(), 1, "Only 1 item should have been removed from the cache.") - if cache.preview(for: preview.url) == nil { + XCTAssertEqual(store.cacheCount(), 1, "Only 1 item should have been removed from the cache.") + if store.preview(for: preview.url, and: fakeEvent()) == nil { XCTFail("The valid preview should still be in the cache.") } } @@ -120,13 +140,13 @@ class URLPreviewCacheTests: XCTestCase { // Given a cache with 2 items. testStoreAndRetrieve() let preview = elementPreview() - cache.store(preview) - XCTAssertEqual(cache.count(), 2, "There should be 2 items in the cache.") + store.store(preview) + XCTAssertEqual(store.cacheCount(), 2, "There should be 2 items in the cache.") // When clearing the cache. - cache.clear() + store.deleteAll() // Then no items should be left in the cache - XCTAssertEqual(cache.count(), 0, "The cache should be empty.") + XCTAssertEqual(store.cacheCount(), 0, "The cache should be empty.") } }