-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement lifetimes and SharpXmppClient.signIn (#18)
- Loading branch information
Showing
8 changed files
with
187 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module Emulsion.ExceptionUtils | ||
|
||
open System.Runtime.ExceptionServices | ||
|
||
let reraise (ex: exn): 'a = | ||
let edi = ExceptionDispatchInfo.Capture ex | ||
edi.Throw() | ||
failwith "Impossible" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
module Emulsion.Lifetimes | ||
|
||
open System | ||
open System.Threading | ||
open System.Threading.Tasks | ||
|
||
type LifetimeDefinition(cts: CancellationTokenSource) = | ||
new() = new LifetimeDefinition(new CancellationTokenSource()) | ||
member __.Lifetime: Lifetime = Lifetime(cts.Token) | ||
member __.Terminate(): unit = cts.Cancel() | ||
interface IDisposable with | ||
member __.Dispose() = cts.Dispose() | ||
and Lifetime(token: CancellationToken) = | ||
member __.Token: CancellationToken = token | ||
member __.CreateNested(): LifetimeDefinition = | ||
let cts = CancellationTokenSource.CreateLinkedTokenSource token | ||
new LifetimeDefinition(cts) | ||
member __.OnTermination(action: Action): unit = | ||
token.Register action |> ignore | ||
|
||
/// Schedules a termination action, and returns an IDisposable. Whenever this instance is disposed, the action will | ||
/// be removed from scheduled on cancellation. | ||
member __.OnTerminationRemovable(action: Action): IDisposable = | ||
upcast token.Register action | ||
|
||
let nestedTaskCompletionSource<'T>(lifetime: Lifetime): TaskCompletionSource<'T> = | ||
let tcs = new TaskCompletionSource<'T>() | ||
|
||
// As an optimization, we'll remove the action after the task has been completed to clean up the memory: | ||
let action = lifetime.OnTerminationRemovable(fun () -> tcs.TrySetCanceled() |> ignore) | ||
tcs.Task.ContinueWith(fun (t: Task<'T>) -> action.Dispose()) |> ignore | ||
|
||
tcs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
module Emulsion.Xmpp.SharpXmppClient | ||
|
||
open System | ||
|
||
open Serilog | ||
open SharpXMPP | ||
open SharpXMPP.XMPP | ||
|
||
open Emulsion | ||
open Emulsion.Lifetimes | ||
open Emulsion.Xmpp.AsyncXmppClient | ||
open SharpXMPP.XMPP.Client.Elements | ||
|
||
/// Establish a connection to the server and log in. Returns a connection lifetime that will terminate if the connection | ||
/// terminates. | ||
let signIn (logger: ILogger) (signInInfo: SignInInfo): Async<XmppClient * Lifetime> = async { | ||
let client = new XmppClient(JID(signInInfo.Login), signInInfo.Password) | ||
let connectionLifetime = new LifetimeDefinition() | ||
client.add_ConnectionFailed <| XmppConnection.ConnectionFailedHandler( | ||
fun _ error -> | ||
logger.Error(error.Exception, "Connection failed: {Message}", error.Message) | ||
connectionLifetime.Terminate() | ||
) | ||
let! cancellationToken = Async.CancellationToken | ||
use _ = cancellationToken.Register(fun () -> | ||
logger.Information("Closing the connection due to external cancellation") | ||
client.Close() | ||
) | ||
do! Async.AwaitTask(client.ConnectAsync cancellationToken) // TODO[F]: Check if it will call the ConnectionFailed handler on cancellation | ||
return client, connectionLifetime.Lifetime | ||
} | ||
|
||
let private addPresenceHandler (lifetime: Lifetime) (client: XmppClient) handler = | ||
let handlerDelegate = XmppConnection.PresenceHandler(fun _ p -> handler p) | ||
client.add_Presence handlerDelegate | ||
lifetime.OnTermination (fun () -> client.remove_Presence handlerDelegate) | ||
|
||
let private isSelfPresence (roomInfo: RoomInfo) (presence: XMPPPresence) = | ||
let presence = SharpXmppHelper.parsePresence presence | ||
let expectedJid = sprintf "%s/%s" roomInfo.RoomJid roomInfo.Nickname | ||
presence.From = expectedJid && Array.contains 110 presence.States | ||
|
||
let private isLeavePresence (roomInfo: RoomInfo) (presence: XMPPPresence) = | ||
let presence = SharpXmppHelper.parsePresence presence | ||
let expectedJid = sprintf "%s/%s" roomInfo.RoomJid roomInfo.Nickname | ||
presence.From = expectedJid && Array.contains 110 presence.States && presence.Type = "unavailable" | ||
|
||
let private extractException (roomInfo: RoomInfo) (presence: XMPPPresence) = | ||
let presence = SharpXmppHelper.parsePresence presence | ||
let expectedJid = sprintf "%s/%s" roomInfo.RoomJid roomInfo.Nickname | ||
if presence.From = expectedJid then | ||
presence.Error | ||
|> Option.map (fun e -> Exception(sprintf "Error: %A" e)) | ||
else None | ||
|
||
let enterRoom (client: XmppClient) (lifetime: Lifetime) (roomInfo: RoomInfo): Async<Lifetime> = async { | ||
use connectionLifetimeDefinition = lifetime.CreateNested() | ||
let connectionLifetime = connectionLifetimeDefinition.Lifetime | ||
|
||
let roomLifetimeDefinition = lifetime.CreateNested() | ||
let roomLifetime = roomLifetimeDefinition.Lifetime | ||
|
||
let tcs = nestedTaskCompletionSource connectionLifetime | ||
|
||
// Enter room successfully handler: | ||
addPresenceHandler connectionLifetime client (fun presence -> | ||
if isSelfPresence roomInfo presence | ||
then tcs.SetResult() | ||
) | ||
|
||
// Error handler: | ||
addPresenceHandler connectionLifetime client (fun presence -> | ||
match extractException roomInfo presence with | ||
| Some ex -> tcs.SetException ex | ||
| None -> () | ||
) | ||
|
||
// Room leave handler: | ||
addPresenceHandler roomLifetime client (fun presence -> | ||
if isLeavePresence roomInfo presence | ||
then roomLifetimeDefinition.Terminate() | ||
) | ||
|
||
try | ||
// Start the enter process, wait for a result: | ||
SharpXmppHelper.joinRoom client roomInfo.RoomJid roomInfo.Nickname | ||
do! Async.AwaitTask tcs.Task | ||
return roomLifetime | ||
with | ||
| ex -> | ||
// In case of an error, terminate the room lifetime: | ||
roomLifetimeDefinition.Terminate() | ||
return ExceptionUtils.reraise ex | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace Emulsion.Xmpp.XmppElements | ||
|
||
open System.Xml.Linq | ||
|
||
type Presence = { | ||
From: string | ||
States: int[] | ||
Error: XElement option | ||
Type: string | ||
} |