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

/// Represents a sync message an event related to the Calls Tab (also known on
/// other platforms as the "call log").
///
/// - SeeAlso ``IncomingCallLogEventSyncMessageManager``
@objc(OutgoingCallLogEventSyncMessage)
public class OutgoingCallLogEventSyncMessage: OutgoingSyncMessage {
    override public class var supportsSecureCoding: Bool { true }

    public required init?(coder: NSCoder) {
        guard let callLogEvent = coder.decodeObject(of: CallLogEvent.self, forKey: "callLogEvent") else {
            return nil
        }
        self.callLogEvent = callLogEvent
        super.init(coder: coder)
    }

    override public func encode(with coder: NSCoder) {
        super.encode(with: coder)
        coder.encode(callLogEvent, forKey: "callLogEvent")
    }

    override public var hash: Int {
        var hasher = Hasher()
        hasher.combine(super.hash)
        hasher.combine(callLogEvent)
        return hasher.finalize()
    }

    override public func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? Self else { return false }
        guard super.isEqual(object) else { return false }
        guard self.callLogEvent == object.callLogEvent else { return false }
        return true
    }

    /// The call log event.
    private let callLogEvent: CallLogEvent

    init(
        callLogEvent: CallLogEvent,
        localThread: TSContactThread,
        tx: DBReadTransaction,
    ) {
        self.callLogEvent = callLogEvent
        super.init(localThread: localThread, tx: tx)
    }

    override public var isUrgent: Bool { false }

    override public func syncMessageBuilder(tx: DBReadTransaction) -> SSKProtoSyncMessageBuilder? {
        let callLogEventBuilder = SSKProtoSyncMessageCallLogEvent.builder()

        callLogEventBuilder.setTimestamp(callLogEvent.timestamp)
        callLogEventBuilder.setType(callLogEvent.eventType.protoType)

        if let callId = callLogEvent.callId {
            callLogEventBuilder.setCallID(callId)
        }

        if let conversationId = callLogEvent.conversationId {
            callLogEventBuilder.setConversationID(conversationId)
        }

        let builder = SSKProtoSyncMessage.builder()
        builder.setCallLogEvent(callLogEventBuilder.buildInfallibly())
        return builder
    }
}

// MARK: -

public extension OutgoingCallLogEventSyncMessage {
    @objc(OutgoingCallLogEvent)
    class CallLogEvent: NSObject, NSSecureCoding {
        public enum EventType: UInt, CaseIterable {
            /// Indicates we cleared our call log in its entirety.
            ///
            /// - SeeAlso
            /// ``OutgoingCallEvent/EventType/deleted``, which indicates that we
            /// deleted a singular individual call.
            ///
            /// That action is part of the `CallEvent` sync message for
            /// historical reasons, in that it predates the `CallLogEvent` sync
            /// message.
            case cleared = 0

            /// Indicates we marked calls as read.
            case markedAsRead = 1

            /// Indicates we marked calls as read in a particular conversation.
            case markedAsReadInConversation = 2
        }

        let eventType: EventType
        let callId: UInt64?
        let conversationId: Data?
        let timestamp: UInt64

        init(
            eventType: EventType,
            callId: UInt64?,
            conversationId: Data?,
            timestamp: UInt64,
        ) {
            self.eventType = eventType
            self.callId = callId
            self.conversationId = conversationId
            self.timestamp = timestamp
        }

        // MARK: NSSecureCoding

        private enum Keys {
            static let eventType = "eventType"
            static let timestamp = "timestamp"
            static let callId = "callId"
            static let conversationId = "conversationId"
        }

        public static var supportsSecureCoding: Bool { true }

        public required init?(coder: NSCoder) {
            guard
                let eventTypeRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.eventType)?.uintValue,
                let eventType = EventType(rawValue: eventTypeRaw),
                let timestamp = coder.decodeObject(of: NSNumber.self, forKey: Keys.timestamp)?.uint64Value
            else {
                owsFailDebug("Missing or unrecognized fields!")
                return nil
            }

            self.eventType = eventType
            self.timestamp = timestamp

            if
                let callId = coder.decodeObject(of: NSNumber.self, forKey: Keys.callId)?.uint64Value,
                let conversationId = coder.decodeObject(of: NSData.self, forKey: Keys.conversationId) as Data?
            {
                self.callId = callId
                self.conversationId = conversationId
            } else {
                self.callId = nil
                self.conversationId = nil
            }
        }

        public func encode(with coder: NSCoder) {
            coder.encode(NSNumber(value: eventType.rawValue), forKey: Keys.eventType)
            coder.encode(NSNumber(value: timestamp), forKey: Keys.timestamp)

            if let callId, let conversationId {
                coder.encode(NSNumber(value: callId), forKey: Keys.callId)
                coder.encode(conversationId as NSData, forKey: Keys.conversationId)
            }
        }
    }
}

// MARK: -

private extension OutgoingCallLogEventSyncMessage.CallLogEvent.EventType {
    var protoType: SSKProtoSyncMessageCallLogEventType {
        switch self {
        case .cleared: return .cleared
        case .markedAsRead: return .markedAsRead
        case .markedAsReadInConversation: return .markedAsReadInConversation
        }
    }
}