@objc(SNContactsMigration) public class ContactsMigration : OWSDatabaseMigration { @objc class func migrationId() -> String { return "112" } override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) { self.doMigrationAsync(completion: completion) } private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) { var contacts: Set = [] Storage.write(with: { transaction in // Current user if let profile = OWSUserProfile.fetch(uniqueId: kLocalProfileUniqueId, transaction: transaction), let sessionID = TSAccountManager.sharedInstance().localNumber() { // Should always exist let contact = Contact(sessionID: sessionID) contact.name = profile.profileName contact.profilePictureURL = profile.avatarUrlPath contact.profilePictureFileName = profile.avatarFileName contact.profilePictureEncryptionKey = profile.profileKey contacts.insert(contact) } // One-on-one chats TSContactThread.enumerateCollectionObjects(with: transaction) { object, _ in guard let thread = object as? TSContactThread else { return } let sessionID = thread.contactIdentifier() let contact = Contact(sessionID: sessionID) var profileOrNil: OWSUserProfile? = nil if sessionID == getUserHexEncodedPublicKey() { profileOrNil = OWSProfileManager.shared().getLocalUserProfile(with: transaction) } else if let profile = OWSUserProfile.fetch(uniqueId: sessionID, transaction: transaction) { profileOrNil = profile } if let profile = profileOrNil { contact.name = profile.profileName contact.profilePictureURL = profile.avatarUrlPath contact.profilePictureFileName = profile.avatarFileName contact.profilePictureEncryptionKey = profile.profileKey } contact.threadID = thread.uniqueId contacts.insert(contact) } // Closed groups TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in guard let thread = object as? TSGroupThread, thread.isClosedGroup else { return } let memberSessionIDs = thread.groupModel.groupMemberIds memberSessionIDs.forEach { memberSessionID in guard !contacts.contains(where: { $0.sessionID == memberSessionID }) else { return } let contact = Contact(sessionID: memberSessionID) if let profile = OWSUserProfile.fetch(uniqueId: memberSessionID, transaction: transaction) { contact.name = profile.profileName contact.profilePictureURL = profile.avatarUrlPath contact.profilePictureFileName = profile.avatarFileName contact.profilePictureEncryptionKey = profile.profileKey } // At this point we know we don't have a one-on-one thread with this contact contacts.insert(contact) } } // Save contacts.forEach { contact in Storage.shared.setContact(contact, using: transaction) } self.save(with: transaction) // Intentionally capture self }, completion: { completion() }) } }