Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTL1 #180

Merged
merged 4 commits into from
Feb 3, 2016
Merged

RTL1 #180

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions ably-ios/ARTPresenceMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@
//

#import <Foundation/Foundation.h>
#import "CompatibilityMacros.h"

@class ARTPresenceMessage;

ART_ASSUME_NONNULL_BEGIN

/// Used to maintain a list of members present on a channel
@interface ARTPresenceMap : NSObject {

}
@interface ARTPresenceMap : NSObject

/// List of members.
/// The key is the clientId and the value is the latest relevant ARTPresenceMessage for that clientId.
@property (readonly, atomic, getter=getMembers) __GENERIC(NSDictionary, NSString *, ARTPresenceMessage *) *members;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no public members property in the API, only get, see http://docs.ably.io/client-lib-development-guide/features/#RSP3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is related with the PresenceMap, not with the Presence himself. Maybe it isn't consistent with other clients.

(RTP2) A PresenceMap should be used to maintain a list of members present on a channel.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The presence API is pretty broken anyway already. I see that you're only adding generics, which I think is OK. I'll get the API right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks @tcard, @ricardopereira ignore my comments tehne


@property (readwrite, nonatomic, assign) int64_t syncSerial;

- (ARTPresenceMessage *)getClient:(NSString *) clientId;
- (void)put:(ARTPresenceMessage *) message;

//of the form <NSString *, ARTPresenceMessage*> where
// the key is the clientId and the value is the latest relevant ARTPresenceMessage for that clientId.
- (NSDictionary *) members;
- (void)startSync;
- (void)endSync;
- (BOOL)isSyncComplete;
Expand All @@ -33,3 +35,5 @@ typedef void(^VoidCb)();
- (void)onSync:(VoidCb) cb;

@end

ART_ASSUME_NONNULL_END
19 changes: 9 additions & 10 deletions ably-ios/ARTPresenceMap.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@interface ARTPresenceMap ()

@property (readwrite, strong, atomic) NSMutableDictionary *mostRecentMessageForMember; //<clientId, artPresenceMessage *> message
@property (readwrite, strong, atomic) __GENERIC(NSMutableDictionary, NSString *, ARTPresenceMessage *) *mostRecentMessageForMember;
@property (readonly, nonatomic, assign) bool syncStarted;
@property (readonly, nonatomic, assign) bool syncComplete;
@property (nonatomic, copy) VoidCb cb;
Expand All @@ -20,23 +20,26 @@ @interface ARTPresenceMap ()

@implementation ARTPresenceMap

-(id) init {
- (id)init {
self = [super init];
if(self) {
self.mostRecentMessageForMember = [NSMutableDictionary dictionary];
_mostRecentMessageForMember = [NSMutableDictionary dictionary];
_syncStarted = false;
_syncComplete = false;
_cb = nil;
}
return self;
}

- (__GENERIC(NSDictionary, NSString *, ARTPresenceMessage *) *)getMembers {
return self.mostRecentMessageForMember;
}

- (void)onSync:(VoidCb)cb {
_cb = cb;
}

- (void) syncMessageProcessed {

- (void)syncMessageProcessed {
if(self.cb) {
self.cb();
}
Expand All @@ -52,14 +55,10 @@ - (void)put:(ARTPresenceMessage *) message {
[self.mostRecentMessageForMember setObject:message forKey:message.clientId];
}
}

- (void)startSync {
self.mostRecentMessageForMember = [NSMutableDictionary dictionary];
_syncStarted = true;

}

- (NSDictionary *) members {
return self.mostRecentMessageForMember;
}

- (void)endSync {
Expand Down
5 changes: 1 addition & 4 deletions ably-ios/ARTRealtimeChannel.m
Original file line number Diff line number Diff line change
Expand Up @@ -274,15 +274,12 @@ - (void)onChannelMessage:(ARTProtocolMessage *)message {
[self onError:message];
break;
case ARTProtocolMessageSync:
[self.presenceMap syncMessageProcessed];
break;
default:
[self.logger warn:@"ARTRealtime, unknown ARTProtocolMessage action: %tu", message.action];
break;
}

if(message.action == ARTProtocolMessageSync) {
[self.presenceMap syncMessageProcessed];
}
}

- (ARTRealtimeChannelState)state {
Expand Down
64 changes: 64 additions & 0 deletions ablySpec/RealtimeClientChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,70 @@ class RealtimeClientChannel: QuickSpec {
override func spec() {
describe("Channel") {

// RTL1
it("should process all incoming messages and presence messages as soon as a Channel becomes attached") {
let options = AblyTests.commonAppSetup()
let client1 = ARTRealtime(options: options)
defer { client1.close() }

let channel1 = client1.channel("room")
channel1.attach()

waitUntil(timeout: testTimeout) { done in
channel1.presence().enterClient("Client 1", data: nil) { status in
expect(status.state).to(equal(ARTState.Ok))
done()
}
}

options.clientId = "Client 2"
let client2 = ARTRealtime(options: options)
defer { client2.close() }

let channel2 = client2.channel(channel1.name)
channel2.attach()

expect(channel2.presence().isSyncComplete()).to(beFalse())

expect(channel1.presenceMap.members).to(haveCount(1))
expect(channel2.presenceMap.members).to(haveCount(0))

expect(channel2.state).toEventually(equal(ARTRealtimeChannelState.Attached), timeout: testTimeout)

expect(channel2.presence().isSyncComplete()).toEventually(beTrue(), timeout: testTimeout)

expect(channel1.presenceMap.members).to(haveCount(1))
expect(channel2.presenceMap.members).to(haveCount(1))

// Check if receives incoming messages
channel2.subscribeToName("Client 1") { message, errorInfo in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another API issue, but why do we have errorInfo here. When you subscribe to a message, you only ever get messages, never errors. Also, subscribeToName is simply subscribe, please raise an issue if not addressed in your changes @tcard

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed here #183.

expect(message.data as? String).to(equal("message"))
}

waitUntil(timeout: testTimeout) { done in
channel1.publish("message", cb: { status in
expect(status.state).to(equal(ARTState.Ok))
done()
})
}

waitUntil(timeout: testTimeout) { done in
channel2.presence().enter(nil) { status in
expect(status.state).to(equal(ARTState.Ok))
done()
}
}

expect(channel1.presenceMap.members).to(haveCount(2))
expect(channel1.presenceMap.members).to(allKeysPass({ $0.hasPrefix("Client") }))
expect(channel1.presenceMap.members).to(allValuesPass({ $0.action == .Enter }))

expect(channel2.presenceMap.members).to(haveCount(2))
expect(channel2.presenceMap.members).to(allKeysPass({ $0.hasPrefix("Client") }))
expect(channel2.presenceMap.members["Client 1"]!.action).to(equal(ARTPresenceAction.Present))
expect(channel2.presenceMap.members["Client 2"]!.action).to(equal(ARTPresenceAction.Enter))
}

// RTL4
describe("attach") {

Expand Down
66 changes: 66 additions & 0 deletions ablySpec/TestUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,69 @@ public func haveParam(key: String, withValue expectedValue: String) -> NonNilMat
return false
}
}

/// A Nimble matcher that succeeds when all Keys from a Dictionary are valid.
public func allKeysPass<U: CollectionType where U: DictionaryLiteralConvertible, U.Generator.Element == (U.Key, U.Value)> (passFunc: (U.Key) -> Bool) -> NonNilMatcherFunc<U> {

let elementEvaluator: (Expression<U.Generator.Element>, FailureMessage) throws -> Bool = {
expression, failureMessage in
failureMessage.postfixMessage = "pass a condition"
let value = try expression.evaluate()!
return passFunc(value.0)
}

return NonNilMatcherFunc { actualExpression, failureMessage in
failureMessage.actualValue = nil
if let actualValue = try actualExpression.evaluate() {
for item in actualValue {
let exp = Expression(expression: { item }, location: actualExpression.location)
if try !elementEvaluator(exp, failureMessage) {
failureMessage.postfixMessage =
"all \(failureMessage.postfixMessage),"
+ " but failed first at element <\(item.0)>"
+ " in <\(actualValue.map({ $0.0 }))>"
return false
}
}
failureMessage.postfixMessage = "all \(failureMessage.postfixMessage)"
} else {
failureMessage.postfixMessage = "all pass (use beNil() to match nils)"
return false
}

return true
}
}

/// A Nimble matcher that succeeds when all Values from a Dictionary are valid.
public func allValuesPass<U: CollectionType where U: DictionaryLiteralConvertible, U.Generator.Element == (U.Key, U.Value)> (passFunc: (U.Value) -> Bool) -> NonNilMatcherFunc<U> {

let elementEvaluator: (Expression<U.Generator.Element>, FailureMessage) throws -> Bool = {
expression, failureMessage in
failureMessage.postfixMessage = "pass a condition"
let value = try expression.evaluate()!
return passFunc(value.1)
}

return NonNilMatcherFunc { actualExpression, failureMessage in
failureMessage.actualValue = nil
if let actualValue = try actualExpression.evaluate() {
for item in actualValue {
let exp = Expression(expression: { item }, location: actualExpression.location)
if try !elementEvaluator(exp, failureMessage) {
failureMessage.postfixMessage =
"all \(failureMessage.postfixMessage),"
+ " but failed first at element <\(item.1)>"
+ " in <\(actualValue.map({ $0.1 }))>"
return false
}
}
failureMessage.postfixMessage = "all \(failureMessage.postfixMessage)"
} else {
failureMessage.postfixMessage = "all pass (use beNil() to match nils)"
return false
}

return true
}
}