Swiftgram/Telegram-iOS/WatchCommunicationManager.swift
2019-06-06 23:17:31 +01:00

200 lines
8.9 KiB
Swift

import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramUI
#if BUCK
import WatchUtils
import AppBinaryPrivate
#endif
final class WatchCommunicationManager {
private let queue: Queue
private let allowBackgroundTimeExtension: (Double) -> Void
private var server: TGBridgeServer!
private let contextDisposable = MetaDisposable()
private let presetsDisposable = MetaDisposable()
let accountContext = Promise<AccountContext?>(nil)
private let presets = Promise<WatchPresetSettings?>(nil)
private let navigateToMessagePipe = ValuePipe<MessageId>()
init(queue: Queue, context: Promise<AuthorizedApplicationContext?>, allowBackgroundTimeExtension: @escaping (Double) -> Void) {
self.queue = queue
self.allowBackgroundTimeExtension = allowBackgroundTimeExtension
let handlers = allWatchRequestHandlers.reduce([String : AnyClass]()) { (map, handler) -> [String : AnyClass] in
var map = map
if let handler = handler as? WatchRequestHandler.Type {
for case let subscription as TGBridgeSubscription.Type in handler.handledSubscriptions {
if let name = subscription.subscriptionName() {
map[name] = handler
}
}
}
return map
}
self.server = TGBridgeServer(handler: { [weak self] subscription -> SSignal? in
guard let strongSelf = self, let subscription = subscription, let handler = handlers[subscription.name] as? WatchRequestHandler.Type else {
return nil
}
return handler.handle(subscription: subscription, manager: strongSelf)
}, fileHandler: { [weak self] path, metadata in
guard let strongSelf = self, let path = path, let metadata = metadata as? [String : Any] else {
return
}
if metadata[TGBridgeIncomingFileTypeKey] as? String == TGBridgeIncomingFileTypeAudio {
let _ = WatchAudioHandler.handleFile(path: path, metadata: metadata, manager: strongSelf).start()
}
}, dispatchOnQueue: { [weak self] block in
if let strongSelf = self, let block = block {
strongSelf.queue.justDispatch(block)
}
}, logFunction: { value in
if let value = value {
Logger.shared.log("WatchBridge", value)
}
}, allowBackgroundTimeExtension: {
allowBackgroundTimeExtension(4.0)
})
self.server.startRunning()
self.contextDisposable.set((combineLatest(self.watchAppInstalled, context.get() |> deliverOn(self.queue))).start(next: { [weak self] appInstalled, appContext in
guard let strongSelf = self, appInstalled else {
return
}
if let context = appContext {
strongSelf.accountContext.set(.single(context.context))
strongSelf.server.setAuthorized(true, userId: context.context.account.peerId.id)
strongSelf.server.setMicAccessAllowed(false)
strongSelf.server.pushContext()
strongSelf.server.setMicAccessAllowed(true)
strongSelf.server.pushContext()
strongSelf.presets.set(context.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.watchPresetSettings])
|> map({ sharedData -> WatchPresetSettings in
return (sharedData.entries[ApplicationSpecificSharedDataKeys.watchPresetSettings] as? WatchPresetSettings) ?? WatchPresetSettings.defaultSettings
}))
} else {
strongSelf.accountContext.set(.single(nil))
strongSelf.server.setAuthorized(false, userId: 0)
strongSelf.server.pushContext()
strongSelf.presets.set(.single(nil))
}
}))
self.presetsDisposable.set((combineLatest(self.watchAppInstalled, self.presets.get() |> distinctUntilChanged |> deliverOn(self.queue), context.get() |> deliverOn(self.queue))).start(next: { [weak self] appInstalled, presets, appContext in
guard let strongSelf = self, let presets = presets, let context = appContext, appInstalled, let tempPath = strongSelf.watchTemporaryStorePath else {
return
}
let presentationData = context.context.sharedContext.currentPresentationData.with { $0 }
let defaultSuggestions: [String : String] = [
"OK": presentationData.strings.Watch_Suggestion_OK,
"Thanks": presentationData.strings.Watch_Suggestion_Thanks,
"WhatsUp": presentationData.strings.Watch_Suggestion_WhatsUp,
"TalkLater": presentationData.strings.Watch_Suggestion_TalkLater,
"CantTalk": presentationData.strings.Watch_Suggestion_CantTalk,
"HoldOn": presentationData.strings.Watch_Suggestion_HoldOn,
"BRB": presentationData.strings.Watch_Suggestion_BRB,
"OnMyWay": presentationData.strings.Watch_Suggestion_OnMyWay
]
var suggestions: [String : String] = [:]
for (key, defaultValue) in defaultSuggestions {
suggestions[key] = presets.customPresets[key] ?? defaultValue
}
let fileManager = FileManager.default
let presetsFileUrl = URL(fileURLWithPath: tempPath + "/presets.dat")
if fileManager.fileExists(atPath: presetsFileUrl.path) {
try? fileManager.removeItem(atPath: presetsFileUrl.path)
}
let data = NSKeyedArchiver.archivedData(withRootObject: suggestions)
try? data.write(to: presetsFileUrl)
let _ = strongSelf.sendFile(url: presetsFileUrl, metadata: [TGBridgeIncomingFileIdentifierKey: "presets"]).start()
}))
}
deinit {
self.contextDisposable.dispose()
self.presetsDisposable.dispose()
}
var arguments: WatchManagerArguments {
return WatchManagerArguments(appInstalled: self.watchAppInstalled, navigateToMessageRequested: self.navigateToMessagePipe.signal(), runningTasks: self.runningTasks)
}
func requestNavigateToMessage(messageId: MessageId) {
self.navigateToMessagePipe.putNext(messageId)
}
private var watchAppInstalled: Signal<Bool, NoError> {
return Signal { subscriber in
let disposable = self.server.watchAppInstalledSignal()?.start(next: { value in
if let value = value as? NSNumber {
subscriber.putNext(value.boolValue)
}
})
return ActionDisposable {
disposable?.dispose()
}
} |> deliverOn(self.queue)
}
private var runningTasks: Signal<WatchRunningTasks?, NoError> {
return Signal { subscriber in
let disposable = self.server.runningRequestsSignal()?.start(next: { value in
if let value = value as? Dictionary<String, Any> {
if let running = value["running"] as? Bool, let version = value["version"] as? Int32 {
subscriber.putNext(WatchRunningTasks(running: running, version: version))
}
}
})
return ActionDisposable {
disposable?.dispose()
}
} |> deliverOn(self.queue)
}
var watchTemporaryStorePath: String? {
return self.server.temporaryFilesURL?.path
}
func sendFile(url: URL, metadata: Dictionary<AnyHashable, Any>, asMessageData: Bool = false) -> Signal<Void, NoError> {
return Signal { subscriber in
self.server.sendFile(with: url, metadata: metadata, asMessageData: asMessageData)
subscriber.putCompletion()
return EmptyDisposable
} |> runOn(self.queue)
}
func sendFile(data: Data, metadata: Dictionary<AnyHashable, Any>) -> Signal<Void, NoError> {
return Signal { subscriber in
self.server.sendFile(with: data, metadata: metadata, errorHandler: {})
subscriber.putCompletion()
return EmptyDisposable
} |> runOn(self.queue)
}
}
func watchCommunicationManager(context: Promise<AuthorizedApplicationContext?>, allowBackgroundTimeExtension: @escaping (Double) -> Void) -> Signal<WatchCommunicationManager?, NoError> {
return Signal { subscriber in
let queue = Queue()
queue.async {
if #available(iOSApplicationExtension 9.0, *) {
subscriber.putNext(WatchCommunicationManager(queue: queue, context: context, allowBackgroundTimeExtension: allowBackgroundTimeExtension))
} else {
subscriber.putNext(nil)
}
subscriber.putCompletion()
}
return EmptyDisposable
}
}