Skip to content

Commit

Permalink
[552] fix ArgumentNullException in SyncService when no recent workout…
Browse files Browse the repository at this point in the history
…s found (#557)

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

* version bump for release
  • Loading branch information
philosowaffle authored Nov 11, 2023
1 parent 2a60f6d commit 7558d91
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 115 deletions.
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

0 comments on commit 7558d91

Please sign in to comment.