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/Jobs/RetrieveDefaultOpenGroupRoo...

184 lines
8.8 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionSnodeKit
import SessionUtilitiesKit
// MARK: - Log.Category
private extension Log.Category {
static let cat: Log.Category = .create("RetrieveDefaultOpenGroupRoomsJob", defaultLevel: .info)
}
// MARK: - RetrieveDefaultOpenGroupRoomsJob
public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
public static let maxFailureCount: Int = -1
public static let requiresThreadId: Bool = false
public static let requiresInteractionId: Bool = false
public static func run(
_ job: Job,
queue: DispatchQueue,
success: @escaping (Job, Bool) -> Void,
failure: @escaping (Job, Error, Bool) -> Void,
deferred: @escaping (Job) -> Void,
using dependencies: Dependencies
) {
/// Don't run when inactive or not in main app
///
/// Additionally, since this job can be triggered by the user viewing the "Join Community" screen it's possible for multiple jobs to run at
/// the same time, we don't want to waste bandwidth by making redundant calls to fetch the default rooms so don't do anything if there
/// is already a job running
guard
dependencies[defaults: .appGroup, key: .isMainAppActive],
dependencies[singleton: .jobRunner]
.jobInfoFor(state: .running, variant: .retrieveDefaultOpenGroupRooms)
.filter({ key, info in key != job.id }) // Exclude this job
.isEmpty
else { return deferred(job) }
// The OpenGroupAPI won't make any API calls if there is no entry for an OpenGroup
// in the database so we need to create a dummy one to retrieve the default room data
let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: OpenGroupAPI.defaultServer)
dependencies[singleton: .storage].write { db in
guard try OpenGroup.exists(db, id: defaultGroupId) == false else { return }
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "",
userCount: 0,
infoUpdates: 0
)
.upserted(db)
}
/// Try to retrieve the default rooms 8 times
dependencies[singleton: .storage]
.readPublisher { [dependencies] db -> Network.PreparedRequest<OpenGroupAPI.CapabilitiesAndRoomsResponse> in
try OpenGroupAPI.preparedCapabilitiesAndRooms(
db,
on: OpenGroupAPI.defaultServer,
using: dependencies
)
}
.flatMap { [dependencies] request in request.send(using: dependencies) }
.subscribe(on: queue, using: dependencies)
.receive(on: queue, using: dependencies)
.retry(8, using: dependencies)
.sinkUntilComplete(
receiveCompletion: { result in
switch result {
case .finished:
Log.info(.cat, "Successfully retrieved default Community rooms")
success(job, false)
case .failure(let error):
Log.error(.cat, "Failed to get default Community rooms due to error: \(error)")
failure(job, error, false)
}
},
receiveValue: { info, response in
let defaultRooms: [OpenGroupManager.DefaultRoomInfo]? = dependencies[singleton: .storage].write { db -> [OpenGroupManager.DefaultRoomInfo] in
// Store the capabilities first
OpenGroupManager.handleCapabilities(
db,
capabilities: response.capabilities.data,
on: OpenGroupAPI.defaultServer
)
let existingImageIds: [String: String] = try OpenGroup
.filter(OpenGroup.Columns.server == OpenGroupAPI.defaultServer)
.filter(OpenGroup.Columns.imageId != nil)
.fetchAll(db)
.reduce(into: [:]) { result, next in result[next.id] = next.imageId }
let result: [OpenGroupManager.DefaultRoomInfo] = try response.rooms.data
.compactMap { room -> OpenGroupManager.DefaultRoomInfo? in
/// Try to insert an inactive version of the OpenGroup (use `insert` rather than
/// `save` as we want it to fail if the room already exists)
do {
return (
room,
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: room.token,
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: room.name,
roomDescription: room.roomDescription,
imageId: room.imageId,
userCount: room.activeUsers,
infoUpdates: room.infoUpdates
)
.inserted(db)
)
}
catch {
return try OpenGroup
.fetchOne(
db,
id: OpenGroup.idFor(
roomToken: room.token,
server: OpenGroupAPI.defaultServer
)
)
.map { (room, $0) }
}
}
/// Schedule the room image download (if it doesn't match out current one)
result.forEach { room, openGroup in
let openGroupId: String = OpenGroup.idFor(roomToken: room.token, server: OpenGroupAPI.defaultServer)
guard
let imageId: String = room.imageId,
imageId != existingImageIds[openGroupId] ||
openGroup.displayPictureFilename == nil
else { return }
dependencies[singleton: .jobRunner].add(
db,
job: Job(
variant: .displayPictureDownload,
shouldBeUnique: true,
details: DisplayPictureDownloadJob.Details(
target: .community(
imageId: imageId,
roomToken: room.token,
server: OpenGroupAPI.defaultServer
),
timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000)
)
),
canStartJob: true
)
}
return result
}
/// Update the `openGroupManager` cache to have the default rooms
dependencies.mutate(cache: .openGroupManager) { cache in
cache.setDefaultRoomInfo(defaultRooms ?? [])
}
}
)
}
public static func run(using dependencies: Dependencies) {
RetrieveDefaultOpenGroupRoomsJob.run(
Job(variant: .retrieveDefaultOpenGroupRooms, behaviour: .runOnce),
queue: DispatchQueue.global(qos: .default),
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
}
}