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.
		
		
		
		
		
			
		
			
				
	
	
		
			223 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			223 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import Foundation
 | 
						|
import GRDB
 | 
						|
import SessionUtil
 | 
						|
import SessionUtilitiesKit
 | 
						|
 | 
						|
/// This migration goes through the current state of the database and generates config dumps for the user config types
 | 
						|
enum _014_GenerateInitialUserConfigDumps: Migration {
 | 
						|
    static let target: TargetMigrations.Identifier = .messagingKit
 | 
						|
    static let identifier: String = "GenerateInitialUserConfigDumps" // stringlint:disable
 | 
						|
    static let needsConfigSync: Bool = true
 | 
						|
    static let minExpectedRunDuration: TimeInterval = 4.0
 | 
						|
    static let fetchedTables: [(TableRecord & FetchableRecord).Type] = [
 | 
						|
        Identity.self, SessionThread.self, Contact.self, Profile.self, ClosedGroup.self,
 | 
						|
        OpenGroup.self, DisappearingMessagesConfiguration.self, GroupMember.self, ConfigDump.self
 | 
						|
    ]
 | 
						|
    static let createdOrAlteredTables: [(TableRecord & FetchableRecord).Type] = []
 | 
						|
    static let droppedTables: [(TableRecord & FetchableRecord).Type] = []
 | 
						|
    
 | 
						|
    static func migrate(_ db: Database) throws {
 | 
						|
        // If we have no ed25519 key then there is no need to create cached dump data
 | 
						|
        guard let secretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey else {
 | 
						|
            Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Create the initial config state
 | 
						|
        let userPublicKey: String = getUserHexEncodedPublicKey(db)
 | 
						|
        let timestampMs: Int64 = Int64(Date().timeIntervalSince1970 * 1000)
 | 
						|
        
 | 
						|
        LibSession.loadState(db, userPublicKey: userPublicKey, ed25519SecretKey: secretKey)
 | 
						|
        
 | 
						|
        // Retrieve all threads (we are going to base the config dump data on the active
 | 
						|
        // threads rather than anything else in the database)
 | 
						|
        let allThreads: [String: SessionThread] = try SessionThread
 | 
						|
            .fetchAll(db)
 | 
						|
            .reduce(into: [:]) { result, next in result[next.id] = next }
 | 
						|
        
 | 
						|
        // MARK: - UserProfile Config Dump
 | 
						|
        
 | 
						|
        try LibSession
 | 
						|
            .config(for: .userProfile, publicKey: userPublicKey)
 | 
						|
            .mutate { conf in
 | 
						|
                try LibSession.update(
 | 
						|
                    profile: Profile.fetchOrCreateCurrentUser(db),
 | 
						|
                    in: conf
 | 
						|
                )
 | 
						|
                
 | 
						|
                try LibSession.updateNoteToSelf(
 | 
						|
                    priority: {
 | 
						|
                        guard allThreads[userPublicKey]?.shouldBeVisible == true else { return LibSession.hiddenPriority }
 | 
						|
                        
 | 
						|
                        return Int32(allThreads[userPublicKey]?.pinnedPriority ?? 0)
 | 
						|
                    }(),
 | 
						|
                    in: conf
 | 
						|
                )
 | 
						|
                
 | 
						|
                if config_needs_dump(conf) {
 | 
						|
                    try LibSession
 | 
						|
                        .createDump(
 | 
						|
                            conf: conf,
 | 
						|
                            for: .userProfile,
 | 
						|
                            publicKey: userPublicKey,
 | 
						|
                            timestampMs: timestampMs
 | 
						|
                        )?
 | 
						|
                        .save(db)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        
 | 
						|
        // MARK: - Contact Config Dump
 | 
						|
        
 | 
						|
        try LibSession
 | 
						|
            .config(for: .contacts, publicKey: userPublicKey)
 | 
						|
            .mutate { conf in
 | 
						|
                // Exclude Note to Self, community, group and outgoing blinded message requests
 | 
						|
                let validContactIds: [String] = allThreads
 | 
						|
                    .values
 | 
						|
                    .filter { thread in
 | 
						|
                        thread.variant == .contact &&
 | 
						|
                        thread.id != userPublicKey &&
 | 
						|
                        SessionId(from: thread.id)?.prefix == .standard
 | 
						|
                    }
 | 
						|
                    .map { $0.id }
 | 
						|
                let contactsData: [ContactInfo] = try Contact
 | 
						|
                    .filter(
 | 
						|
                        Contact.Columns.isBlocked == true ||
 | 
						|
                        validContactIds.contains(Contact.Columns.id)
 | 
						|
                    )
 | 
						|
                    .including(optional: Contact.profile)
 | 
						|
                    .asRequest(of: ContactInfo.self)
 | 
						|
                    .fetchAll(db)
 | 
						|
                let threadIdsNeedingContacts: [String] = validContactIds
 | 
						|
                    .filter { contactId in !contactsData.contains(where: { $0.contact.id == contactId }) }
 | 
						|
                
 | 
						|
                try LibSession.upsert(
 | 
						|
                    contactData: contactsData
 | 
						|
                        .appending(
 | 
						|
                            contentsOf: threadIdsNeedingContacts
 | 
						|
                                .map { contactId in
 | 
						|
                                    ContactInfo(
 | 
						|
                                        contact: Contact.fetchOrCreate(db, id: contactId),
 | 
						|
                                        profile: nil
 | 
						|
                                    )
 | 
						|
                                }
 | 
						|
                        )
 | 
						|
                        .map { data in
 | 
						|
                            LibSession.SyncedContactInfo(
 | 
						|
                                id: data.contact.id,
 | 
						|
                                contact: data.contact,
 | 
						|
                                profile: data.profile,
 | 
						|
                                priority: {
 | 
						|
                                    guard allThreads[data.contact.id]?.shouldBeVisible == true else {
 | 
						|
                                        return LibSession.hiddenPriority
 | 
						|
                                    }
 | 
						|
                                    
 | 
						|
                                    return Int32(allThreads[data.contact.id]?.pinnedPriority ?? 0)
 | 
						|
                                }(),
 | 
						|
                                created: allThreads[data.contact.id]?.creationDateTimestamp
 | 
						|
                            )
 | 
						|
                        },
 | 
						|
                    in: conf
 | 
						|
                )
 | 
						|
                
 | 
						|
                if config_needs_dump(conf) {
 | 
						|
                    try LibSession
 | 
						|
                        .createDump(
 | 
						|
                            conf: conf,
 | 
						|
                            for: .contacts,
 | 
						|
                            publicKey: userPublicKey,
 | 
						|
                            timestampMs: timestampMs
 | 
						|
                        )?
 | 
						|
                        .save(db)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        
 | 
						|
        // MARK: - ConvoInfoVolatile Config Dump
 | 
						|
        
 | 
						|
        try LibSession
 | 
						|
            .config(for: .convoInfoVolatile, publicKey: userPublicKey)
 | 
						|
            .mutate { conf in
 | 
						|
                let volatileThreadInfo: [LibSession.VolatileThreadInfo] = LibSession.VolatileThreadInfo
 | 
						|
                    .fetchAll(db, ids: Array(allThreads.keys))
 | 
						|
                
 | 
						|
                try LibSession.upsert(
 | 
						|
                    convoInfoVolatileChanges: volatileThreadInfo,
 | 
						|
                    in: conf
 | 
						|
                )
 | 
						|
                
 | 
						|
                if config_needs_dump(conf) {
 | 
						|
                    try LibSession
 | 
						|
                        .createDump(
 | 
						|
                            conf: conf,
 | 
						|
                            for: .convoInfoVolatile,
 | 
						|
                            publicKey: userPublicKey,
 | 
						|
                            timestampMs: timestampMs
 | 
						|
                        )?
 | 
						|
                        .save(db)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        
 | 
						|
        // MARK: - UserGroups Config Dump
 | 
						|
        
 | 
						|
        try LibSession
 | 
						|
            .config(for: .userGroups, publicKey: userPublicKey)
 | 
						|
            .mutate { conf in
 | 
						|
                let legacyGroupData: [LibSession.LegacyGroupInfo] = try LibSession.LegacyGroupInfo.fetchAll(db)
 | 
						|
                let communityData: [LibSession.OpenGroupUrlInfo] = try LibSession.OpenGroupUrlInfo
 | 
						|
                    .fetchAll(db, ids: Array(allThreads.keys))
 | 
						|
                
 | 
						|
                try LibSession.upsert(
 | 
						|
                    legacyGroups: legacyGroupData,
 | 
						|
                    in: conf
 | 
						|
                )
 | 
						|
                try LibSession.upsert(
 | 
						|
                    communities: communityData
 | 
						|
                        .map { urlInfo in
 | 
						|
                            LibSession.CommunityInfo(
 | 
						|
                                urlInfo: urlInfo,
 | 
						|
                                priority: Int32(allThreads[urlInfo.threadId]?.pinnedPriority ?? 0)
 | 
						|
                            )
 | 
						|
                        },
 | 
						|
                    in: conf
 | 
						|
                )
 | 
						|
                
 | 
						|
                if config_needs_dump(conf) {
 | 
						|
                    try LibSession
 | 
						|
                        .createDump(
 | 
						|
                            conf: conf,
 | 
						|
                            for: .userGroups,
 | 
						|
                            publicKey: userPublicKey,
 | 
						|
                            timestampMs: timestampMs
 | 
						|
                        )?
 | 
						|
                        .save(db)
 | 
						|
                }
 | 
						|
        }
 | 
						|
                
 | 
						|
        // MARK: - Threads
 | 
						|
        
 | 
						|
        try LibSession.updatingThreads(db, Array(allThreads.values))
 | 
						|
        
 | 
						|
        // MARK: - Syncing
 | 
						|
        
 | 
						|
        // Enqueue a config sync job to ensure the generated configs get synced
 | 
						|
        db.afterNextTransactionNestedOnce(dedupeId: LibSession.syncDedupeId(userPublicKey)) { db in
 | 
						|
            ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey)
 | 
						|
        }
 | 
						|
        
 | 
						|
        Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
 | 
						|
    }
 | 
						|
    
 | 
						|
    struct ContactInfo: FetchableRecord, Decodable, ColumnExpressible {
 | 
						|
        typealias Columns = CodingKeys
 | 
						|
        enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
 | 
						|
            case contact
 | 
						|
            case profile
 | 
						|
        }
 | 
						|
        
 | 
						|
        let contact: Contact
 | 
						|
        let profile: Profile?
 | 
						|
    }
 | 
						|
}
 |