Fix the reconnection logic. Hooray!
This commit is contained in:
parent
505e500634
commit
4174ec85c6
4 changed files with 116 additions and 75 deletions
|
@ -24,9 +24,9 @@ namespace RhinoReminds
|
||||||
|
|
||||||
public event OnConnectedHandler OnConnected;
|
public event OnConnectedHandler OnConnected;
|
||||||
|
|
||||||
public readonly string Jid;
|
public readonly Jid Jid;
|
||||||
public string Username => Jid.Split('@')[0];
|
public string Username => Jid.Node;
|
||||||
public string Hostname => Jid.Split('@')[1];
|
public string Hostname => Jid.Domain;
|
||||||
private readonly string password;
|
private readonly string password;
|
||||||
|
|
||||||
public readonly List<string> AllowedDomains = new List<string>();
|
public readonly List<string> AllowedDomains = new List<string>();
|
||||||
|
@ -35,12 +35,11 @@ namespace RhinoReminds
|
||||||
private ReminderList reminderList = new ReminderList();
|
private ReminderList reminderList = new ReminderList();
|
||||||
|
|
||||||
|
|
||||||
private XmppClient client;
|
private SimpleXmppClient client;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of seconds to wait before trying to reconnect to the
|
/// The initial number of seconds to wait before trying to reconnect to the
|
||||||
/// server again if we loose our connection again.
|
/// server again if we loose our connection again.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int nextBackoffDelay = 1;
|
|
||||||
private int defaultBackoffDelay = 1;
|
private int defaultBackoffDelay = 1;
|
||||||
private float backoffDelayMultiplier = 2;
|
private float backoffDelayMultiplier = 2;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -52,7 +51,7 @@ namespace RhinoReminds
|
||||||
|
|
||||||
public ClientListener(string inJid, string inPassword)
|
public ClientListener(string inJid, string inPassword)
|
||||||
{
|
{
|
||||||
Jid = inJid;
|
Jid = new Jid(inJid);
|
||||||
password = inPassword;
|
password = inPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,26 +63,48 @@ namespace RhinoReminds
|
||||||
reminderList = ReminderList.FromXmlFile(ReminderFilePath);
|
reminderList = ReminderList.FromXmlFile(ReminderFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
client = new XmppClient(Hostname, Username, password);
|
|
||||||
client.Error += errorHandler;
|
|
||||||
client.Message += messageHandlerRoot;
|
|
||||||
client.SubscriptionRequest += subscriptionRequestHandler;
|
|
||||||
|
|
||||||
// Connect to the server. This starts it's own thread that doesn't block the program exiting, apparently
|
// Connect to the server. This starts it's own thread that doesn't block the program exiting, apparently
|
||||||
await connect();
|
await connect();
|
||||||
|
|
||||||
//client.SetStatus(Availability.Online);
|
client.SetStatus(Availability.Online);
|
||||||
|
|
||||||
await watchForReminders();
|
await watchForReminders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task reconnect()
|
||||||
|
{
|
||||||
|
// 1: Ensure we're disconnected from the server.
|
||||||
|
disconnect();
|
||||||
|
|
||||||
|
float nextBackoffDelay = defaultBackoffDelay;
|
||||||
|
|
||||||
|
do {
|
||||||
|
nextBackoffDelay *= backoffDelayMultiplier;
|
||||||
|
|
||||||
|
Console.Error.WriteLine($"[Rhino/Reconnect] Reconnecting in {TimeSpan.FromSeconds(nextBackoffDelay).ToString()}.");
|
||||||
|
Thread.Sleep((int)(nextBackoffDelay * 1000));
|
||||||
|
Console.WriteLine("[Rhino/Reconnect] Attempting to reconnect to the server");
|
||||||
|
} while (!await connect());
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<bool> connect()
|
private async Task<bool> connect()
|
||||||
{
|
{
|
||||||
if (client.Connected)
|
if (client != null) {
|
||||||
|
if (client.Connected) {
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
client.Dispose();
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DateTime startTime = DateTime.Now;
|
DateTime startTime = DateTime.Now;
|
||||||
client.Connect();
|
client = new SimpleXmppClient(Jid, password);
|
||||||
|
client.Error += errorHandler;
|
||||||
|
client.Message += messageHandlerRoot;
|
||||||
|
client.SubscriptionRequest += subscriptionRequestHandler;
|
||||||
|
|
||||||
|
client.Connect("RhinoReminds");
|
||||||
|
|
||||||
while (!client.Connected)
|
while (!client.Connected)
|
||||||
{
|
{
|
||||||
|
@ -99,12 +120,20 @@ namespace RhinoReminds
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void disconnect()
|
||||||
|
{
|
||||||
|
client.Close();
|
||||||
|
client.Dispose();
|
||||||
|
client = null;
|
||||||
|
Console.WriteLine($"[Rhino] Disconnected from server.");
|
||||||
|
}
|
||||||
|
|
||||||
#region XMPP Event Handling
|
#region XMPP Event Handling
|
||||||
|
|
||||||
private bool subscriptionRequestHandler(Jid from)
|
private bool subscriptionRequestHandler(Jid from)
|
||||||
{
|
{
|
||||||
if (!AllowedDomains.Contains("*") && !AllowedDomains.Contains(from.Domain)) {
|
if (!AllowedDomains.Contains("*") && !AllowedDomains.Contains(from.Domain)) {
|
||||||
sendChatMessage(from, "Sorry! The domain of your JID doesn't match the ones in my allowed list.");
|
client.SendChatMessage(from, "Sorry! The domain of your JID doesn't match the ones in my allowed list.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Console.WriteLine($"[Rhino/SubscriptionRequest] Approving subscription from {from}");
|
Console.WriteLine($"[Rhino/SubscriptionRequest] Approving subscription from {from}");
|
||||||
|
@ -115,17 +144,8 @@ namespace RhinoReminds
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[Error] {e.Reason}: {e.Exception}");
|
Console.Error.WriteLine($"[Error] {e.Reason}: {e.Exception}");
|
||||||
|
|
||||||
if(!client.Connected || e.Exception is IOException)
|
if(!client.Connected || e.Exception is IOException) {
|
||||||
{
|
reconnect().Wait();
|
||||||
Console.Error.WriteLine($"[Error/Handler] Reconnecting in {TimeSpan.FromSeconds(nextBackoffDelay).ToString()}.");
|
|
||||||
Thread.Sleep(nextBackoffDelay * 1000);
|
|
||||||
Console.WriteLine("[Error/Handler] Attempting to reconnect to the server");
|
|
||||||
|
|
||||||
if (!connect().Result)
|
|
||||||
nextBackoffDelay = (int)Math.Ceiling(nextBackoffDelay * backoffDelayMultiplier);
|
|
||||||
else
|
|
||||||
nextBackoffDelay = defaultBackoffDelay;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +160,8 @@ namespace RhinoReminds
|
||||||
catch (Exception error)
|
catch (Exception error)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(error);
|
Console.Error.WriteLine(error);
|
||||||
sendChatReply(eventArgs.Message, "Oops! I encountered an error. Please report this to my operator!");
|
client.SendChatReply(eventArgs.Message, "Oops! I encountered an error. Please report this to my operator!");
|
||||||
sendChatReply(eventArgs.Message, $"Technical details: {WebUtility.HtmlEncode(error.ToString())} (stack trace available in server log) ");
|
client.SendChatReply(eventArgs.Message, $"Technical details: {WebUtility.HtmlEncode(error.ToString())} (stack trace available in server log) ");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +180,7 @@ namespace RhinoReminds
|
||||||
private void messageHandler(Message message)
|
private void messageHandler(Message message)
|
||||||
{
|
{
|
||||||
if (!AllowedDomains.Contains("*") && !AllowedDomains.Contains(message.From.Domain)) {
|
if (!AllowedDomains.Contains("*") && !AllowedDomains.Contains(message.From.Domain)) {
|
||||||
sendChatMessage(message.From, "Sorry! The domain of your JID doesn't match the ones in my allowed list.");
|
client.SendChatMessage(message.From, "Sorry! The domain of your JID doesn't match the ones in my allowed list.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,15 +192,15 @@ namespace RhinoReminds
|
||||||
switch (parts[0].ToLower())
|
switch (parts[0].ToLower())
|
||||||
{
|
{
|
||||||
case "help":
|
case "help":
|
||||||
sendChatReply(message, "Hello! I'm a reminder bot written by Starbeamrainbowlabs.");
|
client.SendChatReply(message, "Hello! I'm a reminder bot written by Starbeamrainbowlabs.");
|
||||||
sendChatReply(message, "I can understand messages you send me in regular english.");
|
client.SendChatReply(message, "I can understand messages you send me in regular english.");
|
||||||
sendChatReply(message, "I figure out what you want me to do by looking at the " +
|
client.SendChatReply(message, "I figure out what you want me to do by looking at the " +
|
||||||
"first word you say, and how you want me to do it by using my AI.");
|
"first word you say, and how you want me to do it by using my AI.");
|
||||||
sendChatReply(message, "I currently understand the following instructions:\n");
|
client.SendChatReply(message, "I currently understand the following instructions:\n");
|
||||||
sendChatReply(message, "**Remind:** Set a reminder");
|
client.SendChatReply(message, "**Remind:** Set a reminder");
|
||||||
sendChatReply(message, "**List / Show:** List the reminders I have set");
|
client.SendChatReply(message, "**List / Show:** List the reminders I have set");
|
||||||
sendChatReply(message, "**Delete / Remove:** Delete a reminder by it's number (find this in the reminder list from the instruction above)");
|
client.SendChatReply(message, "**Delete / Remove:** Delete a reminder by it's number (find this in the reminder list from the instruction above)");
|
||||||
sendChatReply(message, "\nExample: 'Remind me to feed the cat tomorrow at 6pm'");
|
client.SendChatReply(message, "\nExample: 'Remind me to feed the cat tomorrow at 6pm'");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
|
@ -201,12 +221,12 @@ namespace RhinoReminds
|
||||||
if (failed.Count > 0) {
|
if (failed.Count > 0) {
|
||||||
string response = string.Join(", ", failed.Select((int nextId) => $"#{nextId}"));
|
string response = string.Join(", ", failed.Select((int nextId) => $"#{nextId}"));
|
||||||
response = $"Sorry! I can't delete reminder{(failed.Count != 1 ? "s" : "")} {response}, as you didn't create {(failed.Count != 1 ? "them":"it")}.";
|
response = $"Sorry! I can't delete reminder{(failed.Count != 1 ? "s" : "")} {response}, as you didn't create {(failed.Count != 1 ? "them":"it")}.";
|
||||||
sendChatReply(message, response);
|
client.SendChatReply(message, response);
|
||||||
}
|
}
|
||||||
if (succeeded.Count > 0) {
|
if (succeeded.Count > 0) {
|
||||||
string response = string.Join(", ", succeeded.Select((int nextId) => $"#{nextId}"));
|
string response = string.Join(", ", succeeded.Select((int nextId) => $"#{nextId}"));
|
||||||
response = $"Deleted reminder{(succeeded.Count != 1 ? "s" : "")} {response} successfully.";
|
response = $"Deleted reminder{(succeeded.Count != 1 ? "s" : "")} {response} successfully.";
|
||||||
sendChatReply(message, response);
|
client.SendChatReply(message, response);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -222,7 +242,7 @@ namespace RhinoReminds
|
||||||
}
|
}
|
||||||
listMessage.AppendLine();
|
listMessage.AppendLine();
|
||||||
listMessage.AppendLine($"({userReminderList.Count()} total)");
|
listMessage.AppendLine($"({userReminderList.Count()} total)");
|
||||||
sendChatReply(message, listMessage.ToString());
|
client.SendChatReply(message, listMessage.ToString());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -231,8 +251,8 @@ namespace RhinoReminds
|
||||||
try {
|
try {
|
||||||
dateTime = AIRecogniser.RecogniseDateTime(messageText, out rawDateTimeString);
|
dateTime = AIRecogniser.RecogniseDateTime(messageText, out rawDateTimeString);
|
||||||
} catch (AIException error) {
|
} catch (AIException error) {
|
||||||
sendChatReply(message, "Sorry, I had trouble figuring out when you wanted reminding about that!");
|
client.SendChatReply(message, "Sorry, I had trouble figuring out when you wanted reminding about that!");
|
||||||
sendChatReply(message, $"(Technical details: {error.Message})");
|
client.SendChatReply(message, $"(Technical details: {error.Message})");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Range dateStringLocation = new Range(
|
Range dateStringLocation = new Range(
|
||||||
|
@ -260,45 +280,20 @@ namespace RhinoReminds
|
||||||
@"and"
|
@"and"
|
||||||
}, RegexOptions.IgnoreCase).Trim();
|
}, RegexOptions.IgnoreCase).Trim();
|
||||||
|
|
||||||
sendChatReply(message, $"Ok! I'll remind you {reminder} at {dateTime}.");
|
client.SendChatReply(message, $"Ok! I'll remind you {reminder} at {dateTime}.");
|
||||||
|
|
||||||
Reminder newReminder = reminderList.CreateReminder(message.From, dateTime, reminder);
|
Reminder newReminder = reminderList.CreateReminder(message.From, dateTime, reminder);
|
||||||
reminderList.Save(ReminderFilePath);
|
reminderList.Save(ReminderFilePath);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sendChatReply(message, "I don't understand that. Try rephrasing it or asking for help.");
|
client.SendChatReply(message, "I don't understand that. Try rephrasing it or asking for help.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#region Outgoing
|
#region Outgoing
|
||||||
/// <summary>
|
|
||||||
/// Sends a chat message to the specified JID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="to">The JID to send the message to.</param>
|
|
||||||
/// <param name="message">The messaage to send.</param>
|
|
||||||
private void sendChatMessage(Jid to, string message) {
|
|
||||||
//Console.WriteLine($"[Rhino/Send/Chat] Sending {message} -> {to}");
|
|
||||||
client.SendMessage(
|
|
||||||
to, message,
|
|
||||||
null, null, MessageType.Chat
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a chat message in direct reply to a given incoming message.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="originalMessage">Original message.</param>
|
|
||||||
/// <param name="reply">Reply.</param>
|
|
||||||
private void sendChatReply(Message originalMessage, string reply)
|
|
||||||
{
|
|
||||||
//Console.WriteLine($"[Rhino/Send/Reply] Sending {reply} -> {originalMessage.From}");
|
|
||||||
client.SendMessage(
|
|
||||||
originalMessage.From, reply,
|
|
||||||
null, originalMessage.Thread, MessageType.Chat
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -353,17 +348,17 @@ namespace RhinoReminds
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sendChatMessage(
|
client.SendChatMessage(
|
||||||
nextReminder.Jid,
|
nextReminder.Jid,
|
||||||
$"Hello! You asked me to remind you {nextReminder.Message} at {nextReminder.Time}.".Trim().Replace(@"\s+", " ")
|
$"Hello! You asked me to remind you {nextReminder.Message} at {nextReminder.Time}.".Trim().Replace(@"\s+", " ")
|
||||||
);
|
);
|
||||||
} catch (Exception error) {
|
} catch (Exception error) {
|
||||||
Console.Error.WriteLine($"[Rhino/Reminderd] Caught error sending message to client: {error}");
|
Console.Error.WriteLine($"[Rhino/Reminderd] Caught error sending message to client: {error}");
|
||||||
Console.Error.WriteLine($"[Rhink/Reminderd] Offending reminder: {nextReminder}");
|
Console.Error.WriteLine($"[Rhink/Reminderd] Offending reminder: {nextReminder}");
|
||||||
sendChatMessage(nextReminder.Jid, "Oops! I encountered an error sending you a reminder. Please contact my operator!");
|
client.SendChatMessage(nextReminder.Jid, "Oops! I encountered an error sending you a reminder. Please contact my operator!");
|
||||||
}
|
}
|
||||||
if (nextWaitingTime.TotalMilliseconds < 0) {
|
if (nextWaitingTime.TotalMilliseconds < 0) {
|
||||||
sendChatMessage(
|
client.SendChatMessage(
|
||||||
nextReminder.Jid,
|
nextReminder.Jid,
|
||||||
"(Sorry I'm late reminding you! I might not have been running at the time, " +
|
"(Sorry I'm late reminding you! I might not have been running at the time, " +
|
||||||
"or you might have scheduled a reminder for the past)"
|
"or you might have scheduled a reminder for the past)"
|
||||||
|
|
|
@ -86,10 +86,13 @@ namespace RhinoReminds
|
||||||
client.AllowedDomains.Add(settings.AllowedDomain);
|
client.AllowedDomains.Add(settings.AllowedDomain);
|
||||||
// Update the avatar if appropriate
|
// Update the avatar if appropriate
|
||||||
if (settings.AvatarFilepath != string.Empty) {
|
if (settings.AvatarFilepath != string.Empty) {
|
||||||
client.OnConnected += (object sender, OnConnectedEventArgs eventArgs) => {
|
OnConnectedHandler handler = null;
|
||||||
|
handler = (object sender, OnConnectedEventArgs eventArgs) => {
|
||||||
client.SetAvatar(settings.AvatarFilepath);
|
client.SetAvatar(settings.AvatarFilepath);
|
||||||
Console.WriteLine($"[Program] Set avatar to '{settings.AvatarFilepath}'.");
|
Console.WriteLine($"[Program] Set avatar to '{settings.AvatarFilepath}'.");
|
||||||
|
client.OnConnected -= handler;
|
||||||
};
|
};
|
||||||
|
client.OnConnected += handler;
|
||||||
}
|
}
|
||||||
// Connect to the server & start listening
|
// Connect to the server & start listening
|
||||||
// Make sure the program doesn't exit whilst we're connected
|
// Make sure the program doesn't exit whilst we're connected
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
<Compile Include="Exceptions.cs" />
|
<Compile Include="Exceptions.cs" />
|
||||||
<Compile Include="Utilities\Range.cs" />
|
<Compile Include="Utilities\Range.cs" />
|
||||||
<Compile Include="Utilities\TextHelpers.cs" />
|
<Compile Include="Utilities\TextHelpers.cs" />
|
||||||
|
<Compile Include="SimpleXmppClient.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|
42
RhinoReminds/SimpleXmppClient.cs
Normal file
42
RhinoReminds/SimpleXmppClient.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using System;
|
||||||
|
using S22.Xmpp;
|
||||||
|
using S22.Xmpp.Client;
|
||||||
|
using S22.Xmpp.Im;
|
||||||
|
|
||||||
|
namespace RhinoReminds
|
||||||
|
{
|
||||||
|
public class SimpleXmppClient : XmppClient
|
||||||
|
{
|
||||||
|
public SimpleXmppClient(Jid user, string password) : base(user.Domain, user.Node, password)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a chat message to the specified JID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="to">The JID to send the message to.</param>
|
||||||
|
/// <param name="message">The messaage to send.</param>
|
||||||
|
public void SendChatMessage(Jid to, string message)
|
||||||
|
{
|
||||||
|
//Console.WriteLine($"[Rhino/Send/Chat] Sending {message} -> {to}");
|
||||||
|
SendMessage(
|
||||||
|
to, message,
|
||||||
|
null, null, MessageType.Chat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a chat message in direct reply to a given incoming message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="originalMessage">Original message.</param>
|
||||||
|
/// <param name="reply">Reply.</param>
|
||||||
|
public void SendChatReply(Message originalMessage, string reply)
|
||||||
|
{
|
||||||
|
//Console.WriteLine($"[Rhino/Send/Reply] Sending {reply} -> {originalMessage.From}");
|
||||||
|
SendMessage(
|
||||||
|
originalMessage.From, reply,
|
||||||
|
null, originalMessage.Thread, MessageType.Chat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue