ConversationsManager.swift 13.6 KB
Newer Older
1
/*
2
 *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

import Foundation
import RxSwift
23
import SwiftyBeaver
24 25 26

class ConversationsManager: MessagesAdapterDelegate {

27 28
    let log = SwiftyBeaver.self

29 30
    let conversationService: ConversationsService
    let accountsService: AccountsService
31
    let nameService: NameService
32
    let dataTransferService: DataTransferService
33
    let callService: CallsService
34

35
    private let disposeBag = DisposeBag()
36
    fileprivate let textPlainMIMEType = "text/plain"
37
    fileprivate let maxSizeForAutoaccept = 20 * 1024 * 1024
38
    private let notificationHandler = LocalNotificationsHelper()
39

40
    // swiftlint:disable cyclomatic_complexity
41
    init(with conversationService: ConversationsService, accountsService: AccountsService, nameService: NameService, dataTransferService: DataTransferService, callService: CallsService) {
42 43
        self.conversationService = conversationService
        self.accountsService = accountsService
44
        self.nameService = nameService
45
        self.dataTransferService = dataTransferService
46
        self.callService = callService
47
        MessagesAdapter.delegate = self
48 49 50 51 52 53 54 55 56

        self.dataTransferService
            .sharedResponseStream
            .filter({ (event) in
                return  event.eventType == ServiceEventType.dataTransferCreated ||
                        event.eventType == ServiceEventType.dataTransferChanged
            })
            .subscribe(onNext: { [unowned self] event in
                guard   let transferId: UInt64 = event.getEventInput(ServiceEventInput.transferId),
57 58
                        let transferInfo = self.dataTransferService.getTransferInfo(withId: transferId),
                let currentAccount = self.accountsService.currentAccount else {
59 60 61 62 63
                    self.log.error("ConversationsManager: can't find transferInfo")
                    return
                }
                switch event.eventType {
                case .dataTransferCreated:
64 65 66 67
                    let photoIdentifier: String? = event.getEventInput(.localPhotolID)
                    self.conversationService
                        .generateDataTransferMessage(transferId: transferId,
                                                     transferInfo: transferInfo,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
68
                                                     accountId: transferInfo.accountId,
69 70
                                                     photoIdentifier: photoIdentifier,
                                                     updateConversation: currentAccount.id == transferInfo.accountId )
71 72 73
                        .subscribe(onCompleted: {
                            guard let transferInfo = self.dataTransferService
                                .getTransferInfo(withId: transferId) else {return}
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
74
                            self.autoAcceptTransfer(transferInfo: transferInfo, transferId: transferId, accountId: transferInfo.accountId)
75
                        }).disposed(by: self.disposeBag)
76

77 78 79 80 81 82
                case .dataTransferChanged:
                    self.log.debug("ConversationsManager: dataTransferChanged - id:\(transferId) status:\(stringFromEventCode(with: transferInfo.lastEvent))")
                    var status: DataTransferStatus = .unknown
                    switch transferInfo.lastEvent {
                    case .closed_by_host, .closed_by_peer:
                        status = DataTransferStatus.canceled
83
                        self.conversationService.dataTransferMessageMap.removeValue(forKey: transferId)
84 85
                    case .invalid, .unsupported, .invalid_pathname, .unjoinable_peer:
                        status = DataTransferStatus.error
86
                        self.conversationService.dataTransferMessageMap.removeValue(forKey: transferId)
87 88
                    case .wait_peer_acceptance, .wait_host_acceptance:
                        status = DataTransferStatus.awaiting
89
                        self.createTransferNotification(info: transferInfo)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
90
                        self.autoAcceptTransfer(transferInfo: transferInfo, transferId: transferId, accountId: transferInfo.accountId)
91 92 93 94
                    case .ongoing:
                        status = DataTransferStatus.ongoing
                    case .finished:
                        status = DataTransferStatus.success
95
                        self.conversationService.dataTransferMessageMap.removeValue(forKey: transferId)
96 97
                    case .created:
                        break
98 99
                    @unknown default:
                        break
100
                    }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
101
                    self.conversationService.transferStatusChanged(status, for: transferId, accountId: transferInfo.accountId, to: transferInfo.peer)
102 103 104 105 106
                default:
                    break
                }
            })
            .disposed(by: disposeBag)
107 108 109 110 111 112 113 114 115 116 117 118 119 120
        self.callService.newMessage.filter({ (event) in
            return  event.eventType == ServiceEventType.newIncomingMessage
        })
            .subscribe(onNext: { [unowned self] event in
                guard let accountId: String = event.getEventInput(ServiceEventInput.accountId),
                    let messageContent: String = event.getEventInput(ServiceEventInput.content),
                    let peerUri: String = event.getEventInput(ServiceEventInput.peerUri)
                    else {return}
                self.handleNewMessage(from: peerUri,
                                      to: accountId,
                                      message: messageContent,
                                      peerName: event.getEventInput(ServiceEventInput.name))
            })
            .disposed(by: disposeBag)
121 122
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
123
    func prepareConversationsForAccount(accountId: String) {
124
      self.conversationService
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
125
        .getConversationsForAccount(accountId: accountId)
126 127 128 129 130 131 132 133 134 135 136
        .subscribe()
        .disposed(by: self.disposeBag)
    }

    // MARK: Message Adapter delegate

    func didReceiveMessage(_ message: [String: String], from senderAccount: String,
                           to receiverAccountId: String) {
        guard let content = message[textPlainMIMEType] else {
            return
        }
137 138 139 140 141 142
        DispatchQueue.main.async { [unowned self] in
            self.handleNewMessage(from: senderAccount,
                                  to: receiverAccountId,
                                  message: content,
                                  peerName: nil)
        }
143
    }
144

145
    func handleNewMessage(from peerUri: String, to accountId: String, message content: String, peerName: String?) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
146 147 148 149 150 151
        guard let currentAccount = self.accountsService.currentAccount else {
            return
        }
        guard let accountForMessage = self.accountsService.getAccount(fromAccountId: accountId) else {
            return
        }
152 153 154
        if UIApplication.shared.applicationState != .active && AccountModelHelper
            .init(withAccount: accountForMessage).isAccountRing() &&
            accountsService.getCurrentProxyState(accountID: accountId) {
155 156
            var data = [String: String]()
            data [NotificationUserInfoKeys.messageContent.rawValue] = content
157
            data [NotificationUserInfoKeys.participantID.rawValue] = peerUri
158
            data [NotificationUserInfoKeys.accountID.rawValue] = accountId
159 160 161
            if let name = peerName {
                data [NotificationUserInfoKeys.name.rawValue] = name
                self.notificationHandler.presentMessageNotification(data: data)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
162
            } else {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
163
                // only for jami accounts
164 165
                if let hash = JamiURI(schema: URIType.ring,
                                      infoHach: peerUri).hash {
166
                    searchNameAndPresentNotification(data: data, hash: hash)
167
                }
168
            }
169
        }
170
        var shouldUpdateConversationsList = false
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
171
        if currentAccount.id == accountForMessage.id {
172 173
            shouldUpdateConversationsList = true
        }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
174 175 176 177 178 179

        let type = AccountModelHelper.init(withAccount: accountForMessage)
            .isAccountSip() ? URIType.sip : URIType.ring
        guard let uriString = JamiURI.init(schema: type,
                                           infoHach: peerUri,
                                           account: accountForMessage).uriString else {return}
180 181
        let message = self.conversationService.createMessage(withId: "",
                                                             withContent: content,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
182
                                                             byAuthor: uriString,
183 184 185
                                                             generated: false,
                                                             incoming: true)
        self.conversationService.saveMessage(message: message,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
186
                                             toConversationWith: uriString,
187
                                             toAccountId: accountId,
188 189 190 191 192
                                             shouldRefreshConversations: shouldUpdateConversationsList)
            .subscribe()
            .disposed(by: self.disposeBag)
    }

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    func createTransferNotification(info: NSDataTransferInfo) {
        if UIApplication.shared.applicationState == .active {
            return
        }
        guard let account = self.accountsService.getAccount(fromAccountId: info.accountId), AccountModelHelper
            .init(withAccount: account).isAccountRing() &&
            accountsService.getCurrentProxyState(accountID: info.accountId) else {
                return
        }
        var data = [String: String]()
        var message = L10n.Notifications.newFile + " "
        if let name = info.path.split(separator: "/").last {
            message += name
        } else {
            message += info.path
        }
        data [NotificationUserInfoKeys.messageContent.rawValue] = message
        data [NotificationUserInfoKeys.participantID.rawValue] = info.peer
        data [NotificationUserInfoKeys.accountID.rawValue] = info.accountId
        if let name = info.displayName {
            data [NotificationUserInfoKeys.name.rawValue] = name
            self.notificationHandler.presentMessageNotification(data: data)
        } else {
            guard let hash = JamiURI(schema: URIType.ring,
                                     infoHach: info.peer).hash else {return}

            searchNameAndPresentNotification(data: data, hash: hash)
        }
    }

    func searchNameAndPresentNotification(data: [String: String], hash: String) {
        var data = data
        self.nameService.usernameLookupStatus.single()
            .filter({ lookupNameResponse in
                return lookupNameResponse.address != nil &&
                    lookupNameResponse.address == hash
            })
            .subscribe(onNext: { [weak self] lookupNameResponse in
                if let name = lookupNameResponse.name, !name.isEmpty {
                    data [NotificationUserInfoKeys.name.rawValue] = name
                    self?.notificationHandler.presentMessageNotification(data: data)
                } else if let address = lookupNameResponse.address {
                    data [NotificationUserInfoKeys.name.rawValue] = address
                    self?.notificationHandler.presentMessageNotification(data: data)
                }
            }).disposed(by: self.disposeBag)
        self.nameService.lookupAddress(withAccount: "", nameserver: "", address: hash)
    }

242 243
    func messageStatusChanged(_ status: MessageStatus, for messageId: UInt64, from accountId: String,
                              to uri: String) {
244 245 246
        guard let account = self.accountsService.getAccount(fromAccountId: accountId) else {
            return
        }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
247 248 249 250 251
        let type = AccountModelHelper
            .init(withAccount: account).isAccountSip() ? URIType.sip : URIType.ring
        guard let stringUri = JamiURI.init(schema: type,
                                           infoHach: uri,
                                           account: account).uriString else {return}
252 253
        self.conversationService.messageStatusChanged(status,
                                                      for: messageId,
254
                                                      fromAccount: account,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
255
                                                      to: stringUri)
256
    }
257 258 259 260 261 262 263 264 265 266 267 268 269 270

    func autoAcceptTransfer(transferInfo: NSDataTransferInfo, transferId: UInt64, accountId: String) {
        if transferInfo.flags != 1 || transferInfo.totalSize > maxSizeForAutoaccept ||
            (transferInfo.lastEvent != .wait_peer_acceptance && transferInfo.lastEvent != .wait_host_acceptance) {
            return
        }
        guard let messageData = self.conversationService.dataTransferMessageMap[transferId] else {return}
        var filename = ""
        if self.dataTransferService.acceptTransfer(withId: transferId, interactionID: messageData.messageID,
                                                   fileName: &filename, accountID: accountId,
                                                   conversationID: String(messageData.conversationID)) != .success {
            self.log.debug("ConversationsManager: accept transfer failed")
        }
    }
271
}