/// <summary> /// Requests <see cref="LogRecord"/>, <see cref="FaultData"/> and <see cref="StatusData"/> records via data feeds. Then, updates local caches of "lookup" data. Finally, iterates through the returned objects, "hydrating" important child objects using their fully-populated counterparts in the caches. /// </summary> /// <param name="feedParameters">Contains the latest data tokens and collections to be used in the next set of data feed calls.</param> /// <returns><see cref="FeedResultData"/></returns> public async Task <FeedResultData> GetFeedDataAsync(FeedParameters feedParameters) { FeedResultData feedResults = new(new List <LogRecord>(), new List <StatusData>(), new List <FaultData>()); FeedResult <LogRecord> feedLogRecordData; FeedResult <StatusData> feedStatusData = null; FeedResult <FaultData> feedFaultData = null; try { if (feedParameters.FeedStartOption == Common.FeedStartOption.CurrentTime || feedParameters.FeedStartOption == Common.FeedStartOption.SpecificTime) { // If the feeds are to be started at the current date/time or at a specific date/time, get the appropriate DateTime value and then use it to populate the fromDate parameter when making the GetFeed() calls. DateTime feedStartTime = DateTime.UtcNow; if (feedParameters.FeedStartOption == Common.FeedStartOption.SpecificTime) { feedStartTime = feedParameters.FeedStartSpecificTimeUTC; } feedLogRecordData = await api.GetFeedLogRecordAsync(feedStartTime); ConsoleUtility.LogListItem("GPS log records received:", feedLogRecordData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedLogRecordData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData); if (UseStatusDataFeed == true) { feedStatusData = await api.GetFeedStatusDataAsync(feedStartTime); ConsoleUtility.LogListItem("StatusData records received:", feedStatusData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedStatusData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData); } if (UseFaultDataFeed == true) { feedFaultData = await api.GetFeedFaultDataAsync(feedStartTime); ConsoleUtility.LogListItem("FaultData records received:", feedFaultData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedFaultData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData); } // Switch to FeedResultToken for subsequent calls. feedParameters.FeedStartOption = Common.FeedStartOption.FeedResultToken; } else { // If the feeds are to be called based on feed result token, use the tokens returned in the toVersion of previous GetFeed() calls (or loaded from file, if continuing where processing last left-off) to populate the fromVersion parameter when making the next GetFeed() calls. feedLogRecordData = await api.GetFeedLogRecordAsync(feedParameters.LastGpsDataToken); ConsoleUtility.LogListItem("GPS log records received:", feedLogRecordData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedLogRecordData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData); if (UseStatusDataFeed == true) { feedStatusData = await api.GetFeedStatusDataAsync(feedParameters.LastStatusDataToken); ConsoleUtility.LogListItem("StatusData records received:", feedStatusData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedStatusData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData); } if (UseFaultDataFeed == true) { feedFaultData = await api.GetFeedFaultDataAsync(feedParameters.LastFaultDataToken); ConsoleUtility.LogListItem("FaultData records received:", feedFaultData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedFaultData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData); } } // Update local caches of "lookup" data. if (DateTime.UtcNow > nextCacheRepopulationTime) { // "Feedless" caches are for object types not available via data feed in the MyGeotab API. In this case, it is necessary to updates all of the objects of each type with every update. Since these lookup data objects are not frequently-changing (if they were, they would be accessible via data feed), these caches are only updated on a specified interval instead of on every call to this GetFeedDataAsync() method in order to avoid unnecessary processing. await UpdateAllFeedlessCachesAsync(); nextCacheRepopulationTime = DateTime.UtcNow.AddMinutes(CacheRepopulationIntervalMinutes); } // For object types supported by the MyGeotab API data feed, the local caches can be updated every time this GetFeedDataAsync() method is executed bacause only new or changed objects are returned. await UpdateDeviceCacheAsync(); await UpdateDiagnosticCacheAsync(); // Use the local caches to "hydrate" child objects of objects returned via data feed. feedParameters.LastGpsDataToken = feedLogRecordData.ToVersion; foreach (LogRecord logRecord in feedLogRecordData.Data) { // Populate relevant LogRecord fields. logRecord.Device = GetDevice(logRecord.Device); feedResults.GpsRecords.Add(logRecord); } if (UseStatusDataFeed == true) { feedParameters.LastStatusDataToken = feedStatusData.ToVersion; foreach (StatusData statusData in feedStatusData.Data) { // Populate relevant StatusData fields. statusData.Device = GetDevice(statusData.Device); statusData.Diagnostic = GetDiagnostic(statusData.Diagnostic); feedResults.StatusData.Add(statusData); } } if (UseFaultDataFeed == true) { feedParameters.LastFaultDataToken = feedFaultData.ToVersion; foreach (FaultData faultData in feedFaultData.Data) { // Populate relevant FaultData fields. faultData.Device = GetDevice(faultData.Device); faultData.Diagnostic = GetDiagnostic(faultData.Diagnostic); faultData.Controller = await GetControllerAsync(faultData.Controller); faultData.FailureMode = await GetFailureModeAsync(faultData.FailureMode); feedResults.FaultData.Add(faultData); } } } catch (Exception e) { Console.ForegroundColor = Common.ConsoleColorForErrors; Console.WriteLine(e.Message); Console.ForegroundColor = Common.ConsoleColorDefault; if (e is HttpRequestException) { await Task.Delay(5000); } if (e is DbUnavailableException) { await Task.Delay(TimeSpan.FromMinutes(5)); } } return(feedResults); }
/// <summary> /// Initializes a new instance of the <see cref="DatabaseWorker"/> class. /// </summary> /// <param name="user">The user.</param> /// <param name="password">The password.</param> /// <param name="database">The database.</param> /// <param name="server">The server.</param> /// <param name="dataFeedTokenFolder">The folder where data feed token files are to be written.</param> /// <param name="outputFolder">The folder where output files are to be written.</param> /// <param name="maximumFileSizeInBytes">The maximum size, in bytes, that a file may reach before a new file is started.</param> /// <param name="feedIntervalSeconds">The number of seconds to wait, after processing a batch of feed results, before executing the next iteration of GetFeed() calls.</param> /// <param name="feedStartOption">The <see cref="Common.FeedStartOption" /> to use.</param> /// <param name="feedStartSpecificTimeUTC">If <paramref name="feedStartOption"/> is set to <see cref="Common.FeedStartOption.SpecificTime"/>, the date and time at which to start the data feeds.</param> /// <param name="trackSpecificVehicles">Whether to track specific vehicles or vehicles that are reporting data.</param> /// <param name="devicesToTrack">If <paramref name="trackSpecificVehicles"/> is <c>true</c>, the list of <see cref="Device"/>s to track.</param> /// <param name="diagnosticsToTrack">The <see cref="Diagnostic"/>s that are to be tracked.</param> public DatabaseWorker(string user, string password, string database, string server, string dataFeedTokenFolder, string outputFolder, long maximumFileSizeInBytes, int feedIntervalSeconds, Common.FeedStartOption feedStartOption, DateTime?feedStartSpecificTimeUTC = null, bool trackSpecificVehicles = false, IList <Device> devicesToTrack = null, IList <Diagnostic> diagnosticsToTrack = null) : base() { // Validate input. if (trackSpecificVehicles == true && (devicesToTrack == null || devicesToTrack.Count == 0)) { throw new ArgumentException($"'trackSpecificVehicles' is set to 'true', but 'devicesToTrack' is null or empty."); } DataFeedTokenFolder = dataFeedTokenFolder; OutputFolder = outputFolder; MaximumFileSizeInBytes = maximumFileSizeInBytes; TrackSpecificVehicles = trackSpecificVehicles; DevicesToTrack = (List <Device>)devicesToTrack; DiagnosticsToTrack = (List <Diagnostic>)diagnosticsToTrack; // Determine whether to use StatusData and/or FaultData feeds based on the diagnostics, if any, that are to be tracked. if (DiagnosticsToTrack != null && DiagnosticsToTrack.Count > 0) { if (DiagnosticsToTrack.Where(diagnosticToTrack => diagnosticToTrack.DiagnosticType == DiagnosticType.Sid || diagnosticToTrack.DiagnosticType == DiagnosticType.Pid || diagnosticToTrack.DiagnosticType == DiagnosticType.SuspectParameter || diagnosticToTrack.DiagnosticType == DiagnosticType.ObdFault || diagnosticToTrack.DiagnosticType == DiagnosticType.GoFault || diagnosticToTrack.DiagnosticType == DiagnosticType.ObdWwhFault || diagnosticToTrack.DiagnosticType == DiagnosticType.ProprietaryFault || diagnosticToTrack.DiagnosticType == DiagnosticType.LegacyFault).Any()) { useFaultDataFeed = true; } if (DiagnosticsToTrack.Where(diagnosticToTrack => diagnosticToTrack.DiagnosticType == DiagnosticType.GoDiagnostic || diagnosticToTrack.DiagnosticType == DiagnosticType.DataDiagnostic).Any()) { useStatusDataFeed = true; } } // Build token file paths. faultDataTokenFilePath = Path.Combine(DataFeedTokenFolder, FaultDataTokenFilename); gpsTokenFilePath = Path.Combine(DataFeedTokenFolder, GpsTokenFilename); statusDataTokenFilePath = Path.Combine(DataFeedTokenFolder, StatusDataTokenFilename); // If feeds are to be started based on feed result token, read previously-written token values from their respective files. long faultDataToken = 0; long gpsToken = 0; long statusDataToken = 0; if (feedStartOption == Common.FeedStartOption.FeedResultToken) { if (File.Exists(faultDataTokenFilePath)) { using (StreamReader faultDataTokenFileReader = new(faultDataTokenFilePath)) { String faultDataTokenString = faultDataTokenFileReader.ReadToEnd(); _ = long.TryParse(faultDataTokenString, out faultDataToken); } } if (File.Exists(gpsTokenFilePath)) { using (StreamReader gpsTokenFileReader = new(gpsTokenFilePath)) { String gpsTokenString = gpsTokenFileReader.ReadToEnd(); _ = long.TryParse(gpsTokenString, out gpsToken); } } if (File.Exists(statusDataTokenFilePath)) { using (StreamReader statusDataTokenFileReader = new(statusDataTokenFilePath)) { String statusDataTokenString = statusDataTokenFileReader.ReadToEnd(); _ = long.TryParse(statusDataTokenString, out statusDataToken); } } } // Instantiate FeedParameters and FeedProcessor objects. feedParameters = new FeedParameters(gpsToken, statusDataToken, faultDataToken, feedStartOption, feedStartSpecificTimeUTC); FeedIntervalSeconds = feedIntervalSeconds; feedProcessor = new FeedProcessor(server, database, user, password, useFaultDataFeed, useStatusDataFeed); TrackedVehicles = new List <TrackedVehicle>(); }