// // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation public extension TSPrivateStoryThread { @objc var distributionListIdentifier: Data? { UUID(uuidString: uniqueId)?.data } @objc class var myStoryUniqueId: String { // My Story always uses a UUID of all 0s "00000000-0000-0000-0000-000000000000" } class func getMyStory(transaction: SDSAnyReadTransaction) -> TSPrivateStoryThread! { anyFetchPrivateStoryThread(uniqueId: myStoryUniqueId, transaction: transaction) } @discardableResult class func getOrCreateMyStory(transaction: SDSAnyWriteTransaction) -> TSPrivateStoryThread! { if let myStory = getMyStory(transaction: transaction) { return myStory } let myStory = TSPrivateStoryThread(uniqueId: myStoryUniqueId, name: "", allowsReplies: true, addresses: [], viewMode: .blockList) myStory.anyInsert(transaction: transaction) return myStory } override func recipientAddresses(with transaction: SDSAnyReadTransaction) -> [SignalServiceAddress] { switch storyViewMode { case .default: owsFailDebug("Unexpectedly have private story with no view mode") return [] case .explicit, .disabled: return addresses case .blockList: return profileManager.allWhitelistedRegisteredAddresses(tx: transaction).filter { !addresses.contains($0) && !$0.isLocalAddress } } } private static let deletedAtTimestampKVS = SDSKeyValueStore(collection: "TSPrivateStoryThread+DeletedAtTimestamp") private static let deletedAtTimestampThreshold = kMonthInterval static func deletedAtTimestamp(forDistributionListIdentifier identifier: Data, transaction: SDSAnyReadTransaction) -> UInt64? { guard let uniqueId = UUID(data: identifier)?.uuidString else { return nil } return deletedAtTimestampKVS.getUInt64(uniqueId, transaction: transaction) } static func recordDeletedAtTimestamp(_ timestamp: UInt64, forDistributionListIdentifier identifier: Data, transaction: SDSAnyWriteTransaction) { guard Date().timeIntervalSince(Date(millisecondsSince1970: timestamp)) < deletedAtTimestampThreshold else { Logger.warn("Ignorning stale deleted at timestamp") return } guard let uniqueId = UUID(data: identifier)?.uuidString else { return } deletedAtTimestampKVS.setUInt64(timestamp, key: uniqueId, transaction: transaction) } static func allDeletedIdentifiers(transaction: SDSAnyReadTransaction) -> [Data] { deletedAtTimestampKVS.allKeys(transaction: transaction).compactMap { UUID(uuidString: $0)?.data } } static func cleanupDeletedTimestamps(transaction tx: SDSAnyWriteTransaction) { var deletedIdentifiers = [Data]() for identifier in deletedAtTimestampKVS.allKeys(transaction: tx) { guard let timestamp = deletedAtTimestampKVS.getUInt64( identifier, transaction: tx ) else { continue } guard Date().timeIntervalSince(Date(millisecondsSince1970: timestamp)) > deletedAtTimestampThreshold else { continue } deletedAtTimestampKVS.removeValue(forKey: identifier, transaction: tx) // If we still have a private story thread for this deleted timestamp, it's // now safe to purge it from the database. if let thread = TSPrivateStoryThread.anyFetchPrivateStoryThread(uniqueId: identifier, transaction: tx) { DependenciesBridge.shared.threadRemover.remove(thread, tx: tx.asV2Write) } UUID(uuidString: identifier).map { deletedIdentifiers.append($0.data) } } Self.storageServiceManager.recordPendingUpdates(updatedStoryDistributionListIds: deletedIdentifiers) } override func updateWithShouldThreadBeVisible(_ shouldThreadBeVisible: Bool, transaction: SDSAnyWriteTransaction) { super.updateWithShouldThreadBeVisible(shouldThreadBeVisible, transaction: transaction) updateWithStoryViewMode(.disabled, transaction: transaction) } }