mirror of https://github.com/oxen-io/session-ios
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.
214 lines
9.2 KiB
Swift
214 lines
9.2 KiB
Swift
// 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 createdTables: [(FetchableRecord & TableRecord).Type] = []
|
|
|
|
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
|
|
try db.alter(table: "thread") { t in
|
|
t.add(column: "isDraft", .boolean).defaults(to: false)
|
|
}
|
|
|
|
try db.alter(table: "closedGroup") { t in
|
|
t.add(column: "groupDescription", .text)
|
|
t.add(column: "displayPictureUrl", .text)
|
|
t.add(column: "displayPictureFilename", .text)
|
|
t.add(column: "displayPictureEncryptionKey", .blob)
|
|
t.add(column: "lastDisplayPictureUpdate", .integer).defaults(to: 0)
|
|
t.add(column: "shouldPoll", .boolean).defaults(to: false)
|
|
t.add(column: "groupIdentityPrivateKey", .blob)
|
|
t.add(column: "authData", .blob)
|
|
t.add(column: "invited", .boolean).defaults(to: false)
|
|
}
|
|
|
|
try db.alter(table: "openGroup") { t in
|
|
t.add(column: "displayPictureFilename", .text)
|
|
t.add(column: "lastDisplayPictureUpdate", .integer).defaults(to: 0)
|
|
}
|
|
|
|
try db.alter(table: "groupMember") { t in
|
|
t.add(column: "roleStatus", .integer)
|
|
.notNull()
|
|
.defaults(to: GroupMember.RoleStatus.accepted)
|
|
}
|
|
|
|
guard
|
|
MigrationHelper.userExists(db),
|
|
let userEd25519SecretKey: Data = MigrationHelper.fetchIdentityValue(db, key: "ed25519SecretKey")
|
|
else { return Storage.update(progress: 1, for: self, in: target, using: dependencies) }
|
|
|
|
let userSessionId: SessionId = MigrationHelper.userSessionId(db)
|
|
|
|
// Update existing groups where the current user is a member to have `shouldPoll` as `true`
|
|
try db.execute(sql: """
|
|
UPDATE closedGroup
|
|
SET shouldPoll = true
|
|
WHERE EXISTS (
|
|
SELECT 1
|
|
FROM groupMember
|
|
WHERE groupMember.groupId = closedGroup.threadId
|
|
AND groupMember.profileId = '\(userSessionId.hexString)'
|
|
)
|
|
""")
|
|
|
|
// If a user had upgraded a different device their config could already contain V2 groups
|
|
// so we should check and, if so, create those
|
|
let cache: LibSession.Cache = LibSession.Cache(userSessionId: userSessionId, using: dependencies)
|
|
let userGroupsConfig: LibSession.Config = try cache.loadState(
|
|
for: .userGroups,
|
|
sessionId: userSessionId,
|
|
userEd25519SecretKey: Array(userEd25519SecretKey),
|
|
groupEd25519SecretKey: nil,
|
|
cachedData: MigrationHelper.configDump(db, for: ConfigDump.Variant.userGroups.rawValue)
|
|
)
|
|
let convoInfoVolatileConfig: LibSession.Config = try cache.loadState(
|
|
for: .convoInfoVolatile,
|
|
sessionId: userSessionId,
|
|
userEd25519SecretKey: Array(userEd25519SecretKey),
|
|
groupEd25519SecretKey: nil,
|
|
cachedData: MigrationHelper.configDump(db, for: ConfigDump.Variant.convoInfoVolatile.rawValue)
|
|
)
|
|
|
|
// Extract all of the user group info
|
|
if case .userGroups(let conf) = userGroupsConfig, case .convoInfoVolatile(let convoInfoVolatileConf) = convoInfoVolatileConfig {
|
|
let extractedUserGroups: LibSession.ExtractedUserGroups = try LibSession.extractUserGroups(
|
|
from: conf,
|
|
using: dependencies
|
|
)
|
|
let volatileThreadInfo: [String: LibSession.VolatileThreadInfo] = try LibSession
|
|
.extractConvoVolatileInfo(from: convoInfoVolatileConf)
|
|
.reduce(into: [:]) { result, next in result[next.threadId] = next }
|
|
|
|
try extractedUserGroups.groups.forEach { group in
|
|
let markedAsUnread: Bool = (volatileThreadInfo[group.groupSessionId]?.changes
|
|
.contains(where: { change in
|
|
switch change {
|
|
case .markedAsUnread(let value): return value
|
|
default: return false
|
|
}
|
|
}))
|
|
.defaulting(to: false)
|
|
|
|
try db.execute(sql: """
|
|
INSERT INTO thread (
|
|
id,
|
|
variant,
|
|
creationDateTimestamp,
|
|
shouldBeVisible,
|
|
isPinned,
|
|
markedAsUnread,
|
|
pinnedPriority
|
|
)
|
|
VALUES (
|
|
'\(group.groupSessionId)',
|
|
\(SessionThread.Variant.group.rawValue),
|
|
\(group.joinedAt),
|
|
true,
|
|
false,
|
|
\(markedAsUnread),
|
|
\(group.priority)
|
|
)
|
|
""")
|
|
try db.execute(
|
|
sql: """
|
|
INSERT INTO closedGroup (
|
|
threadId,
|
|
name,
|
|
formationTimestamp,
|
|
shouldPoll,
|
|
groupIdentityPrivateKey,
|
|
authData,
|
|
invited
|
|
)
|
|
VALUES (
|
|
'\(group.groupSessionId)',
|
|
'\(group.name)',
|
|
\(group.joinedAt),
|
|
\(group.invited == false),
|
|
?,
|
|
?,
|
|
\(group.invited)
|
|
)
|
|
""",
|
|
arguments: [group.groupIdentityPrivateKey, group.authData]
|
|
)
|
|
|
|
/// If the group isn't in the invited state then make sure to subscribe for PNs once the migrations are done
|
|
if !group.invited, let token: String = dependencies[defaults: .standard, key: .deviceToken] {
|
|
db.afterNextTransaction { db in
|
|
try? PushNotificationAPI
|
|
.preparedSubscribe(
|
|
db,
|
|
token: Data(hex: token),
|
|
sessionIds: [SessionId(.group, hex: group.groupSessionId)],
|
|
using: dependencies
|
|
)
|
|
.send(using: dependencies)
|
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies)
|
|
.sinkUntilComplete()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move the `imageData` out of the `OpenGroup` table and on to disk to be consistent with
|
|
// the other display picture logic
|
|
let timestampMs: TimeInterval = TimeInterval(dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000)
|
|
let existingImageInfo: [Row] = try Row.fetchAll(db, sql: """
|
|
SELECT threadid, imageData
|
|
FROM openGroup
|
|
WHERE imageData IS NOT NULL
|
|
""")
|
|
|
|
existingImageInfo.forEach { imageInfo in
|
|
guard
|
|
let threadId: String = imageInfo["threadId"],
|
|
let imageData: Data = imageInfo["imageData"]
|
|
else {
|
|
Log.error("[GroupsRebuildChanges] Failed to extract imageData from community")
|
|
return
|
|
}
|
|
|
|
let fileName: String = dependencies[singleton: .displayPictureManager].generateFilename(
|
|
format: imageData.guessedImageFormat
|
|
)
|
|
|
|
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? imageData.write(to: URL(fileURLWithPath: filePath), options: [.atomic])
|
|
|
|
guard UIImage(contentsOfFile: filePath) != nil else {
|
|
Log.error("[GroupsRebuildChanges] Failed to save Community imageData for \(threadId)")
|
|
return
|
|
}
|
|
|
|
// Update the database with the new info
|
|
try? db.execute(sql: """
|
|
UPDATE openGroup
|
|
SET
|
|
imageData = NULL,
|
|
displayPictureFilename = '\(fileName)',
|
|
lastDisplayPictureUpdate = \(timestampMs)
|
|
WHERE threadId = '\(threadId)'
|
|
""")
|
|
}
|
|
|
|
Storage.update(progress: 1, for: self, in: target, using: dependencies)
|
|
}
|
|
}
|
|
|