-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Basic layout persistence implementation (#755)
- Loading branch information
Showing
13 changed files
with
605 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.9.34723.18 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.LayoutPersistence", "src\MorganStanley.ComposeUI.LayoutPersistence\MorganStanley.ComposeUI.LayoutPersistence.csproj", "{BC4F5C92-F894-45A7-BBEA-A956F89C617B}" | ||
EndProject | ||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{15375C6D-AEF5-42DA-B809-36C55A5133B0}" | ||
EndProject | ||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{387B5C73-2D89-45B1-92E0-71CDBB825F46}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MorganStanley.ComposeUI.LayoutPersistence.Tests", "tests\MorganStanley.ComposeUI.LayoutPersistence.Tests\MorganStanley.ComposeUI.LayoutPersistence.Tests.csproj", "{9727D812-2697-4BB5-8C65-80774842FE99}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{9727D812-2697-4BB5-8C65-80774842FE99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{9727D812-2697-4BB5-8C65-80774842FE99}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{9727D812-2697-4BB5-8C65-80774842FE99}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{9727D812-2697-4BB5-8C65-80774842FE99}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(NestedProjects) = preSolution | ||
{BC4F5C92-F894-45A7-BBEA-A956F89C617B} = {15375C6D-AEF5-42DA-B809-36C55A5133B0} | ||
{9727D812-2697-4BB5-8C65-80774842FE99} = {387B5C73-2D89-45B1-92E0-71CDBB825F46} | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {5AC4D360-3DCE-47E1-8128-C76C86113476} | ||
EndGlobalSection | ||
EndGlobal |
21 changes: 21 additions & 0 deletions
21
...e/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutPersistence.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* Morgan Stanley makes this available to you under the Apache License, | ||
* Version 2.0 (the "License"). You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* See the NOTICE file distributed with this work for additional information | ||
* regarding copyright ownership. Unless required by applicable law or agreed | ||
* to in writing, software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions | ||
* and limitations under the License. | ||
*/ | ||
|
||
namespace MorganStanley.ComposeUI.LayoutPersistence.Abstractions; | ||
|
||
public interface ILayoutPersistence<T> | ||
{ | ||
Task SaveLayoutAsync(string layoutName, T layoutData, CancellationToken cancellationToken = default); | ||
Task<T?> LoadLayoutAsync(string layoutName, CancellationToken cancellationToken = default); | ||
} |
21 changes: 21 additions & 0 deletions
21
...ce/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutSerializer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* Morgan Stanley makes this available to you under the Apache License, | ||
* Version 2.0 (the "License"). You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* See the NOTICE file distributed with this work for additional information | ||
* regarding copyright ownership. Unless required by applicable law or agreed | ||
* to in writing, software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions | ||
* and limitations under the License. | ||
*/ | ||
|
||
namespace MorganStanley.ComposeUI.LayoutPersistence.Abstractions; | ||
|
||
public interface ILayoutSerializer<T> | ||
{ | ||
Task<string> SerializeAsync(T layoutObject, CancellationToken cancellationToken = default); | ||
Task<T?> DeserializeAsync(string layoutData, CancellationToken cancellationToken = default); | ||
} |
98 changes: 98 additions & 0 deletions
98
...persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Morgan Stanley makes this available to you under the Apache License, | ||
* Version 2.0 (the "License"). You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* See the NOTICE file distributed with this work for additional information | ||
* regarding copyright ownership. Unless required by applicable law or agreed | ||
* to in writing, software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions | ||
* and limitations under the License. | ||
*/ | ||
|
||
using MorganStanley.ComposeUI.LayoutPersistence.Abstractions; | ||
|
||
namespace MorganStanley.ComposeUI.LayoutPersistence; | ||
|
||
public class FileLayoutPersistence<T> : ILayoutPersistence<T> | ||
{ | ||
private readonly string _basePath; | ||
private readonly ILayoutSerializer<T> _serializer; | ||
private readonly SemaphoreSlim _semaphore = new(1,1); | ||
|
||
public FileLayoutPersistence(string basePath, ILayoutSerializer<T> serializer) | ||
{ | ||
_basePath = NormalizeFilePath(basePath); | ||
_serializer = serializer; | ||
|
||
if (!Directory.Exists(_basePath)) | ||
{ | ||
Directory.CreateDirectory(_basePath); | ||
} | ||
} | ||
|
||
public async Task SaveLayoutAsync(string layoutName, T layoutData, CancellationToken cancellationToken = default) | ||
{ | ||
var filePath = GetFilePath(layoutName); | ||
|
||
await _semaphore.WaitAsync(cancellationToken); | ||
|
||
try | ||
{ | ||
var serializedData = await _serializer.SerializeAsync(layoutData, cancellationToken); | ||
await File.WriteAllTextAsync(filePath, serializedData, cancellationToken); | ||
} | ||
finally | ||
{ | ||
_semaphore.Release(); | ||
} | ||
} | ||
|
||
public async Task<T?> LoadLayoutAsync(string layoutName, CancellationToken cancellationToken = default) | ||
{ | ||
var filePath = GetFilePath(layoutName); | ||
|
||
if (!File.Exists(filePath)) | ||
{ | ||
throw new FileNotFoundException($"Layout file not found: {filePath}"); | ||
} | ||
|
||
await _semaphore.WaitAsync(cancellationToken); | ||
|
||
try | ||
{ | ||
var serializedData = await File.ReadAllTextAsync(filePath, cancellationToken); | ||
return await _serializer.DeserializeAsync(serializedData, cancellationToken); | ||
} | ||
finally | ||
{ | ||
_semaphore.Release(); | ||
} | ||
} | ||
|
||
private string GetFilePath(string layoutName) | ||
{ | ||
var combinedPath = Path.Combine(_basePath, $"{layoutName}.layout"); | ||
var fullPath = Path.GetFullPath(combinedPath); | ||
|
||
if (!fullPath.StartsWith(_basePath, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
throw new ArgumentException("Invalid layoutName argument. File cannot be saved outside of the base directory.", layoutName); | ||
} | ||
|
||
return fullPath; | ||
} | ||
|
||
private static string NormalizeFilePath(string path) | ||
{ | ||
if (path.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
var normalizedPath = Uri.UnescapeDataString(path[7..]); | ||
return Path.GetFullPath(normalizedPath); | ||
} | ||
|
||
return Path.GetFullPath(path); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...organStanley.ComposeUI.LayoutPersistence/MorganStanley.ComposeUI.LayoutPersistence.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
</Project> |
53 changes: 53 additions & 0 deletions
53
.../dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Morgan Stanley makes this available to you under the Apache License, | ||
* Version 2.0 (the "License"). You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* See the NOTICE file distributed with this work for additional information | ||
* regarding copyright ownership. Unless required by applicable law or agreed | ||
* to in writing, software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions | ||
* and limitations under the License. | ||
*/ | ||
|
||
using System.Text; | ||
using System.Text.Json; | ||
using MorganStanley.ComposeUI.LayoutPersistence.Abstractions; | ||
|
||
namespace MorganStanley.ComposeUI.LayoutPersistence.Serializers; | ||
|
||
public class JsonLayoutSerializer<T> : ILayoutSerializer<T> | ||
{ | ||
private readonly JsonSerializerOptions? _jsonSerializerOptions; | ||
|
||
public JsonLayoutSerializer(JsonSerializerOptions? jsonSerializerOptions = null) | ||
{ | ||
_jsonSerializerOptions = jsonSerializerOptions; | ||
} | ||
|
||
public async Task<string> SerializeAsync(T layoutObject, CancellationToken cancellationToken = default) | ||
{ | ||
if (layoutObject == null) | ||
{ | ||
throw new ArgumentNullException(nameof(layoutObject), "The layout object to serialize cannot be null."); | ||
} | ||
|
||
using var stream = new MemoryStream(); | ||
await JsonSerializer.SerializeAsync(stream, layoutObject, _jsonSerializerOptions, cancellationToken); | ||
|
||
return Encoding.UTF8.GetString(stream.ToArray()); | ||
} | ||
|
||
public async Task<T?> DeserializeAsync(string layoutData, CancellationToken cancellationToken = default) | ||
{ | ||
if (string.IsNullOrWhiteSpace(layoutData)) | ||
{ | ||
throw new ArgumentException("The layout data cannot be null or empty.", nameof(layoutData)); | ||
} | ||
|
||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(layoutData)); | ||
return await JsonSerializer.DeserializeAsync<T>(stream, _jsonSerializerOptions, cancellationToken); | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
...e/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Morgan Stanley makes this available to you under the Apache License, | ||
* Version 2.0 (the "License"). You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* See the NOTICE file distributed with this work for additional information | ||
* regarding copyright ownership. Unless required by applicable law or agreed | ||
* to in writing, software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions | ||
* and limitations under the License. | ||
*/ | ||
|
||
using MorganStanley.ComposeUI.LayoutPersistence.Abstractions; | ||
using System.Text; | ||
using System.Xml.Serialization; | ||
|
||
namespace MorganStanley.ComposeUI.LayoutPersistence.Serializers; | ||
|
||
public class XmlLayoutSerializer<T> : ILayoutSerializer<T> | ||
{ | ||
public async Task<string> SerializeAsync(T layoutObject, CancellationToken cancellationToken = default) | ||
{ | ||
if (layoutObject == null) | ||
{ | ||
throw new ArgumentNullException(nameof(layoutObject), "The layout object to serialize cannot be null."); | ||
} | ||
|
||
using var stream = new MemoryStream(); | ||
var serializer = new XmlSerializer(typeof(T)); | ||
|
||
using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true)) | ||
{ | ||
serializer.Serialize(writer, layoutObject); | ||
await writer.FlushAsync(); | ||
} | ||
|
||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
return Encoding.UTF8.GetString(stream.ToArray()); | ||
} | ||
|
||
public async Task<T?> DeserializeAsync(string layoutData, CancellationToken cancellationToken = default) | ||
{ | ||
if (string.IsNullOrWhiteSpace(layoutData)) | ||
{ | ||
throw new ArgumentException("The layout data cannot be null or empty.", nameof(layoutData)); | ||
} | ||
|
||
var dataBytes = Encoding.UTF8.GetBytes(layoutData); | ||
|
||
using var stream = new MemoryStream(dataBytes); | ||
|
||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
var serializer = new XmlSerializer(typeof(T)); | ||
|
||
return await Task.FromResult((T?)serializer.Deserialize(stream)); | ||
} | ||
} |
Oops, something went wrong.