blob: a0cbf87629a3e4c456594d099741a0877b21368f [file] [log] [blame]
/*
* Copyright (C) 2017-2019 Savoir-faire Linux Inc.
*
* 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
}
class ContactsService {
fileprivate let contactsAdapter: ContactsAdapter
fileprivate let log = SwiftyBeaver.self
fileprivate let disposeBag = DisposeBag()
let contactRequests = Variable([ContactRequestModel]())
let contacts = Variable([ContactModel]())
let contactStatus = PublishSubject<ContactModel>()
fileprivate let responseStream = PublishSubject<ServiceEvent>()
var sharedResponseStream: Observable<ServiceEvent>
let dbManager = DBManager(profileHepler: ProfileDataHelper(), conversationHelper: ConversationDataHelper(), interactionHepler: InteractionDataHelper())
init(withContactsAdapter contactsAdapter: ContactsAdapter) {
self.contactsAdapter = contactsAdapter
self.responseStream.disposed(by: disposeBag)
self.sharedResponseStream = responseStream.share()
ContactsAdapter.delegate = self
}
func contact(withRingId ringId: String) -> ContactModel? {
guard let contact = self.contacts.value.filter({ $0.ringId == ringId }).first else {
return nil
}
return contact
}
func contactRequest(withRingId ringId: String) -> ContactRequestModel? {
guard let contactRequest = self.contactRequests.value.filter({ $0.ringId == ringId }).first else {
return nil
}
return contactRequest
}
func loadContacts(withAccount account: AccountModel) {
//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)
}) {
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 loadContactRequests(withAccount account: AccountModel) {
//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 {
if self.contactRequest(withRingId: contactRequest.ringId) == nil {
self.contactRequests.value.append(contactRequest)
}
}
}
}
func accept(contactRequest: ContactRequestModel, withAccount account: AccountModel) -> Observable<Void> {
return Observable.create { [unowned self] observable in
let success = self.contactsAdapter.acceptTrustRequest(fromContact: contactRequest.ringId,
withAccountId: account.id)
if success {
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,
status: ProfileStatus.trusted)
var event = ServiceEvent(withEventType: .contactAdded)
event.addEventInput(.accountId, value: account.id)
event.addEventInput(.uri, value: contactRequest.ringId)
self.responseStream.onNext(event)
observable.on(.completed)
} else {
observable.on(.error(ContactServiceError.acceptTrustRequestFailed))
}
return Disposables.create { }
}
}
func discard(contactRequest: ContactRequestModel, withAccountId accountId: String) -> Observable<Void> {
return Observable.create { [unowned self] observable in
let success = self.contactsAdapter.discardTrustRequest(fromContact: contactRequest.ringId,
withAccountId: accountId)
//Update the Contact request list
self.removeContactRequest(withRingId: contactRequest.ringId)
if success {
var event = ServiceEvent(withEventType: .contactRequestDiscarded)
event.addEventInput(.accountId, value: accountId)
event.addEventInput(.uri, value: contactRequest.ringId)
self.responseStream.onNext(event)
observable.on(.completed)
} else {
observable.on(.error(ContactServiceError.diacardTrusRequestFailed))
}
return Disposables.create { }
}
}
func sendContactRequest(toContactRingId ringId: String, vCard: CNContact?, withAccount account: AccountModel) -> Completable {
return Completable.create { [unowned self] completable in
do {
var payload: Data?
if let vCard = vCard {
payload = try CNContactVCardSerialization.dataWithImageAndUUID(from: vCard, andImageCompression: 40000)
}
self.contactsAdapter.sendTrustRequest(toContact: ringId, payload: payload, withAccountId: account.id)
var event = ServiceEvent(withEventType: .contactAdded)
event.addEventInput(.accountId, value: account.id)
event.addEventInput(.uri, value: ringId)
self.responseStream.onNext(event)
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
self.contactsAdapter.addContact(withURI: contact.ringId, accountId: account.id)
if self.contact(withRingId: contact.ringId) == nil {
self.contacts.value.append(contact)
}
observable.on(.completed)
return Disposables.create { }
}
}
func removeContact(withRingId ringId: String, ban: Bool, withAccountId accountId: String) -> Observable<Void> {
return Observable.create { [unowned self] observable in
self.contactsAdapter.removeContact(withURI: ringId, accountId: accountId, ban: ban)
self.removeContactRequest(withRingId: ringId)
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)
}
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)
event.addEventInput(.uri, value: contact.ringId)
self.responseStream.onNext(event)
self.contactStatus.onNext(contact)
self.contacts.value = self.contacts.value
}).disposed(by: self.disposeBag)
}
}
extension ContactsService: ContactsAdapterDelegate {
func incomingTrustRequestReceived(from senderAccount: String, to accountId: String, withPayload payload: Data, receivedDate: Date) {
var vCard: CNContact?
do {
let vCards = try CNContactVCardSerialization.contacts(with: payload)
vCard = vCards.first
} catch {
vCard = nil
log.error("Unable to parse the vCard :\(error)")
}
//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)
var event = ServiceEvent(withEventType: .contactRequestReceived)
event.addEventInput(.accountId, value: accountId)
event.addEventInput(.uri, value: senderAccount)
event.addEventInput(.date, value: receivedDate)
self.responseStream.onNext(event)
} 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)")
}
}
func contactAdded(contact uri: String, withAccountId accountId: String, confirmed: Bool) {
//Update trust request list
self.removeContactRequest(withRingId: uri)
// update contact status
if let contact = self.contact(withRingId: uri) {
self.contactStatus.onNext(contact)
if contact.confirmed != confirmed {
contact.confirmed = confirmed
}
self.contactStatus.onNext(contact)
}
//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)
}
}
}
}
log.debug("Contact added :\(uri)")
}
func contactRemoved(contact uri: String, withAccountId accountId: String, banned: Bool) {
guard let contactToRemove = self.contacts.value.filter({ $0.ringId == uri}).first else {
return
}
contactToRemove.banned = banned
self.contactStatus.onNext(contactToRemove)
log.debug("Contact removed :\(uri)")
}
// MARK: - profile
func saveVCard(vCard: CNContact, forContactWithRingId ringID: String) -> Observable<Void> {
let vCardSaved = VCardUtils.saveVCard(vCard: vCard, withName: ringID, inFolder: VCardFolders.contacts.rawValue)
return vCardSaved
}
func loadVCard(forContactWithRingId ringID: String) -> Single<CNContact> {
let vCard = VCardUtils.loadVCard(named: ringID, inFolder: VCardFolders.contacts.rawValue, contactService: self)
return vCard
}
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 { }
})
}
func getProfileForUri(uri: String) ->Observable<Profile> {
return self.dbManager.profileObservable(for: uri, createIfNotExists: false)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
}
}