blob: 220e7bf25b0583226786c7130625b44cc3e14838 [file] [log] [blame]
/*
* Copyright (C) 2017-2019 Savoir-faire Linux Inc.
*
* 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.
*/
// swiftlint:disable identifier_name
import RxSwift
import SwiftyBeaver
enum ProfileNotifications: String {
case messageReceived
case contactAdded
}
enum ProfileNotificationsKeys: String {
case ringID
case accountId
case message
}
struct Base64VCard {
var data: [Int: String] //The key is the number of vCard part
var partsReceived: Int
}
class ProfilesService {
private let ringVCardMIMEType = "x-ring/ring.profile.vcard;"
private var base64VCards = [Int: Base64VCard]()
private let log = SwiftyBeaver.self
var profiles = [String: ReplaySubject<Profile>]()
var accountProfiles = [String: ReplaySubject<Profile>]()
let dbManager: DBManager
let disposeBag = DisposeBag()
init(dbManager: DBManager) {
self.dbManager = dbManager
NotificationCenter.default.addObserver(self, selector: #selector(self.messageReceived(_:)),
name: NSNotification.Name(rawValue: ProfileNotifications.messageReceived.rawValue),
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.contactAdded(_:)),
name: NSNotification.Name(rawValue: ProfileNotifications.contactAdded.rawValue),
object: nil)
}
@objc
private func contactAdded(_ notification: NSNotification) {
guard let ringId = notification.userInfo?[ProfileNotificationsKeys.ringID.rawValue] as? String else {
return
}
guard let accountId = notification.userInfo?[ProfileNotificationsKeys.accountId.rawValue] as? String else {
return
}
let uri = JamiURI(schema: URIType.ring, infoHach: ringId)
let uriString = uri.uriString ?? ringId
self.triggerProfileSignal(uri: uriString, createIfNotexists: false, accountId: accountId)
}
// swiftlint:disable cyclomatic_complexity
@objc
private func messageReceived(_ notification: NSNotification) {
guard let ringId = notification.userInfo?[ProfileNotificationsKeys.ringID.rawValue] as? String else {
return
}
guard let message = notification.userInfo?[ProfileNotificationsKeys.message.rawValue] as? [String: String] else {
return
}
guard let accountId = notification.userInfo?[ProfileNotificationsKeys.accountId.rawValue] as? String else {
return
}
if let vCardKey = message.keys.filter({ $0.hasPrefix(self.ringVCardMIMEType) }).first {
//Parse the key to get the number of parts and the current part number
let components = vCardKey.components(separatedBy: ",")
guard let partComponent = components.filter({ $0.hasPrefix("part=") }).first else {
return
}
guard let ofComponent = components.filter({ $0.hasPrefix("of=") }).first else {
return
}
guard let idComponent = components.filter({ $0.hasPrefix("x-ring/ring.profile.vcard;id=") }).first else {
return
}
guard let part = Int(partComponent.components(separatedBy: "=")[1]) else {
return
}
guard let of = Int(ofComponent.components(separatedBy: "=")[1]) else {
return
}
guard let id = Int(idComponent.components(separatedBy: "=")[1]) else {
return
}
var numberOfReceivedChunk = 1
if var chunk = self.base64VCards[id] {
chunk.data[part] = message[vCardKey]
chunk.partsReceived += 1
numberOfReceivedChunk = chunk.partsReceived
self.base64VCards[id] = chunk
} else {
if let partMessage = message[vCardKey] {
let data: [Int: String] = [part: partMessage]
let chunk = Base64VCard(data: data, partsReceived: numberOfReceivedChunk)
self.base64VCards[id] = chunk
}
}
//Build the vCard when all data are appended
if of == numberOfReceivedChunk {
self.buildVCardFromChunks(cardID: id, ringID: ringId, accountId: accountId)
}
}
}
private func buildVCardFromChunks(cardID: Int, ringID: String, accountId: String) {
guard let vcard = self.base64VCards[cardID] else {
return
}
let vCardChunks = vcard.data
//Append data from sorted part numbers
var vCardData = Data()
for currentPartNumber in vCardChunks.keys.sorted() {
if let currentData = vCardChunks[currentPartNumber]?.data(using: String.Encoding.utf8) {
vCardData.append(currentData)
}
}
if let datautf8 = String(data: vCardData, encoding: .utf8),
let dataUtf16 = datautf8.data(using: String.Encoding.utf16) {
vCardData = dataUtf16
}
//Create the vCard, save and db and emit a new event
if let vCard = CNContactVCardSerialization.parseToVCard(data: vCardData) {
let name = VCardUtils.getName(from: vCard)
var stringImage: String?
if let image = vCard.imageData {
stringImage = image.base64EncodedString()
}
guard let uri = JamiURI.init(schema: URIType.ring,
infoHach: ringID).uriString else {
return
}
_ = self.dbManager
.createOrUpdateRingProfile(profileUri: uri,
alias: name,
image: stringImage,
accountId: accountId)
self.triggerProfileSignal(uri: uri, createIfNotexists: false, accountId: accountId)
}
}
private func triggerProfileSignal(uri: String, createIfNotexists: Bool, accountId: String) {
guard let profileObservable = self.profiles[uri] else {
return
}
self.dbManager
.profileObservable(for: uri, createIfNotExists: createIfNotexists, accountId: accountId)
.subscribe(onNext: {profile in
profileObservable.onNext(profile)
})
.disposed(by: self.disposeBag)
}
func getProfile(uri: String, createIfNotexists: Bool, accountId: String) -> Observable<Profile> {
if let profile = self.profiles[uri] {
return profile.asObservable().share()
}
let profileObservable = ReplaySubject<Profile>.create(bufferSize: 1)
self.profiles[uri] = profileObservable
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.triggerProfileSignal(uri: uri,
createIfNotexists: createIfNotexists,
accountId: accountId)
}
return profileObservable.share()
}
}
// MARK: account profile
extension ProfilesService {
func getAccountProfile(accountId: String) -> Observable<Profile> {
if let profile = self.accountProfiles[accountId] {
return profile.asObservable().share()
}
let profileObservable = ReplaySubject<Profile>.create(bufferSize: 1)
self.accountProfiles[accountId] = profileObservable
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.triggerAccountProfileSignal(accountId: accountId)
}
return profileObservable.share()
}
private func triggerAccountProfileSignal(accountId: String) {
guard let profileObservable = self.accountProfiles[accountId] else {
return
}
self.dbManager
.accountProfileObservable(for: accountId)
.subscribe(onNext: {profile in
profileObservable.onNext(profile)
})
.disposed(by: self.disposeBag)
}
func updateAccountProfile(accountId: String, alias: String?, photo: String?, accountURI: String) {
if self.dbManager
.saveAccountProfile(alias: alias, photo: photo,
accountId: accountId, accountURI: accountURI) {
self.triggerAccountProfileSignal(accountId: accountId)
}
}
}