Closed postacik closed 5 years ago
Hello,
yes. I've been recently working on the Python V2 of the lib, but the C# is the very next thing I'm going to do. I'll be most likely working on it this week-end. Then comes the Javascript lib, which also needs some work.
Great news :))
In the current library you're using a Hashtable to keep keygen event handlers which are not disposed after the keygen response arrives.
But for the message handlers you've used a so called ReverseTrie which removes the event handler in case of unsubscribe.
Are you also going to use a ReverseTrie to keep event handlers for presence requests?
Is it possible to unsubscribe from presence notifications?
Yes, it's possible to unsubscribe from getting notifications. In the Go and JS libs, the Presence function takes two arguments: status
, and changes
. You can simply send a new request with changes
set to false to disable the notifications. I'll probably do it the same way in the C# lib.
Unless I'm missing something, using the ReverseTrie for presence events could work. However, I'm thinking there must be a good reason the main engineer didn't do that in the Go lib. I'll discuss it with him and see whether it's actually feasible.
So, I've been working a bit on the C# lib on Saturday. There is a lot of work to do, and it's actually pretty difficult to design an API to be coherent with itself, and with the versions in other languages.
The C# version uses On
to subscribe, but has an Unsubscribe
method. I'm going to deprecate the On
and call the new one Subscribe
, just like in other libs.
I want to use the trie for Presence handlers. However, I'm not sure how to call the methods related to this feature. The thing is, unsubscribing from Presence is done by sending a Presence request with both status
and changes
flags set to false.
I could check both flags in the Presence method, and add a handler to the trie if any of them is set to true. I would remove the handler from the trie if both flags were false.
I think this is quite ugly, and un-intuitive.
Alternatively, I could consider the Presence
method should only be used when you want to receive the status, the changes, or both. I would not make a check on the flags in the method, and always register the handler with the trie.
I would then create a second method, something like Unpresence
, that would not take any flags as parameters, would send a Presence
request with flags set to false, and unregister with the trie.
What do you think?
Another proposition, 3 methods:
PresenceSubscribe(bool status, handler)
, add the handler in the trie, then sends a request with changes
set to true
and status
set to the parameter.PresenceUnsubscribe
, remove the handler from the trie and sends a Presence Request with all flags set to false, so no more events.PresenceStatus(optional handler)
, adds the handler to the trie if it is provided, then requests a status.That way, all use cases should be covered. Among others
PresenceSubscribe
PresenceSubscribe(false, handler)
and PresenceStatus(no handler)
PresenceStatus(handler)
In any case, one can unsubscribe and remove the handler from the trie with PresenceUnsubscribe
.
Note: this is still not ideal because one could forget to unsubscribe after a PresenceStatus
. Another mechanism could be put in place, flagging a handler for a one time use. If a handler has a "one time use" flag set to true
, and a status is received, the handler would be called then removed. That might be a bit more complex to implement though...
Nice ideas. I was thinking of a similar way of doing it.
For PresenceStatus, flagging the handler for one time use seems to be only reasonable way of doing it.
For status only events, the channel name that arrives always equals to the channel name sent.
However, for subscription (changes = true)
, since events arrive also for sub channels, I had to do something like below:
// Did we receive a presence response?
if (e.Topic == "emitter/presence/")
{
var response = PresenceResponse.FromBinary(e.Message);
if (response != null && response.Status == 200)
{
// Invoke every handler matching the channel
foreach (var item in PresenceHandlers)
{
if (response.Channel.StartsWith(item.Key)) item.Value(response);
}
return;
}
}
And my presence function is like below:
private readonly List<KeyValuePair<string, PresenceHandler>> PresenceHandlers = new List<KeyValuePair<string, PresenceHandler>>();
/// <summary>
/// Asynchronously sends a presence request to the emitter.io service.
/// </summary>
/// <param name="secretKey">The secret key for this request.</param>
/// <param name="channel">The target channel for the requested key.</param>
/// <param name="status">Whether the current state should be retrieved or not.</param>
/// <param name="changes">Whether the future changes should be received or not.</param>
public void Presence(string secretKey, string channel, bool status, bool changes, PresenceHandler handler)
{
// Prepare the request
var request = new PresenceRequest();
request.Key = secretKey;
request.Channel = channel;
request.Status = status;
request.Changes = changes;
// Register the handler
PresenceHandlers.Add(new KeyValuePair<string, PresenceHandler>(request.Channel, handler));
// Serialize and publish the request
this.Publish("emitter/", "presence/", Encoding.UTF8.GetBytes(request.ToJson()));
}
PresenceRequest:
namespace Emitter.Messages
{
/// <summary>
/// Represents a presence request.
/// </summary>
public class PresenceRequest
{
/// <summary>
/// Gets or sets the secret key for this request.
/// </summary>
public string Key;
/// <summary>
/// Gets or sets the target channel for presence.
/// </summary>
public string Channel;
/// <summary>
/// Determines whether the current state should be retrieved or not.
/// </summary>
public bool Status;
/// <summary>
/// Determines whether the future changes should be received or not.
/// </summary>
public bool Changes;
/// <summary>
/// Converts the request to JSON format.
/// </summary>
/// <returns></returns>
public string ToJson()
{
return JsonSerializer.SerializeObject(new Hashtable
{
{"key", this.Key},
{"channel", this.Channel},
{"status", this.Status},
{"changes", this.Changes}
});
}
}
/// <summary>
/// Handles the presence response.
/// </summary>
/// <param name="response">The presence response to handle.</param>
public delegate void PresenceHandler(PresenceResponse response);
}
PresenceResponse:
namespace Emitter.Messages
{
/// <summary>
/// Represents a presence response.
/// </summary>
public class PresenceResponse
{
/// <summary>
/// Gets or sets the presence channel.
/// </summary>
public string Channel;
/// <summary>
/// Gets or sets presence event name.
/// </summary>
public string Event;
/// <summary>
/// Gets or sets presence event time.
/// </summary>
public int Time;
/// <summary>
/// Gets or sets list of subscribers.
/// </summary>
public List<PresenceWho> Who;
/// <summary>
/// Gets or sets the status code returned by emitter.io service.
/// </summary>
public int Status;
/// <summary>
/// Deserializes the JSON presence response.
/// </summary>
/// <param name="json">The json string to deserialize from.</param>
/// <returns></returns>
public static PresenceResponse FromJson(string json)
{
var map = JsonSerializer.DeserializeString(json) as Hashtable;
var response = new PresenceResponse();
response.Status = 200;
if (map.Contains("status"))
{
response.Status = Convert.ToInt32(map["status"].ToString());
}
if (response.Status == 200)
{
response.Channel = (string)map["channel"];
response.Event = (string)map["event"];
response.Time = Convert.ToInt32(map["time"].ToString());
response.Who = new List<PresenceWho>();
if (map.Contains("who"))
{
if (map["who"] is ArrayList)
{
foreach (Hashtable who in (ArrayList)map["who"])
{
response.Who.Add(new PresenceWho { Id = (string)who["id"] });
}
}
if (map["who"] is Hashtable)
{
var who = (Hashtable)map["who"];
response.Who.Add(new PresenceWho { Id = (string)who["id"] });
}
}
}
return response;
}
/// <summary>
/// Deserializes the JSON presence response.
/// </summary>
/// <param name="message">The binary UTF-8 encoded string.</param>
/// <returns></returns>
public static PresenceResponse FromBinary(byte[] message)
{
return FromJson(new string(Encoding.UTF8.GetChars(message)));
}
}
public class PresenceWho
{
/// <summary>
/// Gets or sets the id of the subscriber.
/// </summary>
public string Id;
}
}
This implementation is not complete, yet.
And I'm not sure if I serialized the "who" property properly.
First, I want to let you know I'll be too busy to work on this until tomorrow evening. But I'm currently working on those features on an update
branch : https://github.com/emitter-io/csharp/tree/update
You can have a look at the commits I already did to see what I've already changed.
You'll find PresenceSubscribe
and PresenceUnsubscribe
, and they already use the trie. This means you can give a specific handler to subchannels (if it works properly, it still requires some testing), but you will still receive messages for sub-channels if you do a PresenceSubscribe
for a master channel, that's the way it's intended to work.
I discussed the idea of one-time handlers with the main engineer, and he didn't think it was a good idea. After thinking about it again, I'm not overly concerned about this. It's actually unlikely that people will often only request one status on a channel and then want to unsubscribe immediately. It won't be a common use case...
Edit: On the update
branch, new files are added to the Emitter
project, and I only add links to these files into the project using .NET 4.5. That is the one used by the sample
project. I'll add links into the other .NET versions later, it's annoying and time consuming.
PS: have a nice day.
I found the time to finish (I think) the presence feature on the update
branch. It will work best with the latest version of the emitter broker (I had to do a little modification there too).
I'll test it asap. Thanks :)
Hi,
The C# library for emitter contains less functionality than the go and javascript libraries.
Do you have any plans to update it to add functions for presence and other new emitter features?