mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
286 lines
10 KiB
Swift
286 lines
10 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import SyncCore
|
|
import Postbox
|
|
import TelegramUIPreferences
|
|
import libtgvoip
|
|
|
|
private func callConnectionDescription(_ connection: CallSessionConnection) -> OngoingCallConnectionDescription {
|
|
return OngoingCallConnectionDescription(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag)
|
|
}
|
|
|
|
private let callLogsLimit = 20
|
|
|
|
public func callLogNameForId(id: Int64, account: Account) -> String? {
|
|
let path = callLogsPath(account: account)
|
|
let namePrefix = "\(id)_"
|
|
|
|
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
|
for url in enumerator {
|
|
if let url = url as? URL {
|
|
if url.lastPathComponent.hasPrefix(namePrefix) {
|
|
return url.lastPathComponent
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func callLogsPath(account: Account) -> String {
|
|
return account.basePath + "/calls"
|
|
}
|
|
|
|
private func cleanupCallLogs(account: Account) {
|
|
let path = callLogsPath(account: account)
|
|
let fileManager = FileManager.default
|
|
if !fileManager.fileExists(atPath: path, isDirectory: nil) {
|
|
try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
|
|
}
|
|
|
|
var oldest: (URL, Date)? = nil
|
|
var count = 0
|
|
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
|
for url in enumerator {
|
|
if let url = url as? URL {
|
|
if let date = (try? url.resourceValues(forKeys: Set([.contentModificationDateKey])))?.contentModificationDate {
|
|
if let currentOldest = oldest {
|
|
if date < currentOldest.1 {
|
|
oldest = (url, date)
|
|
}
|
|
} else {
|
|
oldest = (url, date)
|
|
}
|
|
count += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if count > callLogsLimit, let oldest = oldest {
|
|
try? fileManager.removeItem(atPath: oldest.0.path)
|
|
}
|
|
}
|
|
|
|
private let setupLogs: Bool = {
|
|
OngoingCallThreadLocalContext.setupLoggingFunction({ value in
|
|
if let value = value {
|
|
Logger.shared.log("TGVOIP", value)
|
|
}
|
|
})
|
|
return true
|
|
}()
|
|
|
|
public enum OngoingCallContextState {
|
|
case initializing
|
|
case connected
|
|
case failed
|
|
}
|
|
|
|
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue {
|
|
private let queue: Queue
|
|
|
|
init(queue: Queue) {
|
|
self.queue = queue
|
|
|
|
super.init()
|
|
}
|
|
|
|
func dispatch(_ f: @escaping () -> Void) {
|
|
self.queue.async {
|
|
f()
|
|
}
|
|
}
|
|
|
|
func isCurrent() -> Bool {
|
|
return self.queue.isCurrent()
|
|
}
|
|
}
|
|
|
|
private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetworkType {
|
|
switch type {
|
|
case .none:
|
|
return .wifi
|
|
case .wifi:
|
|
return .wifi
|
|
case let .cellular(cellular):
|
|
switch cellular {
|
|
case .edge:
|
|
return .cellularEdge
|
|
case .gprs:
|
|
return .cellularGprs
|
|
case .thirdG, .unknown:
|
|
return .cellular3g
|
|
case .lte:
|
|
return .cellularLte
|
|
}
|
|
}
|
|
}
|
|
|
|
private func ongoingDataSavingForType(_ type: VoiceCallDataSaving) -> OngoingCallDataSaving {
|
|
switch type {
|
|
case .never:
|
|
return .never
|
|
case .cellular:
|
|
return .cellular
|
|
case .always:
|
|
return .always
|
|
default:
|
|
return .never
|
|
}
|
|
}
|
|
|
|
public final class OngoingCallContext {
|
|
public let internalId: CallSessionInternalId
|
|
|
|
private let queue = Queue()
|
|
private let account: Account
|
|
private let callSessionManager: CallSessionManager
|
|
|
|
private var contextRef: Unmanaged<OngoingCallThreadLocalContext>?
|
|
|
|
private let contextState = Promise<OngoingCallState?>(nil)
|
|
public var state: Signal<OngoingCallContextState?, NoError> {
|
|
return self.contextState.get()
|
|
|> map {
|
|
$0.flatMap {
|
|
switch $0 {
|
|
case .initializing:
|
|
return .initializing
|
|
case .connected:
|
|
return .connected
|
|
case .failed:
|
|
return .failed
|
|
default:
|
|
return .failed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private let receptionPromise = Promise<Int32?>(nil)
|
|
public var reception: Signal<Int32?, NoError> {
|
|
return self.receptionPromise.get()
|
|
}
|
|
|
|
private let audioSessionDisposable = MetaDisposable()
|
|
private var networkTypeDisposable: Disposable?
|
|
|
|
public static var maxLayer: Int32 {
|
|
return OngoingCallThreadLocalContext.maxLayer()
|
|
}
|
|
|
|
public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, allowP2P: Bool, audioSessionActive: Signal<Bool, NoError>, logName: String) {
|
|
let _ = setupLogs
|
|
OngoingCallThreadLocalContext.applyServerConfig(serializedData)
|
|
|
|
self.internalId = internalId
|
|
self.account = account
|
|
self.callSessionManager = callSessionManager
|
|
|
|
let queue = self.queue
|
|
|
|
cleanupCallLogs(account: account)
|
|
|
|
let logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log"
|
|
self.audioSessionDisposable.set((audioSessionActive
|
|
|> filter { $0 }
|
|
|> take(1)
|
|
|> deliverOn(queue)).start(next: { [weak self] _ in
|
|
if let strongSelf = self {
|
|
var voipProxyServer: VoipProxyServer?
|
|
if let proxyServer = proxyServer {
|
|
switch proxyServer.connection {
|
|
case let .socks5(username, password):
|
|
voipProxyServer = VoipProxyServer(host: proxyServer.host, port: proxyServer.port, username: username, password: password)
|
|
case .mtp:
|
|
break
|
|
}
|
|
}
|
|
let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType), dataSaving: ongoingDataSavingForType(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescription(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescription), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath)
|
|
|
|
strongSelf.contextRef = Unmanaged.passRetained(context)
|
|
context.stateChanged = { state in
|
|
self?.contextState.set(.single(state))
|
|
}
|
|
context.signalBarsChanged = { signalBars in
|
|
self?.receptionPromise.set(.single(signalBars))
|
|
}
|
|
|
|
strongSelf.networkTypeDisposable = (updatedNetworkType
|
|
|> deliverOn(queue)).start(next: { networkType in
|
|
self?.withContext { context in
|
|
context.setNetworkType(ongoingNetworkTypeForType(networkType))
|
|
}
|
|
})
|
|
}
|
|
}))
|
|
}
|
|
|
|
deinit {
|
|
let contextRef = self.contextRef
|
|
self.queue.async {
|
|
contextRef?.release()
|
|
}
|
|
|
|
self.audioSessionDisposable.dispose()
|
|
self.networkTypeDisposable?.dispose()
|
|
}
|
|
|
|
private func withContext(_ f: @escaping (OngoingCallThreadLocalContext) -> Void) {
|
|
self.queue.async {
|
|
if let contextRef = self.contextRef {
|
|
let context = contextRef.takeUnretainedValue()
|
|
f(context)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func stop(callId: CallId? = nil, sendDebugLogs: Bool = false, debugLogValue: Promise<String?>) {
|
|
self.withContext { context in
|
|
context.stop { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in
|
|
debugLogValue.set(.single(debugLog))
|
|
let delta = NetworkUsageStatsConnectionsEntry(
|
|
cellular: NetworkUsageStatsDirectionsEntry(
|
|
incoming: bytesReceivedMobile,
|
|
outgoing: bytesSentMobile),
|
|
wifi: NetworkUsageStatsDirectionsEntry(
|
|
incoming: bytesReceivedWifi,
|
|
outgoing: bytesSentWifi))
|
|
updateAccountNetworkUsageStats(account: self.account, category: .call, delta: delta)
|
|
|
|
if let callId = callId, let debugLog = debugLog, sendDebugLogs {
|
|
let _ = saveCallDebugLog(network: self.account.network, callId: callId, log: debugLog).start()
|
|
}
|
|
}
|
|
let derivedState = context.getDerivedState()
|
|
let _ = updateVoipDerivedStateInteractively(postbox: self.account.postbox, { _ in
|
|
return VoipDerivedState(data: derivedState)
|
|
}).start()
|
|
}
|
|
}
|
|
|
|
public func setIsMuted(_ value: Bool) {
|
|
self.withContext { context in
|
|
context.setIsMuted(value)
|
|
}
|
|
}
|
|
|
|
public func debugInfo() -> Signal<(String, String), NoError> {
|
|
let poll = Signal<(String, String), NoError> { subscriber in
|
|
self.withContext { context in
|
|
let version = context.version()
|
|
let debugInfo = context.debugInfo()
|
|
if let version = version, let debugInfo = debugInfo {
|
|
subscriber.putNext((version, debugInfo))
|
|
}
|
|
subscriber.putCompletion()
|
|
}
|
|
|
|
return EmptyDisposable
|
|
}
|
|
return (poll |> then(.complete() |> delay(0.5, queue: Queue.concurrentDefaultQueue()))) |> restart
|
|
}
|
|
}
|
|
|