public void GeoFix_Serialize_Fields() { // Test serialization of specific fields GeoFix fix; fix = new GeoFix(); Assert.AreEqual(",,,,,,,,,", fix.ToString(GeoFixField.All)); fix = new GeoFix() { TimeUtc = new DateTime(2011, 3, 3, 10, 55, 15), Latitude = 1.1, Longitude = 2.2, Altitude = 3.3, Course = 4.4, Speed = 5.5, HorizontalAccuracy = 6.6, VerticalAccurancy = 7.7, Technology = GeoFixTechnology.Tower, NetworkStatus = NetworkStatus.Gsm }; Assert.AreEqual(string.Format("{0},,,,,,,,,", fix.TimeUtc.Value.Ticks), fix.ToString(GeoFixField.TimeUtc)); Assert.AreEqual(",1.1,,,,,,,,", fix.ToString(GeoFixField.Latitude)); Assert.AreEqual(",,2.2,,,,,,,", fix.ToString(GeoFixField.Longitude)); Assert.AreEqual(",,,3.3,,,,,,", fix.ToString(GeoFixField.Altitude)); Assert.AreEqual(",,,,4.4,,,,,", fix.ToString(GeoFixField.Course)); Assert.AreEqual(",,,,,5.5,,,,", fix.ToString(GeoFixField.Speed)); Assert.AreEqual(",,,,,,6.6,,,", fix.ToString(GeoFixField.HorizontalAccuracy)); Assert.AreEqual(",,,,,,,7.7,,", fix.ToString(GeoFixField.VerticalAccurancy)); Assert.AreEqual(",,,,,,,,Tower,", fix.ToString(GeoFixField.Technology)); Assert.AreEqual(",,,,,,,,,Gsm", fix.ToString(GeoFixField.NetworkStatus)); }
private void VerifyTestFix(GeoFix fix) { Assert.IsNotNull(fix); // The lat/lon may change. I got these values from visiting http://maxmind.com and // performing a manual lookup on the test IP address. These coordinates are // for Google in MountainView, CA. Assert.AreEqual(37.4192, Math.Round(fix.Latitude, 4, MidpointRounding.AwayFromZero)); Assert.AreEqual(-122.0574, Math.Round(fix.Longitude, 4, MidpointRounding.AwayFromZero)); }
public void GeoFix_Serialize() { GeoFix fix; string[] fields; // Default values fix = new GeoFix(); fields = fix.ToString().Split(','); Assert.AreEqual(10, fields.Length); Assert.AreEqual("", fields[0]); // Null TimeUtc is rendered as empty string Assert.AreEqual("", fields[1]); // NaN coordinates are rendered as empty strings Assert.AreEqual("", fields[2]); Assert.AreEqual("", fields[3]); Assert.AreEqual("", fields[4]); Assert.AreEqual("", fields[5]); Assert.AreEqual("", fields[6]); Assert.AreEqual("", fields[7]); Assert.AreEqual("", fields[8]); // GetFixTechnology.Unknown is rendered as empty Assert.AreEqual("", fields[9]); // NetworkStatus.Unknown is rendered as empty // Set values fix = new GeoFix() { TimeUtc = new DateTime(2011, 3, 3, 10, 55, 15), Latitude = 1.1, Longitude = 2.2, Altitude = 3.3, Course = 4.4, Speed = 5.5, HorizontalAccuracy = 6.6, VerticalAccurancy = 7.7, Technology = GeoFixTechnology.Tower, NetworkStatus = NetworkStatus.Gsm }; fields = fix.ToString().Split(','); Assert.AreEqual(10, fields.Length); Assert.AreEqual(fix.TimeUtc.Value.Ticks.ToString(), fields[0]); Assert.AreEqual("1.1", fields[1]); Assert.AreEqual("2.2", fields[2]); Assert.AreEqual("3.3", fields[3]); Assert.AreEqual("4.4", fields[4]); Assert.AreEqual("5.5", fields[5]); Assert.AreEqual("6.6", fields[6]); Assert.AreEqual("7.7", fields[7]); Assert.AreEqual("Tower", fields[8]); Assert.AreEqual("Gsm", fields[9]); }
/// <summary> /// Archives a location fix for an entity. /// </summary> /// <param name="entityID">The entity identifier.</param> /// <param name="groupID">The group identifier or <c>null</c>.</param> /// <param name="fix">The location fix.</param> /// <remarks> /// <note> /// <see cref="IGeoFixArchiver" /> implementations must silently handle any internal /// error conditions. <see cref="GeoTrackerNode" /> does not expect to see any /// exceptions raised from calls to any of these methods. Implementations should /// catch any exceptions thrown internally and log errors or warnings as necessary. /// </note> /// </remarks> public void Archive(string entityID, string groupID, GeoFix fix) { lock (syncLock) { if (!isRunning) { return; } bufferedFixes.Add(new FixRecord(entityID, groupID, fix)); } }
/// <summary> /// Prepends a <see cref="GeoFix" /> to the head of the list of fixes, /// dropping the fix from the end to keep the total number of fixes /// within the limit passed to the constructor. /// </summary> /// <param name="fix">The received fix.</param> /// <param name="groupID">The associated group ID or <c>null</c>.</param> public void AddFix(GeoFix fix, string groupID) { // Use server time for the timestamp if necessary. if (fix.TimeUtc.HasValue) { var now = DateTime.UtcNow; // Don't allow fixes to be recorded for the future. This could happen due // to clock skew from the source or possibly software bugs. Allowing a fix // from the future would effectively cause all subsequent fixes to be ignored // which seems like a really bad thing (perhaps requiring server restart in // extreme circumstances). if (fix.TimeUtc > now) { fix.TimeUtc = now; } } else { fix.TimeUtc = DateTime.UtcNow; } lock (fixes) { // This is really simple and somewhat inefficient. I'm just going // to insert the new fix at the beginning of the list, making sure // that the list size remains below the limit. while (fixes.Count >= MaxEntityFixes) { fixes.RemoveAt(fixes.Count - 1); } fixes.Insert(0, fix); if (CurrentFix == null) { CurrentFix = fix; } else if (CurrentFix.TimeUtc <= fix.TimeUtc) { CurrentFix = fix; } if (groupID != null) { AddGroup(groupID, fix.TimeUtc.Value); } } }
/// <summary> /// Initiates an asynchronous operation to submits a <see cref="GeoFix" /> /// for an entity to the GeoTracker cluster. /// </summary> /// <param name="entityID">The unique entity ID.</param> /// <param name="groupID">The group ID or <c>null</c>.</param> /// <param name="fix">The <see cref="GeoFix" /> being submitted.</param> /// <param name="callback">The completion callback or <c>null</c>.</param> /// <param name="state">Application state or <c>null</c>.</param> /// <returns>The <see cref="IAsyncResult" /> to be used to track the progress of the operation.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="entityID" /> or <paramref name="fix" /> is <c>null</c>.</exception> /// <remarks> /// <note> /// All calls to <see cref="BeginSubmitEntityFix" /> must eventually be followed by a call to <see cref="EndSubmitEntityFix" />. /// </note> /// </remarks> public IAsyncResult BeginSubmitEntityFix(string entityID, string groupID, GeoFix fix, AsyncCallback callback, object state) { if (entityID == null) { throw new ArgumentNullException("entityID"); } if (fix == null) { throw new ArgumentNullException("fix"); } return(router.BeginQuery(settings.ServerEP, new GeoFixMsg(entityID, groupID, fix), callback, state)); }
public void GeoFix_Deserialize() { GeoFix fix; string v; // Default values fix = new GeoFix(); v = fix.ToString(); fix = new GeoFix(v); Assert.IsNull(fix.TimeUtc); Assert.AreEqual(double.NaN, fix.Latitude); Assert.AreEqual(double.NaN, fix.Longitude); Assert.AreEqual(double.NaN, fix.Altitude); Assert.AreEqual(double.NaN, fix.Course); Assert.AreEqual(double.NaN, fix.Speed); Assert.AreEqual(double.NaN, fix.HorizontalAccuracy); Assert.AreEqual(double.NaN, fix.VerticalAccurancy); Assert.AreEqual(GeoFixTechnology.Unknown, fix.Technology); Assert.AreEqual(NetworkStatus.Unknown, fix.NetworkStatus); // Set values fix = new GeoFix() { TimeUtc = new DateTime(2011, 3, 3, 10, 55, 15), Latitude = 1.1, Longitude = 2.2, Altitude = 3.3, Course = 4.4, Speed = 5.5, HorizontalAccuracy = 6.6, VerticalAccurancy = 7.7, Technology = GeoFixTechnology.GPS, NetworkStatus = NetworkStatus.Cdma }; Assert.AreEqual(new DateTime(2011, 3, 3, 10, 55, 15), fix.TimeUtc); Assert.AreEqual(1.1, fix.Latitude); Assert.AreEqual(2.2, fix.Longitude); Assert.AreEqual(3.3, fix.Altitude); Assert.AreEqual(4.4, fix.Course); Assert.AreEqual(5.5, fix.Speed); Assert.AreEqual(6.6, fix.HorizontalAccuracy); Assert.AreEqual(7.7, fix.VerticalAccurancy); Assert.AreEqual(GeoFixTechnology.GPS, fix.Technology); Assert.AreEqual(NetworkStatus.Cdma, fix.NetworkStatus); }
public void GeoFix_Defaults() { var fix = new GeoFix(); Assert.IsNull(fix.TimeUtc); Assert.AreEqual(double.NaN, fix.Latitude); Assert.AreEqual(double.NaN, fix.Longitude); Assert.AreEqual(double.NaN, fix.Altitude); Assert.AreEqual(double.NaN, fix.Course); Assert.AreEqual(double.NaN, fix.Speed); Assert.AreEqual(double.NaN, fix.HorizontalAccuracy); Assert.AreEqual(double.NaN, fix.VerticalAccurancy); Assert.AreEqual(GeoFixTechnology.Unknown, fix.Technology); Assert.AreEqual(NetworkStatus.Unknown, fix.NetworkStatus); }
/// <summary> /// Loads the message payload from the stream passed. /// </summary> /// <param name="es">The enhanced stream where the output is to be written.</param> /// <param name="cbPayload">Number of bytes of payload data.</param> protected override void ReadPayload(EnhancedStream es, int cbPayload) { int cFixes; EntityID = es.ReadString16(); GroupID = es.ReadString16(); cFixes = es.ReadInt32(); Fixes = new GeoFix[cFixes]; for (int i = 0; i < cFixes; i++) { Fixes[i] = new GeoFix(es.ReadString16()); } }
public void GeoFix_Parse() { GeoFix fix; // Test null or empty input strings. Assert.IsNull(GeoFix.Parse(null)); Assert.IsNull(GeoFix.Parse("")); Assert.IsNull(GeoFix.Parse(" ")); // Test actual parsing. fix = new GeoFix() { TimeUtc = new DateTime(2011, 3, 3, 10, 55, 15), Latitude = 1.1, Longitude = 2.2, Altitude = 3.3, Course = 4.4, Speed = 5.5, HorizontalAccuracy = 6.6, VerticalAccurancy = 7.7, Technology = GeoFixTechnology.GPS, NetworkStatus = NetworkStatus.Cdma }; fix = GeoFix.Parse(fix.ToString()); Assert.AreEqual(new DateTime(2011, 3, 3, 10, 55, 15), fix.TimeUtc); Assert.AreEqual(1.1, fix.Latitude); Assert.AreEqual(2.2, fix.Longitude); Assert.AreEqual(3.3, fix.Altitude); Assert.AreEqual(4.4, fix.Course); Assert.AreEqual(5.5, fix.Speed); Assert.AreEqual(6.6, fix.HorizontalAccuracy); Assert.AreEqual(7.7, fix.VerticalAccurancy); Assert.AreEqual(GeoFixTechnology.GPS, fix.Technology); Assert.AreEqual(NetworkStatus.Cdma, fix.NetworkStatus); }
/// <summary> /// Constructor. /// </summary> /// <param name="entityID">The entity ID.</param> /// <param name="fix">The current <see cref="GeoFix" /> for the entity.</param> public EntityFix(string entityID, GeoFix fix) { this.EntityID = entityID; this.Fix = fix; }
/// <summary> /// Adds an entity location fix to the cache. /// </summary> /// <param name="entityID">The entity ID.</param> /// <param name="groupID">The group ID or <c>null</c>.</param> /// <param name="fix">The location <see cref="GeoFix" />.</param> public void AddEntityFix(string entityID, string groupID, GeoFix fix) { AddEntityFixes(entityID, groupID, new GeoFix[] { fix }); }
/// <summary> /// Synchronously submits a <see cref="GeoFix" /> for an entity to the GeoTracker cluster. /// </summary> /// <param name="entityID">The unique entity ID.</param> /// <param name="groupID">The group ID or <c>null</c>.</param> /// <param name="fix">The <see cref="GeoFix" /> being submitted.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="entityID" /> or <paramref name="fix" /> are <c>null</c>.</exception> public void SubmitEntityFix(string entityID, string groupID, GeoFix fix) { var ar = BeginSubmitEntityFix(entityID, groupID, fix, null, null); EndSubmitEntityFix(ar); }
public FixRecord(string entityID, string groupID, GeoFix fix) { this.EntityID = entityID; this.GroupID = groupID; this.Fix = fix; }
/// <summary> /// Archives a location fix for an entity. /// </summary> /// <param name="entityID">The entity identifier.</param> /// <param name="groupID">The group identifier or <c>null</c>.</param> /// <param name="fix">The location fix.</param> /// <remarks> /// <note> /// <see cref="IGeoFixArchiver" /> implementations must silently handle any internal /// error conditions. <see cref="GeoTrackerNode" /> does not expect to see any /// exceptions raised from calls to any of these methods. Implementations should /// catch any exceptions thrown internally and log errors or warnings as necessary. /// </note> /// </remarks> public void Archive(string entityID, string groupID, GeoFix fix) { }
/// <summary> /// Constructs a message with a single <see cref="GeoFix" />. /// </summary> /// <param name="entityID">The unique entity ID.</param> /// <param name="groupID">The group ID or <c>null</c>.</param> /// <param name="fix">The <see cref="GeoFix" /> to be recorded.</param> public GeoFixMsg(string entityID, string groupID, GeoFix fix) { this.EntityID = entityID; this.GroupID = groupID; this.Fixes = new GeoFix[] { fix }; }
public void GeoTrackerMsgs_IPToGeoFixAck() { EnhancedStream es = new EnhancedMemoryStream(); IPToGeoFixAck msgIn, msgOut; GeoFix fix; Msg.ClearTypes(); LillTek.GeoTracker.Global.RegisterMsgTypes(); // Test the return of an actual fix. fix = new GeoFix() { TimeUtc = new DateTime(2011, 3, 3, 10, 55, 15), Latitude = 1.1, Longitude = 2.2, Altitude = 3.3, Course = 4.4, Speed = 5.5, HorizontalAccuracy = 6.6, VerticalAccurancy = 7.7, Technology = GeoFixTechnology.GPS, NetworkStatus = NetworkStatus.Cdma }; msgOut = new IPToGeoFixAck(fix); Msg.Save(es, msgOut); es.Position = 0; msgIn = (IPToGeoFixAck)Msg.Load(es); Assert.IsNotNull(msgIn); fix = msgIn.GeoFix; Assert.AreEqual(new DateTime(2011, 3, 3, 10, 55, 15), fix.TimeUtc); Assert.AreEqual(1.1, fix.Latitude); Assert.AreEqual(2.2, fix.Longitude); Assert.AreEqual(3.3, fix.Altitude); Assert.AreEqual(4.4, fix.Course); Assert.AreEqual(5.5, fix.Speed); Assert.AreEqual(6.6, fix.HorizontalAccuracy); Assert.AreEqual(7.7, fix.VerticalAccurancy); Assert.AreEqual(GeoFixTechnology.GPS, fix.Technology); Assert.AreEqual(NetworkStatus.Cdma, fix.NetworkStatus); // Test the return of a NULL fix. msgOut = new IPToGeoFixAck((GeoFix)null); es.Position = 0; Msg.Save(es, msgOut); es.Position = 0; msgIn = (IPToGeoFixAck)Msg.Load(es); Assert.IsNotNull(msgIn); Assert.IsNull(msgIn.GeoFix); // Test exception encoding. msgOut = new IPToGeoFixAck(new NotImplementedException("This is a test")); es.Position = 0; Msg.Save(es, msgOut); es.Position = 0; msgIn = (IPToGeoFixAck)Msg.Load(es); Assert.IsNotNull(msgIn); Assert.IsNull(msgIn.GeoFix); Assert.AreEqual("System.NotImplementedException", msgOut.ExceptionTypeName); Assert.AreEqual("This is a test", msgOut.Exception); // Test Clone() msgIn = (IPToGeoFixAck) new IPToGeoFixAck(fix).Clone(); fix = msgIn.GeoFix; Assert.AreEqual(new DateTime(2011, 3, 3, 10, 55, 15), fix.TimeUtc); Assert.AreEqual(1.1, fix.Latitude); Assert.AreEqual(2.2, fix.Longitude); Assert.AreEqual(3.3, fix.Altitude); Assert.AreEqual(4.4, fix.Course); Assert.AreEqual(5.5, fix.Speed); Assert.AreEqual(6.6, fix.HorizontalAccuracy); Assert.AreEqual(7.7, fix.VerticalAccurancy); Assert.AreEqual(GeoFixTechnology.GPS, fix.Technology); Assert.AreEqual(NetworkStatus.Cdma, fix.NetworkStatus); msgIn = (IPToGeoFixAck) new IPToGeoFixAck(new NotImplementedException("This is a test")).Clone(); Assert.IsNotNull(msgIn); Assert.IsNull(msgIn.GeoFix); Assert.AreEqual("System.NotImplementedException", msgOut.ExceptionTypeName); Assert.AreEqual("This is a test", msgOut.Exception); }
/// <summary> /// Archives a location fix for an entity. /// </summary> /// <param name="entityID">The entity identifier.</param> /// <param name="groupID">The group identifier or <c>null</c>.</param> /// <param name="fix">The location fix.</param> /// <remarks> /// <note> /// <see cref="IGeoFixArchiver" /> implementations must silently handle any internal /// error conditions. <see cref="GeoTrackerNode" /> does not expect to see any /// exceptions raised from calls to any of these methods. Implementations should /// catch any exceptions thrown internally and log errors or warnings as necessary. /// </note> /// </remarks> public void Archive(string entityID, string groupID, GeoFix fix) { lock (syncLock) { try { if (isStopped) { throw new InvalidOperationException("AppLogGeoFixArchiver: Cannot log to a stopped archiver."); } if (logWriter == null) { logWriter = new AppLogWriter(logName, SchemaName, SchemaVersion, maxSize); logWriter.PurgeInterval = purgeInterval; } var record = new AppLogRecord(); if (entityID != null) { record["EntityID"] = entityID; } if (groupID != null) { record["GroupID"] = groupID; } if (fix.TimeUtc.HasValue) { record["TimeUtc"] = fix.TimeUtc; } record["Technology"] = fix.Technology; if (!double.IsNaN(fix.Latitude)) { record["Latitude"] = fix.Latitude; } if (!double.IsNaN(fix.Longitude)) { record["Longitude"] = fix.Longitude; } if (!double.IsNaN(fix.Altitude)) { record["Altitude"] = fix.Altitude; } if (!double.IsNaN(fix.Course)) { record["Course"] = fix.Course; } if (!double.IsNaN(fix.Speed)) { record["Speed"] = fix.Speed; } if (!double.IsNaN(fix.HorizontalAccuracy)) { record["HorizontalAccuracy"] = fix.HorizontalAccuracy; } if (!double.IsNaN(fix.VerticalAccurancy)) { record["VerticalAccurancy"] = fix.VerticalAccurancy; } record["NetworkStatus"] = fix.NetworkStatus; logWriter.Write(record); node.IncrementFixesReceivedBy(1); } catch (Exception e) { SysLog.LogException(e); } } }
/// <summary> /// Constructs the ack instance to be used to communicate the configuration text /// text back to the client. /// </summary> /// <param name="fix">The <see cref="GeoFix" /> to be returned or <c>null</c>. /// </param> public IPToGeoFixAck(GeoFix fix) { this.GeoFix = fix; }
public void GeoTracker_Cluster_SubmitFix_Multiple() { // Submit multiple location fixes for several entities and verify. try { const int cEntities = 100; const int cFixes = 10; int cSubmitted; bool fail; TestInit(); // Submit the fixes in parallel. cSubmitted = 0; fail = false; for (int i = 0; i < cFixes; i++) { for (int j = 0; j < cEntities; j++) { var fix = new GeoFix() { Latitude = 10 + i, Longitude = 20 }; client.BeginSubmitEntityFix(j.ToString(), "group", fix, ar => { try { client.EndSubmitEntityFix(ar); } catch (Exception e) { SysLog.LogException(e); fail = true; } finally { Interlocked.Increment(ref cSubmitted); } }, null); } } // Wait for the submissions to complete. Helper.WaitFor(() => cSubmitted == cEntities * cFixes, TimeSpan.FromMinutes(2)); Assert.IsFalse(fail); // Verify that the fixes were distributed across the cluster. var instanceFixes = new int[clusterInstances.Length]; for (int i = 0; i < clusterInstances.Length; i++) { instanceFixes[i] = clusterInstances[i].Node.FixCache.EntityCount; Assert.IsTrue(instanceFixes[i] >= cEntities / InstanceCount * 0.75); } } finally { TestCleanup(); } }
/// <summary> /// Removes all fixes and group memberships with timestamps older than the specified value. /// </summary> /// <param name="purgeTimeUtc">The minimum age for retained fixes.</param> /// <returns><c>true</c> if the all fixes have been purged from the entity.</returns> public bool Purge(DateTime purgeTimeUtc) { int cPurged = 0; lock (fixes) { // Set the slots for purged fixes to NULL. for (int i = 0; i < fixes.Count; i++) { if (fixes[i].TimeUtc < purgeTimeUtc) { fixes[i] = null; cPurged++; } } // Now go back and collapse out the null entries. if (cPurged == 0) { CurrentFix = null; return(fixes.Count == 0); } int pos = 0; for (int i = 0; i < fixes.Count; i++) { if (fixes[i] != null) { fixes[pos++] = fixes[i]; } } for (int i = 0; i < cPurged; i++) { fixes.RemoveAt(fixes.Count - 1); } if (fixes.Count == 0) { groups = null; CurrentFix = null; return(true); } else { if (groups != null) { // Determine whether we need to remove any group references. int retainCount = 0; foreach (var group in groups) { if (group.FixTimeUtc >= purgeTimeUtc) { retainCount++; } } if (retainCount < groups.Length) { var newGroups = new GroupMembership[retainCount]; pos = 0; foreach (var group in groups) { if (group.FixTimeUtc >= purgeTimeUtc) { newGroups[pos++] = group; } } groups = newGroups; } } return(false); } } }