Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Threads/ThreadRemover.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation

/// Responsible for hard-deleting a thread. This is an exceptional action:
/// threads are typically "soft-deleted" by removing their contents and metadata
/// without deleting the ``TSThread`` database record itself. Hard-deletion is
/// only appropriate for scenarios in which we know a thread will be truly
/// *gone forever*; for example, when two threads are merged.
///
/// If you're looking to colloquially "delete a thread", e.g. in response to a
/// user action, you probably want ``ThreadSoftDeleteManager``.
public protocol ThreadRemover {
    func remove(_ thread: TSContactThread, tx: DBWriteTransaction)
    func remove(_ thread: TSPrivateStoryThread, tx: DBWriteTransaction)
}

class ThreadRemoverImpl: ThreadRemover {
    private let chatColorSettingStore: ChatColorSettingStore
    private let databaseStorage: Shims.DatabaseStorage
    private let disappearingMessagesConfigurationStore: DisappearingMessagesConfigurationStore
    private let lastVisibleInteractionStore: LastVisibleInteractionStore
    private let threadAssociatedDataStore: ThreadAssociatedDataStore
    private let threadReadCache: Shims.ThreadReadCache
    private let threadReplyInfoStore: ThreadReplyInfoStore
    private let threadSoftDeleteManager: ThreadSoftDeleteManager
    private let threadStore: ThreadStore
    private let wallpaperStore: WallpaperStore

    init(
        chatColorSettingStore: ChatColorSettingStore,
        databaseStorage: Shims.DatabaseStorage,
        disappearingMessagesConfigurationStore: DisappearingMessagesConfigurationStore,
        lastVisibleInteractionStore: LastVisibleInteractionStore,
        threadAssociatedDataStore: ThreadAssociatedDataStore,
        threadReadCache: Shims.ThreadReadCache,
        threadReplyInfoStore: ThreadReplyInfoStore,
        threadSoftDeleteManager: ThreadSoftDeleteManager,
        threadStore: ThreadStore,
        wallpaperStore: WallpaperStore,
    ) {
        self.chatColorSettingStore = chatColorSettingStore
        self.databaseStorage = databaseStorage
        self.disappearingMessagesConfigurationStore = disappearingMessagesConfigurationStore
        self.lastVisibleInteractionStore = lastVisibleInteractionStore
        self.threadAssociatedDataStore = threadAssociatedDataStore
        self.threadReadCache = threadReadCache
        self.threadReplyInfoStore = threadReplyInfoStore
        self.threadSoftDeleteManager = threadSoftDeleteManager
        self.threadStore = threadStore
        self.wallpaperStore = wallpaperStore
    }

    func remove(_ thread: TSContactThread, tx: DBWriteTransaction) { removeAny(thread, tx: tx) }

    func remove(_ thread: TSPrivateStoryThread, tx: DBWriteTransaction) { removeAny(thread, tx: tx) }

    private func removeAny(_ thread: TSThread, tx: DBWriteTransaction) {
        chatColorSettingStore.setRawSetting(nil, for: thread.uniqueId, tx: tx)
        databaseStorage.updateIdMapping(thread: thread, tx: tx)
        // No sync message, since hard-delete is a local-only concept.
        threadSoftDeleteManager.removeAllInteractions(thread: thread, sendDeleteForMeSyncMessage: false, tx: tx)
        disappearingMessagesConfigurationStore.remove(for: thread, tx: tx)
        threadAssociatedDataStore.remove(for: thread.uniqueId, tx: tx)
        threadReplyInfoStore.remove(for: thread.uniqueId, tx: tx)
        threadStore.removeThread(thread, tx: tx)
        threadReadCache.didRemove(thread: thread, tx: tx)
        wallpaperStore.reset(for: thread, tx: tx)
        lastVisibleInteractionStore.clearLastVisibleInteraction(for: thread, tx: tx)
    }
}

// MARK: -

extension ThreadRemoverImpl {
    enum Shims {
        typealias ThreadReadCache = _ThreadRemoverImpl_ThreadReadCacheShim
        typealias DatabaseStorage = _ThreadRemoverImpl_DatabaseStorageShim
    }

    enum Wrappers {
        typealias ThreadReadCache = _ThreadRemoverImpl_ThreadReadCacheWrapper
        typealias DatabaseStorage = _ThreadRemoverImpl_DatabaseStorageWrapper
    }
}

protocol _ThreadRemoverImpl_ThreadReadCacheShim {
    func didRemove(thread: TSThread, tx: DBWriteTransaction)
}

class _ThreadRemoverImpl_ThreadReadCacheWrapper: _ThreadRemoverImpl_ThreadReadCacheShim {
    private let threadReadCache: ThreadReadCache
    init(_ threadReadCache: ThreadReadCache) {
        self.threadReadCache = threadReadCache
    }

    func didRemove(thread: TSThread, tx: DBWriteTransaction) {
        threadReadCache.didRemove(thread: thread, transaction: tx)
    }
}

protocol _ThreadRemoverImpl_DatabaseStorageShim {
    func updateIdMapping(thread: TSThread, tx: DBWriteTransaction)
}

class _ThreadRemoverImpl_DatabaseStorageWrapper: _ThreadRemoverImpl_DatabaseStorageShim {
    private let databaseStorage: SDSDatabaseStorage
    init(_ databaseStorage: SDSDatabaseStorage) {
        self.databaseStorage = databaseStorage
    }

    func updateIdMapping(thread: TSThread, tx: DBWriteTransaction) {
        databaseStorage.updateIdMapping(thread: thread, transaction: tx)
    }
}

// MARK: - Unit Tests

#if TESTABLE_BUILD

class ThreadRemover_MockThreadReadCache: ThreadRemoverImpl.Shims.ThreadReadCache {
    func didRemove(thread: TSThread, tx: DBWriteTransaction) {}
}

class ThreadRemover_MockDatabaseStorage: ThreadRemoverImpl.Shims.DatabaseStorage {
    func updateIdMapping(thread: TSThread, tx: DBWriteTransaction) {}
}

#endif