public void GetTitle_NullRide_ShouldReturn_RideId() { var workout = new Workout() { Id = "someId" }; var title = WorkoutHelper.GetTitle(workout); title.Should().Be("someId"); }
public void GetTitle_NullInstructor_ShouldReturn_EmptyInstructorName() { var workout = new Workout() { Ride = new Ride() { Title = "My Title" } }; var title = WorkoutHelper.GetTitle(workout); title.Should().Be("My_Title"); }
public void GetTitle_ShouldReturn_RideTitle() { var workout = new Workout() { Ride = new Ride() { Title = "My Title", Instructor = new Instructor() { Name = "My Name" } } }; var title = WorkoutHelper.GetTitle(workout); title.Should().Be("My_Title_with_My_Name"); }
public async Task DownloadLatestWorkoutDataAsync() { if (_config.Peloton.NumWorkoutsToDownload <= 0) { return; } using var tracing = Tracing.Trace(nameof(DownloadLatestWorkoutDataAsync)); await _pelotonApi.InitAuthAsync(); var recentWorkouts = await _pelotonApi.GetWorkoutsAsync(_config.Peloton.NumWorkoutsToDownload); var completedWorkouts = recentWorkouts.data.Where(w => { if (w.Status == "COMPLETE") { return(true); } Log.Debug("Skipping in progress workout. {@WorkoutId} {@WorkoutStatus} {@WorkoutType}", w.Id, w.Status, w.Fitness_Discipline); return(false); }); var filteredWorkouts = completedWorkouts.Where(w => { if (!_config.Peloton.ExcludeWorkoutTypes?.Contains(w.Fitness_Discipline) ?? true) { return(true); } Log.Debug("Skipping excluded workout type. {@WorkoutId} {@WorkoutStatus} {@WorkoutType}", w.Id, w.Status, w.Fitness_Discipline); return(false); }); var workingDir = _config.App.DownloadDirectory; _fileHandler.MkDirIfNotExists(workingDir); foreach (var recentWorkout in filteredWorkouts) { var workoutId = recentWorkout.Id; SyncHistoryItem syncRecord = _dbClient.Get(recentWorkout.Id); if ((syncRecord?.DownloadDate is object)) { Log.Debug("Workout {@WorkoutId} already downloaded, skipping.", recentWorkout.Id); continue; } using var workoutTimer = WorkoutDownloadDuration.NewTimer(); var workoutTask = _pelotonApi.GetWorkoutByIdAsync(recentWorkout.Id); var workoutSamplesTask = _pelotonApi.GetWorkoutSamplesByIdAsync(recentWorkout.Id); await Task.WhenAll(workoutTask, workoutSamplesTask); var workout = workoutTask.GetAwaiter().GetResult(); var workoutSamples = workoutSamplesTask.GetAwaiter().GetResult(); dynamic data = new JObject(); data.Workout = workout; data.WorkoutSamples = workoutSamples; var workoutTitle = string.Empty; P2GWorkout deSerializedData = null; try { deSerializedData = JsonSerializer.Deserialize <P2GWorkout>(data.ToString(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); workoutTitle = WorkoutHelper.GetUniqueTitle(deSerializedData.Workout); if (_config.Format.Json && _config.Format.SaveLocalCopy) { SaveRawData(data, workoutTitle); } } catch (Exception e) { _failedCount++; var title = "workout_failed_to_deserialize_" + _failedCount; Log.Error("Failed to deserialize workout from Peloton. You can find the raw data from the workout here: @FileName", title, e); SaveRawData(data, title); return; } Log.Debug("Write peloton workout details to file for {@WorkoutId}.", workoutId); File.WriteAllText(Path.Join(workingDir, $"{workoutTitle}.json"), data.ToString()); var syncHistoryItem = new SyncHistoryItem(deSerializedData.Workout) { DownloadDate = DateTime.Now, }; _dbClient.Upsert(syncHistoryItem); } }
protected override Tuple <string, ICollection <Mesg> > Convert(Workout workout, WorkoutSamples workoutSamples) { // MESSAGE ORDER MATTERS var messages = new List <Mesg>(); var startTime = GetStartTimeUtc(workout); var endTime = GetEndTimeUtc(workout); var title = WorkoutHelper.GetTitle(workout); var sport = GetGarminSport(workout); var subSport = GetGarminSubSport(workout); if (sport == Sport.Invalid) { Log.Warning("Unsupported Sport Type - Skipping {@Sport}", workout.Fitness_Discipline); return(new Tuple <string, ICollection <Mesg> >(string.Empty, null)); } var fileIdMesg = new FileIdMesg(); fileIdMesg.SetSerialNumber(_serialNumber); fileIdMesg.SetTimeCreated(startTime); fileIdMesg.SetManufacturer(_manufacturerId); fileIdMesg.SetProduct(_productId); fileIdMesg.SetType(Dynastream.Fit.File.Activity); messages.Add(fileIdMesg); var eventMesg = new EventMesg(); eventMesg.SetTimestamp(startTime); eventMesg.SetData(0); eventMesg.SetEvent(Event.Timer); eventMesg.SetEventType(EventType.Start); eventMesg.SetEventGroup(0); messages.Add(eventMesg); var deviceInfoMesg = new DeviceInfoMesg(); deviceInfoMesg.SetTimestamp(startTime); deviceInfoMesg.SetSerialNumber(_serialNumber); deviceInfoMesg.SetManufacturer(_manufacturerId); deviceInfoMesg.SetProduct(_productId); deviceInfoMesg.SetSoftwareVersion(_softwareVersion); deviceInfoMesg.SetDeviceIndex(0); deviceInfoMesg.SetSourceType(SourceType.Local); deviceInfoMesg.SetProductName("PelotonToGarmin"); // Max 20 Chars messages.Add(deviceInfoMesg); var userProfileMesg = new UserProfileMesg(); userProfileMesg.SetPowerSetting(DisplayPower.PercentFtp); messages.Add(userProfileMesg); var sportMesg = new SportMesg(); sportMesg.SetSport(sport); sportMesg.SetSubSport(subSport); messages.Add(sportMesg); var zoneTargetMesg = new ZonesTargetMesg(); zoneTargetMesg.SetFunctionalThresholdPower((ushort)workout.Ftp_Info.Ftp); zoneTargetMesg.SetPwrCalcType(PwrZoneCalc.PercentFtp); var maxHr = GetUserMaxHeartRate(workoutSamples); if (maxHr is object) { zoneTargetMesg.SetMaxHeartRate(maxHr.Value); zoneTargetMesg.SetHrCalcType(HrZoneCalc.PercentMaxHr); } messages.Add(zoneTargetMesg); var trainingMesg = new TrainingFileMesg(); trainingMesg.SetTimestamp(startTime); trainingMesg.SetTimeCreated(startTime); trainingMesg.SetSerialNumber(_serialNumber); trainingMesg.SetManufacturer(_manufacturerId); trainingMesg.SetProduct(_productId); trainingMesg.SetType(Dynastream.Fit.File.Workout); messages.Add(trainingMesg); AddMetrics(messages, workoutSamples, startTime); var workoutSteps = new List <WorkoutStepMesg>(); var laps = new List <LapMesg>(); if (workoutSamples.Target_Performance_Metrics?.Target_Graph_Metrics?.FirstOrDefault(w => w.Type == "cadence")?.Graph_Data is object) { var stepsAndLaps = GetWorkoutStepsAndLaps(workoutSamples, startTime, sport, subSport); workoutSteps = stepsAndLaps.Values.Select(v => v.Item1).ToList(); laps = stepsAndLaps.Values.Select(v => v.Item2).ToList(); } else { laps = GetWorkoutLaps(workoutSamples, startTime, sport, subSport).ToList(); } var workoutMesg = new WorkoutMesg(); workoutMesg.SetWktName(title.Replace(_spaceSeparator, " ")); workoutMesg.SetCapabilities(32); workoutMesg.SetSport(sport); workoutMesg.SetSubSport(subSport); workoutMesg.SetNumValidSteps((ushort)workoutSteps.Count); messages.Add(workoutMesg); // add steps in order foreach (var step in workoutSteps) { messages.Add(step); } // Add laps in order foreach (var lap in laps) { messages.Add(lap); } messages.Add(GetSessionMesg(workout, workoutSamples, startTime, endTime, (ushort)laps.Count)); var activityMesg = new ActivityMesg(); activityMesg.SetTimestamp(endTime); activityMesg.SetTotalTimerTime(workoutSamples.Duration); activityMesg.SetNumSessions(1); activityMesg.SetType(Activity.Manual); activityMesg.SetEvent(Event.Activity); activityMesg.SetEventType(EventType.Stop); var timezoneOffset = (int)TimeZoneInfo.Local.GetUtcOffset(base.GetEndTimeUtc(workout)).TotalSeconds; var timeStamp = (uint)((int)endTime.GetTimeStamp() + timezoneOffset); activityMesg.SetLocalTimestamp(timeStamp); messages.Add(activityMesg); return(new Tuple <string, ICollection <Mesg> >(title, messages)); }
protected void Convert(string format) { if (!_fileHandler.DirExists(_config.App.DownloadDirectory)) { Log.Information("No download directory found. Nothing to do. {@File}", _config.App.DownloadDirectory); return; } var files = _fileHandler.GetFiles(_config.App.DownloadDirectory); if (files.Length == 0) { Log.Information("No files to convert in download directory. Nothing to do."); return; } if (_config.Garmin.Upload) { _fileHandler.MkDirIfNotExists(_config.App.UploadDirectory); } // Foreach file in directory foreach (var file in files) { using var workoutTimer = WorkoutsConverted.WithLabels(format).NewTimer(); // load file and deserialize P2GWorkout workoutData = null; try { workoutData = _fileHandler.DeserializeJson <P2GWorkout>(file); } catch (Exception e) { Log.Error(e, "Failed to load and parse workout data {@File}", file); _fileHandler.MoveFailedFile(file, _config.App.FailedDirectory); continue; } using var tracing = Tracing.Trace("Convert") .WithWorkoutId(workoutData.Workout.Id) .WithTag(TagKey.Format, format); // call internal convert method T converted = default; var workoutTitle = WorkoutHelper.GetUniqueTitle(workoutData.Workout); try { converted = Convert(workoutData.Workout, workoutData.WorkoutSamples); } catch (Exception e) { Log.Error(e, "Failed to convert workout data {@Workout} {@File}", workoutTitle, file); } if (converted is null) { _fileHandler.MoveFailedFile(file, _config.App.FailedDirectory); continue; } // write to output dir var path = Path.Join(_config.App.WorkingDirectory, $"{workoutTitle}.{format}"); try { Save(converted, path); } catch (Exception e) { Log.Error(e, "Failed to write {@Format} file for {@Workout}", format, workoutTitle); continue; } // copy to local save if (_config.Format.SaveLocalCopy) { try { _fileHandler.MkDirIfNotExists(_config.App.TcxDirectory); _fileHandler.MkDirIfNotExists(_config.App.FitDirectory); var dir = format == "fit" ? _config.App.FitDirectory : _config.App.TcxDirectory; var backupDest = Path.Join(dir, $"{workoutTitle}.{format}"); _fileHandler.Copy(path, backupDest, overwrite: true); Log.Information("Backed up file {@File}", backupDest); } catch (Exception e) { Log.Error(e, "Failed to backup {@Format} file for {@Workout}", format, workoutTitle); continue; } } // copy to upload dir if (_config.Garmin.Upload && _config.Garmin.FormatToUpload == format) { try { var uploadDest = Path.Join(_config.App.UploadDirectory, $"{workoutTitle}.{format}"); _fileHandler.Copy(path, uploadDest, overwrite: true); Log.Debug("Prepped {@Format} file {@Path} for upload.", format, uploadDest); } catch (Exception e) { Log.Error(e, "Failed to copy {@Format} file for {@Workout}", format, workoutTitle); continue; } } // update db item with conversion date SyncHistoryItem syncRecord = _dbClient.Get(workoutData.Workout.Id); if (syncRecord is null) { syncRecord = new SyncHistoryItem(workoutData.Workout) { DownloadDate = System.DateTime.Now, }; } syncRecord.ConvertedToFit = syncRecord.ConvertedToFit || format == "fit"; syncRecord.ConvertedToTcx = syncRecord.ConvertedToTcx || format == "tcx"; _dbClient.Upsert(syncRecord); } }
protected ConvertStatus Convert(FileFormat format, P2GWorkout workoutData) { using var tracing = Tracing.Trace($"{nameof(IConverter)}.{nameof(Convert)}.Workout")? .WithWorkoutId(workoutData.Workout.Id) .WithTag(TagKey.Format, format.ToString()); var status = new ConvertStatus(); // call internal convert method T converted = default; var workoutTitle = WorkoutHelper.GetUniqueTitle(workoutData.Workout); try { converted = Convert(workoutData.Workout, workoutData.WorkoutSamples); } catch (Exception e) { _logger.Error(e, "Failed to convert workout data to format {@Format} {@Workout}", format, workoutTitle); status.Success = false; status.ErrorMessage = "Failed to convert workout data."; tracing?.AddTag("excetpion.message", e.Message); tracing?.AddTag("exception.stacktrace", e.StackTrace); tracing?.AddTag("convert.success", false); tracing?.AddTag("convert.errormessage", status.ErrorMessage); return(status); } // write to output dir var path = Path.Join(_config.App.WorkingDirectory, $"{workoutTitle}.{format}"); try { _fileHandler.MkDirIfNotExists(_config.App.WorkingDirectory); Save(converted, path); status.Success = true; } catch (Exception e) { status.Success = false; status.ErrorMessage = "Failed to save converted workout for upload."; _logger.Error(e, "Failed to write {@Format} file for {@Workout}", format, workoutTitle); tracing?.AddTag("excetpion.message", e.Message); tracing?.AddTag("exception.stacktrace", e.StackTrace); tracing?.AddTag("convert.success", false); tracing?.AddTag("convert.errormessage", status.ErrorMessage); return(status); } // copy to local save try { SaveLocalCopy(path, workoutTitle); } catch (Exception e) { _logger.Error(e, "Failed to backup {@Format} file for {@Workout}", format, workoutTitle); } // copy to upload dir if (_config.Garmin.Upload && _config.Garmin.FormatToUpload == format) { try { var uploadDest = Path.Join(_config.App.UploadDirectory, $"{workoutTitle}.{format}"); _fileHandler.MkDirIfNotExists(_config.App.UploadDirectory); _fileHandler.Copy(path, uploadDest, overwrite: true); _logger.Debug("Prepped {@Format} for upload: {@Path}", format, uploadDest); } catch (Exception e) { _logger.Error(e, "Failed to copy {@Format} file for {@Workout}", format, workoutTitle); status.Success = false; status.ErrorMessage = $"Failed to save file for {@format} and workout {workoutTitle} to Upload directory"; tracing?.AddTag("excetpion.message", e.Message); tracing?.AddTag("exception.stacktrace", e.StackTrace); tracing?.AddTag("convert.success", false); tracing?.AddTag("convert.errormessage", status.ErrorMessage); return(status); } } return(status); }
public HttpResponseMessage Put([FromBody] WorkoutDiaryEntryDTO workoutDiaryEntryDTO) { try { // Выбираем запись из БД для обновления var workoutDiaryEntry = unitOfWork.WorkoutDiaryEntryRepository.Get( f => (f.CreateUserId == currentUserId && f.WorkoutDiaryEntryId == workoutDiaryEntryDTO.WorkoutDiaryEntryId), null, iE => iE.Exercise, iS => iS.Sets) .SingleOrDefault(); // Записи нет в БД FT if (workoutDiaryEntry == null) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } var workoutDiaryEntryUpdateObj = Mapper.Map <WorkoutDiaryEntry>(workoutDiaryEntryDTO); foreach (WorkoutSet newSet in workoutDiaryEntryUpdateObj.Sets) { var oldSet = workoutDiaryEntry.Sets.Where( i => i.WorkoutSetId == newSet.WorkoutSetId) .SingleOrDefault(); if (oldSet == null) { // Сета нет в БД. Добавляем. unitOfWork.WorkoutSetRepository.Insert(new WorkoutSet { Duration = newSet.Duration, Distance = newSet.Distance, IsCompleted = newSet.IsCompleted, Order = newSet.Order, Reps = newSet.Reps, Rest = newSet.Rest, Weight = newSet.Weight, WorkoutDiaryEntryId = workoutDiaryEntry.WorkoutDiaryEntryId }); } else { // Сет есть в БД. Обновляем. oldSet.Distance = newSet.Distance; oldSet.Duration = newSet.Duration; oldSet.IsCompleted = newSet.IsCompleted; oldSet.Order = newSet.Order; oldSet.Reps = newSet.Reps; oldSet.Rest = newSet.Rest; oldSet.Weight = newSet.Weight; } } workoutDiaryEntry.DateUTC = workoutDiaryEntryUpdateObj.DateUTC; #region __Подсчитываем количество сожженных калорий__ if (workoutDiaryEntry.Exercise.MET.HasValue && workoutDiaryEntry.Exercise.MET.Value != 0) { var setsDuration = workoutDiaryEntry.Sets.Select(s => s.Duration).Sum(); if (setsDuration > 0) { // Получаем информацию о пользователе для подсчета сожженных калорий (вес, возраст) var userProfile = unitOfWork.UserProfileRepository.Get( f => (f.UserId == currentUserId), null, i => i.Weights) .SingleOrDefault(); // Пользователя нет в БД if (userProfile == null) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } if (userProfile.Weights.Any()) { var weight = userProfile.Weights.OrderByDescending(d => d.Date).First().Weight; if (weight > 0) { workoutDiaryEntry.BurnedCalories = WorkoutHelper.GetBurnedCalories(workoutDiaryEntry.Exercise.MET.Value, weight, setsDuration); } } } } #endregion // Информация о изменении workoutDiaryEntry.CreationInfo.LastModifiedTimeUTC = TimeHelper.DateTimeUtcNow; // Обновляем запись unitOfWork.WorkoutDiaryEntryRepository.Update(workoutDiaryEntry); unitOfWork.Save(); //Преобразовываем обратно, чтобы вернуть клиенту workoutDiaryEntryDTO = Mapper.Map <WorkoutDiaryEntryDTO>(workoutDiaryEntry); } catch (System.Data.Entity.Validation.DbEntityValidationException ex) { // Retrieve the error messages as a list of strings. var errorMessages = ex.EntityValidationErrors .SelectMany(x => x.ValidationErrors) .Select(x => x.ErrorMessage); // Join the list to a single string. var fullErrorMessage = string.Join("; ", errorMessages); throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError)); } catch (Exception ex) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message)); } return(Request.CreateResponse <WorkoutDiaryEntryDTO>(HttpStatusCode.OK, workoutDiaryEntryDTO)); }
public HttpResponseMessage Post([FromBody] WorkoutDiaryEntryDTO workoutDiaryEntryDTO) { var currentTime = TimeHelper.UnixMsNow; // Информация о создании workoutDiaryEntryDTO.CreateUserId = currentUserId; workoutDiaryEntryDTO.CreationInfo.CreationTime = workoutDiaryEntryDTO.CreationInfo.LastModifiedTime = currentTime; try { var workoutDiaryEntry = Mapper.Map <WorkoutDiaryEntry>(workoutDiaryEntryDTO); // Для избежания повторного добавления упражнения - // обнуляем его (сохраняя во временной переменной)и // выставляем индекс упражнения в записи Exercise tempExercise = null; if (workoutDiaryEntry.Exercise != null) { // Запоминаем упражнение tempExercise = workoutDiaryEntry.Exercise; // Устанавлиавем идентификатор упражнения workoutDiaryEntry.ExerciseId = workoutDiaryEntry.Exercise.ExerciseId; #region __Подсчитываем количество сожженных калорий__ if (workoutDiaryEntry.Exercise.MET.HasValue && workoutDiaryEntry.Exercise.MET.Value != 0) { var setsDuration = workoutDiaryEntry.Sets.Select(s => s.Duration).Sum(); if (setsDuration > 0) { // Получаем информацию о пользователе для подсчета сожженных калорий (вес, возраст) var userProfile = unitOfWork.UserProfileRepository.Get( f => (f.UserId == currentUserId), null, i => i.Weights) .SingleOrDefault(); // Пользователя нет в БД if (userProfile == null) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } if (userProfile.Weights.Any()) { var weight = userProfile.Weights.OrderByDescending(d => d.Date).First().Weight; if (weight > 0) { workoutDiaryEntry.BurnedCalories = WorkoutHelper.GetBurnedCalories(workoutDiaryEntry.Exercise.MET.Value, weight, setsDuration); } } } } #endregion // Обнуляем упражнение, чтобы не обновлялось в БД workoutDiaryEntry.Exercise = null; } // Добавляем запись в базу unitOfWork.WorkoutDiaryEntryRepository.Insert(workoutDiaryEntry); unitOfWork.Save(); // Восстанавливаем упражение, чтобы вернуть клиенту if (tempExercise != null) { workoutDiaryEntry.Exercise = tempExercise; } // Преобразовываем обратно, чтобы вернуть клиенту workoutDiaryEntryDTO = Mapper.Map <WorkoutDiaryEntryDTO>(workoutDiaryEntry); } catch (Exception ex) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message)); } return(Request.CreateResponse <WorkoutDiaryEntryDTO>(HttpStatusCode.Created, workoutDiaryEntryDTO)); }