Commit 7cd6e685 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Andreas Traczyk

call: save and send vcard

This patch:
- Send vcard during call and save received vcard in database.
- save call details when place call

Change-Id: I2f0a3db9c7db8ea71e7df8f0bb562866f96ea9ae
Reviewed-by: Andreas Traczyk's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
parent 2ebdc425
......@@ -31,7 +31,6 @@ enum VCardFiles: String {
case myProfile
}
class VCardUtils {
class func saveVCard(vCard: CNContact, withName name: String, inFolder folder: String) -> Observable<Void> {
return Observable.create { observable in
if let directoryURL = VCardUtils.getFilePath(forFile: name, inFolder: folder, createIfNotExists: true) {
......@@ -124,4 +123,40 @@ class VCardUtils {
}
return name
}
class func sendVCard(card: CNContact, callID: String, accountID: String, sender: CallsService) {
do {
let vCard = card
let vCardData = try CNContactVCardSerialization.dataWithImageAndUUID(from: vCard, andImageCompression: 40000)
guard var vCardString = String(data: vCardData, encoding: String.Encoding.utf8) else {
return
}
var vcardLength = vCardString.count
let chunkSize = 1024
let idKey = Int64(arc4random_uniform(10000000))
let total = vcardLength / chunkSize + (((vcardLength % chunkSize) == 0) ? 0 : 1)
var i = 1
while vcardLength > 0 {
var chunk = [String: String]()
let id = "id=" + "\(idKey)" + ","
let part = "part=" + "\(i)" + ","
let of = "of=" + "\(total)"
let key = "x-ring/ring.profile.vcard;" + id + part + of
if vcardLength >= chunkSize {
let body = String(vCardString.prefix(chunkSize))
let index = vCardString.index(vCardString.startIndex, offsetBy: (chunkSize))
vCardString = String(vCardString.suffix(from: index))
vcardLength = vCardString.count
chunk[key] = body
} else {
vcardLength = 0
chunk[key] = vCardString
}
i += 1
sender.sendChunk(callID: callID, message: chunk, accountId: accountID)
}
} catch {
print(error)
}
}
}
......@@ -36,5 +36,6 @@
- (NSString*)placeCallWithAccountId:(NSString*)accountId toRingId:(NSString*)ringId;
- (NSDictionary<NSString*,NSString*>*)callDetailsWithCallId:(NSString*)callId;
- (NSArray<NSString*>*)calls;
- (void) sendTextMessageWithCallID:(NSString*)callId message:(NSDictionary*)message accountId:(NSString*)accountId sMixed:(bool)isMixed;
@end
......@@ -161,6 +161,10 @@ static id <CallsAdapterDelegate> _delegate;
return [NSString stringWithUTF8String:callId.c_str()];
}
- (void)sendTextMessageWithCallID:(NSString*)callId message:(NSDictionary*)message accountId:(NSString*)accountId sMixed:(bool)isMixed {
sendTextMessage(std::string([callId UTF8String]), [Utils dictionnaryToMap:message], std::string([accountId UTF8String]), isMixed);
}
- (NSDictionary<NSString*,NSString*>*)callDetailsWithCallId:(NSString*)callId {
std::map<std::string, std::string> callDetails = getCallDetails(std::string([callId UTF8String]));
return [Utils mapToDictionnary:callDetails];
......
......@@ -56,6 +56,17 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
}).disposed(by: self.disposeBag)
//Data bindings
self.viewModel.contactImageData.asObservable()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] dataOrNil in
if let imageData = dataOrNil {
if let image = UIImage(data: imageData) {
self?.profileImageView.image = image
}
}
}).disposed(by: self.disposeBag)
self.viewModel.dismisVC
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] dismiss in
......@@ -83,5 +94,4 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
func removeFromScreen() {
self.dismiss(animated: false)
}
}
......@@ -37,10 +37,41 @@ class CallViewModel: Stateable, ViewModel {
private let disposeBag = DisposeBag()
fileprivate let log = SwiftyBeaver.self
var call: CallModel?
var call: CallModel? {
didSet {
guard let call = self.call else {
return
}
self.contactsService.getProfileForUri(uri: call.participantRingId)
.subscribe(onNext: { [unowned self] profile in
self.profileUpdated(profile: profile)
})
.disposed(by: self.disposeBag)
self.callService
.sharedResponseStream
.filter({ (event) in
if let uri: String = event.getEventInput(ServiceEventInput.uri) {
return event.eventType == ServiceEventType.profileUpdated
&& uri == call.participantRingId
}
return false
})
.subscribe(onNext: { [unowned self] _ in
self.contactsService.getProfileForUri(uri: call.participantRingId)
.subscribe(onNext: { profile in
self.profileUpdated(profile: profile)
})
.disposed(by: self.disposeBag)
})
.disposed(by: disposeBag)
}
}
// data for ViewCintroller binding
var contactImageData = Variable<Data?>(nil)
lazy var dismisVC: Observable<Bool> = {
return callService.currentCall.map({[weak self] call in
return call.state == .over || call.state == .failure && call.callId == self?.call?.callId
......@@ -91,6 +122,7 @@ class CallViewModel: Stateable, ViewModel {
}
})
}()
required init(with injectionBag: InjectionBag) {
self.callService = injectionBag.callService
self.contactsService = injectionBag.contactsService
......@@ -130,6 +162,7 @@ class CallViewModel: Stateable, ViewModel {
}
func placeCall(with uri: String, userName: String) {
guard let account = self.accountService.currentAccount else {
return
}
......@@ -138,9 +171,16 @@ class CallViewModel: Stateable, ViewModel {
userName: userName)
.subscribe(onSuccess: { [unowned self] callModel in
self.call = callModel
self.log.info("Call placed: \(callModel.callId)")
}, onError: { [unowned self] error in
self.log.error("Failed to place the call")
}).disposed(by: self.disposeBag)
}
func profileUpdated(profile: Profile) {
guard let photo = profile.photo else {
return
}
guard let data = NSData(base64Encoded: photo, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data? else {
return
}
self.contactImageData.value = data
}
}
......@@ -158,7 +158,6 @@ final class ProfileDataHelper {
guard rowId > 0 else {
throw DataAccessError.databaseError
}
return
}
}
}
......@@ -32,6 +32,11 @@ enum CallServiceError: Error {
case placeCallFailed
}
struct Base64VCard {
var data: [Int: String] //The key is the number of vCard part
var partsReceived: Int
}
class CallsService: CallsAdapterDelegate {
fileprivate let disposeBag = DisposeBag()
......@@ -39,14 +44,21 @@ class CallsService: CallsAdapterDelegate {
fileprivate let log = SwiftyBeaver.self
fileprivate var calls = [String: CallModel]()
fileprivate let ringVCardMIMEType = "x-ring/ring.profile.vcard"
fileprivate var base64VCards = [Int: Base64VCard]() //The key is the vCard id
fileprivate let ringVCardMIMEType = "x-ring/ring.profile.vcard;"
let currentCall = ReplaySubject<CallModel>.create(bufferSize: 1)
let newCall = Variable<CallModel>(CallModel(withCallId: "", callDetails: [:]))
//let receivedVCard = PublishSubject<Profile>()
let dbManager = DBManager(profileHepler: ProfileDataHelper(), conversationHelper: ConversationDataHelper(), interactionHepler: InteractionDataHelper())
fileprivate let responseStream = PublishSubject<ServiceEvent>()
var sharedResponseStream: Observable<ServiceEvent>
init(withCallsAdapter callsAdapter: CallsAdapter) {
self.callsAdapter = callsAdapter
self.responseStream.disposed(by: disposeBag)
self.sharedResponseStream = responseStream.share()
CallsAdapter.delegate = self
}
......@@ -113,9 +125,12 @@ class CallsService: CallsAdapterDelegate {
func placeCall(withAccount account: AccountModel, toRingId ringId: String, userName: String) -> Single<CallModel> {
//Create and emit the call
let call = CallModel(withCallId: ringId, callDetails: [String: String]())
var callDetails = [String: String]()
callDetails[CallDetailKey.callTypeKey.rawValue] = String(describing: CallType.outgoing)
callDetails[CallDetailKey.displayNameKey.rawValue] = userName
callDetails[CallDetailKey.accountIdKey.rawValue] = account.id
let call = CallModel(withCallId: ringId, callDetails: callDetails)
call.state = .connecting
call.registeredName = userName
return Single<CallModel>.create(subscribe: { single in
if let callId = self.callsAdapter.placeCall(withAccountId: account.id,
toRingId: "ring:\(ringId)"),
......@@ -150,6 +165,13 @@ class CallsService: CallsAdapterDelegate {
//Update the call
call?.state = CallState(rawValue: state)!
//send vCard
if (call?.state == .ringing && call?.callType == .outgoing) ||
(call?.state == .current && call?.callType == .incoming) {
let accountID = call?.accountId
self.sendVCard(callID: callId, accountID: accountID!)
}
//Emit the call to the observers
self.currentCall.onNext(call!)
......@@ -161,8 +183,109 @@ class CallsService: CallsAdapterDelegate {
}
}
func sendVCard(callID: String, accountID: String) {
if accountID.isEmpty || callID.isEmpty {
return
}
VCardUtils.loadVCard(named: VCardFiles.myProfile.rawValue,
inFolder: VCardFolders.profile.rawValue)
.subscribe(onSuccess: { [unowned self] card in
VCardUtils.sendVCard(card: card,
callID: callID,
accountID: accountID,
sender: self)
}).disposed(by: disposeBag)
}
func sendChunk(callID: String, message: [String: String], accountId: String) {
self.callsAdapter.sendTextMessage(withCallID: callID,
message: message,
accountId: accountId,
sMixed: true)
}
func didReceiveMessage(withCallId callId: String, fromURI uri: String, message: [String: String]) {
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 {
let partMessage = message[vCardKey]
let data: [Int: String] = [part: partMessage!]
let chunk = Base64VCard(data: data, partsReceived: numberOfReceivedChunk)
self.base64VCards[id] = chunk
}
//Emit the vCard when all data are appended
if of == numberOfReceivedChunk {
guard let vcard = self.base64VCards[id] 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)
}
}
//Create the vCard, save and db and emite an event
do {
if let vCard = try CNContactVCardSerialization.contacts(with: vCardData).first {
let name = VCardUtils.getName(from: vCard)
var stringImage: String?
if let image = vCard.imageData {
stringImage = image.base64EncodedString()
}
let uri = uri.replacingOccurrences(of: "@ring.dht", with: "")
_ = self.dbManager
.createOrUpdateRingProfile(profileUri: uri,
alias: name,
image: stringImage,
status: ProfileStatus.untrasted)
var event = ServiceEvent(withEventType: .profileUpdated)
event.addEventInput(.uri, value: uri)
self.responseStream.onNext(event)
}
} catch {
self.log.error(error)
}
}
}
}
func receivingCall(withAccountId accountId: String, callId: String, fromURI uri: String) {
......
......@@ -295,4 +295,9 @@ extension ContactsService: ContactsAdapterDelegate {
let vCard = VCardUtils.loadVCard(named: ringID, inFolder: VCardFolders.contacts.rawValue, contactService: self)
return vCard
}
func getProfileForUri(uri: String) ->Observable<Profile> {
return self.dbManager.profileObservable(for: uri, createIfNotExists: false)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
}
}
......@@ -36,6 +36,7 @@ enum ServiceEventType {
case contactRequestSended
case contactRequestReceived
case contactRequestDiscarded
case profileUpdated
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment