Change the internal backing list to a SortedSet to reduce complexity & increase stability
This commit is contained in:
parent
adfb482072
commit
579078edf0
5 changed files with 144 additions and 74 deletions
|
@ -9,7 +9,6 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using RhinoReminds.Utilities;
|
using RhinoReminds.Utilities;
|
||||||
using S22.Xmpp;
|
using S22.Xmpp;
|
||||||
using S22.Xmpp.Client;
|
|
||||||
using S22.Xmpp.Im;
|
using S22.Xmpp.Im;
|
||||||
using SBRL.Geometry;
|
using SBRL.Geometry;
|
||||||
|
|
||||||
|
@ -34,20 +33,20 @@ namespace RhinoReminds
|
||||||
public string ReminderFilePath { get; set; } = "./reminders.xml";
|
public string ReminderFilePath { get; set; } = "./reminders.xml";
|
||||||
private ReminderList reminderList = new ReminderList();
|
private ReminderList reminderList = new ReminderList();
|
||||||
private CancellationTokenSource reminderWatcherReset;
|
private CancellationTokenSource reminderWatcherReset;
|
||||||
private CancellationToken reminderWatcherResetToken;
|
private CancellationToken reminderWatcherResetToken => reminderWatcherReset.Token;
|
||||||
|
|
||||||
private SimpleXmppClient client;
|
private SimpleXmppClient client;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The initial 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 defaultBackoffDelay = 1;
|
private readonly int defaultBackoffDelay = 1;
|
||||||
private float backoffDelayMultiplier = 2;
|
private readonly float backoffDelayMultiplier = 2;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If a connection attempt doesn't succeed in this number of seconds,
|
/// If a connection attempt doesn't succeed in this number of seconds,
|
||||||
/// give up and try again later.
|
/// give up and try again later.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int giveUpTimeout = 30;
|
private readonly int giveUpTimeout = 30;
|
||||||
|
|
||||||
|
|
||||||
public ClientListener(string inJid, string inPassword)
|
public ClientListener(string inJid, string inPassword)
|
||||||
|
@ -226,7 +225,7 @@ namespace RhinoReminds
|
||||||
}
|
}
|
||||||
if (succeeded.Count > 0) {
|
if (succeeded.Count > 0) {
|
||||||
// Ensure that the reminder thread picks up the changes
|
// Ensure that the reminder thread picks up the changes
|
||||||
resetReminderWatcher();
|
interruptReminderWatcher();
|
||||||
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.";
|
||||||
client.SendChatReply(message, response);
|
client.SendChatReply(message, response);
|
||||||
|
@ -236,7 +235,7 @@ namespace RhinoReminds
|
||||||
case "list":
|
case "list":
|
||||||
case "show":
|
case "show":
|
||||||
// Filter by reminders for this user.
|
// Filter by reminders for this user.
|
||||||
IEnumerable<Reminder> userReminderList = reminderList.Reminders.Values.Where(
|
IEnumerable<Reminder> userReminderList = reminderList.Reminders.Where(
|
||||||
(Reminder next) => message.From.GetBareJid() == next.JidObj.GetBareJid()
|
(Reminder next) => message.From.GetBareJid() == next.JidObj.GetBareJid()
|
||||||
);
|
);
|
||||||
StringBuilder listMessage = new StringBuilder("I've got the following reminders on my list:\n");
|
StringBuilder listMessage = new StringBuilder("I've got the following reminders on my list:\n");
|
||||||
|
@ -283,9 +282,19 @@ namespace RhinoReminds
|
||||||
@"and"
|
@"and"
|
||||||
}, RegexOptions.IgnoreCase).Trim();
|
}, RegexOptions.IgnoreCase).Trim();
|
||||||
|
|
||||||
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);
|
||||||
|
if (newReminder == null) {
|
||||||
|
client.SendChatReply(
|
||||||
|
message,
|
||||||
|
"Oops! It looks like you've already got a reminder idential "
|
||||||
|
+ "to that one set, so I wasn't able to set a reminder for you. Please contact my "
|
||||||
|
+ "operator, as this is probably a bug."
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SendChatReply(message, $"Ok! I'll remind you {reminder} at {dateTime}.");
|
||||||
reminderList.Save(ReminderFilePath);
|
reminderList.Save(ReminderFilePath);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -302,17 +311,19 @@ namespace RhinoReminds
|
||||||
|
|
||||||
#region Reminder Listening
|
#region Reminder Listening
|
||||||
|
|
||||||
private void resetReminderWatcher()
|
private void interruptReminderWatcher()
|
||||||
{
|
{
|
||||||
// We don't need to create a new token here, as the watcher does thisi automagically
|
reminderWatcherReset?.Cancel();
|
||||||
reminderWatcherReset.Cancel();
|
reminderWatcherReset = new CancellationTokenSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task watchForReminders()
|
private async Task watchForReminders()
|
||||||
{
|
{
|
||||||
reminderWatcherReset = new CancellationTokenSource();
|
interruptReminderWatcher();
|
||||||
reminderWatcherResetToken = reminderWatcherReset.Token;
|
|
||||||
Reminder nextReminder = reminderList.GetNextReminder();
|
Reminder nextReminder = reminderList.GetNextReminder();
|
||||||
|
|
||||||
|
// ----- Events -----
|
||||||
|
// This will run on the firing thread, not on this thread
|
||||||
reminderList.OnReminderListUpdate += (object sender, Reminder newReminder) => {
|
reminderList.OnReminderListUpdate += (object sender, Reminder newReminder) => {
|
||||||
Reminder newNextReminder = reminderList.GetNextReminder();
|
Reminder newNextReminder = reminderList.GetNextReminder();
|
||||||
//Console.WriteLine("[Rhino/Reminderd/Canceller] Reminder added - comparing.");
|
//Console.WriteLine("[Rhino/Reminderd/Canceller] Reminder added - comparing.");
|
||||||
|
@ -320,52 +331,46 @@ namespace RhinoReminds
|
||||||
if (nextReminder != newNextReminder) {
|
if (nextReminder != newNextReminder) {
|
||||||
//Console.WriteLine($"[Rhino/Reminderd/Canceller] Cancelling");
|
//Console.WriteLine($"[Rhino/Reminderd/Canceller] Cancelling");
|
||||||
nextReminder = newNextReminder;
|
nextReminder = newNextReminder;
|
||||||
reminderWatcherReset.Cancel();
|
interruptReminderWatcher();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// ------------------
|
||||||
|
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
nextReminder = reminderList.GetNextReminder();
|
||||||
|
// Wait for the next reminder
|
||||||
TimeSpan nextWaitingTime;
|
TimeSpan nextWaitingTime;
|
||||||
try {
|
try {
|
||||||
if (nextReminder != null) {
|
if (nextReminder != null) {
|
||||||
nextWaitingTime = nextReminder.Time - DateTime.Now;
|
nextWaitingTime = nextReminder.Time - DateTime.Now;
|
||||||
if (DateTime.Now < nextReminder.Time) {
|
if (DateTime.Now < nextReminder.Time)
|
||||||
|
{
|
||||||
//Console.WriteLine($"[Rhino/Reminderd] Sleeping for {nextWaitingTime}");
|
//Console.WriteLine($"[Rhino/Reminderd] Sleeping for {nextWaitingTime}");
|
||||||
await Task.Delay(nextWaitingTime, reminderWatcherResetToken);
|
await Task.Delay(nextWaitingTime, reminderWatcherResetToken);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
//Console.WriteLine("[Rhino/Reminderd] Sleeping until interrupted");
|
//Console.WriteLine("[Rhino/Reminderd] Sleeping until interrupted");
|
||||||
await Task.Delay(Timeout.Infinite, reminderWatcherResetToken);
|
await Task.Delay(Timeout.Infinite, reminderWatcherResetToken);
|
||||||
}
|
}
|
||||||
} catch (TaskCanceledException) {
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
//Console.WriteLine("[Rhino/Reminderd] Sleep interrupted, recalculating");
|
//Console.WriteLine("[Rhino/Reminderd] Sleep interrupted, recalculating");
|
||||||
reminderWatcherReset = new CancellationTokenSource();
|
interruptReminderWatcher();
|
||||||
reminderWatcherResetToken = reminderWatcherReset.Token;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reminderWatcherResetToken.IsCancellationRequested) {
|
if (reminderWatcherResetToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
Console.WriteLine("[Rhino/Reminderd] Sleep interrupted, recalculating (but no exception thrown)");
|
Console.WriteLine("[Rhino/Reminderd] Sleep interrupted, recalculating (but no exception thrown)");
|
||||||
reminderWatcherReset = new CancellationTokenSource();
|
interruptReminderWatcher();
|
||||||
reminderWatcherResetToken = reminderWatcherReset.Token;
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"[Rhino/Reminderd] Sending notification {nextReminder}");
|
Console.WriteLine($"[Rhino/Reminderd] Sending notification {nextReminder}");
|
||||||
|
sendAndDeleteReminder(nextReminder);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client.SendChatMessage(
|
|
||||||
nextReminder.Jid,
|
|
||||||
$"Hello! You asked me to remind you {nextReminder.Message} at {nextReminder.Time}.".Trim().Replace(@"\s+", " ")
|
|
||||||
);
|
|
||||||
} catch (Exception error) {
|
|
||||||
Console.Error.WriteLine($"[Rhino/Reminderd] Caught error sending message to client: {error}");
|
|
||||||
Console.Error.WriteLine($"[Rhink/Reminderd] Offending reminder: {nextReminder}");
|
|
||||||
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) {
|
||||||
client.SendChatMessage(
|
client.SendChatMessage(
|
||||||
nextReminder.Jid,
|
nextReminder.Jid,
|
||||||
|
@ -373,12 +378,29 @@ namespace RhinoReminds
|
||||||
"or you might have scheduled a reminder for the past)"
|
"or you might have scheduled a reminder for the past)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
reminderList.DeleteReminder(nextReminder);
|
|
||||||
reminderList.Save(ReminderFilePath);
|
|
||||||
nextReminder = reminderList.GetNextReminder();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendAndDeleteReminder(Reminder nextReminder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.SendChatMessage(
|
||||||
|
nextReminder.Jid,
|
||||||
|
$"Hello! You asked me to remind you {nextReminder.Message} at {nextReminder.Time}.".Trim().Replace(@"\s+", " ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[Rhino/Reminderd] Caught error sending message to client: {error}");
|
||||||
|
Console.Error.WriteLine($"[Rhink/Reminderd] Offending reminder: {nextReminder}");
|
||||||
|
client.SendChatMessage(nextReminder.Jid, "Oops! I encountered an error sending you a reminder. Please contact my operator!");
|
||||||
|
}
|
||||||
|
|
||||||
|
reminderList.DeleteReminder(nextReminder);
|
||||||
|
reminderList.Save(ReminderFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
RhinoReminds/CompareReminders.cs
Normal file
17
RhinoReminds/CompareReminders.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace RhinoReminds
|
||||||
|
{
|
||||||
|
public class CompareReminders : IComparer<Reminder>
|
||||||
|
{
|
||||||
|
public CompareReminders()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Compare(Reminder x, Reminder y)
|
||||||
|
{
|
||||||
|
return (int)(x.Time - y.Time).TotalMilliseconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using S22.Xmpp;
|
using S22.Xmpp;
|
||||||
|
@ -6,29 +7,59 @@ using S22.Xmpp;
|
||||||
namespace RhinoReminds
|
namespace RhinoReminds
|
||||||
{
|
{
|
||||||
|
|
||||||
public class Reminder
|
public class Reminder : IEquatable<Reminder>
|
||||||
{
|
{
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
public string Jid { get; }
|
public string Jid => JidObj.ToString();
|
||||||
public Jid JidObj => new Jid(Jid);
|
public Jid JidObj { get; }
|
||||||
public DateTime Time { get; }
|
public DateTime Time { get; private set; }
|
||||||
public string Message { get; }
|
public string Message { get; }
|
||||||
|
|
||||||
public Reminder(int inId, string inJid, DateTime inTime, string inMessage)
|
public Reminder(int inId, Jid inJid, DateTime inTime, string inMessage)
|
||||||
{
|
{
|
||||||
Id = inId;
|
Id = inId;
|
||||||
Jid = inJid;
|
JidObj = inJid.GetBareJid();
|
||||||
Time = inTime;
|
Time = inTime;
|
||||||
Message = inMessage;
|
Message = inMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public void TweakTime() {
|
||||||
{
|
Time = Time.AddMilliseconds(1);
|
||||||
return $"[Reminder Id={Id}, Jid={Jid}, Time={Time}, Message={Message}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region XML
|
|
||||||
|
|
||||||
|
#region Overrides
|
||||||
|
|
||||||
|
public override bool Equals(object obj) {
|
||||||
|
if (!(obj is Reminder)) return false;
|
||||||
|
Reminder otherReminder = obj as Reminder;
|
||||||
|
return otherReminder.Id == Id &&
|
||||||
|
otherReminder.JidObj == JidObj && // Will *always* be a bare Jid
|
||||||
|
otherReminder.Time == Time &&
|
||||||
|
otherReminder.Message == Message;
|
||||||
|
}
|
||||||
|
// For IEquatable<Reminder> implementation
|
||||||
|
public bool Equals(Reminder otherReminder) => Equals((object)otherReminder);
|
||||||
|
|
||||||
|
public override int GetHashCode() {
|
||||||
|
int hashCode = -81903051;
|
||||||
|
hashCode = (hashCode * -1521134295) + Id.GetHashCode();
|
||||||
|
hashCode = (hashCode * -1521134295) + EqualityComparer<Jid>.Default.GetHashCode(JidObj);
|
||||||
|
hashCode = (hashCode * -1521134295) + Time.GetHashCode();
|
||||||
|
hashCode = (hashCode * -1521134295) + EqualityComparer<string>.Default.GetHashCode(Message);
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
public static bool operator ==(Reminder reminder1, Reminder reminder2) => EqualityComparer<Reminder>.Default.Equals(reminder1, reminder2);
|
||||||
|
public static bool operator !=(Reminder reminder1, Reminder reminder2) => !(reminder1 == reminder2);
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"[Reminder Id={Id}, Jid={Jid}, Time={Time}, Message=\"{Message}\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region XML
|
||||||
|
|
||||||
public void WriteToXml(XmlWriter xml)
|
public void WriteToXml(XmlWriter xml)
|
||||||
{
|
{
|
||||||
|
@ -66,7 +97,8 @@ namespace RhinoReminds
|
||||||
return new Reminder(id, jid, dateTime, message);
|
return new Reminder(id, jid, dateTime, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using S22.Xmpp;
|
using S22.Xmpp;
|
||||||
|
@ -14,7 +13,7 @@ namespace RhinoReminds
|
||||||
private int nextId = 0;
|
private int nextId = 0;
|
||||||
private readonly object saveLock = new object();
|
private readonly object saveLock = new object();
|
||||||
|
|
||||||
public SortedList<DateTime, Reminder> Reminders = new SortedList<DateTime, Reminder>();
|
public SortedSet<Reminder> Reminders = new SortedSet<Reminder>(new CompareReminders());
|
||||||
|
|
||||||
public event OnReminderListUpdateHandler OnReminderListUpdate;
|
public event OnReminderListUpdateHandler OnReminderListUpdate;
|
||||||
|
|
||||||
|
@ -29,29 +28,28 @@ namespace RhinoReminds
|
||||||
{
|
{
|
||||||
Reminder result = new Reminder(nextId++, $"{inJid.Node}@{inJid.Domain}", time, message);
|
Reminder result = new Reminder(nextId++, $"{inJid.Node}@{inJid.Domain}", time, message);
|
||||||
Console.WriteLine($"[Rhino/ReminderList] Created reminder {result}");
|
Console.WriteLine($"[Rhino/ReminderList] Created reminder {result}");
|
||||||
while (Reminders.ContainsKey(time))
|
while (Reminders.Contains(result))
|
||||||
time = time.AddMilliseconds(1);
|
result.TweakTime();
|
||||||
Reminders.Add(time, result);
|
|
||||||
|
Reminders.Add(result);
|
||||||
OnReminderListUpdate(this, result);
|
OnReminderListUpdate(this, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Reminder GetNextReminder()
|
public Reminder GetNextReminder() {
|
||||||
{
|
|
||||||
if (Reminders.Count == 0)
|
if (Reminders.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return Reminders.Values[0];
|
return Reminders.Min;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Reminder GetById(int targetId)
|
public Reminder GetById(int targetId) {
|
||||||
{
|
return Reminders.First((Reminder nextReminder) => nextReminder.Id == targetId);
|
||||||
return Reminders.First((KeyValuePair<DateTime, Reminder> nextPair) => nextPair.Value.Id == targetId).Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteReminder(Reminder nextReminder)
|
public void DeleteReminder(Reminder nextReminder) {
|
||||||
{
|
if (!Reminders.Remove(nextReminder))
|
||||||
Reminders.Remove(nextReminder.Time);
|
throw new ApplicationException($"Error: Failed to remove the reminder {nextReminder} from the list!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +60,7 @@ namespace RhinoReminds
|
||||||
// Make sure that the reminder thread doesn't try to save the reminders at the exact same time
|
// Make sure that the reminder thread doesn't try to save the reminders at the exact same time
|
||||||
// we receive a request for a new reminder
|
// we receive a request for a new reminder
|
||||||
lock (saveLock)
|
lock (saveLock)
|
||||||
{ // FUTURE: We could go lockless here with some work, but it's not worth it for the teeny chance & low overhead
|
{ // FUTURE: We could go lockless here with some work, but it's not worth it for the teensy chance & low overhead
|
||||||
XmlWriter xml = XmlWriter.Create(
|
XmlWriter xml = XmlWriter.Create(
|
||||||
filename,
|
filename,
|
||||||
new XmlWriterSettings() { Indent = true }
|
new XmlWriterSettings() { Indent = true }
|
||||||
|
@ -74,7 +72,7 @@ namespace RhinoReminds
|
||||||
xml.WriteElementString("NextId", nextId.ToString());
|
xml.WriteElementString("NextId", nextId.ToString());
|
||||||
|
|
||||||
xml.WriteStartElement("Reminders");
|
xml.WriteStartElement("Reminders");
|
||||||
foreach (Reminder nextReminder in Reminders.Values)
|
foreach (Reminder nextReminder in Reminders)
|
||||||
nextReminder.WriteToXml(xml);
|
nextReminder.WriteToXml(xml);
|
||||||
xml.WriteEndElement();
|
xml.WriteEndElement();
|
||||||
|
|
||||||
|
@ -89,15 +87,15 @@ namespace RhinoReminds
|
||||||
XmlDocument xml = new XmlDocument();
|
XmlDocument xml = new XmlDocument();
|
||||||
xml.Load(filepath);
|
xml.Load(filepath);
|
||||||
|
|
||||||
ReminderList result = new ReminderList();
|
ReminderList result = new ReminderList() {
|
||||||
result.nextId = int.Parse(xml.GetElementsByTagName("NextId")[0].InnerText);
|
nextId = int.Parse(xml.GetElementsByTagName("NextId")[0].InnerText)
|
||||||
foreach (XmlNode reminderXML in xml.GetElementsByTagName("Reminders")[0].ChildNodes)
|
};
|
||||||
{
|
|
||||||
|
foreach (XmlNode reminderXML in xml.GetElementsByTagName("Reminders")[0].ChildNodes) {
|
||||||
Reminder nextReminder = Reminder.FromXml(reminderXML);
|
Reminder nextReminder = Reminder.FromXml(reminderXML);
|
||||||
DateTime timeKey = nextReminder.Time;
|
while (result.Reminders.Contains(nextReminder))
|
||||||
while (result.Reminders.ContainsKey(timeKey))
|
nextReminder.TweakTime();
|
||||||
timeKey = timeKey.AddMilliseconds(1);
|
result.Reminders.Add(nextReminder);
|
||||||
result.Reminders.Add(timeKey, nextReminder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
<Compile Include="Utilities\Range.cs" />
|
<Compile Include="Utilities\Range.cs" />
|
||||||
<Compile Include="Utilities\TextHelpers.cs" />
|
<Compile Include="Utilities\TextHelpers.cs" />
|
||||||
<Compile Include="SimpleXmppClient.cs" />
|
<Compile Include="SimpleXmppClient.cs" />
|
||||||
|
<Compile Include="CompareReminders.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|
Loading…
Reference in a new issue