ios: process commands and messages asynchronously, on the background thread (#367)

* ios: process commands and messages asynchronously, on the background thread

* move model updates to main thread
This commit is contained in:
Evgeny Poberezkin
2022-02-24 17:16:41 +00:00
committed by GitHub
parent 9c57ab5221
commit 1150c04298
14 changed files with 210 additions and 156 deletions
+63 -56
View File
@@ -232,10 +232,10 @@ enum TerminalItem: Identifiable {
}
}
func chatSendCmd(_ cmd: ChatCommand) throws -> ChatResponse {
func chatSendCmdSync(_ cmd: ChatCommand) -> ChatResponse {
var c = cmd.cmdString.cString(using: .utf8)!
logger.debug("chatSendCmd \(cmd.cmdType)")
let resp = chatResponse(chat_send_cmd(getChatCtrl(), &c)!)
let resp = chatResponse(chat_send_cmd(getChatCtrl(), &c))
logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)")
if case let .response(_, json) = resp {
logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)")
@@ -247,13 +247,22 @@ func chatSendCmd(_ cmd: ChatCommand) throws -> ChatResponse {
return resp
}
func chatRecvMsg() throws -> ChatResponse {
chatResponse(chat_recv_msg(getChatCtrl())!)
func chatSendCmd(_ cmd: ChatCommand) async -> ChatResponse {
await withCheckedContinuation { cont in
cont.resume(returning: chatSendCmdSync(cmd))
}
}
func chatRecvMsg() async -> ChatResponse {
await withCheckedContinuation { cont in
let resp = chatResponse(chat_recv_msg(getChatCtrl())!)
cont.resume(returning: resp)
}
}
func apiGetActiveUser() throws -> User? {
let _ = getChatCtrl()
let r = try chatSendCmd(.showActiveUser)
let r = chatSendCmdSync(.showActiveUser)
switch r {
case let .activeUser(user): return user
case .chatCmdError(.error(.noActiveUser)): return nil
@@ -262,43 +271,43 @@ func apiGetActiveUser() throws -> User? {
}
func apiCreateActiveUser(_ p: Profile) throws -> User {
let r = try chatSendCmd(.createActiveUser(profile: p))
let r = chatSendCmdSync(.createActiveUser(profile: p))
if case let .activeUser(user) = r { return user }
throw r
}
func apiStartChat() throws {
let r = try chatSendCmd(.startChat)
let r = chatSendCmdSync(.startChat)
if case .chatStarted = r { return }
throw r
}
func apiGetChats() throws -> [Chat] {
let r = try chatSendCmd(.apiGetChats)
let r = chatSendCmdSync(.apiGetChats)
if case let .apiChats(chats) = r { return chats.map { Chat.init($0) } }
throw r
}
func apiGetChat(type: ChatType, id: Int64) throws -> Chat {
let r = try chatSendCmd(.apiGetChat(type: type, id: id))
func apiGetChat(type: ChatType, id: Int64) async throws -> Chat {
let r = await chatSendCmd(.apiGetChat(type: type, id: id))
if case let .apiChat(chat) = r { return Chat.init(chat) }
throw r
}
func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) throws -> ChatItem {
let r = try chatSendCmd(.apiSendMessage(type: type, id: id, msg: msg))
func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) async throws -> ChatItem {
let r = await chatSendCmd(.apiSendMessage(type: type, id: id, msg: msg))
if case let .newChatItem(aChatItem) = r { return aChatItem.chatItem }
throw r
}
func apiAddContact() throws -> String {
let r = try chatSendCmd(.addContact)
func apiAddContact() async throws -> String {
let r = await chatSendCmd(.addContact)
if case let .invitation(connReqInvitation) = r { return connReqInvitation }
throw r
}
func apiConnect(connReq: String) throws {
let r = try chatSendCmd(.connect(connReq: connReq))
func apiConnect(connReq: String) async throws {
let r = await chatSendCmd(.connect(connReq: connReq))
switch r {
case .sentConfirmation: return
case .sentInvitation: return
@@ -306,14 +315,14 @@ func apiConnect(connReq: String) throws {
}
}
func apiDeleteChat(type: ChatType, id: Int64) throws {
let r = try chatSendCmd(.apiDeleteChat(type: type, id: id))
func apiDeleteChat(type: ChatType, id: Int64) async throws {
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id))
if case .contactDeleted = r { return }
throw r
}
func apiUpdateProfile(profile: Profile) throws -> Profile? {
let r = try chatSendCmd(.updateProfile(profile: profile))
func apiUpdateProfile(profile: Profile) async throws -> Profile? {
let r = await chatSendCmd(.updateProfile(profile: profile))
switch r {
case .userProfileNoChange: return nil
case let .userProfileUpdated(_, toProfile): return toProfile
@@ -321,20 +330,20 @@ func apiUpdateProfile(profile: Profile) throws -> Profile? {
}
}
func apiCreateUserAddress() throws -> String {
let r = try chatSendCmd(.createMyAddress)
func apiCreateUserAddress() async throws -> String {
let r = await chatSendCmd(.createMyAddress)
if case let .userContactLinkCreated(connReq) = r { return connReq }
throw r
}
func apiDeleteUserAddress() throws {
let r = try chatSendCmd(.deleteMyAddress)
func apiDeleteUserAddress() async throws {
let r = await chatSendCmd(.deleteMyAddress)
if case .userContactLinkDeleted = r { return }
throw r
}
func apiGetUserAddress() throws -> String? {
let r = try chatSendCmd(.showMyAddress)
func apiGetUserAddress() async throws -> String? {
let r = await chatSendCmd(.showMyAddress)
switch r {
case let .userContactLink(connReq):
return connReq
@@ -344,59 +353,59 @@ func apiGetUserAddress() throws -> String? {
}
}
func apiAcceptContactRequest(contactReqId: Int64) throws -> Contact {
let r = try chatSendCmd(.apiAcceptContact(contactReqId: contactReqId))
func apiAcceptContactRequest(contactReqId: Int64) async throws -> Contact {
let r = await chatSendCmd(.apiAcceptContact(contactReqId: contactReqId))
if case let .acceptingContactRequest(contact) = r { return contact }
throw r
}
func apiRejectContactRequest(contactReqId: Int64) throws {
let r = try chatSendCmd(.apiRejectContact(contactReqId: contactReqId))
func apiRejectContactRequest(contactReqId: Int64) async throws {
let r = await chatSendCmd(.apiRejectContact(contactReqId: contactReqId))
if case .contactRequestRejected = r { return }
throw r
}
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) throws {
let r = try chatSendCmd(.apiChatRead(type: type, id: id, itemRange: itemRange))
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws {
let r = await chatSendCmd(.apiChatRead(type: type, id: id, itemRange: itemRange))
if case .cmdOk = r { return }
throw r
}
func acceptContactRequest(_ contactRequest: UserContactRequest) {
func acceptContactRequest(_ contactRequest: UserContactRequest) async {
do {
let contact = try apiAcceptContactRequest(contactReqId: contactRequest.apiId)
let contact = try await apiAcceptContactRequest(contactReqId: contactRequest.apiId)
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
ChatModel.shared.replaceChat(contactRequest.id, chat)
DispatchQueue.main.async { ChatModel.shared.replaceChat(contactRequest.id, chat) }
} catch let error {
logger.error("acceptContactRequest error: \(error.localizedDescription)")
}
}
func rejectContactRequest(_ contactRequest: UserContactRequest) {
func rejectContactRequest(_ contactRequest: UserContactRequest) async {
do {
try apiRejectContactRequest(contactReqId: contactRequest.apiId)
ChatModel.shared.removeChat(contactRequest.id)
try await apiRejectContactRequest(contactReqId: contactRequest.apiId)
DispatchQueue.main.async { ChatModel.shared.removeChat(contactRequest.id) }
} catch let error {
logger.error("rejectContactRequest: \(error.localizedDescription)")
}
}
func markChatRead(_ chat: Chat) {
func markChatRead(_ chat: Chat) async {
do {
let minItemId = chat.chatStats.minUnreadItemId
let itemRange = (minItemId, chat.chatItems.last?.id ?? minItemId)
let cInfo = chat.chatInfo
try apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange)
ChatModel.shared.markChatItemsRead(cInfo)
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange)
DispatchQueue.main.async { ChatModel.shared.markChatItemsRead(cInfo) }
} catch {
logger.error("markChatRead apiChatRead error: \(error.localizedDescription)")
}
}
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) {
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
do {
try apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id))
ChatModel.shared.markChatItemRead(cInfo, cItem)
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id))
DispatchQueue.main.async { ChatModel.shared.markChatItemRead(cInfo, cItem) }
} catch {
logger.error("markChatItemRead apiChatRead error: \(error.localizedDescription)")
}
@@ -411,7 +420,7 @@ func initializeChat() {
}
class ChatReceiver {
private var receiveLoop: DispatchWorkItem?
private var receiveLoop: Task<Void, Never>?
private var receiveMessages = true
private var _lastMsgTime = Date.now
@@ -424,18 +433,16 @@ class ChatReceiver {
receiveMessages = true
_lastMsgTime = .now
if receiveLoop != nil { return }
let loop = DispatchWorkItem(qos: .default, flags: []) {
while self.receiveMessages {
do {
processReceivedMsg(try chatRecvMsg())
self._lastMsgTime = .now
} catch {
logger.error("ChatReceiver.start chatRecvMsg error: \(error.localizedDescription)")
}
}
receiveLoop = Task { await receiveMsgLoop() }
}
func receiveMsgLoop() async {
let msg = await chatRecvMsg()
self._lastMsgTime = .now
processReceivedMsg(msg)
if self.receiveMessages {
await receiveMsgLoop()
}
receiveLoop = loop
DispatchQueue.global().async(execute: loop)
}
func stop() {