diff --git a/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAircraftApi.cs b/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAircraftApi.cs
index 3e8f138..1152d71 100644
--- a/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAircraftApi.cs
+++ b/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAircraftApi.cs
@@ -1,4 +1,5 @@
using BaseStationReader.Entities.Interfaces;
+using BaseStationReader.Entities.Logging;
using BaseStationReader.Entities.Tracking;
namespace BaseStationReader.Logic.Api.AirLabs
@@ -7,7 +8,7 @@ public class AirLabsAircraftApi : ExternalApiBase, IAircraftApi
{
private readonly string _baseAddress;
- public AirLabsAircraftApi(ITrackerHttpClient client, string url, string key) : base(client)
+ public AirLabsAircraftApi(ITrackerLogger logger, ITrackerHttpClient client, string url, string key) : base(logger, client)
{
_baseAddress = $"{url}?api_key={key}";
}
@@ -19,7 +20,9 @@ public AirLabsAircraftApi(ITrackerHttpClient client, string url, string key) : b
///
public async Task?> LookupAircraft(string address)
{
- return await MakeApiRequest($"&hex={address}");
+ Logger.LogMessage(Severity.Info, $"Looking up aircraft with address {address}");
+ var properties = await MakeApiRequest($"&hex={address}");
+ return properties;
}
///
@@ -52,8 +55,11 @@ public AirLabsAircraftApi(ITrackerHttpClient client, string url, string key) : b
{ ApiProperty.ModelICAO, apiResponse!["icao"]?.GetValue() ?? "" }
};
}
- catch
+ catch (Exception ex)
{
+ var message = $"Error processing response: {ex.Message}";
+ Logger.LogMessage(Severity.Error, message);
+ Logger.LogException(ex);
properties = null;
}
}
diff --git a/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAirlinesApi.cs b/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAirlinesApi.cs
index bbaf020..857eb9c 100644
--- a/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAirlinesApi.cs
+++ b/src/BaseStationReader.Logic/Api/AirLabs/AirLabsAirlinesApi.cs
@@ -1,5 +1,7 @@
using BaseStationReader.Entities.Interfaces;
+using BaseStationReader.Entities.Logging;
using BaseStationReader.Entities.Tracking;
+using System.Text.Json.Nodes;
namespace BaseStationReader.Logic.Api.AirLabs
{
@@ -7,7 +9,7 @@ public class AirLabsAirlinesApi : ExternalApiBase, IAirlinesApi
{
private readonly string _baseAddress;
- public AirLabsAirlinesApi(ITrackerHttpClient client, string url, string key) : base(client)
+ public AirLabsAirlinesApi(ITrackerLogger logger, ITrackerHttpClient client, string url, string key) : base(logger, client)
{
_baseAddress = $"{url}?api_key={key}";
}
@@ -19,6 +21,7 @@ public AirLabsAirlinesApi(ITrackerHttpClient client, string url, string key) : b
///
public async Task?> LookupAirlineByIATACode(string iata)
{
+ Logger.LogMessage(Severity.Info, $"Looking up airline with IATA code {iata}");
return await MakeApiRequest($"&iata_code={iata}");
}
@@ -29,6 +32,7 @@ public AirLabsAirlinesApi(ITrackerHttpClient client, string url, string key) : b
///
public async Task?> LookupAirlineByICAOCode(string icao)
{
+ Logger.LogMessage(Severity.Info, $"Looking up airline with ICAO code {icao}");
return await MakeApiRequest($"&icao_code={icao}");
}
@@ -55,13 +59,16 @@ public AirLabsAirlinesApi(ITrackerHttpClient client, string url, string key) : b
// Extract the values into a dictionary
properties = new()
{
- { ApiProperty.AirlineIATA, apiResponse!["iata_code"]!.GetValue() },
- { ApiProperty.AirlineICAO, apiResponse!["icao_code"]!.GetValue() },
- { ApiProperty.AirlineName, apiResponse!["name"]!.GetValue() },
+ { ApiProperty.AirlineIATA, apiResponse!["iata_code"]?.GetValue() ?? "" },
+ { ApiProperty.AirlineICAO, apiResponse!["icao_code"]?.GetValue() ?? "" },
+ { ApiProperty.AirlineName, apiResponse!["name"]?.GetValue() ?? "" },
};
}
- catch
+ catch (Exception ex)
{
+ var message = $"Error processing response: {ex.Message}";
+ Logger.LogMessage(Severity.Error, message);
+ Logger.LogException(ex);
properties = null;
}
}
diff --git a/src/BaseStationReader.Logic/Api/ExternalApiBase.cs b/src/BaseStationReader.Logic/Api/ExternalApiBase.cs
index 519cc85..fabc8d5 100644
--- a/src/BaseStationReader.Logic/Api/ExternalApiBase.cs
+++ b/src/BaseStationReader.Logic/Api/ExternalApiBase.cs
@@ -1,4 +1,6 @@
using BaseStationReader.Entities.Interfaces;
+using BaseStationReader.Entities.Logging;
+using BaseStationReader.Entities.Tracking;
using System.Text.Json.Nodes;
namespace BaseStationReader.Logic.Api
@@ -7,8 +9,11 @@ public abstract class ExternalApiBase
{
private readonly ITrackerHttpClient _client;
- protected ExternalApiBase(ITrackerHttpClient client)
+ protected ITrackerLogger Logger { get; private set; }
+
+ protected ExternalApiBase(ITrackerLogger logger, ITrackerHttpClient client)
{
+ Logger = logger;
_client = client;
}
@@ -35,12 +40,43 @@ protected ExternalApiBase(ITrackerHttpClient client)
}
}
}
- catch
+ catch (Exception ex)
{
+ var message = $"Error calling {endpoint}: {ex.Message}";
+ Logger.LogMessage(Severity.Error, message);
+ Logger.LogException(ex);
node = null;
}
return node;
}
+
+ ///
+ /// Log the content of a properties dictionary resulting from an external API call
+ ///
+ ///
+ protected void LogProperties(Dictionary? properties)
+ {
+ // Check the properties dictionary isn't NULL
+ if (properties != null)
+ {
+ // Not a NULL dictionary, so iterate over all the properties it contains
+ foreach (var property in properties)
+ {
+ // Construct a message containing the property name and the value, replacing
+ // null values with "NULL"
+ var value = property.Value != null ? property.Value.ToString() : "NULL";
+ var message = $"API property {property.Key.ToString()} = {value}";
+
+ // Log the message for this property
+ Logger.LogMessage(Severity.Info, message);
+ }
+ }
+ else
+ {
+ // Log the fact that the properties dictionary is NULL
+ Logger.LogMessage(Severity.Warning, "API lookup generated a NULL properties dictionary");
+ }
+ }
}
}
diff --git a/src/BaseStationReader.Tests/AirLabsAircraftApiTest.cs b/src/BaseStationReader.Tests/AirLabsAircraftApiTest.cs
index 572c128..b181e17 100644
--- a/src/BaseStationReader.Tests/AirLabsAircraftApiTest.cs
+++ b/src/BaseStationReader.Tests/AirLabsAircraftApiTest.cs
@@ -21,8 +21,9 @@ public class AirLabsAircraftApiTest
[TestInitialize]
public void Initialise()
{
+ var logger = new MockFileLogger();
_client = new MockTrackerHttpClient();
- _api = new AirLabsAircraftApi(_client, "", "");
+ _api = new AirLabsAircraftApi(logger, _client, "", "");
}
[TestMethod]
diff --git a/src/BaseStationReader.Tests/AirLabsAirlinesApiTest.cs b/src/BaseStationReader.Tests/AirLabsAirlinesApiTest.cs
index 59955f6..5c677d4 100644
--- a/src/BaseStationReader.Tests/AirLabsAirlinesApiTest.cs
+++ b/src/BaseStationReader.Tests/AirLabsAirlinesApiTest.cs
@@ -14,6 +14,8 @@ namespace BaseStationReader.Tests
public class AirLabsAirlinesApiTest
{
private const string Response = "{\"response\": [{\"name\": \"Jet2.com\", \"iata_code\": \"LS\", \"icao_code\": \"EXS\"}]}";
+ private const string NoIATACode = "{\"response\": [{\"name\": \"Jet2.com\", \"iata_code\": null, \"icao_code\": \"EXS\"}]}";
+ private const string NoICAOCode = "{\"response\": [{\"name\": \"Jet2.com\", \"iata_code\": \"LS\", \"icao_code\": null}]}";
private MockTrackerHttpClient? _client = null;
private IAirlinesApi? _api = null;
@@ -21,8 +23,9 @@ public class AirLabsAirlinesApiTest
[TestInitialize]
public void Initialise()
{
+ var logger = new MockFileLogger();
_client = new MockTrackerHttpClient();
- _api = new AirLabsAirlinesApi(_client, "", "");
+ _api = new AirLabsAirlinesApi(logger, _client, "", "");
}
[TestMethod]
@@ -51,6 +54,32 @@ public void GetAirlineByICAOCodeTest()
Assert.AreEqual("Jet2.com", properties[ApiProperty.AirlineName]);
}
+ [TestMethod]
+ public void NoIATACodeTest()
+ {
+ _client!.AddResponse(NoIATACode);
+ var properties = Task.Run(() => _api!.LookupAirlineByICAOCode("EXS")).Result;
+
+ Assert.IsNotNull(properties);
+ Assert.AreEqual(3, properties.Count);
+ Assert.AreEqual("", properties[ApiProperty.AirlineIATA]);
+ Assert.AreEqual("EXS", properties[ApiProperty.AirlineICAO]);
+ Assert.AreEqual("Jet2.com", properties[ApiProperty.AirlineName]);
+ }
+
+ [TestMethod]
+ public void NoICAOCodeTest()
+ {
+ _client!.AddResponse(NoICAOCode);
+ var properties = Task.Run(() => _api!.LookupAirlineByICAOCode("EXS")).Result;
+
+ Assert.IsNotNull(properties);
+ Assert.AreEqual(3, properties.Count);
+ Assert.AreEqual("LS", properties[ApiProperty.AirlineIATA]);
+ Assert.AreEqual("", properties[ApiProperty.AirlineICAO]);
+ Assert.AreEqual("Jet2.com", properties[ApiProperty.AirlineName]);
+ }
+
[TestMethod]
public void InvalidJsonResponseTest()
{
diff --git a/src/BaseStationReader.Tests/AircraftLookupManagerTest.cs b/src/BaseStationReader.Tests/AircraftLookupManagerTest.cs
index 4789f94..6f9607a 100644
--- a/src/BaseStationReader.Tests/AircraftLookupManagerTest.cs
+++ b/src/BaseStationReader.Tests/AircraftLookupManagerTest.cs
@@ -47,9 +47,10 @@ public void Initialise()
_manufacturerId = Task.Run(() => manufacturerManager.AddAsync(ManufacturerName)).Result.Id;
// Create the API wrappers
+ var logger = new MockFileLogger();
_client = new MockTrackerHttpClient();
- var airlinesApi = new AirLabsAirlinesApi(_client, "", "");
- var aircraftApi = new AirLabsAircraftApi(_client, "", "");
+ var airlinesApi = new AirLabsAirlinesApi(logger, _client, "", "");
+ var aircraftApi = new AirLabsAircraftApi(logger, _client, "", "");
// Finally, create a lookup manager
_manager = new AircraftLookupManager(_airlines, _details, _models, airlinesApi, aircraftApi);
diff --git a/src/BaseStationReader.UI/Models/AircraftLookupModel.cs b/src/BaseStationReader.UI/Models/AircraftLookupModel.cs
index 0f4a14e..2ce89d0 100644
--- a/src/BaseStationReader.UI/Models/AircraftLookupModel.cs
+++ b/src/BaseStationReader.UI/Models/AircraftLookupModel.cs
@@ -1,5 +1,6 @@
using BaseStationReader.Data;
using BaseStationReader.Entities.Config;
+using BaseStationReader.Entities.Interfaces;
using BaseStationReader.Entities.Lookup;
using BaseStationReader.Logic.Api;
using BaseStationReader.Logic.Api.AirLabs;
@@ -14,7 +15,7 @@ public class AircraftLookupModel
{
private readonly AircraftLookupManager _lookupManager;
- public AircraftLookupModel(TrackerApplicationSettings settings)
+ public AircraftLookupModel(ITrackerLogger logger, TrackerApplicationSettings settings)
{
// Create a database context
var context = new BaseStationReaderDbContextFactory().CreateDbContext(Array.Empty());
@@ -31,8 +32,8 @@ public AircraftLookupModel(TrackerApplicationSettings settings)
// Create the API wrappers
var client = TrackerHttpClient.Instance;
- var airlinesApi = new AirLabsAirlinesApi(client, airlinesUrl, key);
- var aircraftApi = new AirLabsAircraftApi(client, aircraftUrl, key);
+ var airlinesApi = new AirLabsAirlinesApi(logger, client, airlinesUrl, key);
+ var aircraftApi = new AirLabsAircraftApi(logger, client, aircraftUrl, key);
// Finally, create a lookup manager
_lookupManager = new AircraftLookupManager(airlinesManager, detailsManager, modelsManager, airlinesApi, aircraftApi);
diff --git a/src/BaseStationReader.UI/Models/LiveViewModel.cs b/src/BaseStationReader.UI/Models/LiveViewModel.cs
index 72e8fe7..2b3787c 100644
--- a/src/BaseStationReader.UI/Models/LiveViewModel.cs
+++ b/src/BaseStationReader.UI/Models/LiveViewModel.cs
@@ -1,6 +1,8 @@
using BaseStationReader.Entities.Config;
+using BaseStationReader.Entities.Events;
using BaseStationReader.Entities.Expressions;
using BaseStationReader.Entities.Interfaces;
+using BaseStationReader.Entities.Logging;
using BaseStationReader.Entities.Tracking;
using BaseStationReader.Logic.Database;
using BaseStationReader.Logic.Tracking;
@@ -14,6 +16,7 @@ namespace BaseStationReader.UI.Models
{
public class LiveViewModel
{
+ private ITrackerLogger? _logger = null;
private ITrackerWrapper? _wrapper = null;
public ObservableCollection TrackedAircraft { get; private set; } = new();
@@ -27,8 +30,12 @@ public class LiveViewModel
///
public void Initialise(ITrackerLogger logger, TrackerApplicationSettings settings)
{
+ _logger = logger;
_wrapper = new TrackerWrapper(logger, settings);
_wrapper.Initialise();
+ _wrapper.AircraftAdded += OnAircraftAdded;
+ _wrapper.AircraftUpdated += OnAircraftUpdated;
+ _wrapper.AircraftRemoved += OnAircraftRemoved;
}
///
@@ -84,5 +91,35 @@ public void Refresh()
// Update the observable collection from the filtered aircraft list
TrackedAircraft = new ObservableCollection(aircraft);
}
+
+ ///
+ /// Handle the event raised when a new aircraft is detected
+ ///
+ ///
+ ///
+ private void OnAircraftAdded(object? sender, AircraftNotificationEventArgs e)
+ {
+ _logger!.LogMessage(Severity.Info, $"Added new aircraft {e.Aircraft.Address}");
+ }
+
+ ///
+ /// Handle the event raised when a new aircraft is updated
+ ///
+ ///
+ ///
+ private void OnAircraftUpdated(object? sender, AircraftNotificationEventArgs e)
+ {
+ _logger!.LogMessage(Severity.Debug, $"Updated aircraft {e.Aircraft.Address}");
+ }
+
+ ///
+ /// Handle the event raised when a new aircraft is removed
+ ///
+ ///
+ ///
+ private void OnAircraftRemoved(object? sender, AircraftNotificationEventArgs e)
+ {
+ _logger!.LogMessage(Severity.Debug, $"Removed aircraft {e.Aircraft.Address}");
+ }
}
}
diff --git a/src/BaseStationReader.UI/ViewModels/AircraftLookupWindowViewModel.cs b/src/BaseStationReader.UI/ViewModels/AircraftLookupWindowViewModel.cs
index 15a861a..98656ed 100644
--- a/src/BaseStationReader.UI/ViewModels/AircraftLookupWindowViewModel.cs
+++ b/src/BaseStationReader.UI/ViewModels/AircraftLookupWindowViewModel.cs
@@ -1,4 +1,5 @@
using BaseStationReader.Entities.Config;
+using BaseStationReader.Entities.Interfaces;
using BaseStationReader.Entities.Lookup;
using BaseStationReader.UI.Models;
using ReactiveUI;
@@ -12,10 +13,10 @@ public class AircraftLookupWindowViewModel : AircraftLookupCriteria
public ReactiveCommand CloseCommand { get; private set; }
- public AircraftLookupWindowViewModel(TrackerApplicationSettings settings, AircraftLookupCriteria? initialValues)
+ public AircraftLookupWindowViewModel(ITrackerLogger logger, TrackerApplicationSettings settings, AircraftLookupCriteria? initialValues)
{
// Set up the aircraft lookup model
- _aircraftLookup = new AircraftLookupModel(settings);
+ _aircraftLookup = new AircraftLookupModel(logger, settings);
// Populate from the initial values, if supplied
Address = initialValues?.Address;
diff --git a/src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs b/src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs
index 7664f9b..3ee53c9 100644
--- a/src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs
+++ b/src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs
@@ -26,6 +26,11 @@ public class MainWindowViewModel : ViewModelBase
///
public TrackerApplicationSettings? Settings { get; set; }
+ ///
+ /// Logging provider
+ ///
+ public ITrackerLogger? Logger { get; set; }
+
///
/// True if the tracker is actively tracking
///
@@ -119,7 +124,7 @@ public MainWindowViewModel()
ShowAircraftLookupDialog = new Interaction();
ShowAircraftLookupCommand = ReactiveCommand.CreateFromTask(async () =>
{
- var dialogViewModel = new AircraftLookupWindowViewModel(Settings!, AircraftLookupCriteria);
+ var dialogViewModel = new AircraftLookupWindowViewModel(Logger!, Settings!, AircraftLookupCriteria);
var result = await ShowAircraftLookupDialog.Handle(dialogViewModel);
return result;
});
@@ -146,10 +151,8 @@ public MainWindowViewModel()
///
/// Initialise the tracker
///
- ///
- ///
- public void InitialiseTracker(ITrackerLogger logger, TrackerApplicationSettings settings)
- => _liveView.Initialise(logger, settings);
+ public void InitialiseTracker()
+ => _liveView.Initialise(Logger!, Settings!);
///
/// Start the tracker
diff --git a/src/BaseStationReader.UI/Views/MainWindow.axaml.cs b/src/BaseStationReader.UI/Views/MainWindow.axaml.cs
index f471a0a..ba16af0 100644
--- a/src/BaseStationReader.UI/Views/MainWindow.axaml.cs
+++ b/src/BaseStationReader.UI/Views/MainWindow.axaml.cs
@@ -12,6 +12,7 @@
using Avalonia.Threading;
using BaseStationReader.Entities.Config;
using BaseStationReader.Entities.Interfaces;
+using BaseStationReader.Entities.Logging;
using BaseStationReader.Entities.Tracking;
using BaseStationReader.Logic.Configuration;
using BaseStationReader.Logic.Logging;
@@ -29,7 +30,7 @@ namespace BaseStationReader.UI.Views
{
public partial class MainWindow : ReactiveWindow
{
- private DispatcherTimer _timer = new DispatcherTimer();
+ private readonly DispatcherTimer _timer = new DispatcherTimer();
private ITrackerLogger? _logger = null;
private bool _aircraftLookupIsEnabled = false;
@@ -54,13 +55,20 @@ private void OnLoaded(object? source, RoutedEventArgs e)
// Set the title, based on the version set in the project properties
Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo info = FileVersionInfo.GetVersionInfo(assembly.Location);
- Title = $"Aircraft Database Viewer {info.FileVersion}";
+ Title = $"Aircraft Tracker {info.FileVersion}";
// Load the settings and configure the logger
ViewModel!.Settings = new TrackerConfigReader().Read("appsettings.json");
_logger = new FileLogger();
_logger.Initialise(ViewModel!.Settings!.LogFile, ViewModel!.Settings.MinimumLogLevel);
+ // Log the startup messages
+ _logger.LogMessage(Severity.Info, new string('=', 80));
+ _logger.LogMessage(Severity.Info, Title);
+
+ // Make the logger available to the view model
+ ViewModel.Logger = _logger;
+
// Configure the column titles and visibility
ConfigureColumns(TrackedAircraftGrid);
ConfigureColumns(DatabaseGrid);
@@ -234,7 +242,7 @@ private void OnStartTracking(object source, RoutedEventArgs e)
ViewModel!.LiveViewFilters = null;
// Start tracking and perform an initial refresh
- ViewModel!.InitialiseTracker(_logger!, ViewModel.Settings);
+ ViewModel!.InitialiseTracker();
ViewModel.StartTracking();
_timer.Start();
diff --git a/src/BaseStationReader.UI/appsettings.json b/src/BaseStationReader.UI/appsettings.json
index f5e8b8c..79bd8c0 100644
--- a/src/BaseStationReader.UI/appsettings.json
+++ b/src/BaseStationReader.UI/appsettings.json
@@ -32,7 +32,7 @@
"ApiServiceKeys": [
{
"Service": "AirLabs",
- "Key": "4cbea986-cbb2-4abd-88f2-7b1a246db63f"
+ "Key": ""
}
],
"Columns": [