From 23968f6949a797ccf4fba70f9516a032b00e27df Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 14 May 2019 18:20:04 +0200 Subject: [PATCH 1/6] MXEvent: Fix JSON parsing of MXEvent.unsignedData.transationId --- MatrixSDK/JSONModels/Event/MXEventUnsignedData.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MatrixSDK/JSONModels/Event/MXEventUnsignedData.m b/MatrixSDK/JSONModels/Event/MXEventUnsignedData.m index fbaaf8fb06..7a4af36873 100644 --- a/MatrixSDK/JSONModels/Event/MXEventUnsignedData.m +++ b/MatrixSDK/JSONModels/Event/MXEventUnsignedData.m @@ -39,7 +39,7 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary MXJSONModelSetString(unsignedData->_prevSender, JSONDictionary[@"prev_sender"]); MXJSONModelSetDictionary(unsignedData->_prevContent, JSONDictionary[@"prev_content"]); MXJSONModelSetDictionary(unsignedData->_redactedBecause, JSONDictionary[@"redacted_because"]); - MXJSONModelSetDictionary(unsignedData->_transactionId, JSONDictionary[@"transaction_id"]); + MXJSONModelSetString(unsignedData->_transactionId, JSONDictionary[@"transaction_id"]); MXJSONModelSetDictionary(unsignedData->_inviteRoomState, JSONDictionary[@"invite_room_state"]); MXJSONModelSetMXJSONModel(unsignedData->_relations, MXEventRelations, JSONDictionary[@"m.relations"]); } From db8ab856439cff3a09993a8162480f4223608e9b Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 14 May 2019 19:24:35 +0200 Subject: [PATCH 2/6] Aggregations: Add a store (implemented with realm) to store reactions counts --- MatrixSDK.xcodeproj/project.pbxproj | 44 ++++ MatrixSDK/Aggregations/Data/MXReactionCount.h | 7 +- MatrixSDK/Aggregations/Data/MXReactionCount.m | 5 + .../Data/Store/MXAggregationsStore.h | 55 +++++ .../Store/Realm/MXRealmAggregationsMapper.h | 35 ++++ .../Store/Realm/MXRealmAggregationsMapper.m | 44 ++++ .../Store/Realm/MXRealmAggregationsStore.h | 27 +++ .../Store/Realm/MXRealmAggregationsStore.m | 192 ++++++++++++++++++ .../Data/Store/Realm/MXRealmReactionCount.h | 42 ++++ .../Data/Store/Realm/MXRealmReactionCount.m | 31 +++ MatrixSDK/Aggregations/MXAggregations.h | 7 + MatrixSDK/Aggregations/MXAggregations.m | 119 +++++++++-- MatrixSDKTests/MXReactionTests.m | 6 +- 13 files changed, 592 insertions(+), 22 deletions(-) create mode 100644 MatrixSDK/Aggregations/Data/Store/MXAggregationsStore.h create mode 100644 MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.h create mode 100644 MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.m create mode 100644 MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.h create mode 100644 MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.m create mode 100644 MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.h create mode 100644 MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.m diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 50a50794ad..35bb6ca0f4 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -39,6 +39,12 @@ 32114A851A262CE000FF2EC4 /* MXStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 32114A841A262CE000FF2EC4 /* MXStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32114A8F1A262ECB00FF2EC4 /* MXNoStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 32114A8D1A262ECB00FF2EC4 /* MXNoStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32114A901A262ECB00FF2EC4 /* MXNoStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 32114A8E1A262ECB00FF2EC4 /* MXNoStore.m */; }; + 32133015228AF4EF0070BA9B /* MXRealmAggregationsStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 32133013228AF4EF0070BA9B /* MXRealmAggregationsStore.h */; }; + 32133016228AF4EF0070BA9B /* MXRealmAggregationsStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 32133014228AF4EF0070BA9B /* MXRealmAggregationsStore.m */; }; + 32133019228B010C0070BA9B /* MXRealmReactionCount.h in Headers */ = {isa = PBXBuildFile; fileRef = 32133017228B010C0070BA9B /* MXRealmReactionCount.h */; }; + 3213301A228B010C0070BA9B /* MXRealmReactionCount.m in Sources */ = {isa = PBXBuildFile; fileRef = 32133018228B010C0070BA9B /* MXRealmReactionCount.m */; }; + 3213301D228B190F0070BA9B /* MXRealmAggregationsMapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 3213301B228B190F0070BA9B /* MXRealmAggregationsMapper.h */; }; + 3213301E228B190F0070BA9B /* MXRealmAggregationsMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3213301C228B190F0070BA9B /* MXRealmAggregationsMapper.m */; }; 321809B919EEBF3000377451 /* MXEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 321809B819EEBF3000377451 /* MXEventTests.m */; }; 321B413F1E09937E009EEEC7 /* MXRoomSummary.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B413D1E09937E009EEEC7 /* MXRoomSummary.h */; settings = {ATTRIBUTES = (Public, ); }; }; 321B41401E09937E009EEEC7 /* MXRoomSummary.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B413E1E09937E009EEEC7 /* MXRoomSummary.m */; }; @@ -185,6 +191,7 @@ 327E9AF02289C61100A98BC1 /* MXAggregations.m in Sources */ = {isa = PBXBuildFile; fileRef = 327E9AEE2289C61100A98BC1 /* MXAggregations.m */; }; 327E9AF62289D53800A98BC1 /* MXReactionCount.h in Headers */ = {isa = PBXBuildFile; fileRef = 327E9AF42289D53800A98BC1 /* MXReactionCount.h */; settings = {ATTRIBUTES = (Public, ); }; }; 327E9AF72289D53800A98BC1 /* MXReactionCount.m in Sources */ = {isa = PBXBuildFile; fileRef = 327E9AF52289D53800A98BC1 /* MXReactionCount.m */; }; + 327E9AFC228AC22800A98BC1 /* MXAggregationsStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 327E9AFA228AC22800A98BC1 /* MXAggregationsStore.h */; }; 327F8DB21C6112BA00581CA3 /* MXRoomThirdPartyInvite.h in Headers */ = {isa = PBXBuildFile; fileRef = 327F8DB01C6112BA00581CA3 /* MXRoomThirdPartyInvite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 327F8DB31C6112BA00581CA3 /* MXRoomThirdPartyInvite.m in Sources */ = {isa = PBXBuildFile; fileRef = 327F8DB11C6112BA00581CA3 /* MXRoomThirdPartyInvite.m */; }; 3281E89E19E299C000976E1A /* MXErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3281E89D19E299C000976E1A /* MXErrorTests.m */; }; @@ -467,6 +474,12 @@ 32114A841A262CE000FF2EC4 /* MXStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXStore.h; sourceTree = ""; }; 32114A8D1A262ECB00FF2EC4 /* MXNoStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXNoStore.h; sourceTree = ""; }; 32114A8E1A262ECB00FF2EC4 /* MXNoStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXNoStore.m; sourceTree = ""; }; + 32133013228AF4EF0070BA9B /* MXRealmAggregationsStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRealmAggregationsStore.h; sourceTree = ""; }; + 32133014228AF4EF0070BA9B /* MXRealmAggregationsStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRealmAggregationsStore.m; sourceTree = ""; }; + 32133017228B010C0070BA9B /* MXRealmReactionCount.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRealmReactionCount.h; sourceTree = ""; }; + 32133018228B010C0070BA9B /* MXRealmReactionCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRealmReactionCount.m; sourceTree = ""; }; + 3213301B228B190F0070BA9B /* MXRealmAggregationsMapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRealmAggregationsMapper.h; sourceTree = ""; }; + 3213301C228B190F0070BA9B /* MXRealmAggregationsMapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRealmAggregationsMapper.m; sourceTree = ""; }; 321809B819EEBF3000377451 /* MXEventTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MXEventTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 321B413D1E09937E009EEEC7 /* MXRoomSummary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomSummary.h; sourceTree = ""; }; 321B413E1E09937E009EEEC7 /* MXRoomSummary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomSummary.m; sourceTree = ""; }; @@ -617,6 +630,7 @@ 327E9AF12289C6B300A98BC1 /* MXAggregations_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXAggregations_Private.h; sourceTree = ""; }; 327E9AF42289D53800A98BC1 /* MXReactionCount.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXReactionCount.h; sourceTree = ""; }; 327E9AF52289D53800A98BC1 /* MXReactionCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXReactionCount.m; sourceTree = ""; }; + 327E9AFA228AC22800A98BC1 /* MXAggregationsStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXAggregationsStore.h; sourceTree = ""; }; 327F8DB01C6112BA00581CA3 /* MXRoomThirdPartyInvite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomThirdPartyInvite.h; sourceTree = ""; }; 327F8DB11C6112BA00581CA3 /* MXRoomThirdPartyInvite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomThirdPartyInvite.m; sourceTree = ""; }; 3281E89D19E299C000976E1A /* MXErrorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXErrorTests.m; sourceTree = ""; }; @@ -1314,12 +1328,35 @@ 327E9AF32289D4F700A98BC1 /* Data */ = { isa = PBXGroup; children = ( + 327E9AF9228AC1F600A98BC1 /* Store */, 327E9AF42289D53800A98BC1 /* MXReactionCount.h */, 327E9AF52289D53800A98BC1 /* MXReactionCount.m */, ); path = Data; sourceTree = ""; }; + 327E9AF9228AC1F600A98BC1 /* Store */ = { + isa = PBXGroup; + children = ( + 327E9AFA228AC22800A98BC1 /* MXAggregationsStore.h */, + 327E9AFE228AD4BE00A98BC1 /* Realm */, + ); + path = Store; + sourceTree = ""; + }; + 327E9AFE228AD4BE00A98BC1 /* Realm */ = { + isa = PBXGroup; + children = ( + 32133013228AF4EF0070BA9B /* MXRealmAggregationsStore.h */, + 32133014228AF4EF0070BA9B /* MXRealmAggregationsStore.m */, + 32133017228B010C0070BA9B /* MXRealmReactionCount.h */, + 32133018228B010C0070BA9B /* MXRealmReactionCount.m */, + 3213301B228B190F0070BA9B /* MXRealmAggregationsMapper.h */, + 3213301C228B190F0070BA9B /* MXRealmAggregationsMapper.m */, + ); + path = Realm; + sourceTree = ""; + }; 3281E8B219E42DFE00976E1A /* JSONModels */ = { isa = PBXGroup; children = ( @@ -1822,6 +1859,7 @@ B146D4EF21A5AF7F00D8C2C6 /* MXRealmEventScan.h in Headers */, B146D49C21A5A04300D8C2C6 /* MXMediaScanStoreDelegate.h in Headers */, 32322A4B1E575F65005DD155 /* MXAllowedCertificates.h in Headers */, + 3213301D228B190F0070BA9B /* MXRealmAggregationsMapper.h in Headers */, 32A1515B1DB525DA00400192 /* NSObject+sortedKeys.h in Headers */, 329D3E621E251027002E2F1E /* MXRoomSummaryUpdater.h in Headers */, 321B413F1E09937E009EEEC7 /* MXRoomSummary.h in Headers */, @@ -1835,6 +1873,7 @@ 3252DCBD224D144C0032264F /* MXKeyVerificationAccept.h in Headers */, 323547D52226D3F500F15F94 /* MXWellKnown.h in Headers */, 32FFB4F0217E146A00C96002 /* MXRecoveryKey.h in Headers */, + 32133015228AF4EF0070BA9B /* MXRealmAggregationsStore.h in Headers */, 32CE6FB81A409B1F00317F1E /* MXFileStoreMetaData.h in Headers */, 32A31BC820D401FC005916C7 /* MXRoomFilter.h in Headers */, B146D47421A5945800D8C2C6 /* MXAntivirusScanStatus.h in Headers */, @@ -1866,6 +1905,7 @@ 32B76EA320FDE2BE00B095F6 /* MXRoomMembersCount.h in Headers */, 32C6F93319DD814400EA4E9C /* MatrixSDK.h in Headers */, 324BE46C1E422766008D99D4 /* MXMegolmSessionData.h in Headers */, + 327E9AFC228AC22800A98BC1 /* MXAggregationsStore.h in Headers */, 323547DC2226FC5700F15F94 /* MXCredentials.h in Headers */, 021AFBA62179E91900742B2C /* MXEncryptedContentKey.h in Headers */, 3252DCC9224D1AEF0032264F /* MXKeyVerificationMac.h in Headers */, @@ -1954,6 +1994,7 @@ B146D4B221A5A21800D8C2C6 /* MXEventScanStore.h in Headers */, B17982F92119E4A2001FD722 /* MXRoomTombStoneContent.h in Headers */, 32A31BBE20D3F2EC005916C7 /* MXFilterObject.h in Headers */, + 32133019228B010C0070BA9B /* MXRealmReactionCount.h in Headers */, 3275FD9821A6B53300B9C13D /* MXLoginPolicyData.h in Headers */, F082946D1DB66C3D00CEAB63 /* MXInvite3PID.h in Headers */, 3233606F1A403A0D0071A488 /* MXFileStore.h in Headers */, @@ -2157,6 +2198,7 @@ 32FFB4F1217E146A00C96002 /* MXRecoveryKey.m in Sources */, 32DC15D51A8CF874006F9AD3 /* MXPushRuleEventMatchConditionChecker.m in Sources */, 320BBF411D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.m in Sources */, + 3213301E228B190F0070BA9B /* MXRealmAggregationsMapper.m in Sources */, 3259CD541DF860C300186944 /* MXRealmCryptoStore.m in Sources */, 321B41401E09937E009EEEC7 /* MXRoomSummary.m in Sources */, 32CAB1081A91EA34008C5BB9 /* MXPushRuleRoomMemberCountConditionChecker.m in Sources */, @@ -2218,6 +2260,7 @@ B146D4F221A5AF7F00D8C2C6 /* MXRealmEventScanMapper.m in Sources */, 32CE6FB91A409B1F00317F1E /* MXFileStoreMetaData.m in Sources */, 3264DB921CEC528D00B99881 /* MXAccountData.m in Sources */, + 3213301A228B010C0070BA9B /* MXRealmReactionCount.m in Sources */, 3250E7CB220C913900736CB5 /* MXCryptoTools.m in Sources */, 3252DCBE224D144C0032264F /* MXKeyVerificationAccept.m in Sources */, 322691331E5EF77D00966A6E /* MXDeviceListOperation.m in Sources */, @@ -2266,6 +2309,7 @@ 3252DCCA224D1AEF0032264F /* MXKeyVerificationMac.m in Sources */, 3295401A216385F100E300FC /* MXServerNoticeContent.m in Sources */, 02CAD439217DD12F0074700B /* MXContentScanResult.m in Sources */, + 32133016228AF4EF0070BA9B /* MXRealmAggregationsStore.m in Sources */, B146D47B21A5958400D8C2C6 /* MXAntivirusScanStatusFormatter.m in Sources */, 32A151491DAF7C0C00400192 /* MXKey.m in Sources */, F0C34CBB1C18C93700C36F09 /* MXSDKOptions.m in Sources */, diff --git a/MatrixSDK/Aggregations/Data/MXReactionCount.h b/MatrixSDK/Aggregations/Data/MXReactionCount.h index 39647bcf16..4d3d0aa3c3 100644 --- a/MatrixSDK/Aggregations/Data/MXReactionCount.h +++ b/MatrixSDK/Aggregations/Data/MXReactionCount.h @@ -22,7 +22,12 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) NSString *reaction; @property (nonatomic) NSUInteger count; -@property (nonatomic) BOOL myUserHasReacted; + +// The id of the event if our user has made this reaction +@property (nonatomic, nullable) NSString *myUserReactionEventId; + +// YES if our user has made this reaction +@property (nonatomic, readonly) BOOL myUserHasReacted; @end diff --git a/MatrixSDK/Aggregations/Data/MXReactionCount.m b/MatrixSDK/Aggregations/Data/MXReactionCount.m index d89c232a51..4fc8a58d0f 100644 --- a/MatrixSDK/Aggregations/Data/MXReactionCount.m +++ b/MatrixSDK/Aggregations/Data/MXReactionCount.m @@ -18,4 +18,9 @@ @implementation MXReactionCount +- (BOOL)myUserHasReacted +{ + return (_myUserReactionEventId != nil); +} + @end diff --git a/MatrixSDK/Aggregations/Data/Store/MXAggregationsStore.h b/MatrixSDK/Aggregations/Data/Store/MXAggregationsStore.h new file mode 100644 index 0000000000..ce08217f82 --- /dev/null +++ b/MatrixSDK/Aggregations/Data/Store/MXAggregationsStore.h @@ -0,0 +1,55 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXReactionCount.h" +#import "MXCredentials.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Store for aggregations data. + */ +@protocol MXAggregationsStore + +/** + Create a aggregations store for the passed credentials. + + @param credentials the credentials of the account. + @return the store. Call the open method before using it. + */ +- (instancetype)initWithCredentials:(MXCredentials *)credentials; + + +#pragma - Single object CRUD operations + +- (void)addOrUpdateReactionCount:(MXReactionCount*)reactionCount onEvent:(NSString*)eventId inRoom:(NSString*)roomId; +- (BOOL)hasReactionCountsOnEvent:(NSString*)eventId; +- (nullable MXReactionCount*)reactionCountForReaction:(NSString*)reaction onEvent:(NSString*)eventId; +- (void)deleteReactionCountsForReaction:(NSString*)reaction onEvent:(NSString*)eventId; + + +#pragma - Batch operations + +- (void)setReactionCounts:(NSArray *)reactionCounts onEvent:(NSString*)eventId inRoom:(NSString*)roomId; +- (nullable NSArray *)reactionCountsOnEvent:(NSString*)eventId; +- (void)deleteAllReactionCountsInRoom:(NSString*)roomId; +- (void)deleteAll; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.h b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.h new file mode 100644 index 0000000000..19de0161ed --- /dev/null +++ b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.h @@ -0,0 +1,35 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXReactionCount.h" +#import "MXRealmReactionCount.h" + +NS_ASSUME_NONNULL_BEGIN + + +/** + `MXRealmAggregationsMapper` is used to convert `MXRealmReactionCount` into `MXReactionCount` and vice versa. + */ +@interface MXRealmAggregationsMapper : NSObject + +- (MXReactionCount*)reactionCountFromRealmReactionCount:(MXRealmReactionCount*)realmReactionCount; +- (MXRealmReactionCount*)realmReactionCountFromReactionCount:(MXReactionCount*)reactionCount onEvent:(NSString*)eventId inRoomd:(NSString*)roomId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.m b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.m new file mode 100644 index 0000000000..1f56507fd3 --- /dev/null +++ b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsMapper.m @@ -0,0 +1,44 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRealmAggregationsMapper.h" + +@implementation MXRealmAggregationsMapper + +- (MXReactionCount*)reactionCountFromRealmReactionCount:(MXRealmReactionCount*)realmReactionCount +{ + MXReactionCount *reactionCount = [MXReactionCount new]; + reactionCount.reaction = realmReactionCount.reaction; + reactionCount.count = realmReactionCount.count; + reactionCount.myUserReactionEventId = realmReactionCount.myUserReactionEventId; + + return reactionCount; +} + +- (MXRealmReactionCount*)realmReactionCountFromReactionCount:(MXReactionCount*)reactionCount onEvent:(NSString*)eventId inRoomd:(NSString*)roomId +{ + MXRealmReactionCount *realmReactionCount= [MXRealmReactionCount new]; + realmReactionCount.eventId = eventId; + realmReactionCount.roomId = roomId; + realmReactionCount.reaction = reactionCount.reaction; + realmReactionCount.count = reactionCount.count; + realmReactionCount.myUserReactionEventId = reactionCount.myUserReactionEventId; + realmReactionCount.primaryKey = [MXRealmReactionCount primaryKeyFromEventId:eventId andReaction:reactionCount.reaction]; + + return realmReactionCount; +} + +@end diff --git a/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.h b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.h new file mode 100644 index 0000000000..d68328102f --- /dev/null +++ b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.h @@ -0,0 +1,27 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXAggregationsStore.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXRealmAggregationsStore : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.m b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.m new file mode 100644 index 0000000000..6fba09969a --- /dev/null +++ b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmAggregationsStore.m @@ -0,0 +1,192 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRealmAggregationsStore.h" + +#import +#import "MXRealmHelper.h" + +#import "MXRealmAggregationsMapper.h" + + +@interface MXRealmAggregationsStore () + +@property (nonatomic) NSString *userId; +@property (nonatomic) MXRealmAggregationsMapper *mapper; + +@end + + +@implementation MXRealmAggregationsStore + +- (nonnull instancetype)initWithCredentials:(nonnull MXCredentials *)credentials +{ + self = [super init]; + if (self) + { + self.userId = credentials.userId; + self.mapper = [MXRealmAggregationsMapper new]; + } + return self; +} + + +#pragma - Single object CRUD operations + +- (void)addOrUpdateReactionCount:(nonnull MXReactionCount *)reactionCount onEvent:(nonnull NSString *)eventId inRoom:(nonnull NSString *)roomId +{ + RLMRealm *realm = self.realm; + + [realm transactionWithBlock:^{ + MXRealmReactionCount *realmReactionCount = [self.mapper realmReactionCountFromReactionCount:reactionCount + onEvent:eventId + inRoomd:roomId]; + [realm addOrUpdateObject:realmReactionCount]; + }]; +} + +- (BOOL)hasReactionCountsOnEvent:(NSString*)eventId +{ + RLMResults *realmReactionCounts = [MXRealmReactionCount objectsInRealm:self.realm + where:@"eventId = %@", eventId]; + return (realmReactionCounts.count > 0); +} + +- (nullable MXReactionCount *)reactionCountForReaction:(nonnull NSString *)reaction onEvent:(nonnull NSString *)eventId +{ + MXRealmReactionCount *realmReactionCount = [MXRealmReactionCount objectsInRealm:self.realm + where:@"primaryKey = %@", [MXRealmReactionCount primaryKeyFromEventId:eventId andReaction:reaction]].firstObject; + + MXReactionCount *reactionCount; + if (realmReactionCount) + { + reactionCount = [self.mapper reactionCountFromRealmReactionCount:realmReactionCount]; + } + + return reactionCount; +} + +- (void)deleteReactionCountsForReaction:(nonnull NSString *)reaction onEvent:(nonnull NSString *)eventId +{ + RLMRealm *realm = self.realm; + + [realm transactionWithBlock:^{ + RLMResults *realmReactionCounts = [MXRealmReactionCount objectsInRealm:self.realm + where:@"primaryKey = %@", [MXRealmReactionCount primaryKeyFromEventId:eventId andReaction:reaction]]; + [realm deleteObjects:realmReactionCounts]; + }]; +} + + +#pragma - Batch operations + +- (void)setReactionCounts:(nonnull NSArray *)reactionCounts onEvent:(nonnull NSString *)eventId inRoom:(nonnull NSString *)roomId +{ + RLMRealm *realm = self.realm; + + [realm transactionWithBlock:^{ + for (MXReactionCount *reactionCount in reactionCounts) + { + MXRealmReactionCount *realmReactionCount = [self.mapper realmReactionCountFromReactionCount:reactionCount + onEvent:eventId + inRoomd:roomId]; + [realm addOrUpdateObject:realmReactionCount]; + } + }]; +} + +- (nullable NSArray *)reactionCountsOnEvent:(nonnull NSString *)eventId +{ + RLMResults *realmReactionCounts = [MXRealmReactionCount objectsInRealm:self.realm + where:@"eventId = %@", eventId]; + + NSMutableArray *reactionCounts; + if (realmReactionCounts.count) + { + reactionCounts = [NSMutableArray arrayWithCapacity:realmReactionCounts.count]; + for (MXRealmReactionCount *realmReactionCount in realmReactionCounts) + { + MXReactionCount *reactionCount = [self.mapper reactionCountFromRealmReactionCount:realmReactionCount]; + [reactionCounts addObject:reactionCount]; + } + } + + return reactionCounts; +} + +- (void)deleteAllReactionCountsInRoom:(nonnull NSString *)roomId +{ + RLMRealm *realm = self.realm; + + [realm transactionWithBlock:^{ + RLMResults *realmReactionCounts = [MXRealmReactionCount objectsInRealm:self.realm + where:@"roomId = %@", roomId]; + [realm deleteObjects:realmReactionCounts]; + }]; +} + +- (void)deleteAll +{ + RLMRealm *realm = self.realm; + + [realm transactionWithBlock:^{ + [realm deleteAllObjects]; + }]; +} + + +#pragma mark - Private + +- (nullable RLMRealm*)realm +{ + NSError *error; + RLMRealm *realm = [RLMRealm realmWithConfiguration:self.realmConfiguration error:&error]; + + if (error) + { + NSLog(@"[MXRealmFileProvider] realmForUser gets error: %@", error); + } + + return realm; +} + +- (nonnull RLMRealmConfiguration*)realmConfiguration +{ + RLMRealmConfiguration *realmConfiguration = [RLMRealmConfiguration defaultConfiguration]; + + NSString *fileName = @"Aggregations"; + // TODO: Use an MXFileManager to handle directory move from app container to shared container + NSURL *rootDirectoryURL = [[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil] URLByAppendingPathComponent:self.userId]; + NSString *realmFileExtension = [MXRealmHelper realmFileExtension]; + + NSURL *realmFileFolderURL = [rootDirectoryURL URLByAppendingPathComponent:@"Aggregations" isDirectory:YES]; + NSURL *realmFileURL = [[realmFileFolderURL URLByAppendingPathComponent:fileName isDirectory:NO] URLByAppendingPathExtension:realmFileExtension]; + + NSError *folderCreationError; + [[NSFileManager defaultManager] createDirectoryAtURL:realmFileFolderURL withIntermediateDirectories:YES attributes:nil error:&folderCreationError]; + + if (folderCreationError) + { + NSLog(@"[MXScanRealmFileProvider] Fail to create Realm folder %@ with error: %@", realmFileFolderURL, folderCreationError); + } + + realmConfiguration.fileURL = realmFileURL; + realmConfiguration.deleteRealmIfMigrationNeeded = YES; + + return realmConfiguration; +} + +@end diff --git a/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.h b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.h new file mode 100644 index 0000000000..ced00e662c --- /dev/null +++ b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.h @@ -0,0 +1,42 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + + +/** + `MXRealmReactionCount` is a Realm representation of `MXReactionCount`. + */ +@interface MXRealmReactionCount : RLMObject + +@property NSString *eventId; +@property NSString *roomId; +@property (nonatomic) NSString *reaction; +@property (nonatomic) NSInteger count; +@property (nonatomic) NSString *myUserReactionEventId; + +// We need a primary key to use [RLMRealm addOrUpdateObject] +@property (nonatomic) NSString *primaryKey; + ++ (NSString*)primaryKeyFromEventId:(NSString*)eventId andReaction:(NSString*)reaction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.m b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.m new file mode 100644 index 0000000000..ce3f217cf3 --- /dev/null +++ b/MatrixSDK/Aggregations/Data/Store/Realm/MXRealmReactionCount.m @@ -0,0 +1,31 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRealmReactionCount.h" + +@implementation MXRealmReactionCount + ++ (NSString *)primaryKey +{ + return @"primaryKey"; +} + ++ (NSString *)primaryKeyFromEventId:(NSString *)eventId andReaction:(NSString *)reaction +{ + return [NSString stringWithFormat:@"%@_%@", eventId, reaction]; +} + +@end diff --git a/MatrixSDK/Aggregations/MXAggregations.h b/MatrixSDK/Aggregations/MXAggregations.h index 86d783b744..e8ec00933f 100644 --- a/MatrixSDK/Aggregations/MXAggregations.h +++ b/MatrixSDK/Aggregations/MXAggregations.h @@ -57,6 +57,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSArray *)reactionsOnEvent:(NSString*)eventId inRoom:(NSString*)roomId; +/** + Clear cached data. + + Note: An initial sync is then required to get valid data. + */ +- (void)resetData; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Aggregations/MXAggregations.m b/MatrixSDK/Aggregations/MXAggregations.m index a4cdd9bc2e..110908ef15 100644 --- a/MatrixSDK/Aggregations/MXAggregations.m +++ b/MatrixSDK/Aggregations/MXAggregations.m @@ -24,11 +24,14 @@ #import "MXEventAnnotationChunk.h" #import "MXEventAnnotation.h" +#import "MXRealmAggregationsStore.h" + @interface MXAggregations () @property (nonatomic, weak) MXRestClient *restClient; -@property (nonatomic, weak) id store; +@property (nonatomic, weak) id matrixStore; +@property (nonatomic) id store; @end @@ -45,6 +48,7 @@ - (MXHTTPOperation*)sendReaction:(NSString*)reaction success:(void (^)(NSString *eventId))success failure:(void (^)(NSError *error))failure { + // TODO: sendReaction should return only when the actual reaction event comes back the sync return [self.restClient sendRelationToEvent:eventId inRoom:roomId relationType:MXEventRelationTypeAnnotation @@ -58,25 +62,11 @@ - (MXHTTPOperation*)sendReaction:(NSString*)reaction - (nullable NSArray *)reactionsOnEvent:(NSString *)eventId inRoom:(NSString *)roomId { - NSMutableArray *reactions; + NSArray *reactions = [self.store reactionCountsOnEvent:eventId]; - // TODO: change that to use a separate dedicated store where data is aggregated - MXEvent *event = [self.store eventWithEventId:eventId inRoom:roomId]; - if (event) + if (!reactions) { - reactions = [NSMutableArray array]; - - for (MXEventAnnotation *annotation in event.unsignedData.relations.annotation.chunk) - { - if ([annotation.type isEqualToString:MXEventAnnotationReaction]) - { - MXReactionCount *reactionCount = [MXReactionCount new]; - reactionCount.reaction = annotation.key; - reactionCount.count = annotation.count; - - [reactions addObject:reactionCount]; - } - } + reactions = [self reactionCountsFromMatrixStoreOnEvent:eventId inRoom:roomId]; } return reactions; @@ -91,7 +81,8 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession if (self) { self.restClient = mxSession.matrixRestClient; - self.store = mxSession.store; + self.matrixStore = mxSession.store; + self.store = [[MXRealmAggregationsStore alloc] initWithCredentials:mxSession.matrixRestClient.credentials]; [mxSession listenToEventsOfTypes:@[kMXEventTypeStringReaction] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { @@ -105,10 +96,98 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession return self; } +- (void)resetData +{ + [self.store deleteAll]; +} + + +#pragma mark - Private methods - + - (void)handleReaction:(MXEvent *)event { - // TODO: but need a dedicated store + NSString *parentEventId = event.relatesTo.eventId; + NSString *reaction = event.relatesTo.key; + + if (parentEventId && reaction) + { + [self addReaction:reaction toEvent:parentEventId reactionEvent:event]; + } + else + { + NSLog(@"[MXAggregations] handleReaction: ERROR: invalid reaction event: %@", event); + } +} + +- (void)addReaction:(NSString*)reaction toEvent:(NSString*)eventId reactionEvent:(MXEvent *)reactionEvent +{ + // Update the current reaction count if it exists + MXReactionCount *reactionCount = [self.store reactionCountForReaction:reaction onEvent:eventId]; + + if (!reactionCount) + { + if ([self.store hasReactionCountsOnEvent:eventId]) + { + // Else, if the aggregations store has already reaction on the event, create a new reaction count object + reactionCount = [MXReactionCount new]; + reactionCount.reaction = reaction; + } + else + { + // Else, this is maybe the data is not yet transferred from the default matrix store to + // the aggregation store. + // Do the import + NSArray *reactions = [self reactionCountsFromMatrixStoreOnEvent:eventId inRoom:reactionEvent.roomId]; + if (reactions) + { + [self.store setReactionCounts:reactions onEvent:eventId inRoom:reactionEvent.eventId]; + } + + reactionCount = [self.store reactionCountForReaction:reaction onEvent:eventId]; + if (!reactionCount) + { + // If we still have no reaction count object, create one + reactionCount = [MXReactionCount new]; + reactionCount.reaction = reaction; + } + } + } + + // Add the reaction + reactionCount.count++; + + // Store reaction made by our user + if ([reactionEvent.sender isEqualToString:self.restClient.credentials.userId]) + { + reactionCount.myUserReactionEventId = reactionEvent.eventId; + } + + [self.store addOrUpdateReactionCount:reactionCount onEvent:eventId inRoom:reactionEvent.roomId]; } +- (nullable NSArray *)reactionCountsFromMatrixStoreOnEvent:(NSString*)eventId inRoom:(NSString*)roomId +{ + NSMutableArray *reactions; + + MXEvent *event = [self.matrixStore eventWithEventId:eventId inRoom:roomId]; + if (event) + { + NSMutableArray *reactions = [NSMutableArray array]; + + for (MXEventAnnotation *annotation in event.unsignedData.relations.annotation.chunk) + { + if ([annotation.type isEqualToString:MXEventAnnotationReaction]) + { + MXReactionCount *reactionCount = [MXReactionCount new]; + reactionCount.reaction = annotation.key; + reactionCount.count = annotation.count; + + [reactions addObject:reactionCount]; + } + } + } + + return reactions; +} @end diff --git a/MatrixSDKTests/MXReactionTests.m b/MatrixSDKTests/MXReactionTests.m index e84da511b0..e208117a6d 100644 --- a/MatrixSDKTests/MXReactionTests.m +++ b/MatrixSDKTests/MXReactionTests.m @@ -58,7 +58,10 @@ - (void)createScenario:(void(^)(MXSession *mxSession, MXRoom *room, XCTestExpect [mxSession.aggregations sendReaction:@"👍" toEvent:eventId inRoom:room.roomId success:^(NSString *reactionEventId) { - readyToTest(mxSession, room, expectation, eventId, reactionEventId); + // TODO: sendReaction should return only when the actual reaction event comes back the sync + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + readyToTest(mxSession, room, expectation, eventId, reactionEventId); + }); } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); @@ -119,6 +122,7 @@ - (void)testAggregationsFromInitialSync MXRestClient *restClient = mxSession.matrixRestClient; + [mxSession.aggregations resetData]; [mxSession close]; mxSession = nil; From 91ffd1a5b34c95ea61adf0cc6f7c101f52edc68f Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 15 May 2019 07:40:37 +0200 Subject: [PATCH 3/6] Aggregations: store aggregated reactions only for events in timelines we have Because of gappy syncs, we cannot keep aggregated data in-sync --- MatrixSDK/Aggregations/MXAggregations.m | 16 +++++++++++++--- MatrixSDK/Aggregations/MXAggregations_Private.h | 9 +++++++++ MatrixSDK/Data/MXEventTimeline.m | 4 ++++ MatrixSDK/MXSession.m | 1 + 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/MatrixSDK/Aggregations/MXAggregations.m b/MatrixSDK/Aggregations/MXAggregations.m index 110908ef15..35b3769112 100644 --- a/MatrixSDK/Aggregations/MXAggregations.m +++ b/MatrixSDK/Aggregations/MXAggregations.m @@ -72,6 +72,11 @@ - (MXHTTPOperation*)sendReaction:(NSString*)reaction return reactions; } +- (void)resetData +{ + [self.store deleteAll]; +} + #pragma mark - SDK-Private methods - @@ -96,9 +101,9 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession return self; } -- (void)resetData +- (void)resetDataInRoom:(NSString *)roomId { - [self.store deleteAll]; + [self.store deleteAllReactionCountsInRoom:roomId]; } @@ -111,7 +116,12 @@ - (void)handleReaction:(MXEvent *)event if (parentEventId && reaction) { - [self addReaction:reaction toEvent:parentEventId reactionEvent:event]; + // Manage aggregated reactions only for events in timelines we have + MXEvent *parentEvent = [self.matrixStore eventWithEventId:parentEventId inRoom:event.roomId]; + if (parentEvent) + { + [self addReaction:reaction toEvent:parentEventId reactionEvent:event]; + } } else { diff --git a/MatrixSDK/Aggregations/MXAggregations_Private.h b/MatrixSDK/Aggregations/MXAggregations_Private.h index 75b4fe7e84..8d71032483 100644 --- a/MatrixSDK/Aggregations/MXAggregations_Private.h +++ b/MatrixSDK/Aggregations/MXAggregations_Private.h @@ -33,6 +33,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithMatrixSession:(MXSession *)mxSession; +/** + Clear cached data for a room. + + Events for that rooms are no more part of our timelines. + Because of gappy syncs, we cannot guarantee the data is up-to-date. So, erase it. + We will get aggregated data again in bundled data when paginating events. + */ +- (void)resetDataInRoom:(NSString *)roomId; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Data/MXEventTimeline.m b/MatrixSDK/Data/MXEventTimeline.m index ac092181d0..fad6a7109a 100644 --- a/MatrixSDK/Data/MXEventTimeline.m +++ b/MatrixSDK/Data/MXEventTimeline.m @@ -20,6 +20,7 @@ #import "MXSession.h" #import "MXMemoryStore.h" +#import "MXAggregations_Private.h" #import "MXError.h" #import "MXTools.h" @@ -443,6 +444,9 @@ - (void)handleJoinedRoomSync:(MXRoomSync *)roomSync { // Flush the existing messages for this room by keeping state events. [store deleteAllMessagesInRoom:_state.roomId]; + + // Flush aggregated data for the events in the timeline + [room.mxSession.aggregations resetDataInRoom:_state.roomId]; } for (MXEvent *event in roomSync.timeline.events) diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index a9f3a71008..5959bc6582 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -2215,6 +2215,7 @@ - (void)removeRoom:(NSString *)roomId // Clean the store [_store deleteRoom:roomId]; + [_aggregations resetDataInRoom:roomId]; // And remove the room and its summary from the list [rooms removeObjectForKey:roomId]; From 77f67cf4d63487371c94e3dd2289a67a82f3bdb2 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 15 May 2019 10:26:30 +0200 Subject: [PATCH 4/6] Aggregations: Add a change notification mechanism --- MatrixSDK.xcodeproj/project.pbxproj | 16 +++++ .../Aggregations/Data/MXReactionCountChange.h | 31 ++++++++++ .../Aggregations/Data/MXReactionCountChange.m | 24 ++++++++ .../Data/MXReactionCountChangeListener.h | 32 ++++++++++ .../Data/MXReactionCountChangeListener.m | 21 +++++++ MatrixSDK/Aggregations/MXAggregations.h | 19 ++++++ MatrixSDK/Aggregations/MXAggregations.m | 58 +++++++++++++++++++ MatrixSDKTests/MXReactionTests.m | 36 ++++++++++++ 8 files changed, 237 insertions(+) create mode 100644 MatrixSDK/Aggregations/Data/MXReactionCountChange.h create mode 100644 MatrixSDK/Aggregations/Data/MXReactionCountChange.m create mode 100644 MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h create mode 100644 MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.m diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 35bb6ca0f4..3c56ee2a1b 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -45,6 +45,10 @@ 3213301A228B010C0070BA9B /* MXRealmReactionCount.m in Sources */ = {isa = PBXBuildFile; fileRef = 32133018228B010C0070BA9B /* MXRealmReactionCount.m */; }; 3213301D228B190F0070BA9B /* MXRealmAggregationsMapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 3213301B228B190F0070BA9B /* MXRealmAggregationsMapper.h */; }; 3213301E228B190F0070BA9B /* MXRealmAggregationsMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3213301C228B190F0070BA9B /* MXRealmAggregationsMapper.m */; }; + 32133021228BF7BC0070BA9B /* MXReactionCountChange.h in Headers */ = {isa = PBXBuildFile; fileRef = 3213301F228BF7BC0070BA9B /* MXReactionCountChange.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32133022228BF7BC0070BA9B /* MXReactionCountChange.m in Sources */ = {isa = PBXBuildFile; fileRef = 32133020228BF7BC0070BA9B /* MXReactionCountChange.m */; }; + 32133025228BFA800070BA9B /* MXReactionCountChangeListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 32133023228BFA800070BA9B /* MXReactionCountChangeListener.h */; }; + 32133026228BFA800070BA9B /* MXReactionCountChangeListener.m in Sources */ = {isa = PBXBuildFile; fileRef = 32133024228BFA800070BA9B /* MXReactionCountChangeListener.m */; }; 321809B919EEBF3000377451 /* MXEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 321809B819EEBF3000377451 /* MXEventTests.m */; }; 321B413F1E09937E009EEEC7 /* MXRoomSummary.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B413D1E09937E009EEEC7 /* MXRoomSummary.h */; settings = {ATTRIBUTES = (Public, ); }; }; 321B41401E09937E009EEEC7 /* MXRoomSummary.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B413E1E09937E009EEEC7 /* MXRoomSummary.m */; }; @@ -480,6 +484,10 @@ 32133018228B010C0070BA9B /* MXRealmReactionCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRealmReactionCount.m; sourceTree = ""; }; 3213301B228B190F0070BA9B /* MXRealmAggregationsMapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRealmAggregationsMapper.h; sourceTree = ""; }; 3213301C228B190F0070BA9B /* MXRealmAggregationsMapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRealmAggregationsMapper.m; sourceTree = ""; }; + 3213301F228BF7BC0070BA9B /* MXReactionCountChange.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXReactionCountChange.h; sourceTree = ""; }; + 32133020228BF7BC0070BA9B /* MXReactionCountChange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXReactionCountChange.m; sourceTree = ""; }; + 32133023228BFA800070BA9B /* MXReactionCountChangeListener.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXReactionCountChangeListener.h; sourceTree = ""; }; + 32133024228BFA800070BA9B /* MXReactionCountChangeListener.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXReactionCountChangeListener.m; sourceTree = ""; }; 321809B819EEBF3000377451 /* MXEventTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MXEventTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 321B413D1E09937E009EEEC7 /* MXRoomSummary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomSummary.h; sourceTree = ""; }; 321B413E1E09937E009EEEC7 /* MXRoomSummary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomSummary.m; sourceTree = ""; }; @@ -1331,6 +1339,10 @@ 327E9AF9228AC1F600A98BC1 /* Store */, 327E9AF42289D53800A98BC1 /* MXReactionCount.h */, 327E9AF52289D53800A98BC1 /* MXReactionCount.m */, + 3213301F228BF7BC0070BA9B /* MXReactionCountChange.h */, + 32133020228BF7BC0070BA9B /* MXReactionCountChange.m */, + 32133023228BFA800070BA9B /* MXReactionCountChangeListener.h */, + 32133024228BFA800070BA9B /* MXReactionCountChangeListener.m */, ); path = Data; sourceTree = ""; @@ -1926,6 +1938,7 @@ 322691321E5EF77D00966A6E /* MXDeviceListOperation.h in Headers */, 32481A841C03572900782AD3 /* MXRoomAccountData.h in Headers */, 327E9AE12285497100A98BC1 /* MXEventContentRelatesTo.h in Headers */, + 32133025228BFA800070BA9B /* MXReactionCountChangeListener.h in Headers */, 321CFDFB2254E728004D31DF /* MXTransactionCancelCode.h in Headers */, 32A9770421626E5C00919CC0 /* MXServerNotices.h in Headers */, 021AFBA42179E91900742B2C /* MXEncryptedContentFile.h in Headers */, @@ -2030,6 +2043,7 @@ 3256E3811DCB91EB003C9718 /* MXCryptoConstants.h in Headers */, 320DFDE619DD99B60068622A /* MXHTTPClient.h in Headers */, 320A8840217F4E3F002EA952 /* MXMegolmBackupAuthData.h in Headers */, + 32133021228BF7BC0070BA9B /* MXReactionCountChange.h in Headers */, B146D4D721A5A44E00D8C2C6 /* MXScanRealmFileProvider.h in Headers */, 320DFDDB19DD99B60068622A /* MXRoom.h in Headers */, ); @@ -2311,11 +2325,13 @@ 02CAD439217DD12F0074700B /* MXContentScanResult.m in Sources */, 32133016228AF4EF0070BA9B /* MXRealmAggregationsStore.m in Sources */, B146D47B21A5958400D8C2C6 /* MXAntivirusScanStatusFormatter.m in Sources */, + 32133022228BF7BC0070BA9B /* MXReactionCountChange.m in Sources */, 32A151491DAF7C0C00400192 /* MXKey.m in Sources */, F0C34CBB1C18C93700C36F09 /* MXSDKOptions.m in Sources */, 320BBF441D6C81550079890E /* MXEventsEnumeratorOnArray.m in Sources */, 32618E7220ED2DF500E1D2EA /* MXFilterJSONModel.m in Sources */, 323F8865212D4E480001C73C /* MXMatrixVersions.m in Sources */, + 32133026228BFA800070BA9B /* MXReactionCountChangeListener.m in Sources */, 320A883D217F4E35002EA952 /* MXMegolmBackupCreationInfo.m in Sources */, 320DFDDC19DD99B60068622A /* MXRoom.m in Sources */, F08B8D5D1E014711006171A8 /* NSData+MatrixSDK.m in Sources */, diff --git a/MatrixSDK/Aggregations/Data/MXReactionCountChange.h b/MatrixSDK/Aggregations/Data/MXReactionCountChange.h new file mode 100644 index 0000000000..b86fcb5e0f --- /dev/null +++ b/MatrixSDK/Aggregations/Data/MXReactionCountChange.h @@ -0,0 +1,31 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXReactionCount.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXReactionCountChange : NSObject + +@property (nonatomic, nullable) NSArray *inserted; +@property (nonatomic, nullable) NSArray *modified; +@property (nonatomic, nullable) NSArray *deleted; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Aggregations/Data/MXReactionCountChange.m b/MatrixSDK/Aggregations/Data/MXReactionCountChange.m new file mode 100644 index 0000000000..d8b42a2b3b --- /dev/null +++ b/MatrixSDK/Aggregations/Data/MXReactionCountChange.m @@ -0,0 +1,24 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXReactionCountChange.h" + +/** + `MXReactionCountChange` represents changes on the reactions counts on an event. + */ +@implementation MXReactionCountChange + +@end diff --git a/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h b/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h new file mode 100644 index 0000000000..7b5bed4ab8 --- /dev/null +++ b/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h @@ -0,0 +1,32 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXReactionCountChange.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXReactionCountChangeListener : NSObject + +@property (nonatomic) NSString *roomId; + +// eventId -> changes +@property (nonatomic) void (^block)(NSDictionary *changes); + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.m b/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.m new file mode 100644 index 0000000000..003280da59 --- /dev/null +++ b/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.m @@ -0,0 +1,21 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXReactionCountChangeListener.h" + +@implementation MXReactionCountChangeListener + +@end diff --git a/MatrixSDK/Aggregations/MXAggregations.h b/MatrixSDK/Aggregations/MXAggregations.h index e8ec00933f..8d07bcfbb7 100644 --- a/MatrixSDK/Aggregations/MXAggregations.h +++ b/MatrixSDK/Aggregations/MXAggregations.h @@ -18,6 +18,7 @@ #import "MXHTTPOperation.h" #import "MXReactionCount.h" +#import "MXReactionCountChange.h" NS_ASSUME_NONNULL_BEGIN @@ -57,6 +58,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSArray *)reactionsOnEvent:(NSString*)eventId inRoom:(NSString*)roomId; + +/** + Add a listener to aggregated updates within a room. + + @param roomId the id of the room. + @param block the block called on updates. eventId -> reactionCounts changes + @return a listener id. + */ +- (id)listenToReactionCountUpdateInRoom:(NSString*)roomId block:(void (^)(NSDictionary *changes))block; + +/** + Remove a listener. + + @param listener the listener id. + */ +- (void)removeListener:(id)listener; + + /** Clear cached data. diff --git a/MatrixSDK/Aggregations/MXAggregations.m b/MatrixSDK/Aggregations/MXAggregations.m index 35b3769112..607408f271 100644 --- a/MatrixSDK/Aggregations/MXAggregations.m +++ b/MatrixSDK/Aggregations/MXAggregations.m @@ -25,6 +25,7 @@ #import "MXEventAnnotation.h" #import "MXRealmAggregationsStore.h" +#import "MXReactionCountChangeListener.h" @interface MXAggregations () @@ -32,6 +33,7 @@ @interface MXAggregations () @property (nonatomic, weak) MXRestClient *restClient; @property (nonatomic, weak) id matrixStore; @property (nonatomic) id store; +@property (nonatomic) NSMutableArray *listeners; @end @@ -72,6 +74,22 @@ - (MXHTTPOperation*)sendReaction:(NSString*)reaction return reactions; } +- (id)listenToReactionCountUpdateInRoom:(NSString *)roomId block:(void (^)(NSDictionary * _Nonnull))block +{ + MXReactionCountChangeListener *listener = [MXReactionCountChangeListener new]; + listener.roomId = roomId; + listener.block = block; + + [self.listeners addObject:listener]; + + return listener; +} + +- (void)removeListener:(id)listener +{ + [self.listeners removeObject:listener]; +} + - (void)resetData { [self.store deleteAll]; @@ -88,6 +106,7 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession self.restClient = mxSession.matrixRestClient; self.matrixStore = mxSession.store; self.store = [[MXRealmAggregationsStore alloc] initWithCredentials:mxSession.matrixRestClient.credentials]; + self.listeners = [NSMutableArray array]; [mxSession listenToEventsOfTypes:@[kMXEventTypeStringReaction] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { @@ -131,6 +150,8 @@ - (void)handleReaction:(MXEvent *)event - (void)addReaction:(NSString*)reaction toEvent:(NSString*)eventId reactionEvent:(MXEvent *)reactionEvent { + BOOL isANewReaction = NO; + // Update the current reaction count if it exists MXReactionCount *reactionCount = [self.store reactionCountForReaction:reaction onEvent:eventId]; @@ -141,6 +162,7 @@ - (void)addReaction:(NSString*)reaction toEvent:(NSString*)eventId reactionEvent // Else, if the aggregations store has already reaction on the event, create a new reaction count object reactionCount = [MXReactionCount new]; reactionCount.reaction = reaction; + isANewReaction = YES; } else { @@ -159,6 +181,7 @@ - (void)addReaction:(NSString*)reaction toEvent:(NSString*)eventId reactionEvent // If we still have no reaction count object, create one reactionCount = [MXReactionCount new]; reactionCount.reaction = reaction; + isANewReaction = YES; } } } @@ -172,7 +195,14 @@ - (void)addReaction:(NSString*)reaction toEvent:(NSString*)eventId reactionEvent reactionCount.myUserReactionEventId = reactionEvent.eventId; } + // Update store [self.store addOrUpdateReactionCount:reactionCount onEvent:eventId inRoom:reactionEvent.roomId]; + + // Notify + [self notifyReactionCountChangeListenersOfRoom:reactionEvent.roomId + event:eventId + reactionCount:reactionCount + isNewReaction:isANewReaction]; } - (nullable NSArray *)reactionCountsFromMatrixStoreOnEvent:(NSString*)eventId inRoom:(NSString*)roomId @@ -200,4 +230,32 @@ - (void)addReaction:(NSString*)reaction toEvent:(NSString*)eventId reactionEvent return reactions; } +- (void)notifyReactionCountChangeListenersOfRoom:(NSString*)roomId event:(NSString*)eventId reactionCount:(MXReactionCount*)reactionCount isNewReaction:(BOOL)isNewReaction +{ + MXReactionCountChange *reactionCountChange = [MXReactionCountChange new]; + if (isNewReaction) + { + reactionCountChange.inserted = @[reactionCount]; + } + else + { + reactionCountChange.modified = @[reactionCount]; + } + + [self notifyReactionCountChangeListenersOfRoom:roomId changes:@{ + eventId:reactionCountChange + }]; +} + +- (void)notifyReactionCountChangeListenersOfRoom:(NSString*)roomId changes:(NSDictionary*)changes +{ + for (MXReactionCountChangeListener *listener in self.listeners) + { + if ([listener.roomId isEqualToString:roomId]) + { + listener.block(changes); + } + } +} + @end diff --git a/MatrixSDKTests/MXReactionTests.m b/MatrixSDKTests/MXReactionTests.m index e208117a6d..3dc9d56a06 100644 --- a/MatrixSDKTests/MXReactionTests.m +++ b/MatrixSDKTests/MXReactionTests.m @@ -179,6 +179,42 @@ - (void)testAggregationsLive }]; } +// - Run the initial condition scenario +// - Add one more reaction +// -> We must get notified about the reaction count change +- (void)testAggregationsListener +{ + // - Run the initial condition scenario + [self createScenario:^(MXSession *mxSession, MXRoom *room, XCTestExpectation *expectation, NSString *eventId, NSString *reactionEventId) { + + // -> We must get notified about the reaction count change + [mxSession.aggregations listenToReactionCountUpdateInRoom:room.roomId block:^(NSDictionary * _Nonnull changes) { + + XCTAssertEqual(changes.count, 1, @"Only one change"); + + MXReactionCountChange *change = changes[eventId]; + XCTAssertNotNil(change); + XCTAssertNil(change.modified); + XCTAssertNil(change.deleted); + + XCTAssertEqual(change.inserted.count, 1, @"Only one change"); + MXReactionCount *reactionCount = change.inserted.firstObject; + XCTAssertEqualObjects(reactionCount.reaction, @"😄"); + XCTAssertEqual(reactionCount.count, 1); + XCTAssertTrue(reactionCount.myUserHasReacted,); + + [expectation fulfill]; + }]; + + // - Add one more reaction + [mxSession.aggregations sendReaction:@"😄" toEvent:eventId inRoom:room.roomId success:^(NSString *eventId) { + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + @end #pragma clang diagnostic pop From acbde885b5e4913afd6a4554e9b976a63505f0e1 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 May 2019 10:38:22 +0200 Subject: [PATCH 5/6] Aggregations: Update comments --- MatrixSDK/Aggregations/Data/MXReactionCount.h | 3 +++ MatrixSDK/Aggregations/MXAggregations.h | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/MatrixSDK/Aggregations/Data/MXReactionCount.h b/MatrixSDK/Aggregations/Data/MXReactionCount.h index 4d3d0aa3c3..d6f11a4a43 100644 --- a/MatrixSDK/Aggregations/Data/MXReactionCount.h +++ b/MatrixSDK/Aggregations/Data/MXReactionCount.h @@ -18,6 +18,9 @@ NS_ASSUME_NONNULL_BEGIN +/** + `MXReactionCount` provides number of reactions on a reaction. + */ @interface MXReactionCount : NSObject @property (nonatomic) NSString *reaction; diff --git a/MatrixSDK/Aggregations/MXAggregations.h b/MatrixSDK/Aggregations/MXAggregations.h index 8d07bcfbb7..80d3360fb9 100644 --- a/MatrixSDK/Aggregations/MXAggregations.h +++ b/MatrixSDK/Aggregations/MXAggregations.h @@ -60,7 +60,9 @@ NS_ASSUME_NONNULL_BEGIN /** - Add a listener to aggregated updates within a room. + Add a listener to aggregated updates of events within a room. + + Only updates on events stored in timelines are sent. @param roomId the id of the room. @param block the block called on updates. eventId -> reactionCounts changes From 4a80c846df7640eaf71c4683f54885cc558c900b Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 May 2019 14:12:43 +0200 Subject: [PATCH 6/6] Aggregations: Fix Steve's review comments --- MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h | 2 +- MatrixSDK/Aggregations/MXAggregations.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h b/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h index 7b5bed4ab8..d5b72a94b7 100644 --- a/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h +++ b/MatrixSDK/Aggregations/Data/MXReactionCountChangeListener.h @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) NSString *roomId; // eventId -> changes -@property (nonatomic) void (^block)(NSDictionary *changes); +@property (nonatomic, copy) void (^notificationBlock)(NSDictionary *changes); @end diff --git a/MatrixSDK/Aggregations/MXAggregations.m b/MatrixSDK/Aggregations/MXAggregations.m index 607408f271..833f26bf08 100644 --- a/MatrixSDK/Aggregations/MXAggregations.m +++ b/MatrixSDK/Aggregations/MXAggregations.m @@ -78,7 +78,7 @@ - (id)listenToReactionCountUpdateInRoom:(NSString *)roomId block:(void (^)(NSDic { MXReactionCountChangeListener *listener = [MXReactionCountChangeListener new]; listener.roomId = roomId; - listener.block = block; + listener.notificationBlock = block; [self.listeners addObject:listener]; @@ -253,7 +253,7 @@ - (void)notifyReactionCountChangeListenersOfRoom:(NSString*)roomId changes:(NSDi { if ([listener.roomId isEqualToString:roomId]) { - listener.block(changes); + listener.notificationBlock(changes); } } }