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 <andreas.traczyk@savoirfairelinux.com>
diff --git a/Ring/Ring/Account/VCardUtils.swift b/Ring/Ring/Account/VCardUtils.swift
index 9a360b1..4bf21f3 100644
--- a/Ring/Ring/Account/VCardUtils.swift
+++ b/Ring/Ring/Account/VCardUtils.swift
@@ -31,7 +31,6 @@
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 @@
}
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)
+ }
+ }
}
diff --git a/Ring/Ring/Bridging/CallsAdapter.h b/Ring/Ring/Bridging/CallsAdapter.h
index eb82cec..3d0e2a3 100644
--- a/Ring/Ring/Bridging/CallsAdapter.h
+++ b/Ring/Ring/Bridging/CallsAdapter.h
@@ -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
diff --git a/Ring/Ring/Bridging/CallsAdapter.mm b/Ring/Ring/Bridging/CallsAdapter.mm
index fda3582..d970ee1 100644
--- a/Ring/Ring/Bridging/CallsAdapter.mm
+++ b/Ring/Ring/Bridging/CallsAdapter.mm
@@ -161,6 +161,10 @@
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];
diff --git a/Ring/Ring/Calls/CallViewController.swift b/Ring/Ring/Calls/CallViewController.swift
index 51f977c..c378561 100644
--- a/Ring/Ring/Calls/CallViewController.swift
+++ b/Ring/Ring/Calls/CallViewController.swift
@@ -56,6 +56,17 @@
}).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 @@
func removeFromScreen() {
self.dismiss(animated: false)
}
-
}
diff --git a/Ring/Ring/Calls/CallViewModel.swift b/Ring/Ring/Calls/CallViewModel.swift
index 6791413..0759cf2 100644
--- a/Ring/Ring/Calls/CallViewModel.swift
+++ b/Ring/Ring/Calls/CallViewModel.swift
@@ -37,10 +37,41 @@
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 @@
}
})
}()
+
required init(with injectionBag: InjectionBag) {
self.callService = injectionBag.callService
self.contactsService = injectionBag.contactsService
@@ -130,6 +162,7 @@
}
func placeCall(with uri: String, userName: String) {
+
guard let account = self.accountService.currentAccount else {
return
}
@@ -138,9 +171,16 @@
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
+ }
}
diff --git a/Ring/Ring/Database/DBHelpers/ProfileDataHelper.swift b/Ring/Ring/Database/DBHelpers/ProfileDataHelper.swift
index 01a1819..0b1a43a 100644
--- a/Ring/Ring/Database/DBHelpers/ProfileDataHelper.swift
+++ b/Ring/Ring/Database/DBHelpers/ProfileDataHelper.swift
@@ -158,7 +158,6 @@
guard rowId > 0 else {
throw DataAccessError.databaseError
}
- return
}
}
}
diff --git a/Ring/Ring/Services/CallsService.swift b/Ring/Ring/Services/CallsService.swift
index 99d85a0..4a09f7a 100644
--- a/Ring/Ring/Services/CallsService.swift
+++ b/Ring/Ring/Services/CallsService.swift
@@ -32,6 +32,11 @@
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 @@
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 @@
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 @@
//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 @@
}
}
+ 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) {
diff --git a/Ring/Ring/Services/ContactsService.swift b/Ring/Ring/Services/ContactsService.swift
index 34b51cb..15f0973 100644
--- a/Ring/Ring/Services/ContactsService.swift
+++ b/Ring/Ring/Services/ContactsService.swift
@@ -295,4 +295,9 @@
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))
+ }
}
diff --git a/Ring/Ring/Services/ServiceEvent.swift b/Ring/Ring/Services/ServiceEvent.swift
index 76decfa..0ee0a73 100644
--- a/Ring/Ring/Services/ServiceEvent.swift
+++ b/Ring/Ring/Services/ServiceEvent.swift
@@ -36,6 +36,7 @@
case contactRequestSended
case contactRequestReceived
case contactRequestDiscarded
+ case profileUpdated
}
/**