blob: 501ec855951ef6c8d726f16d81e5bcfbf95239ce [file] [log] [blame]
kkostiuk74d1ae42021-06-17 11:10:15 -04001/*
2 * Copyright (C) 2021-2022 Savoir-faire Linux Inc.
3 *
4 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21import Foundation
22import UIKit
23import MobileCoreServices
24import Photos
25import os
26
27class AdapterService {
28 enum InteractionAttributes: String {
29 case interactionId = "id"
30 case type = "type"
31 case invited = "invited"
32 case fileId = "fileId"
33 case displayName = "displayName"
34 case body = "body"
35 case author = "author"
36 case timestamp = "timestamp"
37 case parent = "linearizedParent"
38 case action = "action"
39 case duration = "duration"
40 }
41
42 enum InteractionType: String {
43 case message = "text/plain"
44 case fileTransfer = "application/data-transfer+json"
45 }
46
47 enum EventType: Int {
48 case message
49 case fileTransferDone
50 case fileTransferInProgress
51 case syncCompleted
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -040052 case invitation
kkostiuk74d1ae42021-06-17 11:10:15 -040053 }
54
55 enum PeerConnectionRequestType {
56 case call(peerId: String, isVideo: Bool)
57 case gitMessage
58 case unknown
59 }
60
61 enum DataTransferEventCode: Int {
62 case invalid
63 case created
64 case unsupported
65 case waitPeeracceptance
66 case waitHostAcceptance
67 case ongoing
68 case finished
69 case closedByHost
70 case closedByPeer
71 case invalidPathname
72 case unjoinablePeer
73
74 func isCompleted() -> Bool {
75 switch self {
76 case .finished, .closedByHost, .closedByPeer, .unjoinablePeer, .invalidPathname:
77 return true
78 default:
79 return false
80 }
81 }
82 }
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -040083
kkostiuk74d1ae42021-06-17 11:10:15 -040084 private let maxSizeForAutoaccept = 20 * 1024 * 1024
85
Kateryna Kostiuk84834422023-04-11 10:53:56 -040086 private var adapter: Adapter!
kkostiuk74d1ae42021-06-17 11:10:15 -040087 var eventHandler: ((EventType, EventData) -> Void)?
88 var loadingFiles = [String: EventData]()
89
90 init(withAdapter adapter: Adapter) {
91 self.adapter = adapter
92 Adapter.delegate = self
93 }
94
Kateryna Kostiuk19437652022-08-02 13:02:21 -040095 func startAccountsWithListener(accountId: String, listener: @escaping (EventType, EventData) -> Void) {
kkostiuk74d1ae42021-06-17 11:10:15 -040096 self.eventHandler = listener
Kateryna Kostiuk19437652022-08-02 13:02:21 -040097 start(accountId: accountId)
kkostiuk74d1ae42021-06-17 11:10:15 -040098 }
99
Kateryna Kostiukba9a7b12023-05-02 09:33:52 -0400100 func decrypt(keyPath: String, accountId: String, messagesPath: String, value: [String: Any]) -> PeerConnectionRequestType {
101 let result = adapter.decrypt(keyPath, accountId: accountId, treated: messagesPath, value: value)
kkostiuk74d1ae42021-06-17 11:10:15 -0400102 guard let peerId = result?.keys.first,
103 let type = result?.values.first else {
104 return .unknown}
105 switch type {
106 case "videoCall":
107 return PeerConnectionRequestType.call(peerId: peerId, isVideo: true)
108 case "audioCall":
109 return PeerConnectionRequestType.call(peerId: peerId, isVideo: false)
110 case "text/plain", "application/im-gitmessage-id":
111 return PeerConnectionRequestType.gitMessage
112 default:
113 return .unknown
114 }
115 }
116
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400117 func start(accountId: String) {
118 self.adapter.start(accountId)
kkostiuk74d1ae42021-06-17 11:10:15 -0400119 }
120
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400121 func removeDelegate() {
122 Adapter.delegate = nil
123 self.adapter = nil
124 }
125
kkostiuk74d1ae42021-06-17 11:10:15 -0400126 func stop() {
127 self.adapter.stop()
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400128 removeDelegate()
kkostiuk74d1ae42021-06-17 11:10:15 -0400129 }
130
kkostiuke10e6572022-07-26 15:41:12 -0400131 func getNameFor(address: String, accountId: String) -> String {
132 return adapter.getNameFor(address, accountId: accountId)
133 }
134
135 func getNameServerFor(accountId: String) -> String {
136 return adapter.nameServer(forAccountId: accountId)
137 }
138
kkostiuk74d1ae42021-06-17 11:10:15 -0400139 private func fileAlreadyDownloaded(fileName: String, accountId: String, conversationId: String) -> Bool {
140 guard let url = getFileUrlFor(fileName: fileName, accountId: accountId, conversationId: conversationId) else {
141 return false
142 }
143 return FileManager.default.fileExists(atPath: url.path)
144 }
145
146 private func getFileUrlFor(fileName: String, accountId: String, conversationId: String) -> URL? {
147 guard let documentsURL = Constants.documentsPath else {
148 return nil
149 }
150 let pathUrl = documentsURL.appendingPathComponent(accountId)
151 .appendingPathComponent("conversation_data")
152 .appendingPathComponent(conversationId)
153 .appendingPathComponent(fileName)
154 return pathUrl
155 }
156}
157
158extension AdapterService: AdapterDelegate {
159
160 func didReceiveMessage(_ message: [String: String],
161 from senderAccount: String,
162 messageId: String,
163 to receiverAccountId: String) {
164 guard let content = message["text/plain"],
165 let handler = self.eventHandler else { return }
Alireza92b10592022-11-23 14:01:45 -0500166 handler(.message, EventData(accountId: receiverAccountId, jamiId: senderAccount, conversationId: "", content: content, groupTitle: ""))
kkostiuk74d1ae42021-06-17 11:10:15 -0400167 }
168
169 func dataTransferEvent(withFileId transferId: String, withEventCode eventCode: Int, accountId: String, conversationId: String, interactionId: String) {
170 guard let handler = self.eventHandler,
171 let data = loadingFiles[transferId],
172 let code = DataTransferEventCode(rawValue: eventCode),
173 code.isCompleted() else { return }
174 handler(.fileTransferDone, data)
175 loadingFiles.removeValue(forKey: transferId)
176 }
177
178 func conversationSyncCompleted(accountId: String) {
179 guard let handler = self.eventHandler else {
180 return
181 }
Alireza92b10592022-11-23 14:01:45 -0500182 handler(.syncCompleted, EventData(accountId: accountId))
kkostiuk74d1ae42021-06-17 11:10:15 -0400183 }
184
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400185 func receivedConversationRequest(accountId: String, conversationId: String, metadata: [String: String]) {
186 guard let handler = self.eventHandler else {
187 return
188 }
Kateryna Kostiukb277ba42023-02-15 09:41:27 -0500189 let contentMessage = L10n.Conversation.incomingRequest
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400190 var groupTitle = ""
191 var peerId = ""
192 if let title = metadata["title"], !title.isEmpty {
193 groupTitle = title
194 }
195 if let from = metadata["from"] {
196 peerId = from
197 }
198 if !peerId.isEmpty || !groupTitle.isEmpty {
Alireza92b10592022-11-23 14:01:45 -0500199 handler(.invitation, EventData(accountId: accountId, jamiId: peerId, conversationId: conversationId, content: contentMessage, groupTitle: groupTitle))
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400200 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400201 }
202
203 func newInteraction(conversationId: String, accountId: String, message: [String: String]) {
204 guard let handler = self.eventHandler else {
205 return
206 }
207 guard let type = message[InteractionAttributes.type.rawValue],
208 let interactionType = InteractionType(rawValue: type) else {
209 return
210 }
211 let from = message[InteractionAttributes.author.rawValue] ?? ""
212 let content = message[InteractionAttributes.body.rawValue] ?? ""
213 switch interactionType {
214 case .message:
Alireza92b10592022-11-23 14:01:45 -0500215 handler(.message, EventData(accountId: accountId, jamiId: from, conversationId: conversationId, content: content, groupTitle: ""))
kkostiuk74d1ae42021-06-17 11:10:15 -0400216 case.fileTransfer:
217 guard let fileId = message[InteractionAttributes.fileId.rawValue],
218 let url = self.getFileUrlFor(fileName: fileId, accountId: accountId, conversationId: conversationId) else {
219 return
220 }
Alireza92b10592022-11-23 14:01:45 -0500221 let data = EventData(accountId: accountId, jamiId: from, conversationId: conversationId, content: url.path, groupTitle: "")
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400222 // check if the file has already been downloaded. If no, download the file if filesize is less than a downloading limit
kkostiuk74d1ae42021-06-17 11:10:15 -0400223 if fileAlreadyDownloaded(fileName: fileId, accountId: accountId, conversationId: conversationId) {
224 handler(.fileTransferDone, data)
225 } else {
226 guard let interactionId = message[InteractionAttributes.interactionId.rawValue],
227 let size = message["totalSize"],
228 (Int(size) ?? (maxSizeForAutoaccept + 1)) <= maxSizeForAutoaccept else { return }
229 let path = ""
230 self.adapter.downloadFile(withFileId: fileId, accountId: accountId, conversationId: conversationId, interactionId: interactionId, withFilePath: path)
231 self.loadingFiles[fileId] = data
232 handler(.fileTransferInProgress, data)
233 }
234 }
235 }
236}