From d4b745a322f7652634d782b56c39af9e47bf77d2 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 30 Mar 2021 09:54:40 +1100 Subject: [PATCH] Cache open group preview images --- .../Database/Storage+OpenGroups.swift | 38 +++++++++++++------ .../Open Groups/V2/OpenGroupAPIV2.swift | 29 +++++++++++--- .../General/SNUserDefaults.swift | 1 + 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/SessionMessagingKit/Database/Storage+OpenGroups.swift b/SessionMessagingKit/Database/Storage+OpenGroups.swift index e4eee29c4..93a563a45 100644 --- a/SessionMessagingKit/Database/Storage+OpenGroups.swift +++ b/SessionMessagingKit/Database/Storage+OpenGroups.swift @@ -179,8 +179,8 @@ extension Storage { private static let openGroupUserCountCollection = "SNOpenGroupUserCountCollection" private static let openGroupMessageIDCollection = "LKMessageIDCollection" - private static let openGroupProfilePictureURLCollection = "LokiPublicChatAvatarURLCollection" - + private static let openGroupImageCollection = "SNOpenGroupImageCollection" + public func getUserCount(forV2OpenGroupWithID openGroupID: String) -> UInt64? { var result: UInt64? Storage.read { transaction in @@ -205,25 +205,20 @@ extension Storage { (transaction as! YapDatabaseReadWriteTransaction).setObject(messageID, forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) } - public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any) { - let collection = openGroupID - (transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection) - } - public func setLastProfilePictureUploadDate(_ date: Date) { UserDefaults.standard[.lastProfilePictureUpload] = date } - public func getProfilePictureURL(forOpenGroupWithID openGroupID: String) -> String? { - var result: String? + public func getOpenGroupImage(for room: String, on server: String) -> Data? { + var result: Data? Storage.read { transaction in - result = transaction.object(forKey: openGroupID, inCollection: Storage.openGroupProfilePictureURLCollection) as? String + result = transaction.object(forKey: "\(server).\(room)", inCollection: Storage.openGroupImageCollection) as? Data } return result } - public func setProfilePictureURL(to profilePictureURL: String?, forOpenGroupWithID openGroupID: String, using transaction: Any) { - (transaction as! YapDatabaseReadWriteTransaction).setObject(profilePictureURL, forKey: openGroupID, inCollection: Storage.openGroupProfilePictureURLCollection) + public func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(data, forKey: "\(server).\(room)", inCollection: Storage.openGroupImageCollection) } @@ -350,4 +345,23 @@ extension Storage { public func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) { (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.oldLastDeletionServerIDCollection) } + + public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any) { + let collection = openGroupID + (transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection) + } + + private static let openGroupProfilePictureURLCollection = "LokiPublicChatAvatarURLCollection" + + public func getProfilePictureURL(forOpenGroupWithID openGroupID: String) -> String? { + var result: String? + Storage.read { transaction in + result = transaction.object(forKey: openGroupID, inCollection: Storage.openGroupProfilePictureURLCollection) as? String + } + return result + } + + public func setProfilePictureURL(to profilePictureURL: String?, forOpenGroupWithID openGroupID: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(profilePictureURL, forKey: openGroupID, inCollection: Storage.openGroupProfilePictureURLCollection) + } } diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift index 55040c344..5d6ad72a7 100644 --- a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift +++ b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift @@ -1,8 +1,6 @@ import PromiseKit import SessionSnodeKit -// TODO: Cache group images - @objc(SNOpenGroupAPIV2) public final class OpenGroupAPIV2 : NSObject { private static var moderators: [String:[String:Set]] = [:] // Server URL to room ID to set of moderator IDs @@ -97,10 +95,10 @@ public final class OpenGroupAPIV2 : NSObject { if request.useOnionRouting { guard let publicKey = SNMessagingKitConfiguration.shared.storage.getOpenGroupPublicKey(for: request.server) else { return Promise(error: Error.noPublicKey) } if request.isAuthRequired, let room = request.room { // Because auth happens on a per-room basis, we need both to make an authenticated request - return getAuthToken(for: room, on: request.server).then(on: DispatchQueue.global(qos: .default)) { authToken -> Promise in + return getAuthToken(for: room, on: request.server).then(on: DispatchQueue.global(qos: .userInitiated)) { authToken -> Promise in tsRequest.setValue(authToken, forHTTPHeaderField: "Authorization") let promise = OnionRequestAPI.sendOnionRequest(tsRequest, to: request.server, using: publicKey) - promise.catch(on: DispatchQueue.global(qos: .default)) { error in + promise.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in // A 401 means that we didn't provide a (valid) auth token for a route that required one. We use this as an // indication that the token we're using has expired. Note that a 403 has a different meaning; it means that // we provided a valid token but it doesn't have a high enough permission level for the route in question. @@ -373,12 +371,33 @@ public final class OpenGroupAPIV2 : NSObject { } public static func getGroupImage(for room: String, on server: String) -> Promise { - if let promise = groupImagePromises["\(server).\(room)"] { + // Normally the image for a given group is stored with the group thread, so it's only + // fetched once. However, on the join open group screen we show images for groups the + // user * hasn't * joined yet. We don't want to re-fetch these images every time the + // user opens the app because that could slow the app down or be data-intensive. So + // instead we assume that these images don't change that often and just fetch them once + // a week. We also assume that they're all fetched at the same time as well, so that + // we only need to maintain one date in user defaults. On top of all of this we also + // don't double up on fetch requests by storing the existing request as a promise if + // there is one. + let lastOpenGroupImageUpdate = UserDefaults.standard[.lastOpenGroupImageUpdate] + let now = Date() + let timeSinceLastUpdate = given(lastOpenGroupImageUpdate) { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude + let updateInterval: TimeInterval = 7 * 24 * 60 * 60 + if let data = Storage.shared.getOpenGroupImage(for: room, on: server), server == defaultServer, timeSinceLastUpdate < updateInterval { + return Promise.value(data) + } else if let promise = groupImagePromises["\(server).\(room)"] { return promise } else { let request = Request(verb: .get, room: room, server: server, endpoint: "group_image") let promise: Promise = send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in guard let base64EncodedFile = json["result"] as? String, let file = Data(base64Encoded: base64EncodedFile) else { throw Error.parsingFailed } + if server == defaultServer { + Storage.shared.write { transaction in + Storage.shared.setOpenGroupImage(to: file, for: room, on: server, using: transaction) + } + UserDefaults.standard[.lastOpenGroupImageUpdate] = now + } return file } groupImagePromises["\(server).\(room)"] = promise diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index eaa1929ca..aa563e94f 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -15,6 +15,7 @@ public enum SNUserDefaults { case lastConfigurationSync case lastDisplayNameUpdate case lastProfilePictureUpdate + case lastOpenGroupImageUpdate } public enum Double : Swift.String {