Skip to content

Commit

Permalink
Use new v2 storage models to serialize and deserialize everywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
LiamMorrow committed Dec 29, 2023
1 parent 9184a6b commit 3e15f9f
Show file tree
Hide file tree
Showing 23 changed files with 275 additions and 114 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public CurrentSessionStateDaoV2(

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

Expand Down
16 changes: 16 additions & 0 deletions LiftLog.Ui/Models/SessionBlueprintDao/SessionBlueprintDaoV2.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ 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;
Expand Down
23 changes: 10 additions & 13 deletions LiftLog.Ui/Models/SessionHistoryDao/SessionHistoryDaoV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public SessionHistoryDaoV2(IEnumerable<SessionDaoV2> completedSessions)
}

public static SessionHistoryDaoV2 FromModel(SessionHistoryDaoContainer model) =>
new(model.CompletedSessions.Values.Select(SessionDaoV2.FromModel).Cast<SessionDaoV2>());
new(model.CompletedSessions.Values.Select(SessionDaoV2.FromModel));

public SessionHistoryDaoContainer ToModel() =>
new(CompletedSessions.Select(x => x.ToModel()).ToImmutableDictionary(x => x.Id, x => x));
Expand All @@ -35,17 +35,14 @@ public SessionDaoV2(
Bodyweight = bodyweight;
}

[return: NotNullIfNotNull(nameof(model))]
public static SessionDaoV2? FromModel(Lib.Models.Session? model) =>
model is null
? null
: new(
model.Id,
SessionBlueprintDaoV2.FromModel(model.Blueprint),
model.RecordedExercises.Select(RecordedExerciseDaoV2.FromModel).ToImmutableList(),
model.Date,
model.Bodyweight
);
public static SessionDaoV2 FromModel(Lib.Models.Session model) =>
new(
model.Id,
SessionBlueprintDaoV2.FromModel(model.Blueprint),
model.RecordedExercises.Select(RecordedExerciseDaoV2.FromModel).ToImmutableList(),
model.Date,
model.Bodyweight
);

public Lib.Models.Session ToModel() =>
new(
Expand Down Expand Up @@ -104,7 +101,7 @@ public PotentialSetDaoV2(RecordedSetDaoV2? recordedSet, decimal weight)
public static PotentialSetDaoV2 FromModel(Lib.Models.PotentialSet model) =>
new(RecordedSetDaoV2.FromModel(model.Set), model.Weight);

public Lib.Models.PotentialSet ToModel() => new(RecordedSet.ToModel(), Weight);
public Lib.Models.PotentialSet ToModel() => new(RecordedSet?.ToModel(), Weight);
}

internal partial class RecordedSetDaoV2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";
package LiftLog.Ui.Models.SessionHistoryDao;

import "Models/Utils.proto";
import "google/protobuf/wrappers.proto";
import "Models/SessionBlueprintDao/SessionBlueprintDaoV2.proto";

message SessionHistoryDaoV2 {
Expand All @@ -21,7 +22,7 @@ message RecordedExerciseDaoV2 {
LiftLog.Ui.Models.SessionBlueprintDao.ExerciseBlueprintDaoV2 exercise_blueprint = 1;
LiftLog.Ui.Models.DecimalValue weight = 2;
repeated PotentialSetDaoV2 potential_sets = 3;
optional string notes = 4;
optional google.protobuf.StringValue notes = 4;
bool per_set_weight = 5;
}

Expand Down
16 changes: 16 additions & 0 deletions LiftLog.Ui/Models/SettingStorageDao/SettingsStorageDaoV2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using LiftLog.Ui.Models.SessionBlueprintDao;
using LiftLog.Ui.Models.SessionHistoryDao;

namespace LiftLog.Ui.Models.SettingsStorageDao;

internal partial class SettingsStorageDaoV2
{
public SettingsStorageDaoV2(
IEnumerable<SessionDaoV2> sessions,
IEnumerable<SessionBlueprintDaoV2> program
)
{
Sessions.AddRange(sessions);
Program.AddRange(program);
}
}
47 changes: 47 additions & 0 deletions LiftLog.Ui/Models/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ public static implicit operator DecimalValue(decimal value)
var nanos = decimal.ToInt32((value - units) * NanoFactor);
return new DecimalValue(units, nanos);
}

public static implicit operator decimal?(DecimalValue? grpcDecimal)
{
if (grpcDecimal is null)
{
return null;
}
return grpcDecimal.Units + grpcDecimal.Nanos / NanoFactor;
}

public static implicit operator DecimalValue?(decimal? value)
{
if (value is null)
{
return null;
}
var units = decimal.ToInt64(value.Value);
var nanos = decimal.ToInt32((value.Value - units) * NanoFactor);
return new DecimalValue(units, nanos);
}
}

internal partial class DateOnlyDao
Expand All @@ -39,6 +59,14 @@ public DateOnlyDao(int year, int month, int day)

public static implicit operator DateOnlyDao(DateOnly model) =>
new(model.Year, model.Month, model.Day);

public static implicit operator DateOnlyDao?(DateOnly? model) =>
model is null
? null
: new DateOnlyDao(model.Value.Year, model.Value.Month, model.Value.Day);

public static implicit operator DateOnly?(DateOnlyDao? dao) =>
dao is null ? null : new DateOnly(dao.Year, dao.Month, dao.Day);
}

internal partial class TimeOnlyDao
Expand All @@ -56,6 +84,19 @@ public static implicit operator TimeOnly(TimeOnlyDao dao) =>

public static implicit operator TimeOnlyDao(TimeOnly model) =>
new(model.Hour, model.Minute, model.Second, model.Millisecond);

public static implicit operator TimeOnlyDao?(TimeOnly? model) =>
model is null
? null
: new TimeOnlyDao(
model.Value.Hour,
model.Value.Minute,
model.Value.Second,
model.Value.Millisecond
);

public static implicit operator TimeOnly?(TimeOnlyDao? dao) =>
dao is null ? null : new TimeOnly(dao.Hour, dao.Minute, dao.Second, dao.Millisecond);
}

internal partial class UUIDDao
Expand All @@ -68,4 +109,10 @@ public UUIDDao(Guid value)
public static implicit operator UUIDDao(Guid value) => new(value);

public static implicit operator Guid(UUIDDao dao) => new(dao.Value.ToByteArray());

public static implicit operator UUIDDao?(Guid? value) =>
value is null ? null : new UUIDDao(value.Value);

public static implicit operator Guid?(UUIDDao? dao) =>
dao is null ? null : new Guid(dao.Value.ToByteArray());
}
2 changes: 2 additions & 0 deletions LiftLog.Ui/Services/IKeyValueStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ namespace LiftLog.Ui.Services;
public interface IKeyValueStore
{
ValueTask<string?> GetItemAsync(string key);
ValueTask<byte[]?> GetItemBytesAsync(string key);
ValueTask SetItemAsync(string key, string value);
ValueTask SetItemAsync(string key, byte[] value);
}
6 changes: 3 additions & 3 deletions LiftLog.Ui/Services/ITextExporter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace LiftLog.Ui.Services;

public interface ITextExporter
public interface IExporter
{
Task ExportTextAsync(string text);
Task<string> ImportTextAsync();
Task ExportBytesAsync(byte[] bytes);
Task<byte[]> ImportBytesAsync();
}
20 changes: 11 additions & 9 deletions LiftLog.Ui/Services/KeyValueCurrentProgramRepository.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Buffers.Text;
using System.Collections.Immutable;
using System.Text.Json;
using Google.Protobuf;
using LiftLog.Lib;
using LiftLog.Lib.Models;
using LiftLog.Lib.Serialization;
Expand Down Expand Up @@ -27,13 +29,10 @@ public async ValueTask PersistSessionsInProgramAsync(IReadOnlyList<SessionBluepr
{
await InitialiseAsync();
_sessions = sessions.ToImmutableList();
await keyValueStore.SetItemAsync($"{StorageKey}-Version", "1");
await keyValueStore.SetItemAsync($"{StorageKey}-Version", "2");
await keyValueStore.SetItemAsync(
StorageKey,
JsonSerializer.Serialize(
SessionBlueprintContainerDaoV1.FromModel(_sessions),
StorageJsonContext.Context.SessionBlueprintContainerDaoV1
)
SessionBlueprintContainerDaoV2.FromModel(_sessions).ToByteArray()
);
}

Expand All @@ -44,19 +43,22 @@ private async ValueTask InitialiseAsync()
var version = await keyValueStore.GetItemAsync($"{StorageKey}-Version");
if (version is null)
{
version = "1";
await keyValueStore.SetItemAsync($"{StorageKey}-Version", "1");
version = "2";
await keyValueStore.SetItemAsync($"{StorageKey}-Version", "2");
}
var storedDataJson = await keyValueStore.GetItemAsync(StorageKey);
var storedData = version switch
{
"1"
=> JsonSerializer
.Deserialize<SessionBlueprintContainerDaoV1>(
storedDataJson ?? "null",
await keyValueStore.GetItemAsync(StorageKey) ?? "null",
StorageJsonContext.Context.SessionBlueprintContainerDaoV1
)
?.ToModel(),
"2"
=> SessionBlueprintContainerDaoV2
.Parser.ParseFrom(await keyValueStore.GetItemBytesAsync(StorageKey) ?? [])
.ToModel(),
_ => throw new Exception($"Unknown version {version} of {StorageKey}"),
};
_sessions = storedData ?? ImmutableList.Create<SessionBlueprint>();
Expand Down
Loading

0 comments on commit 3e15f9f

Please sign in to comment.