Skip to content

Commit

Permalink
Merge pull request #107 from LiamMorrow/serialization-improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
LiamMorrow authored Dec 29, 2023
2 parents 89ec9ec + 45e7d94 commit 4a25b56
Show file tree
Hide file tree
Showing 28 changed files with 693 additions and 98 deletions.
2 changes: 1 addition & 1 deletion LiftLog.App/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public static MauiApp CreateMauiApp()
builder.Services.AddSingleton<IKeyValueStore, AppDataFileStorageKeyValueStore>();
builder.Services.AddSingleton<IPreferenceStore, SecureStoragePreferenceStore>();
builder.Services.AddScoped<INotificationService, MauiNotificationService>();
builder.Services.AddScoped<ITextExporter, MauiShareTextExporter>();
builder.Services.AddScoped<IExporter, MauiShareExporter>();

builder.Services.AddSingleton(new HttpClient());
builder.Services.AddScoped<IAiWorkoutPlanner, ApiBasedAiWorkoutPlanner>();
Expand Down
17 changes: 17 additions & 0 deletions LiftLog.App/Services/AppDataFileStorageKeyValueStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,28 @@ public class AppDataFileStorageKeyValueStore : IKeyValueStore
return null;
}

public async ValueTask<byte[]?> GetItemBytesAsync(string key)
{
var fileName = GetFileName(key);
var exists = File.Exists(fileName);
if (exists)
{
var content = await File.ReadAllBytesAsync(fileName);
return content;
}
return null;
}

public async ValueTask SetItemAsync(string key, string value)
{
await File.WriteAllTextAsync(GetFileName(key), value);
}

public async ValueTask SetItemAsync(string key, byte[] value)
{
await File.WriteAllBytesAsync(GetFileName(key), value);
}

private string GetFileName(string key)
{
var dataDir = FileSystem.Current.AppDataDirectory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,35 @@

namespace LiftLog.App.Services;

public class MauiShareTextExporter(IShare share, IFilePicker filePicker) : ITextExporter
public class MauiShareExporter(IShare share, IFilePicker filePicker) : IExporter
{
public async Task ExportTextAsync(string text)
public async Task ExportBytesAsync(byte[] bytes)
{
string fileName = "liftlog-export.json.gz";
string fileName = "export.liftlogbackup.gz";
string file = Path.Combine(FileSystem.CacheDirectory, fileName);

using (FileStream stream = File.Create(file))
using (GZipStream gzip = new(stream, CompressionMode.Compress))
using (StreamWriter writer = new(gzip))
{
await writer.WriteAsync(text);
}
await gzip.WriteAsync(bytes);

await share.RequestAsync(
new ShareFileRequest { Title = "Export Data", File = new ShareFile(file) }
);
}

public async Task<string> ImportTextAsync()
public async Task<byte[]> ImportBytesAsync()
{
var file = await filePicker.PickAsync();

if (file == null)
{
return "";
return Array.Empty<byte>();
}

using FileStream stream = File.OpenRead(file.FullPath);
using GZipStream gzip = new(stream, CompressionMode.Decompress);
using StreamReader reader = new(gzip);
return await reader.ReadToEndAsync();
using MemoryStream memoryStream = new();
await gzip.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
21 changes: 21 additions & 0 deletions LiftLog.Tests/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json;
using FluentAssertions;
using Google.Protobuf;
using LiftLog.Lib.Models;
using LiftLog.Ui.Models.SessionHistoryDao;

Expand Down Expand Up @@ -32,4 +33,24 @@ public void SessionHistoryDaoV1_SerializeDeserialize_RoundTrip()
deserialized!.Should().Be(sessionHistoryDao);
deserialized!.ToModel().CompletedSessions.First().Value.Should().Be(session);
}

[Fact]
public void SessionHistoryDaoV2_SerializeDeserialize_RoundTrip()
{
var session = Sessions.CreateSession();
var sessionHistoryDao = SessionHistoryDaoV2.FromModel(
new SessionHistoryDaoContainer(
CompletedSessions: new Dictionary<Guid, Session>
{
{ session.Id, session }
}.ToImmutableDictionary()
)
);

var serialized = sessionHistoryDao.ToByteArray();
var deserialized = SessionHistoryDaoV2.Parser.ParseFrom(serialized);

deserialized!.Should().Be(sessionHistoryDao);
deserialized!.ToModel().CompletedSessions.First().Value.Should().Be(session);
}
}
15 changes: 15 additions & 0 deletions LiftLog.Ui/LiftLog.Ui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Protobuf generates code with lower case types -->
<NoWarn>CS8981;</NoWarn>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -24,6 +26,12 @@
<PackageReference Include="Fluxor" Version="5.9.1" />
<PackageReference Include="Fluxor.Blazor.Web" Version="5.9.1" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />

<PackageReference Include="Google.Protobuf" Version="3.18.0" />
<PackageReference Include="Grpc.Tools" Version="2.40.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>


Expand All @@ -47,4 +55,11 @@
<ProjectReference Include="..\LiftLog.Lib\LiftLog.Lib.csproj" />
</ItemGroup>

<ItemGroup>
<Protobuf Include="Models\Utils.proto" GrpcServices="None" AdditionalProtocArguments="--csharp_opt=internal_access=true"/>
<Protobuf Include="Models\SessionHistoryDao\SessionHistoryDaoV2.proto" GrpcServices="None" AdditionalProtocArguments="--csharp_opt=internal_access=true"/>
<Protobuf Include="Models\SessionBlueprintDao\SessionBlueprintDaoV2.proto" GrpcServices="None" AdditionalProtocArguments="--csharp_opt=internal_access=true"/>
<Protobuf Include="Models\SettingStorageDao\SettingsStorageDaoV2.proto" GrpcServices="None" AdditionalProtocArguments="--csharp_opt=internal_access=true"/>
<Protobuf Include="Models\CurrentSessionStateDao\CurrentSessionStateDaoV2.proto" GrpcServices="None" AdditionalProtocArguments="--csharp_opt=internal_access=true"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace LiftLog.Ui.Models.CurrentSessionStateDao;

using LiftLog.Ui.Models.SessionHistoryDao;
using LiftLog.Ui.Store.CurrentSession;

internal partial class CurrentSessionStateDaoV2
{
public CurrentSessionStateDaoV2(
SessionDaoV2? workoutSession,
SessionDaoV2? historySession,
UUIDDao? latestSetTimerNotificationId
)
{
WorkoutSession = workoutSession;
HistorySession = historySession;
LatestSetTimerNotificationId = latestSetTimerNotificationId;
}

public static CurrentSessionStateDaoV2 FromModel(CurrentSessionState model) =>
new(
model.WorkoutSession is null ? null : SessionDaoV2.FromModel(model.WorkoutSession),
model.HistorySession is null ? null : SessionDaoV2.FromModel(model.HistorySession),
model.LatestSetTimerNotificationId
);

public CurrentSessionState ToModel() =>
new(WorkoutSession?.ToModel(), HistorySession?.ToModel(), LatestSetTimerNotificationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syntax = "proto3";
package LiftLog.Ui.Models.CurrentSessionStateDao;

import "Models/Utils.proto";
import "Models/SessionHistoryDao/SessionHistoryDaoV2.proto";

message CurrentSessionStateDaoV2 {
optional LiftLog.Ui.Models.SessionHistoryDao.SessionDaoV2 workout_session = 1;
optional LiftLog.Ui.Models.SessionHistoryDao.SessionDaoV2 history_session = 2;
optional LiftLog.Ui.Models.UUIDDao latest_set_timer_notification_id = 3;
}
102 changes: 102 additions & 0 deletions LiftLog.Ui/Models/SessionBlueprintDao/SessionBlueprintDaoV2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.Collections.Immutable;
using Google.Protobuf.WellKnownTypes;
using LiftLog.Lib;

namespace LiftLog.Ui.Models.SessionBlueprintDao;

internal partial class SessionBlueprintContainerDaoV2
{
public SessionBlueprintContainerDaoV2(IEnumerable<SessionBlueprintDaoV2> sessions)
{
SessionBlueprints.AddRange(sessions);
}

public static SessionBlueprintContainerDaoV2 FromModel(
ImmutableList<Lib.Models.SessionBlueprint> model
) => new(model.Select(SessionBlueprintDaoV2.FromModel));

public ImmutableListValue<Lib.Models.SessionBlueprint> ToModel() =>
SessionBlueprints.Select(x => x.ToModel()).ToImmutableList();
}

internal partial class SessionBlueprintDaoV2
{
public SessionBlueprintDaoV2(
string name,
IEnumerable<ExerciseBlueprintDaoV2> exerciseBlueprints
)
{
Name = name;
ExerciseBlueprints.AddRange(exerciseBlueprints);
}

public static SessionBlueprintDaoV2 FromModel(Lib.Models.SessionBlueprint model) =>
new(model.Name, model.Exercises.Select(ExerciseBlueprintDaoV2.FromModel).ToImmutableList());

public Lib.Models.SessionBlueprint ToModel() =>
new(Name, ExerciseBlueprints.Select(x => x.ToModel()).ToImmutableList());
}

internal partial class ExerciseBlueprintDaoV2
{
public ExerciseBlueprintDaoV2(
string name,
int sets,
int repsPerSet,
DecimalValue initialWeight,
DecimalValue weightIncreaseOnSuccess,
RestDaoV2 restBetweenSets,
bool supersetWithNext
)
{
Name = name;
Sets = sets;
RepsPerSet = repsPerSet;
InitialWeight = initialWeight;
WeightIncreaseOnSuccess = weightIncreaseOnSuccess;
RestBetweenSets = restBetweenSets;
SupersetWithNext = supersetWithNext;
}

public static ExerciseBlueprintDaoV2 FromModel(Lib.Models.ExerciseBlueprint model) =>
new(
model.Name,
model.Sets,
model.RepsPerSet,
model.InitialWeight,
model.WeightIncreaseOnSuccess,
RestDaoV2.FromModel(model.RestBetweenSets),
model.SupersetWithNext
);

public Lib.Models.ExerciseBlueprint ToModel() =>
new(
Name,
Sets,
RepsPerSet,
InitialWeight,
WeightIncreaseOnSuccess,
RestBetweenSets.ToModel(),
SupersetWithNext
);
}

internal partial class RestDaoV2
{
public RestDaoV2(Duration minRest, Duration maxRest, Duration failureRest)
{
MinRest = minRest;
MaxRest = maxRest;
FailureRest = failureRest;
}

public static RestDaoV2 FromModel(Lib.Models.Rest model) =>
new(
Duration.FromTimeSpan(model.MinRest),
Duration.FromTimeSpan(model.MaxRest),
Duration.FromTimeSpan(model.FailureRest)
);

public Lib.Models.Rest ToModel() =>
new(MinRest.ToTimeSpan(), MaxRest.ToTimeSpan(), FailureRest.ToTimeSpan());
}
31 changes: 31 additions & 0 deletions LiftLog.Ui/Models/SessionBlueprintDao/SessionBlueprintDaoV2.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
syntax = "proto3";
package LiftLog.Ui.Models.SessionBlueprintDao;

import "Models/Utils.proto";
import "google/protobuf/duration.proto";

message SessionBlueprintContainerDaoV2 {
repeated SessionBlueprintDaoV2 session_blueprints = 1;
}

message SessionBlueprintDaoV2 {
string name = 1;
repeated ExerciseBlueprintDaoV2 exercise_blueprints = 2;
}


message ExerciseBlueprintDaoV2 {
string name = 1;
int32 sets = 2;
int32 reps_per_set = 3;
LiftLog.Ui.Models.DecimalValue initial_weight = 4;
LiftLog.Ui.Models.DecimalValue weight_increase_on_success = 5;
RestDaoV2 rest_between_sets = 6;
bool superset_with_next = 7;
}

message RestDaoV2 {
google.protobuf.Duration min_rest = 1;
google.protobuf.Duration max_rest = 2;
google.protobuf.Duration failure_rest = 3;
}
Loading

0 comments on commit 4a25b56

Please sign in to comment.