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

refactoring #531

Merged
merged 12 commits into from
Jan 30, 2024
Prev Previous commit
Next Next commit
removed deserialization for now
mizrael committed Jan 29, 2024
commit f6246d8d10969de397efd369d03da772ee3cde1a
18 changes: 0 additions & 18 deletions src/Persistence.Tests/MsApp/MsappArchiveTests.cs
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
using Microsoft.PowerPlatform.PowerApps.Persistence;
using Microsoft.PowerPlatform.PowerApps.Persistence.Extensions;
using Microsoft.PowerPlatform.PowerApps.Persistence.Utils;
using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;

namespace Persistence.Tests.MsApp;

@@ -73,21 +72,4 @@ public void AddEntryTests(string[] entries)
var action = () => msappArchive.GetRequiredEntry("not-exist");
action.Invoking(a => a()).Should().Throw<FileNotFoundException>();
}

[TestMethod]
[DataRow(@"_TestData/AppsWithYaml/HelloWorld.msapp", 14, 2, "HelloScreen", "screen", 8)]
public void Msapp_ShouldHave_Screens(string testFile, int allEntriesCount, int controlsCount,
string topLevelControlName, string topLevelControlType,
int topLevelRulesCount)
{
// Arrange: Create new ZipArchive in memory
using var msappArchive = new MsappArchive(testFile, YamlSerializationFactory.CreateDeserializer());

// Assert
msappArchive.CanonicalEntries.Count.Should().Be(allEntriesCount);
msappArchive.Screens.Count.Should().Be(controlsCount);
msappArchive.Screens.Should().ContainSingle(c => c.Name == "App");

var screen = msappArchive.Screens.Single(c => c.Name == topLevelControlName);
}
}
2 changes: 1 addition & 1 deletion src/Persistence.Tests/Yaml/DeserializerInvalidTests.cs
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ public void Deserialize_ShouldFailWhenYamlIsInvalid()
// Arrange
var deserializer = YamlSerializationFactory.CreateDeserializer();

var files = Directory.GetFiles(@"_TestData/InvalidYaml", $"*{YamlUtils.YamlFxFileExtension}", SearchOption.AllDirectories);
var files = Directory.GetFiles(@"_TestData/InvalidYaml", $"*.fx.yaml", SearchOption.AllDirectories);
// Uncomment to test single file
// var files = new string[] { @"_TestData/InvalidYaml/InvalidName.fx.yaml" };

18 changes: 0 additions & 18 deletions src/Persistence.Tests/Yaml/ValidSerializerTests.cs
Original file line number Diff line number Diff line change
@@ -98,22 +98,4 @@ public void Serialize_ShouldCreateValidYamlForCustomControl()
var sut = serializer.Serialize(graph);
sut.Should().Be($"Control: http://localhost/#customcontrol{Environment.NewLine}Name: CustomControl1{Environment.NewLine}Properties:{Environment.NewLine} Text: I am a custom control{Environment.NewLine}");
}

[TestMethod]
public void Serialize_ShouldNotIncludeEnditorState()
{
var graph = new CustomControl("CustomControl1")
{
ControlUri = "http://localhost/#customcontrol",
EditorState = new()
{
Name = "CustomControl1",
},
};

var serializer = YamlSerializationFactory.CreateSerializer();

var sut = serializer.Serialize(graph);
sut.Should().NotContain(nameof(Control.EditorState));
}
}
2 changes: 0 additions & 2 deletions src/Persistence/Models/Control.cs
Original file line number Diff line number Diff line change
@@ -51,6 +51,4 @@ public required string Name
/// list of child controls nested under this control.
/// </summary>
public Control[] Controls { get; init; } = Array.Empty<Control>();

public ControlEditorState? EditorState { get; set; }
}
25 changes: 0 additions & 25 deletions src/Persistence/Models/ControlEditorState.cs

This file was deleted.

122 changes: 9 additions & 113 deletions src/Persistence/MsappArchive.cs
Original file line number Diff line number Diff line change
@@ -3,67 +3,40 @@

using System.IO.Compression;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.PowerPlatform.PowerApps.Persistence.Models;
using Microsoft.PowerPlatform.PowerApps.Persistence.Utils;
using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;
using YamlDotNet.Serialization;

namespace Microsoft.PowerPlatform.PowerApps.Persistence;


/// <summary>
/// Represents a .msapp file.
/// </summary>
public class MsappArchive : IMsappArchive, IDisposable
{
#region Constants

public const string JsonFileExtension = ".json";

#endregion

#region Fields

private readonly Lazy<IDictionary<string, ZipArchiveEntry>> _canonicalEntries;
private readonly Lazy<List<Screen>> _screens;
private bool _isDisposed;
private readonly ILogger<MsappArchive>? _logger;
private readonly Stream _stream;
private readonly bool _leaveOpen;
private readonly IDeserializer? _deserializer;

#endregion

#region Internal classes

/// <summary>
/// Helper class for deserializing the top level control editor state.
/// </summary>
private class TopParentJson
{
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value
public ControlEditorState? TopParent { get; set; }
#pragma warning restore CS0649
}

#endregion

#region Constructors

public MsappArchive(string path, IDeserializer? deserializer = null, ILogger<MsappArchive>? logger = null)
: this(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read), ZipArchiveMode.Read, leaveOpen: false, deserializer, logger)
public MsappArchive(string path, ILogger<MsappArchive>? logger = null)
: this(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read), ZipArchiveMode.Read, leaveOpen: false, logger)
{
}

public MsappArchive(Stream stream, IDeserializer? deserializer = null, ILogger<MsappArchive>? logger = null)
: this(stream, ZipArchiveMode.Read, leaveOpen: false, entryNameEncoding: null, deserializer, logger)
public MsappArchive(Stream stream, ILogger<MsappArchive>? logger = null)
: this(stream, ZipArchiveMode.Read, leaveOpen: false, entryNameEncoding: null, logger)
{
}

public MsappArchive(Stream stream, ZipArchiveMode mode, IDeserializer? deserializer = null, ILogger<MsappArchive>? logger = null)
: this(stream, mode, leaveOpen: false, entryNameEncoding: null, deserializer, logger)
public MsappArchive(Stream stream, ZipArchiveMode mode, ILogger<MsappArchive>? logger = null)
: this(stream, mode, leaveOpen: false, entryNameEncoding: null, logger)
{
}

@@ -75,18 +48,16 @@ public MsappArchive(Stream stream, ZipArchiveMode mode, IDeserializer? deseriali
/// <param name="leaveOpen">
/// true to leave the stream open after the System.IO.Compression.ZipArchive object is disposed; otherwise, false
/// </param>
/// <param name="deserializer"></param>
/// <param name="logger"></param>
public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, IDeserializer? deserializer = null, ILogger<MsappArchive>? logger = null)
: this(stream, mode, leaveOpen, null, deserializer, logger)
public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, ILogger<MsappArchive>? logger = null)
: this(stream, mode, leaveOpen, null, logger)
{
}

public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding? entryNameEncoding, IDeserializer? deserializer = null, ILogger<MsappArchive>? logger = null)
public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding? entryNameEncoding, ILogger<MsappArchive>? logger = null)
{
_stream = stream;
_leaveOpen = leaveOpen;
_deserializer = deserializer;
_logger = logger;
ZipArchive = new ZipArchive(stream, mode, leaveOpen, entryNameEncoding);
_canonicalEntries = new Lazy<IDictionary<string, ZipArchiveEntry>>
@@ -106,7 +77,6 @@ public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding

return canonicalEntries;
});
_screens = new Lazy<List<Screen>>(LoadScreens);
}

#endregion
@@ -131,8 +101,6 @@ public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding
/// </summary>
public long CompressedSize => ZipArchive.Entries.Sum(zipArchiveEntry => zipArchiveEntry.CompressedLength);

public IReadOnlyList<Screen> Screens => _screens.Value.AsReadOnly();

#endregion

#region Methods
@@ -187,78 +155,6 @@ public ZipArchiveEntry CreateEntry(string entryName)

#endregion

#region Private Methods

private List<Screen> LoadScreens()
{
_logger?.LogInformation("Loading top level screens from Yaml.");

var screens = new Dictionary<string, Screen>();
foreach (var yamlEntry in GetDirectoryEntries(Path.Combine(Directories.Src, Directories.Controls), YamlUtils.YamlFileExtension))
{
using var textReader = new StreamReader(yamlEntry.Open());
try
{
var screen = _deserializer!.Deserialize(textReader) as Screen;
screens.Add(screen!.Name, screen);
}
catch (Exception ex)
{
throw new PersistenceException("Failed to deserialize control yaml file.", ex) { FileName = yamlEntry.FullName };
}
}

_logger?.LogInformation("Loading top level controls editor state.");
var controlEditorStates = new Dictionary<string, ControlEditorState>();
foreach (var editorStateEntry in GetDirectoryEntries(Path.Combine(Directories.Controls), JsonFileExtension))
{
try
{
var topParentJson = JsonSerializer.Deserialize<TopParentJson>(editorStateEntry.Open());
controlEditorStates.Add(topParentJson!.TopParent!.Name, topParentJson.TopParent);
}
catch (Exception ex)
{
throw new PersistenceException("Failed to deserialize control editor state file.", ex) { FileName = editorStateEntry.FullName };
}
}

// Merge the editor state into the controls
foreach (var control in screens.Values)
{
if (controlEditorStates.TryGetValue(control.Name, out var editorState))
{
MergeControlEditorState(control, editorState);
controlEditorStates.Remove(control.Name);
}
}

return screens.Values.ToList();
}

private static void MergeControlEditorState(Control control, ControlEditorState controlEditorState)
{
control.EditorState = controlEditorState;
if (control.Controls == null)
return;

foreach (var child in control.Controls)
{
if (controlEditorState.Controls == null)
continue;

// Find the editor state for the child by name
var childEditorState = controlEditorState.Controls.FirstOrDefault(c => c.Name == child.Name);
if (childEditorState == null)
continue;

MergeControlEditorState(child, childEditorState);
}
controlEditorState.Controls = null;
}

#endregion

#region IDisposable

protected virtual void Dispose(bool disposing)
3 changes: 1 addition & 2 deletions src/Persistence/Yaml/SerializerBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -44,8 +44,7 @@ private static TBuilder AddAttributeOverrides<TBuilder>(this TBuilder builder)
.WithAttributeOverride(type, nameof(Control.ControlUri), new YamlMemberAttribute() { Order = 0, Alias = YamlFields.Control })
.WithAttributeOverride(type, nameof(Control.Name), new YamlMemberAttribute() { Order = 1 })
.WithAttributeOverride(type, nameof(Control.Properties), new YamlMemberAttribute() { Order = 2 })
.WithAttributeOverride(type, nameof(Control.Controls), new YamlMemberAttribute() { Order = 3 })
.WithAttributeOverride(type, nameof(Control.EditorState), new YamlIgnoreAttribute());
.WithAttributeOverride(type, nameof(Control.Controls), new YamlMemberAttribute() { Order = 3 });
}

return builder;
28 changes: 0 additions & 28 deletions src/Persistence/Yaml/YamlUtils.cs

This file was deleted.