|
|
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
//
|
|
|
|
// stringlint:disable
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import UIKit.UIImage
|
|
|
|
import GRDB
|
|
|
|
import SessionSnodeKit
|
|
|
|
import SessionUtilitiesKit
|
|
|
|
|
|
|
|
enum _022_GroupsRebuildChanges: Migration {
|
|
|
|
static let target: TargetMigrations.Identifier = .messagingKit
|
|
|
|
static let identifier: String = "GroupsRebuildChanges"
|
|
|
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
|
|
|
static var requirements: [MigrationRequirement] = [.sessionIdCached, .libSessionStateLoaded]
|
|
|
|
static var fetchedTables: [(FetchableRecord & TableRecord).Type] = [
|
|
|
|
Identity.self, OpenGroup.self
|
|
|
|
]
|
|
|
|
static var createdOrAlteredTables: [(FetchableRecord & TableRecord).Type] = [
|
|
|
|
SessionThread.self, ClosedGroup.self, OpenGroup.self, GroupMember.self
|
|
|
|
]
|
|
|
|
static let droppedTables: [(TableRecord & FetchableRecord).Type] = []
|
|
|
|
|
|
|
|
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
|
|
|
|
try db.alter(table: SessionThread.self) { t in
|
|
|
|
t.add(.isDraft, .boolean).defaults(to: false)
|
|
|
|
}
|
|
|
|
|
|
|
|
try db.alter(table: ClosedGroup.self) { t in
|
|
|
|
t.add(.groupDescription, .text)
|
|
|
|
t.add(.displayPictureUrl, .text)
|
|
|
|
t.add(.displayPictureFilename, .text)
|
|
|
|
t.add(.displayPictureEncryptionKey, .blob)
|
|
|
|
t.add(.lastDisplayPictureUpdate, .integer).defaults(to: 0)
|
|
|
|
t.add(.shouldPoll, .boolean).defaults(to: false)
|
|
|
|
t.add(.groupIdentityPrivateKey, .blob)
|
|
|
|
t.add(.authData, .blob)
|
|
|
|
t.add(.invited, .boolean).defaults(to: false)
|
|
|
|
}
|
|
|
|
|
|
|
|
try db.alter(table: OpenGroup.self) { t in
|
|
|
|
t.add(.displayPictureFilename, .text)
|
|
|
|
t.add(.lastDisplayPictureUpdate, .integer).defaults(to: 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
try db.alter(table: GroupMember.self) { t in
|
|
|
|
t.add(.roleStatus, .integer)
|
|
|
|
.notNull()
|
|
|
|
.defaults(to: GroupMember.RoleStatus.accepted)
|
|
|
|
}
|
|
|
|
|
|
|
|
let userSessionId: SessionId = dependencies[cache: .general].sessionId
|
|
|
|
|
|
|
|
// Update existing groups where the current user is a member to have `shouldPoll` as `true`
|
|
|
|
try ClosedGroup
|
|
|
|
.joining(
|
|
|
|
required: ClosedGroup.members
|
|
|
|
.filter(GroupMember.Columns.profileId == userSessionId.hexString)
|
|
|
|
)
|
|
|
|
.updateAll(
|
|
|
|
db,
|
|
|
|
ClosedGroup.Columns.shouldPoll.set(to: true)
|
|
|
|
)
|
|
|
|
|
|
|
|
// If a user had upgraded a different device their config could already contain V2 groups
|
|
|
|
// so we should check and, if so, create those
|
|
|
|
try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
guard case .userGroups(let conf) = cache.config(for: .userGroups, sessionId: userSessionId) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract all of the user group info
|
|
|
|
let extractedUserGroups: LibSession.ExtractedUserGroups = try LibSession.extractUserGroups(
|
|
|
|
from: conf,
|
|
|
|
using: dependencies
|
|
|
|
)
|
|
|
|
|
|
|
|
try extractedUserGroups.groups.forEach { group in
|
|
|
|
// Add a new group if it doesn't already exist
|
|
|
|
try MessageReceiver.handleNewGroup(
|
|
|
|
db,
|
|
|
|
groupSessionId: group.groupSessionId,
|
|
|
|
groupIdentityPrivateKey: group.groupIdentityPrivateKey,
|
|
|
|
name: group.name,
|
|
|
|
authData: group.authData,
|
|
|
|
joinedAt: group.joinedAt,
|
|
|
|
invited: group.invited,
|
|
|
|
forceMarkAsInvited: false,
|
|
|
|
using: dependencies
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move the `imageData` out of the `OpenGroup` table and on to disk to be consistent with
|
|
|
|
// the other display picture logic
|
|
|
|
let existingImageInfo: [OpenGroupImageInfo] = try OpenGroup
|
|
|
|
.filter(OpenGroup.Columns.deprecatedColumn("imageData") != nil)
|
|
|
|
.select(OpenGroup.Columns.threadId, OpenGroup.Columns.deprecatedColumn("imageData"))
|
|
|
|
.asRequest(of: OpenGroupImageInfo.self)
|
|
|
|
.fetchAll(db)
|
|
|
|
|
|
|
|
existingImageInfo.forEach { imageInfo in
|
|
|
|
let fileName: String = dependencies[singleton: .displayPictureManager].generateFilename()
|
|
|
|
|
|
|
|
guard let filePath: String = try? dependencies[singleton: .displayPictureManager].filepath(for: fileName) else {
|
|
|
|
Log.error("[GroupsRebuildChanges] Failed to generate community file path for current file name")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the decrypted display picture to disk
|
|
|
|
try? imageInfo.data.write(to: URL(fileURLWithPath: filePath), options: [.atomic])
|
|
|
|
|
|
|
|
guard UIImage(contentsOfFile: filePath) != nil else {
|
|
|
|
Log.error("[GroupsRebuildChanges] Failed to save Community imageData for \(imageInfo.threadId)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the database with the new info
|
|
|
|
_ = try? OpenGroup
|
|
|
|
.filter(id: imageInfo.threadId)
|
|
|
|
.updateAll( // Unsynced so no 'updateAllAndConfig'
|
|
|
|
db,
|
|
|
|
OpenGroup.Columns.deprecatedColumn("imageData").set(to: nil),
|
|
|
|
OpenGroup.Columns.displayPictureFilename.set(to: fileName),
|
|
|
|
OpenGroup.Columns.lastDisplayPictureUpdate.set(
|
|
|
|
to: TimeInterval(dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.update(progress: 1, for: self, in: target, using: dependencies)
|
|
|
|
}
|
|
|
|
|
|
|
|
struct OpenGroupImageInfo: FetchableRecord, Decodable, ColumnExpressible {
|
|
|
|
typealias Columns = CodingKeys
|
|
|
|
enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
|
|
|
case threadId
|
|
|
|
case data = "imageData"
|
|
|
|
}
|
|
|
|
|
|
|
|
let threadId: String
|
|
|
|
let data: Data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|