ContactsService.swift 15.2 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 23 24 25 26 27 28 29 30
 *
 *  Author: Silbino Gonçalves Matado <silbino.gmatado@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 Contacts
import SwiftyBeaver
import RxSwift

enum ContactServiceError: Error {
    case acceptTrustRequestFailed
    case diacardTrusRequestFailed
    case vCardSerializationFailed
    case loadVCardFailed
    case saveVCardFailed
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
31
    case removeContactFailed
32 33 34 35 36 37
}

class ContactsService {

    fileprivate let contactsAdapter: ContactsAdapter
    fileprivate let log = SwiftyBeaver.self
38
    fileprivate let disposeBag = DisposeBag()
39 40 41 42

    let contactRequests = Variable([ContactRequestModel]())
    let contacts = Variable([ContactModel]())

43 44
    let contactStatus = PublishSubject<ContactModel>()

45 46
    fileprivate let responseStream = PublishSubject<ServiceEvent>()
    var sharedResponseStream: Observable<ServiceEvent>
47
    let dbManager: DBManager
48

49
    init(withContactsAdapter contactsAdapter: ContactsAdapter, dbManager: DBManager) {
50
        self.contactsAdapter = contactsAdapter
51 52
        self.responseStream.disposed(by: disposeBag)
        self.sharedResponseStream = responseStream.share()
53
        self.dbManager = dbManager
54 55 56
        ContactsAdapter.delegate = self
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
57 58
    func contact(withUri uri: String) -> ContactModel? {
        guard let contact = self.contacts.value.filter({ $0.uriString == uri }).first else {
59 60 61 62 63 64 65
            return nil
        }

        return contact
    }

    func contactRequest(withRingId ringId: String) -> ContactRequestModel? {
66
        guard let contactRequest = self.contactRequests.value.filter({ $0.ringId == ringId }).first else {
67 68
            return nil
        }
69
        return contactRequest
70 71 72
    }

    func loadContacts(withAccount account: AccountModel) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
        if AccountModelHelper.init(withAccount: account).isAccountSip() {
            self.loadSipContacts(withAccount: account)
            return
        }
        loadJamiContacts(withAccount: account)
    }

    func loadSipContacts(withAccount account: AccountModel) {
        guard let profiles = self.dbManager
            .getProfilesForAccount(accountID: account.id) else {return}
        let contacts = profiles.map({ profile in
            return ContactModel(withUri: JamiURI.init(schema: URIType.sip, infoHach: profile.uri))
        })
        self.contacts.value.removeAll()
        for contact in contacts {
            if self.contacts.value.index(of: contact) == nil {
                self.contacts.value.append(contact)
                self.log.debug("contact: \(String(describing: contact.userName))")
            }
        }
    }

    func loadJamiContacts(withAccount account: AccountModel) {
96 97 98 99 100 101 102
        //Load contacts from daemon
        let contactsDictionaries = self.contactsAdapter.contacts(withAccountId: account.id)

        //Serialize them
        if let contacts = contactsDictionaries?.map({ contactDict in
            return ContactModel(withDictionary: contactDict)
        }) {
103
            self.contacts.value.removeAll()
104
            for contact in contacts {
105 106
                if self.contacts.value.index(of: contact) == nil {
                    self.contacts.value.append(contact)
107
                    self.log.debug("contact: \(String(describing: contact.userName))")
108
                }
109 110 111 112 113
            }
        }
    }

    func loadContactRequests(withAccount account: AccountModel) {
114
        self.contactRequests.value.removeAll()
115 116 117 118 119 120 121 122
        //Load trust requests from daemon
        let trustRequestsDictionaries = self.contactsAdapter.trustRequests(withAccountId: account.id)

        //Create contact requests from daemon trust requests
        if let contactRequests = trustRequestsDictionaries?.map({ dictionary in
            return ContactRequestModel(withDictionary: dictionary, accountId: account.id)
        }) {
            for contactRequest in contactRequests {
123 124 125
                if self.contactRequest(withRingId: contactRequest.ringId) == nil {
                    self.contactRequests.value.append(contactRequest)
                }
126 127 128 129 130 131 132
            }
        }
    }

    func accept(contactRequest: ContactRequestModel, withAccount account: AccountModel) -> Observable<Void> {
        return Observable.create { [unowned self] observable in
            let success = self.contactsAdapter.acceptTrustRequest(fromContact: contactRequest.ringId,
133
                                                                  withAccountId: account.id)
134
            if success {
135 136 137 138 139 140 141 142 143
                var stringImage: String?
                if let vCard = contactRequest.vCard, let image = vCard.imageData {
                    stringImage = image.base64EncodedString()
                }
                let name = VCardUtils.getName(from: contactRequest.vCard)
                _ = self.dbManager
                    .createOrUpdateRingProfile(profileUri: contactRequest.ringId,
                                               alias: name,
                                               image: stringImage,
144
                                               accountId: account.id)
145 146 147 148
                var event = ServiceEvent(withEventType: .contactAdded)
                event.addEventInput(.accountId, value: account.id)
                event.addEventInput(.uri, value: contactRequest.ringId)
                self.responseStream.onNext(event)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
149 150
                var data = [String: Any]()
                data[ProfileNotificationsKeys.ringID.rawValue] = contactRequest.ringId
151
                data[ProfileNotificationsKeys.accountId.rawValue] = account.id
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
152
                NotificationCenter.default.post(name: NSNotification.Name(ProfileNotifications.contactAdded.rawValue), object: nil, userInfo: data)
153 154 155 156 157 158 159 160 161
                observable.on(.completed)
            } else {
                observable.on(.error(ContactServiceError.acceptTrustRequestFailed))
            }

            return Disposables.create { }
        }
    }

162
    func discard(contactRequest: ContactRequestModel, withAccountId accountId: String) -> Observable<Void> {
163 164
        return Observable.create { [unowned self] observable in
            let success = self.contactsAdapter.discardTrustRequest(fromContact: contactRequest.ringId,
165
                                                                   withAccountId: accountId)
166 167 168 169 170

            //Update the Contact request list
            self.removeContactRequest(withRingId: contactRequest.ringId)

            if success {
171
                var event = ServiceEvent(withEventType: .contactRequestDiscarded)
172
                event.addEventInput(.accountId, value: accountId)
173 174
                event.addEventInput(.uri, value: contactRequest.ringId)
                self.responseStream.onNext(event)
175 176 177 178 179 180 181 182
                observable.on(.completed)
            } else {
                observable.on(.error(ContactServiceError.diacardTrusRequestFailed))
            }
            return Disposables.create { }
        }
    }

183
    func sendContactRequest(toContactRingId ringId: String, withAccount account: AccountModel) -> Completable {
184 185
        return Completable.create { [unowned self] completable in
            do {
186
                var payload: Data?
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
                if let accountProfile = self.dbManager.accountProfile(for: account.id) {
                    let vCard = CNMutableContact()
                    var cardChanged = false
                    if let name = accountProfile.alias {
                        vCard.familyName = name
                        cardChanged = true
                    }
                    if let photo = accountProfile.photo {
                        vCard.imageData = NSData(base64Encoded: photo,
                                                options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data?
                        cardChanged = true
                    }
                    if cardChanged {
                        payload = try CNContactVCardSerialization.dataWithImageAndUUID(from: vCard, andImageCompression: 40000)
                    }
202
                }
203
                self.contactsAdapter.sendTrustRequest(toContact: ringId, payload: payload, withAccountId: account.id)
204
                var event = ServiceEvent(withEventType: .contactAdded)
205 206 207
                event.addEventInput(.accountId, value: account.id)
                event.addEventInput(.uri, value: ringId)
                self.responseStream.onNext(event)
208 209 210 211 212 213 214 215 216 217
                completable(.completed)
            } catch {
                completable(.error(ContactServiceError.vCardSerializationFailed))
            }
            return Disposables.create { }
        }
    }

    func addContact(contact: ContactModel, withAccount account: AccountModel) -> Observable<Void> {
        return Observable.create { [unowned self] observable in
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
218 219
            self.contactsAdapter.addContact(withURI: contact.hash, accountId: account.id)
            if self.contact(withUri: contact.uriString ?? "") == nil {
220 221
                self.contacts.value.append(contact)
            }
222 223 224 225 226
            observable.on(.completed)
            return Disposables.create { }
        }
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
227
    func removeContact(withUri uri: String, ban: Bool, withAccountId accountId: String) -> Observable<Void> {
228
        return Observable.create { [unowned self] observable in
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
229 230 231 232 233
            guard let hash = JamiURI
                .init(schema: URIType.ring,
                      infoHach: uri).hash else {
                observable.on(.error(ContactServiceError.removeContactFailed))
                return Disposables.create { }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
234
            }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
235 236
            self.contactsAdapter.removeContact(withURI: hash, accountId: accountId, ban: ban)
            self.removeContactRequest(withRingId: hash)
237 238 239 240 241 242 243 244 245 246 247 248 249 250
            observable.on(.completed)
            return Disposables.create { }
        }
    }

    fileprivate func removeContactRequest(withRingId ringId: String) {
        guard let contactRequestToRemove = self.contactRequests.value.filter({ $0.ringId == ringId}).first else {
            return
        }
        guard let index = self.contactRequests.value.index(where: { $0 === contactRequestToRemove }) else {
            return
        }
        self.contactRequests.value.remove(at: index)
    }
251 252 253 254 255 256 257 258

    func unbanContact(contact: ContactModel, account: AccountModel) {
        contact.banned = false
        self.addContact(contact: contact,
                        withAccount: account)
            .subscribe( onCompleted: {
                var event = ServiceEvent(withEventType: .contactAdded)
                event.addEventInput(.accountId, value: account.id)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
259
                event.addEventInput(.uri, value: contact.hash)
260 261 262 263 264
                self.responseStream.onNext(event)
                self.contactStatus.onNext(contact)
                self.contacts.value = self.contacts.value
            }).disposed(by: self.disposeBag)
    }
265 266 267 268 269 270
}

extension ContactsService: ContactsAdapterDelegate {

    func incomingTrustRequestReceived(from senderAccount: String, to accountId: String, withPayload payload: Data, receivedDate: Date) {

271
        var vCard: CNContact?
272 273
        if let contactVCard = CNContactVCardSerialization.parseToVCard(data: payload) {
            vCard = contactVCard
274
        }
275

276 277 278 279 280 281 282
        //Update trust request list
        if self.contactRequest(withRingId: senderAccount) == nil {
            let contactRequest = ContactRequestModel(withRingId: senderAccount,
                                                     vCard: vCard,
                                                     receivedDate: receivedDate,
                                                     accountId: accountId)
            self.contactRequests.value.append(contactRequest)
283 284 285 286 287
            var event = ServiceEvent(withEventType: .contactRequestReceived)
            event.addEventInput(.accountId, value: accountId)
            event.addEventInput(.uri, value: senderAccount)
            event.addEventInput(.date, value: receivedDate)
            self.responseStream.onNext(event)
288 289 290 291 292 293 294 295 296
        } else {
            // If the contact request already exists, update it's relevant data
            if let contactRequest = self.contactRequest(withRingId: senderAccount) {
                contactRequest.vCard = vCard
                contactRequest.receivedDate = receivedDate
            }
            log.debug("Incoming trust request received from :\(senderAccount)")
        }

297 298 299 300
    }

    func contactAdded(contact uri: String, withAccountId accountId: String, confirmed: Bool) {
        //Update trust request list
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
301 302 303
        if let hash = JamiURI.init(schema: URIType.ring, infoHach: uri).hash {
            self.removeContactRequest(withRingId: hash)
        }
304
        // update contact status
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
305
        if let contact = self.contact(withUri: uri) {
306
            self.contactStatus.onNext(contact)
307 308 309
            if contact.confirmed != confirmed {
                contact.confirmed = confirmed
            }
310
            self.contactStatus.onNext(contact)
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
        }
            //sync contacts with daemon contacts
        else {

            let contactsDictionaries = self.contactsAdapter.contacts(withAccountId: accountId)

            //Serialize them
            if let contacts = contactsDictionaries?.map({ contactDict in
                return ContactModel(withDictionary: contactDict)
            }) {
                for contact in contacts {
                    if self.contacts.value.index(of: contact) == nil {
                        self.contacts.value.append(contact)
                        contactStatus.onNext(contact)
                    }
                }
            }

        }
330 331 332 333
        log.debug("Contact added :\(uri)")
    }

    func contactRemoved(contact uri: String, withAccountId accountId: String, banned: Bool) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
334
        guard let contactToRemove = self.contacts.value.filter({ $0.hash == uri}).first else {
335 336 337 338
            return
        }
        contactToRemove.banned = banned
        self.contactStatus.onNext(contactToRemove)
339 340
        log.debug("Contact removed :\(uri)")
    }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
341

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
    func getContactRequestVCard(forContactWithRingId ringID: String) -> Single<CNContact> {
        return Single.create(subscribe: { single in
            if let contactRequest = self.contactRequest(withRingId: ringID) {
                if let vCard = contactRequest.vCard {
                    single(.success(vCard))
                } else {
                    single(.error(ContactServiceError.loadVCardFailed))
                }
            } else {
                single(.error(ContactServiceError.loadVCardFailed))
            }
            return Disposables.create { }
        })
    }

357 358
    func getProfileForUri(uri: String, accountId: String) ->Observable<Profile> {
        return self.dbManager.profileObservable(for: uri, createIfNotExists: false, accountId: accountId)
359 360
            .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
    }
361
}