Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[552] fix ArgumentNullException in SyncService when no recent workouts found #557

Merged
merged 2 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/PelotonToGarmin.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/PelotonToGarmin.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/PelotonToGarmin.sln"
],
"problemMatcher": "$msCompile"
}
]
}
2 changes: 1 addition & 1 deletion src/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ public static class Constants
public const string ConsoleAppName = "p2g_console";
public const string ApiName = "p2g_api";
public const string WebUIName = "p2g_webui";
public const string AppVersion = "3.6.0";
public const string AppVersion = "3.6.1";
}
}
192 changes: 96 additions & 96 deletions src/Sync/SyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
using Common.Dto;
using Common.Dto.Peloton;
using Common.Observe;
using Common.Service;
using Common.Service;
using Common.Stateful;
using Conversion;
using Garmin;
using Garmin.Auth;
using Garmin.Auth;
using Peloton;
using Prometheus;
using Serilog;
Expand All @@ -33,7 +33,7 @@ public class SyncService : ISyncService
private readonly IGarminUploader _garminUploader;
private readonly IEnumerable<IConverter> _converters;
private readonly ISyncStatusDb _db;
private readonly IFileHandling _fileHandler;
private readonly IFileHandling _fileHandler;
private readonly ISettingsService _settingsService;

public SyncService(ISettingsService settingService, IPelotonService pelotonService, IGarminUploader garminUploader, IEnumerable<IConverter> converters, ISyncStatusDb dbClient, IFileHandling fileHandler)
Expand All @@ -50,9 +50,9 @@ public async Task<SyncResult> SyncAsync(int numWorkouts)
{
using var timer = SyncHistogram.NewTimer();
using var activity = Tracing.Trace($"{nameof(SyncService)}.{nameof(SyncAsync)}.ByNumWorkouts")
.WithTag("numWorkouts", numWorkouts.ToString());
var settings = await _settingsService.GetSettingsAsync();
.WithTag("numWorkouts", numWorkouts.ToString());

var settings = await _settingsService.GetSettingsAsync();
return await SyncWithWorkoutLoaderAsync(() => _pelotonService.GetRecentWorkoutsAsync(numWorkouts), settings.Peloton.ExcludeWorkoutTypes);
}

Expand All @@ -62,17 +62,17 @@ public async Task<SyncResult> SyncAsync(IEnumerable<string> workoutIds, ICollect
using var activity = Tracing.Trace($"{nameof(SyncService)}.{nameof(SyncAsync)}.ByWorkoutIds");

var response = new SyncResult();
var recentWorkouts = workoutIds.Select(w => new Workout() { Id = w }).ToList();
var recentWorkouts = workoutIds.Select(w => new Workout() { Id = w }).ToList();
var settings = await _settingsService.GetSettingsAsync();

UserData? userData = null;
try
{
userData = await _pelotonService.GetUserDataAsync();
}
catch (Exception e)
try
{
_logger.Warning(e, $"Failed to fetch user data from Peloton: {e.Message}, FTP info may be missing for certain non-class workout types (Just Ride).");
userData = await _pelotonService.GetUserDataAsync();
}
catch (Exception e)
{
_logger.Warning(e, $"Failed to fetch user data from Peloton: {e.Message}, FTP info may be missing for certain non-class workout types (Just Ride).");
}

P2GWorkout[] workouts = { };
Expand Down Expand Up @@ -104,31 +104,31 @@ public async Task<SyncResult> SyncAsync(IEnumerable<string> workoutIds, ICollect

return true;
});

var filteredWorkoutsCount = filteredWorkouts.Count();
activity?.AddTag("workouts.filtered", filteredWorkoutsCount);
_logger.Information("Found {@NumWorkouts} workouts remaining after filtering ExcludedWorkoutTypes.", filteredWorkoutsCount);
if (!filteredWorkouts.Any())
{
_logger.Information("No workouts to sync. Sync complete.");
response.ConversionSuccess = true;
response.SyncSuccess = true;
return response;
activity?.AddTag("workouts.filtered", filteredWorkoutsCount);
_logger.Information("Found {@NumWorkouts} workouts remaining after filtering ExcludedWorkoutTypes.", filteredWorkoutsCount);

if (!filteredWorkouts.Any())
{
_logger.Information("No workouts to sync. Sync complete.");
response.ConversionSuccess = true;
response.SyncSuccess = true;
return response;
}

var convertStatuses = new List<ConvertStatus>();
try
{
_logger.Information("Converting workouts...");
var tasks = new List<Task<ConvertStatus>>();
foreach (var workout in filteredWorkouts)
{
workout.UserData = userData;
tasks.AddRange(_converters.Select(c => c.ConvertAsync(workout)));
}
await Task.WhenAll(tasks);
{
_logger.Information("Converting workouts...");
var tasks = new List<Task<ConvertStatus>>();
foreach (var workout in filteredWorkouts)
{
workout.UserData = userData;
tasks.AddRange(_converters.Select(c => c.ConvertAsync(workout)));
}

await Task.WhenAll(tasks);
convertStatuses = tasks.Select(t => t.GetAwaiter().GetResult()).ToList();
}
catch (Exception e)
Expand All @@ -139,54 +139,54 @@ public async Task<SyncResult> SyncAsync(IEnumerable<string> workoutIds, ICollect
response.ConversionSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = $"Unexpected error. Failed to convert workouts. {e.Message} Check logs for more details." });
return response;
}
if (!convertStatuses.Any() || convertStatuses.All(c => c.Result == ConversionResult.Skipped))
{
_logger.Information("All converters were skipped. Ensure you have atleast one output Format configured in your settings. Converting to FIT or TCX is required prior to uploading to Garmin Connect.");
response.SyncSuccess = false;
response.ConversionSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = "All converters were skipped. Ensure you have atleast one output Format configured in your settings. Converting to FIT or TCX is required prior to uploading to Garmin Connect." });
return response;
}
if (convertStatuses.All(c => c.Result == ConversionResult.Failed))
{
_logger.Error("All configured converters failed to convert workouts.");
response.SyncSuccess = false;
response.ConversionSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = "All configured converters failed to convert workouts. Successfully, converting to FIT or TCX is required prior to uploading to Garmin Connect. See logs for more details." });
return response;
}
foreach (var convertStatus in convertStatuses)
if (convertStatus.Result == ConversionResult.Failed)
response.Errors.Add(new ErrorResponse() { Message = convertStatus.ErrorMessage });
}

if (!convertStatuses.Any() || convertStatuses.All(c => c.Result == ConversionResult.Skipped))
{
_logger.Information("All converters were skipped. Ensure you have atleast one output Format configured in your settings. Converting to FIT or TCX is required prior to uploading to Garmin Connect.");
response.SyncSuccess = false;
response.ConversionSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = "All converters were skipped. Ensure you have atleast one output Format configured in your settings. Converting to FIT or TCX is required prior to uploading to Garmin Connect." });
return response;
}

if (convertStatuses.All(c => c.Result == ConversionResult.Failed))
{
_logger.Error("All configured converters failed to convert workouts.");
response.SyncSuccess = false;
response.ConversionSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = "All configured converters failed to convert workouts. Successfully, converting to FIT or TCX is required prior to uploading to Garmin Connect. See logs for more details." });
return response;
}

foreach (var convertStatus in convertStatuses)
if (convertStatus.Result == ConversionResult.Failed)
response.Errors.Add(new ErrorResponse() { Message = convertStatus.ErrorMessage });

response.ConversionSuccess = true;

try
{
await _garminUploader.UploadToGarminAsync();
response.UploadToGarminSuccess = true;
}
catch (ArgumentException ae)
{
_logger.Error(ae, $"Failed to upload to Garmin Connect. {ae.Message}");
}
catch (ArgumentException ae)
{
_logger.Error(ae, $"Failed to upload to Garmin Connect. {ae.Message}");

response.SyncSuccess = false;
response.UploadToGarminSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = $"Failed to upload workouts to Garmin Connect. {ae.Message}" });
return response;
}
catch (GarminAuthenticationError gae)
{
_logger.Error(gae, $"Sync failed to authenticate with Garmin. {gae.Message}");
return response;
}
catch (GarminAuthenticationError gae)
{
_logger.Error(gae, $"Sync failed to authenticate with Garmin. {gae.Message}");

response.SyncSuccess = false;
response.UploadToGarminSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = gae.Message });
return response;
return response;
}
catch (Exception e)
{
Expand All @@ -196,7 +196,7 @@ public async Task<SyncResult> SyncAsync(IEnumerable<string> workoutIds, ICollect
response.UploadToGarminSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = $"Failed to upload workouts to Garmin Connect. {e.Message}" });
return response;
}
}
finally
{
_fileHandler.Cleanup(settings.App.DownloadDirectory);
Expand All @@ -206,11 +206,11 @@ public async Task<SyncResult> SyncAsync(IEnumerable<string> workoutIds, ICollect

response.SyncSuccess = true;
return response;
}
private IEnumerable<string> FilterToCompletedWorkoutIds(ICollection<Workout> workouts)
{
return workouts
}

private IEnumerable<string> FilterToCompletedWorkoutIds(ICollection<Workout> workouts)
{
return workouts?
.Where(w =>
{
var shouldKeep = w.Status == "COMPLETE";
Expand All @@ -219,31 +219,31 @@ private IEnumerable<string> FilterToCompletedWorkoutIds(ICollection<Workout> wor
_logger.Debug("Skipping in progress workout. {@WorkoutId} {@WorkoutStatus} {@WorkoutType} {@WorkoutTitle}", w.Id, w.Status, w.Fitness_Discipline, w.Title);
return false;
})
.Select(r => r.Id);
}
private async Task<SyncResult> SyncWithWorkoutLoaderAsync(Func<Task<ServiceResult<ICollection<Workout>>>> loader, ICollection<WorkoutType>? exclude)
{
.Select(r => r.Id) ?? new List<string>();
}

private async Task<SyncResult> SyncWithWorkoutLoaderAsync(Func<Task<ServiceResult<ICollection<Workout>>>> loader, ICollection<WorkoutType>? exclude)
{
using var activity = Tracing.Trace($"{nameof(SyncService)}.{nameof(SyncAsync)}.SyncWithWorkoutLoaderAsync");

ICollection<Workout> recentWorkouts;
var syncTime = await _db.GetSyncStatusAsync();
var syncTime = await _db.GetSyncStatusAsync();
var settings = await _settingsService.GetSettingsAsync();
syncTime.LastSyncTime = DateTime.Now;
syncTime.LastSyncTime = DateTime.Now;

try
{
{
var recentWorkoutsServiceResult = await loader();
recentWorkouts = recentWorkoutsServiceResult.Result;
}
catch (ArgumentException ae)
{
catch (ArgumentException ae)
{
var errorMessage = $"Failed to fetch workouts from Peloton: {ae.Message}";

_logger.Error(ae, errorMessage);
activity?.AddTag("exception.message", ae.Message);
activity?.AddTag("exception.stacktrace", ae.StackTrace);
activity?.AddTag("exception.stacktrace", ae.StackTrace);

syncTime.SyncStatus = Status.UnHealthy;
syncTime.LastErrorMessage = errorMessage;
await _db.UpsertSyncStatusAsync(syncTime);
Expand All @@ -252,16 +252,16 @@ private async Task<SyncResult> SyncWithWorkoutLoaderAsync(Func<Task<ServiceResul
response.SyncSuccess = false;
response.PelotonDownloadSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = $"{errorMessage}" });
return response;
return response;
}
catch (Exception ex)
{
var errorMessage = "Failed to fetch workouts from Peloton.";

_logger.Error(ex, errorMessage);
activity?.AddTag("exception.message", ex.Message);
activity?.AddTag("exception.stacktrace", ex.StackTrace);
activity?.AddTag("exception.stacktrace", ex.StackTrace);

syncTime.SyncStatus = Status.UnHealthy;
syncTime.LastErrorMessage = errorMessage;
await _db.UpsertSyncStatusAsync(syncTime);
Expand All @@ -271,11 +271,11 @@ private async Task<SyncResult> SyncWithWorkoutLoaderAsync(Func<Task<ServiceResul
response.PelotonDownloadSuccess = false;
response.Errors.Add(new ErrorResponse() { Message = $"{errorMessage} Check logs for more details." });
return response;
}
}

var completedWorkouts = FilterToCompletedWorkoutIds(recentWorkouts);
var completedWorkoutsCount = completedWorkouts.Count();

var completedWorkoutsCount = completedWorkouts.Count();
_logger.Information("Found {@NumWorkouts} completed workouts.", completedWorkoutsCount);
activity?.AddTag("workouts.completed", completedWorkoutsCount);

Expand All @@ -286,7 +286,7 @@ private async Task<SyncResult> SyncWithWorkoutLoaderAsync(Func<Task<ServiceResul

await _db.UpsertSyncStatusAsync(syncTime);

return result;
return result;
}
}
}
Loading
Loading