You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionMessagingKit/Utilities/Profile+CurrentUser.swift

181 lines
8.4 KiB
Swift

// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import UIKit.UIImage
import GRDB
import SessionUtilitiesKit
// MARK: - Current User Profile
public extension Profile {
static func isTooLong(profileName: String) -> Bool {
return (profileName.utf8CString.count > LibSession.sizeMaxNameBytes)
}
static func updateLocal(
queue: DispatchQueue,
profileName: String,
displayPictureUpdate: DisplayPictureManager.Update = .none,
success: ((Database) throws -> ())? = nil,
failure: ((DisplayPictureError) -> ())? = nil,
using dependencies: Dependencies
) {
let userSessionId: SessionId = dependencies[cache: .general].sessionId
let isRemovingAvatar: Bool = {
switch displayPictureUpdate {
case .remove: return true
default: return false
}
}()
switch displayPictureUpdate {
case .none, .remove, .updateTo:
dependencies[singleton: .storage].writeAsync { db in
if isRemovingAvatar {
let existingProfileUrl: String? = try Profile
.filter(id: userSessionId.hexString)
.select(.profilePictureUrl)
.asRequest(of: String.self)
.fetchOne(db)
let existingProfileFileName: String? = try Profile
.filter(id: userSessionId.hexString)
.select(.profilePictureFileName)
.asRequest(of: String.self)
.fetchOne(db)
// Remove any cached avatar image value
if let fileName: String = existingProfileFileName {
dependencies.mutate(cache: .displayPicture) { $0.imageData[fileName] = nil }
}
switch existingProfileUrl {
case .some: Log.verbose("Updating local profile on service with cleared avatar.")
case .none: Log.verbose("Updating local profile on service with no avatar.")
}
}
try Profile.updateIfNeeded(
db,
publicKey: userSessionId.hexString,
name: profileName,
displayPictureUpdate: displayPictureUpdate,
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
calledFromConfig: nil,
using: dependencies
)
Log.info("Successfully updated service with profile.")
try success?(db)
}
case .uploadImageData(let data):
DisplayPictureManager.prepareAndUploadDisplayPicture(
queue: queue,
imageData: data,
success: { downloadUrl, fileName, newProfileKey in
dependencies[singleton: .storage].writeAsync { db in
try Profile.updateIfNeeded(
db,
publicKey: userSessionId.hexString,
name: profileName,
displayPictureUpdate: .updateTo(url: downloadUrl, key: newProfileKey, fileName: fileName),
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
calledFromConfig: nil,
using: dependencies
)
dependencies[defaults: .standard, key: .lastProfilePictureUpload] = dependencies.dateNow
Log.info("Successfully updated service with profile.")
try success?(db)
}
},
failure: failure,
using: dependencies
)
}
}
static func updateIfNeeded(
_ db: Database,
publicKey: String,
name: String?,
blocksCommunityMessageRequests: Bool? = nil,
displayPictureUpdate: DisplayPictureManager.Update,
sentTimestamp: TimeInterval,
calledFromConfig configTriggeringChange: ConfigDump.Variant?,
using dependencies: Dependencies
) throws {
let isCurrentUser = (publicKey == dependencies[cache: .general].sessionId.hexString)
let profile: Profile = Profile.fetchOrCreate(db, id: publicKey)
var profileChanges: [ConfigColumnAssignment] = []
// Name
if let name: String = name, !name.isEmpty, name != profile.name {
if sentTimestamp > (profile.lastNameUpdate ?? 0) || (isCurrentUser && configTriggeringChange == .userProfile) {
profileChanges.append(Profile.Columns.name.set(to: name))
profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp))
}
}
// Blocks community message requests flag
if let blocksCommunityMessageRequests: Bool = blocksCommunityMessageRequests, sentTimestamp > (profile.lastBlocksCommunityMessageRequests ?? 0) {
profileChanges.append(Profile.Columns.blocksCommunityMessageRequests.set(to: blocksCommunityMessageRequests))
profileChanges.append(Profile.Columns.lastBlocksCommunityMessageRequests.set(to: sentTimestamp))
}
// Profile picture & profile key
if sentTimestamp > (profile.lastProfilePictureUpdate ?? 0) || (isCurrentUser && configTriggeringChange == .userProfile) {
switch displayPictureUpdate {
case .none: break
case .uploadImageData: preconditionFailure("Invalid options for this function")
case .remove:
profileChanges.append(Profile.Columns.profilePictureUrl.set(to: nil))
profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: nil))
profileChanges.append(Profile.Columns.profilePictureFileName.set(to: nil))
profileChanges.append(Profile.Columns.lastProfilePictureUpdate.set(to: sentTimestamp))
case .updateTo(let url, let key, .some(let fileName)) where FileManager.default.fileExists(atPath: DisplayPictureManager.filepath(for: fileName, using: dependencies)):
// Update the 'lastProfilePictureUpdate' timestamp for either external or local changes
profileChanges.append(Profile.Columns.profilePictureFileName.set(to: fileName))
profileChanges.append(Profile.Columns.lastProfilePictureUpdate.set(to: sentTimestamp))
if url != profile.profilePictureUrl {
profileChanges.append(Profile.Columns.profilePictureUrl.set(to: url))
}
if key != profile.profileEncryptionKey && key.count == DisplayPictureManager.aes256KeyByteLength {
profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: key))
}
case .updateTo(let url, let key, _):
dependencies[singleton: .jobRunner].add(
db,
job: Job(
variant: .displayPictureDownload,
shouldBeUnique: true,
details: DisplayPictureDownloadJob.Details(
target: .profile(id: profile.id, url: url, encryptionKey: key),
timestamp: sentTimestamp
)
),
canStartJob: true
)
}
}
// Persist any changes
if !profileChanges.isEmpty {
try profile.upsert(db)
try Profile
.filter(id: publicKey)
.updateAllAndConfig(
db,
profileChanges,
calledFromConfig: configTriggeringChange,
using: dependencies
)
}
}
}