-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
- Loading branch information
There are no files selected for viewing
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 |
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); | ||
} |
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); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* 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; | ||
|
||
try | ||
{ | ||
if (!Directory.Exists(_basePath)) | ||
{ | ||
Directory.CreateDirectory(_basePath); | ||
} | ||
} | ||
catch (Exception ex) | ||
Check warning on line 37 in src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs Codecov / codecov/patchsrc/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs#L37
|
||
{ | ||
throw new IOException($"Failed to create or access the directory: {_basePath}.", ex); | ||
Check warning on line 39 in src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs Codecov / codecov/patchsrc/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs#L39
|
||
} | ||
} | ||
|
||
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); | ||
Check warning on line 103 in src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs Codecov / codecov/patchsrc/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs#L103
|
||
} | ||
} |
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> |
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."); | ||
Check warning on line 34 in src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs Codecov / codecov/patchsrc/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs#L34
|
||
} | ||
|
||
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)); | ||
Check warning on line 47 in src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs Codecov / codecov/patchsrc/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs#L47
|
||
} | ||
|
||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(layoutData)); | ||
return await JsonSerializer.DeserializeAsync<T>(stream, _jsonSerializerOptions, cancellationToken); | ||
} | ||
} |
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."); | ||
Check warning on line 27 in src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs Codecov / codecov/patchsrc/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs#L27
|
||
} | ||
|
||
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)); | ||
Check warning on line 48 in src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs Codecov / codecov/patchsrc/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs#L48
|
||
} | ||
|
||
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)); | ||
} | ||
} |