diff --git a/build/tasks.ps1 b/build/tasks.ps1 index 5cc8a8f..cae6f83 100644 --- a/build/tasks.ps1 +++ b/build/tasks.ps1 @@ -1,13 +1,13 @@  $root = [System.IO.Path]::GetFullPath("$PSScriptRoot\..") -$sln_file = "$root\src\Sa.sln" +$sln_file = "$root\src\Sa.slnx" $sln_platform = "Any CPU" $config = "Release" $dist_folder = "$root\dist" $msbuild_verbosity = "n" -$projects = @( +$projects = @( "Sa.Media", "Sa.Media.FFmpeg", diff --git a/src/Sa.HybridFileStorage.Postgres/FileIdParser.cs b/src/Sa.HybridFileStorage.Postgres/FileIdParser.cs index 0fadf49..c3f5ccf 100644 --- a/src/Sa.HybridFileStorage.Postgres/FileIdParser.cs +++ b/src/Sa.HybridFileStorage.Postgres/FileIdParser.cs @@ -3,11 +3,10 @@ namespace Sa.HybridFileStorage.Postgres; internal static class FileIdParser - { private const string DateFormat = "yyyy/MM/dd/HH"; - public static (int tenantId, long timestamp) ParseFromFileId(string fileId) + public static (int tenantId, long timestamp) ParseFromFileId(string fileId, string tableName) { if (string.IsNullOrWhiteSpace(fileId)) { @@ -16,13 +15,15 @@ public static (int tenantId, long timestamp) ParseFromFileId(string fileId) ReadOnlySpan span = fileId.AsSpan(); - int separatorIndex = span.IndexOf("://"); + string seporator = $"://{tableName}/"; + + int separatorIndex = span.IndexOf(seporator); if (separatorIndex == -1) { throw new FormatException("Invalid file ID format."); } - ReadOnlySpan subParts = span[(separatorIndex + 3)..]; // +3 for skip "://" + ReadOnlySpan subParts = span[(separatorIndex + seporator.Length)..]; // +3 for skip "://files/" int firstSlashIndex = subParts.IndexOf('/'); if (firstSlashIndex == -1) @@ -48,8 +49,8 @@ public static (int tenantId, long timestamp) ParseFromFileId(string fileId) } - public static string FormatToFileId(string storageType, int tenantId, DateTimeOffset date, string fileName) - => $"{storageType}://{tenantId}/{date.ToString(DateFormat, CultureInfo.InvariantCulture)}/{NormalizeFileName(fileName)}"; + public static string FormatToFileId(string storageType, string tableName, int tenantId, DateTimeOffset date, string fileName) + => $"{storageType}://{tableName}/{tenantId}/{date.ToString(DateFormat, CultureInfo.InvariantCulture)}/{NormalizeFileName(fileName)}"; public static string NormalizeFileName(string fileName) => fileName.TrimStart('\\', '/').Replace('\\', '/'); diff --git a/src/Sa.HybridFileStorage.Postgres/PostgresFileStorage.cs b/src/Sa.HybridFileStorage.Postgres/PostgresFileStorage.cs index e0f2884..aa66547 100644 --- a/src/Sa.HybridFileStorage.Postgres/PostgresFileStorage.cs +++ b/src/Sa.HybridFileStorage.Postgres/PostgresFileStorage.cs @@ -12,8 +12,7 @@ internal sealed class PostgresFileStorage( StorageOptions options, TimeProvider? timeProvider = null) : IFileStorage { - - private readonly string _qualifiedTableName = $"{options.SchemaName}.\"{options.TableName.Trim('"')}\""; + private readonly string _qualifiedTableName = $"{options.SchemaName}.\"{options.TableName}\""; public string StorageType { get; } = options.StorageType; @@ -36,7 +35,7 @@ public async Task UploadAsync(UploadFileInput metadata, Stream fi await partManager.EnsureParts(_qualifiedTableName, now, [metadata.TenantId], cancellationToken); - string fileId = FileIdParser.FormatToFileId(StorageType, metadata.TenantId, now, metadata.FileName); + string fileId = FileIdParser.FormatToFileId(StorageType, options.TableName, metadata.TenantId, now, metadata.FileName); string fileExtension = FileIdParser.GetFileExtension(metadata.FileName); long createdAt = now.ToUnixTimeSeconds(); @@ -83,13 +82,13 @@ ON CONFLICT DO NOTHING return new StorageResult(fileId, fileId, StorageType, now); } - public bool CanProcess(string fileId) => fileId.StartsWith($"{StorageType}:://"); + public bool CanProcess(string fileId) => fileId.StartsWith($"{StorageType}://{options.TableName}/"); public async Task DeleteAsync(string fileId, CancellationToken cancellationToken) { EnsureWritable(); - (int tenantId, long timestamp) = FileIdParser.ParseFromFileId(fileId); + (int tenantId, long timestamp) = FileIdParser.ParseFromFileId(fileId, options.TableName); int rowsAffected = await dataSource.ExecuteNonQuery( $""" DELETE FROM {_qualifiedTableName} WHERE tenant_id = @tenant_id AND created_at >= @timestamp AND id = @id @@ -101,7 +100,7 @@ public async Task DeleteAsync(string fileId, CancellationToken cancellatio public async Task DownloadAsync(string fileId, Func loadStream, CancellationToken cancellationToken) { - (int tenantId, long timestamp) = FileIdParser.ParseFromFileId(fileId); + (int tenantId, long timestamp) = FileIdParser.ParseFromFileId(fileId, options.TableName); int rowsAffected = await dataSource.ExecuteReader( $""" diff --git a/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageConfiguration.cs b/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageConfiguration.cs index 75d809a..070dcea 100644 --- a/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageConfiguration.cs +++ b/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageConfiguration.cs @@ -57,7 +57,12 @@ public PostgresFileStorageConfiguration(IServiceCollection services) var time = sp.GetService(); var sm = sp.GetRequiredService(); - var storage = new PostgresFileStorage(dataSource, pm, sm, _options.StorageOptions, time); + StorageOptions options = _options.StorageOptions with + { + TableName = _options.StorageOptions.TableName.Trim('"') + }; + + var storage = new PostgresFileStorage(dataSource, pm, sm, options, time); return storage; }); } diff --git a/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageOptions.cs b/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageOptions.cs index 6220fea..c49f9e8 100644 --- a/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageOptions.cs +++ b/src/Sa.HybridFileStorage.Postgres/PostgresFileStorageOptions.cs @@ -2,7 +2,7 @@ namespace Sa.HybridFileStorage.Postgres; -public class StorageOptions +public record StorageOptions { public string SchemaName { get; set; } = "public"; public string TableName { get; set; } = "files"; diff --git a/src/Sa.HybridFileStorage.Postgres/Sa.HybridFileStorage.Postgres.csproj b/src/Sa.HybridFileStorage.Postgres/Sa.HybridFileStorage.Postgres.csproj index 3fff425..1d43533 100644 --- a/src/Sa.HybridFileStorage.Postgres/Sa.HybridFileStorage.Postgres.csproj +++ b/src/Sa.HybridFileStorage.Postgres/Sa.HybridFileStorage.Postgres.csproj @@ -3,7 +3,7 @@ - 0.3.0 + 0.3.1 File storage management in Pg diff --git a/src/Sa.HybridFileStorage/Domain/StorageResult.cs b/src/Sa.HybridFileStorage/Domain/StorageResult.cs index 8ead5df..ca9c432 100644 --- a/src/Sa.HybridFileStorage/Domain/StorageResult.cs +++ b/src/Sa.HybridFileStorage/Domain/StorageResult.cs @@ -1,3 +1,22 @@ namespace Sa.HybridFileStorage.Domain; +/// +/// Represents the result of a file upload operation. +/// FileId follows URI format: {storage_type}://{path_to_resource}[?parameters] +/// Examples: +/// +/// // PostgreSQL storage +/// new StorageResult("pg://files/user_avatars/12345", "https://cdn.example.com/avatars/12345", "pg", DateTimeOffset.Now) +/// +/// // Amazon S3 storage +/// new StorageResult("s3://my-bucket/documents/invoice.pdf", "https://my-bucket.s3.amazonaws.com/documents/invoice.pdf", "s3", DateTimeOffset.Now) +/// +/// // Local file system storage +/// new StorageResult("file:///var/www/uploads/image.png", "/api/files/download/file/var/www/uploads/image.png", "file", DateTimeOffset.Now) +/// +/// +/// Unique file identifier in URI format: {storage_type}://{path}[?params] +/// Publicly accessible URL for downloading the file +/// Type of storage backend used ("pg", "s3", "file", "azure") +/// Timestamp when the file was uploaded public record StorageResult(string FileId, string AbsoluteUrl, string StorageType, DateTimeOffset UploadedAt); diff --git a/src/Sa.sln b/src/Sa.sln deleted file mode 100644 index 8de4f52..0000000 --- a/src/Sa.sln +++ /dev/null @@ -1,316 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.11.35312.102 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa", "Sa\Sa.csproj", "{BB3A8ECF-5D33-4C41-AF4E-BD384528AAC0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Schedule", "Sa.Schedule\Sa.Schedule.csproj", "{D8098DBA-E1D4-4005-A062-541FAC08C5E6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{610D8708-2CB6-403A-B865-3C1FE0115519}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schedule.Console", "Samples\Schedule.Console\Schedule.Console.csproj", "{175E26B3-95F8-44C5-AE18-F7F310A3D04B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BDD5208F-0CF4-4C6A-B443-6527514C89E8}" - ProjectSection(SolutionItems) = preProject - Tests\Host.Test.Properties.xml = Tests\Host.Test.Properties.xml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Outbox", "Sa.Outbox\Sa.Outbox.csproj", "{958CAA75-B8D6-4356-B4AA-B75658C17374}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Outbox.PostgreSql", "Sa.Outbox.PostgreSql\Sa.Outbox.PostgreSql.csproj", "{2EA016FD-E9EA-4231-89F1-63737868EAB2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Outbox", "Outbox", "{6197ECD9-AA02-4EF1-AAD0-D84D428A7352}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schedule", "Schedule", "{272C3FEB-29B5-4619-8CAC-9A750E55604A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data", "Data", "{F9621075-6836-47F5-9160-F9C4743F6147}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Data.PostgreSql", "Sa.Data.PostgreSql\Sa.Data.PostgreSql.csproj", "{F899211C-2E6E-4DF8-839A-11D5CB9B04D3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PostgreSql", "PostgreSql", "{96FCE737-A321-445B-8EC1-05D8863461EA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.MediaTests", "Tests\Sa.MediaTests\Sa.MediaTests.csproj", "{F776F33F-E44B-4FF5-BFB6-43C98667FD66}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Fixtures", "Fixtures", "{6FAA2520-3F94-4BD7-A67E-87A9961E094E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{161802B9-8D73-45AF-B52B-62A416CD9FBC}" - ProjectSection(SolutionItems) = preProject - Common.NuGet.Properties.xml = Common.NuGet.Properties.xml - Common.Properties.xml = Common.Properties.xml - Tests\Host.Test.Properties.xml = Tests\Host.Test.Properties.xml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Data.PostgreSqlTests", "Tests\Sa.Data.PostgreSqlTests\Sa.Data.PostgreSqlTests.csproj", "{0A171BD6-3200-4FE8-9C19-B44E3B84529F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Fixture", "Tests\Fixtures\Sa.Fixture\Sa.Fixture.csproj", "{8D998D31-07BA-4185-B273-03EC3C68DA35}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Data.PostgreSql.Fixture", "Tests\Fixtures\Sa.Data.PostgreSql.Fixture\Sa.Data.PostgreSql.Fixture.csproj", "{944004BF-81CA-4953-B482-AAE275CE601D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Outbox.PostgreSqlTests", "Tests\Sa.Outbox.PostgreSqlTests\Sa.Outbox.PostgreSqlTests.csproj", "{52F64196-9FA5-4B50-AE71-CF6C4DF4B23E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaTests", "Tests\SaTests\SaTests.csproj", "{4C199038-F002-45B6-9158-2FD27A7392DC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Partitional", "Partitional", "{71B86B2B-03B6-4637-BC64-5E09BAF88192}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Partitional.PostgreSql", "Sa.Partitional.PostgreSql\Sa.Partitional.PostgreSql.csproj", "{23D36989-1D57-40AE-970D-085B18283D44}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.Partitional.PostgreSqlTests", "Tests\Sa.Partitional.PostgreSqlTests\Sa.Partitional.PostgreSqlTests.csproj", "{F35C5D81-7632-46A2-82C0-CAC9AC65AF30}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sa.ScheduleTests", "Tests\Sa.ScheduleTests\Sa.ScheduleTests.csproj", "{4FEF2F15-D4DC-416A-8640-66152F42CDFE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HybridFileStorage", "HybridFileStorage", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorage", "Sa.HybridFileStorage\Sa.HybridFileStorage.csproj", "{1B1DD442-8EB2-C9C1-E7CC-119CF6769E04}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HybridFileStorage.Console", "Samples\HybridFileStorage.Console\HybridFileStorage.Console.csproj", "{8BF84A00-E90B-4223-9D67-9C03FE1D38E7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Partitional.ConsoleApp", "Samples\Partitional.ConsoleApp\Partitional.ConsoleApp.csproj", "{5882EE9F-DC0D-46C6-8273-5E80455F424C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PgOutbox.ConsoleApp", "Samples\PgOutbox.ConsoleApp\PgOutbox.ConsoleApp.csproj", "{D410E914-539E-4438-AC48-D215A61DEC4C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "S3", "S3", "{502C4C67-C940-438B-A51C-42639264ECA2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Data.S3", "Sa.Data.S3\Sa.Data.S3.csproj", "{0C688E96-1998-65CC-E835-21C528EF82BD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Data.S3Tests", "Tests\Sa.Data.S3Tests\Sa.Data.S3Tests.csproj", "{7FED77E6-89F7-05C9-66B8-1613E88E06CA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Data.S3.Fixture", "Tests\Fixtures\Sa.Data.S3.Fixture\Sa.Data.S3.Fixture.csproj", "{B3423371-52FC-6E22-1FA5-4E1F33BFCC71}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorage.FileSystem", "Sa.HybridFileStorage.FileSystem\Sa.HybridFileStorage.FileSystem.csproj", "{D3B28250-9D8E-3DFF-EF7D-D7F6D190DA6B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorage.Postgres", "Sa.HybridFileStorage.Postgres\Sa.HybridFileStorage.Postgres.csproj", "{47AE5AB4-5FB9-B745-61F5-1D28305A03E3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorage.PostgresTests", "Tests\Sa.HybridFileStorage.PostgresTests\Sa.HybridFileStorage.PostgresTests.csproj", "{E698DF44-9C3F-CD27-1096-595B10C0F615}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorage.S3", "Sa.HybridFileStorage.S3\Sa.HybridFileStorage.S3.csproj", "{77A751E6-628C-4964-8532-4BD505EB2D3B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorage.S3Tests", "Tests\Sa.HybridFileStorage.S3Tests\Sa.HybridFileStorage.S3Tests.csproj", "{27A12FF2-C9DD-4124-B036-0169B2FED086}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorage.FileSystemTests", "Tests\Sa.HybridFileStorage.FileSystemTests\Sa.HybridFileStorage.FileSystemTests.csproj", "{5F02040C-392D-5B04-C80F-71C728693A09}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{21727B12-1186-4C5E-9CA0-C7A9CED092A8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Configuration", "Sa.Configuration\Sa.Configuration.csproj", "{324F7B8C-4EB8-4284-B5E1-C28298EA6BC0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.ConfigurationTests", "Tests\Sa.ConfigurationTests\Sa.ConfigurationTests.csproj", "{C612C2A5-3237-42D1-911F-2A9197580793}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Configuration.PostgreSql", "Sa.Configuration.PostgreSql\Sa.Configuration.PostgreSql.csproj", "{0FB2C6FE-D291-453A-B6DE-76F97A7E1B99}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Configuration.PostgreSqlTests", "Tests\Sa.Configuration.PostgreSqlTests\Sa.Configuration.PostgreSqlTests.csproj", "{DC68BF20-F35E-02C7-E7AE-EB276D3B8CD8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.HybridFileStorageTests", "Tests\Sa.HybridFileStorageTests\Sa.HybridFileStorageTests.csproj", "{FF81CA73-8518-415E-53F2-DBE53F4AF7CF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Outbox.Support", "Sa.Outbox.Support\Sa.Outbox.Support.csproj", "{CAC8522E-31A5-A6A3-7144-7739AF7C65B2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Media", "Media", "{5069D241-2B8E-4472-8A3C-DF706F895BB1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Media", "Sa.Media\Sa.Media.csproj", "{D81F4ED4-9117-7D8E-4115-9F6E683A730E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Media.FFmpeg", "Sa.Media.FFmpeg\Sa.Media.FFmpeg.csproj", "{391881E9-D4AC-F6EE-638A-CA62F4E9FC21}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sa.Media.FFmpegTests", "Tests\Sa.Media.FFmpegTests\Sa.Media.FFmpegTests.csproj", "{E2A62945-4A01-C97C-7228-FC7503F232A9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BB3A8ECF-5D33-4C41-AF4E-BD384528AAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB3A8ECF-5D33-4C41-AF4E-BD384528AAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB3A8ECF-5D33-4C41-AF4E-BD384528AAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BB3A8ECF-5D33-4C41-AF4E-BD384528AAC0}.Release|Any CPU.Build.0 = Release|Any CPU - {D8098DBA-E1D4-4005-A062-541FAC08C5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D8098DBA-E1D4-4005-A062-541FAC08C5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8098DBA-E1D4-4005-A062-541FAC08C5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D8098DBA-E1D4-4005-A062-541FAC08C5E6}.Release|Any CPU.Build.0 = Release|Any CPU - {175E26B3-95F8-44C5-AE18-F7F310A3D04B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {175E26B3-95F8-44C5-AE18-F7F310A3D04B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {175E26B3-95F8-44C5-AE18-F7F310A3D04B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {175E26B3-95F8-44C5-AE18-F7F310A3D04B}.Release|Any CPU.Build.0 = Release|Any CPU - {958CAA75-B8D6-4356-B4AA-B75658C17374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {958CAA75-B8D6-4356-B4AA-B75658C17374}.Debug|Any CPU.Build.0 = Debug|Any CPU - {958CAA75-B8D6-4356-B4AA-B75658C17374}.Release|Any CPU.ActiveCfg = Release|Any CPU - {958CAA75-B8D6-4356-B4AA-B75658C17374}.Release|Any CPU.Build.0 = Release|Any CPU - {2EA016FD-E9EA-4231-89F1-63737868EAB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2EA016FD-E9EA-4231-89F1-63737868EAB2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2EA016FD-E9EA-4231-89F1-63737868EAB2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2EA016FD-E9EA-4231-89F1-63737868EAB2}.Release|Any CPU.Build.0 = Release|Any CPU - {F899211C-2E6E-4DF8-839A-11D5CB9B04D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F899211C-2E6E-4DF8-839A-11D5CB9B04D3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F899211C-2E6E-4DF8-839A-11D5CB9B04D3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F899211C-2E6E-4DF8-839A-11D5CB9B04D3}.Release|Any CPU.Build.0 = Release|Any CPU - {F776F33F-E44B-4FF5-BFB6-43C98667FD66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F776F33F-E44B-4FF5-BFB6-43C98667FD66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F776F33F-E44B-4FF5-BFB6-43C98667FD66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F776F33F-E44B-4FF5-BFB6-43C98667FD66}.Release|Any CPU.Build.0 = Release|Any CPU - {0A171BD6-3200-4FE8-9C19-B44E3B84529F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A171BD6-3200-4FE8-9C19-B44E3B84529F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A171BD6-3200-4FE8-9C19-B44E3B84529F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A171BD6-3200-4FE8-9C19-B44E3B84529F}.Release|Any CPU.Build.0 = Release|Any CPU - {8D998D31-07BA-4185-B273-03EC3C68DA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D998D31-07BA-4185-B273-03EC3C68DA35}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D998D31-07BA-4185-B273-03EC3C68DA35}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D998D31-07BA-4185-B273-03EC3C68DA35}.Release|Any CPU.Build.0 = Release|Any CPU - {944004BF-81CA-4953-B482-AAE275CE601D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {944004BF-81CA-4953-B482-AAE275CE601D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {944004BF-81CA-4953-B482-AAE275CE601D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {944004BF-81CA-4953-B482-AAE275CE601D}.Release|Any CPU.Build.0 = Release|Any CPU - {52F64196-9FA5-4B50-AE71-CF6C4DF4B23E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52F64196-9FA5-4B50-AE71-CF6C4DF4B23E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52F64196-9FA5-4B50-AE71-CF6C4DF4B23E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52F64196-9FA5-4B50-AE71-CF6C4DF4B23E}.Release|Any CPU.Build.0 = Release|Any CPU - {4C199038-F002-45B6-9158-2FD27A7392DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C199038-F002-45B6-9158-2FD27A7392DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C199038-F002-45B6-9158-2FD27A7392DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C199038-F002-45B6-9158-2FD27A7392DC}.Release|Any CPU.Build.0 = Release|Any CPU - {23D36989-1D57-40AE-970D-085B18283D44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23D36989-1D57-40AE-970D-085B18283D44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23D36989-1D57-40AE-970D-085B18283D44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23D36989-1D57-40AE-970D-085B18283D44}.Release|Any CPU.Build.0 = Release|Any CPU - {F35C5D81-7632-46A2-82C0-CAC9AC65AF30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F35C5D81-7632-46A2-82C0-CAC9AC65AF30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F35C5D81-7632-46A2-82C0-CAC9AC65AF30}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F35C5D81-7632-46A2-82C0-CAC9AC65AF30}.Release|Any CPU.Build.0 = Release|Any CPU - {4FEF2F15-D4DC-416A-8640-66152F42CDFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FEF2F15-D4DC-416A-8640-66152F42CDFE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FEF2F15-D4DC-416A-8640-66152F42CDFE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FEF2F15-D4DC-416A-8640-66152F42CDFE}.Release|Any CPU.Build.0 = Release|Any CPU - {1B1DD442-8EB2-C9C1-E7CC-119CF6769E04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B1DD442-8EB2-C9C1-E7CC-119CF6769E04}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B1DD442-8EB2-C9C1-E7CC-119CF6769E04}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B1DD442-8EB2-C9C1-E7CC-119CF6769E04}.Release|Any CPU.Build.0 = Release|Any CPU - {8BF84A00-E90B-4223-9D67-9C03FE1D38E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BF84A00-E90B-4223-9D67-9C03FE1D38E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BF84A00-E90B-4223-9D67-9C03FE1D38E7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BF84A00-E90B-4223-9D67-9C03FE1D38E7}.Release|Any CPU.Build.0 = Release|Any CPU - {5882EE9F-DC0D-46C6-8273-5E80455F424C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5882EE9F-DC0D-46C6-8273-5E80455F424C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5882EE9F-DC0D-46C6-8273-5E80455F424C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5882EE9F-DC0D-46C6-8273-5E80455F424C}.Release|Any CPU.Build.0 = Release|Any CPU - {D410E914-539E-4438-AC48-D215A61DEC4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D410E914-539E-4438-AC48-D215A61DEC4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D410E914-539E-4438-AC48-D215A61DEC4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D410E914-539E-4438-AC48-D215A61DEC4C}.Release|Any CPU.Build.0 = Release|Any CPU - {0C688E96-1998-65CC-E835-21C528EF82BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C688E96-1998-65CC-E835-21C528EF82BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C688E96-1998-65CC-E835-21C528EF82BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C688E96-1998-65CC-E835-21C528EF82BD}.Release|Any CPU.Build.0 = Release|Any CPU - {7FED77E6-89F7-05C9-66B8-1613E88E06CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7FED77E6-89F7-05C9-66B8-1613E88E06CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7FED77E6-89F7-05C9-66B8-1613E88E06CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7FED77E6-89F7-05C9-66B8-1613E88E06CA}.Release|Any CPU.Build.0 = Release|Any CPU - {B3423371-52FC-6E22-1FA5-4E1F33BFCC71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3423371-52FC-6E22-1FA5-4E1F33BFCC71}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3423371-52FC-6E22-1FA5-4E1F33BFCC71}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3423371-52FC-6E22-1FA5-4E1F33BFCC71}.Release|Any CPU.Build.0 = Release|Any CPU - {D3B28250-9D8E-3DFF-EF7D-D7F6D190DA6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3B28250-9D8E-3DFF-EF7D-D7F6D190DA6B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3B28250-9D8E-3DFF-EF7D-D7F6D190DA6B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3B28250-9D8E-3DFF-EF7D-D7F6D190DA6B}.Release|Any CPU.Build.0 = Release|Any CPU - {47AE5AB4-5FB9-B745-61F5-1D28305A03E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47AE5AB4-5FB9-B745-61F5-1D28305A03E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47AE5AB4-5FB9-B745-61F5-1D28305A03E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47AE5AB4-5FB9-B745-61F5-1D28305A03E3}.Release|Any CPU.Build.0 = Release|Any CPU - {E698DF44-9C3F-CD27-1096-595B10C0F615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E698DF44-9C3F-CD27-1096-595B10C0F615}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E698DF44-9C3F-CD27-1096-595B10C0F615}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E698DF44-9C3F-CD27-1096-595B10C0F615}.Release|Any CPU.Build.0 = Release|Any CPU - {77A751E6-628C-4964-8532-4BD505EB2D3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77A751E6-628C-4964-8532-4BD505EB2D3B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77A751E6-628C-4964-8532-4BD505EB2D3B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77A751E6-628C-4964-8532-4BD505EB2D3B}.Release|Any CPU.Build.0 = Release|Any CPU - {27A12FF2-C9DD-4124-B036-0169B2FED086}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27A12FF2-C9DD-4124-B036-0169B2FED086}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27A12FF2-C9DD-4124-B036-0169B2FED086}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27A12FF2-C9DD-4124-B036-0169B2FED086}.Release|Any CPU.Build.0 = Release|Any CPU - {5F02040C-392D-5B04-C80F-71C728693A09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F02040C-392D-5B04-C80F-71C728693A09}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F02040C-392D-5B04-C80F-71C728693A09}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F02040C-392D-5B04-C80F-71C728693A09}.Release|Any CPU.Build.0 = Release|Any CPU - {324F7B8C-4EB8-4284-B5E1-C28298EA6BC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {324F7B8C-4EB8-4284-B5E1-C28298EA6BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {324F7B8C-4EB8-4284-B5E1-C28298EA6BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {324F7B8C-4EB8-4284-B5E1-C28298EA6BC0}.Release|Any CPU.Build.0 = Release|Any CPU - {C612C2A5-3237-42D1-911F-2A9197580793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C612C2A5-3237-42D1-911F-2A9197580793}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C612C2A5-3237-42D1-911F-2A9197580793}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C612C2A5-3237-42D1-911F-2A9197580793}.Release|Any CPU.Build.0 = Release|Any CPU - {0FB2C6FE-D291-453A-B6DE-76F97A7E1B99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0FB2C6FE-D291-453A-B6DE-76F97A7E1B99}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0FB2C6FE-D291-453A-B6DE-76F97A7E1B99}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0FB2C6FE-D291-453A-B6DE-76F97A7E1B99}.Release|Any CPU.Build.0 = Release|Any CPU - {DC68BF20-F35E-02C7-E7AE-EB276D3B8CD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC68BF20-F35E-02C7-E7AE-EB276D3B8CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC68BF20-F35E-02C7-E7AE-EB276D3B8CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC68BF20-F35E-02C7-E7AE-EB276D3B8CD8}.Release|Any CPU.Build.0 = Release|Any CPU - {FF81CA73-8518-415E-53F2-DBE53F4AF7CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF81CA73-8518-415E-53F2-DBE53F4AF7CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF81CA73-8518-415E-53F2-DBE53F4AF7CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF81CA73-8518-415E-53F2-DBE53F4AF7CF}.Release|Any CPU.Build.0 = Release|Any CPU - {CAC8522E-31A5-A6A3-7144-7739AF7C65B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CAC8522E-31A5-A6A3-7144-7739AF7C65B2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CAC8522E-31A5-A6A3-7144-7739AF7C65B2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CAC8522E-31A5-A6A3-7144-7739AF7C65B2}.Release|Any CPU.Build.0 = Release|Any CPU - {D81F4ED4-9117-7D8E-4115-9F6E683A730E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D81F4ED4-9117-7D8E-4115-9F6E683A730E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D81F4ED4-9117-7D8E-4115-9F6E683A730E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D81F4ED4-9117-7D8E-4115-9F6E683A730E}.Release|Any CPU.Build.0 = Release|Any CPU - {391881E9-D4AC-F6EE-638A-CA62F4E9FC21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {391881E9-D4AC-F6EE-638A-CA62F4E9FC21}.Debug|Any CPU.Build.0 = Debug|Any CPU - {391881E9-D4AC-F6EE-638A-CA62F4E9FC21}.Release|Any CPU.ActiveCfg = Release|Any CPU - {391881E9-D4AC-F6EE-638A-CA62F4E9FC21}.Release|Any CPU.Build.0 = Release|Any CPU - {E2A62945-4A01-C97C-7228-FC7503F232A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2A62945-4A01-C97C-7228-FC7503F232A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2A62945-4A01-C97C-7228-FC7503F232A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2A62945-4A01-C97C-7228-FC7503F232A9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D8098DBA-E1D4-4005-A062-541FAC08C5E6} = {272C3FEB-29B5-4619-8CAC-9A750E55604A} - {175E26B3-95F8-44C5-AE18-F7F310A3D04B} = {610D8708-2CB6-403A-B865-3C1FE0115519} - {958CAA75-B8D6-4356-B4AA-B75658C17374} = {6197ECD9-AA02-4EF1-AAD0-D84D428A7352} - {2EA016FD-E9EA-4231-89F1-63737868EAB2} = {6197ECD9-AA02-4EF1-AAD0-D84D428A7352} - {F899211C-2E6E-4DF8-839A-11D5CB9B04D3} = {96FCE737-A321-445B-8EC1-05D8863461EA} - {96FCE737-A321-445B-8EC1-05D8863461EA} = {F9621075-6836-47F5-9160-F9C4743F6147} - {F776F33F-E44B-4FF5-BFB6-43C98667FD66} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {6FAA2520-3F94-4BD7-A67E-87A9961E094E} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {0A171BD6-3200-4FE8-9C19-B44E3B84529F} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {8D998D31-07BA-4185-B273-03EC3C68DA35} = {6FAA2520-3F94-4BD7-A67E-87A9961E094E} - {944004BF-81CA-4953-B482-AAE275CE601D} = {6FAA2520-3F94-4BD7-A67E-87A9961E094E} - {52F64196-9FA5-4B50-AE71-CF6C4DF4B23E} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {4C199038-F002-45B6-9158-2FD27A7392DC} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {23D36989-1D57-40AE-970D-085B18283D44} = {71B86B2B-03B6-4637-BC64-5E09BAF88192} - {F35C5D81-7632-46A2-82C0-CAC9AC65AF30} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {4FEF2F15-D4DC-416A-8640-66152F42CDFE} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {1B1DD442-8EB2-C9C1-E7CC-119CF6769E04} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {8BF84A00-E90B-4223-9D67-9C03FE1D38E7} = {610D8708-2CB6-403A-B865-3C1FE0115519} - {5882EE9F-DC0D-46C6-8273-5E80455F424C} = {610D8708-2CB6-403A-B865-3C1FE0115519} - {D410E914-539E-4438-AC48-D215A61DEC4C} = {610D8708-2CB6-403A-B865-3C1FE0115519} - {502C4C67-C940-438B-A51C-42639264ECA2} = {F9621075-6836-47F5-9160-F9C4743F6147} - {0C688E96-1998-65CC-E835-21C528EF82BD} = {502C4C67-C940-438B-A51C-42639264ECA2} - {7FED77E6-89F7-05C9-66B8-1613E88E06CA} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {B3423371-52FC-6E22-1FA5-4E1F33BFCC71} = {6FAA2520-3F94-4BD7-A67E-87A9961E094E} - {D3B28250-9D8E-3DFF-EF7D-D7F6D190DA6B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {47AE5AB4-5FB9-B745-61F5-1D28305A03E3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {E698DF44-9C3F-CD27-1096-595B10C0F615} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {77A751E6-628C-4964-8532-4BD505EB2D3B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {27A12FF2-C9DD-4124-B036-0169B2FED086} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {5F02040C-392D-5B04-C80F-71C728693A09} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {324F7B8C-4EB8-4284-B5E1-C28298EA6BC0} = {21727B12-1186-4C5E-9CA0-C7A9CED092A8} - {C612C2A5-3237-42D1-911F-2A9197580793} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {0FB2C6FE-D291-453A-B6DE-76F97A7E1B99} = {21727B12-1186-4C5E-9CA0-C7A9CED092A8} - {DC68BF20-F35E-02C7-E7AE-EB276D3B8CD8} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {FF81CA73-8518-415E-53F2-DBE53F4AF7CF} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - {CAC8522E-31A5-A6A3-7144-7739AF7C65B2} = {6197ECD9-AA02-4EF1-AAD0-D84D428A7352} - {D81F4ED4-9117-7D8E-4115-9F6E683A730E} = {5069D241-2B8E-4472-8A3C-DF706F895BB1} - {391881E9-D4AC-F6EE-638A-CA62F4E9FC21} = {5069D241-2B8E-4472-8A3C-DF706F895BB1} - {E2A62945-4A01-C97C-7228-FC7503F232A9} = {BDD5208F-0CF4-4C6A-B443-6527514C89E8} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {968372C4-E303-4F1F-A924-7568CCDE3E7C} - EndGlobalSection -EndGlobal diff --git a/src/Sa.slnx b/src/Sa.slnx new file mode 100644 index 0000000..4b6c4d6 --- /dev/null +++ b/src/Sa.slnx @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tests/Sa.HybridFileStorage.PostgresTests/FileIdParserTests.cs b/src/Tests/Sa.HybridFileStorage.PostgresTests/FileIdParserTests.cs index 48f2240..0bfda1c 100644 --- a/src/Tests/Sa.HybridFileStorage.PostgresTests/FileIdParserTests.cs +++ b/src/Tests/Sa.HybridFileStorage.PostgresTests/FileIdParserTests.cs @@ -7,8 +7,8 @@ public class FileIdParserTests [Fact] public void ParseFromFileId_ValidFileId_ReturnsTenantIdAndTimestamp() { - string fileId = "pg://123/2023/10/05/12/foo/some.txt"; - var (tenantId, timestamp) = FileIdParser.ParseFromFileId(fileId); + string fileId = "pg://files/123/2023/10/05/12/foo/some.txt"; + var (tenantId, timestamp) = FileIdParser.ParseFromFileId(fileId, "files"); Assert.Equal(123, tenantId); Assert.Equal(new DateTimeOffset(2023, 10, 5, 12, 0, 0, TimeSpan.Zero).ToUnixTimeSeconds(), timestamp); } @@ -17,7 +17,7 @@ public void ParseFromFileId_ValidFileId_ReturnsTenantIdAndTimestamp() public void ParseFromFileId_InvalidFileId_ThrowsFormatException() { string fileId = "invalid_file_id"; - Assert.Throws(() => FileIdParser.ParseFromFileId(fileId)); + Assert.Throws(() => FileIdParser.ParseFromFileId(fileId, "files")); } [Fact] @@ -27,8 +27,8 @@ public void FormatToFileId_ValidParameters_ReturnsFormattedFileId() int tenantId = 123; DateTimeOffset date = new(2023, 10, 5, 12, 0, 0, TimeSpan.Zero); string fileName = "example.txt"; - string result = FileIdParser.FormatToFileId(storageType, tenantId, date, fileName); - Assert.Equal("pg://123/2023/10/05/12/example.txt", result); + string result = FileIdParser.FormatToFileId(storageType, "files", tenantId, date, fileName); + Assert.Equal("pg://files/123/2023/10/05/12/example.txt", result); } [Fact] diff --git a/src/Tests/Sa.HybridFileStorage.PostgresTests/PostgresFileStorageTests.cs b/src/Tests/Sa.HybridFileStorage.PostgresTests/PostgresFileStorageTests.cs index 9ac38bd..e1859f1 100644 --- a/src/Tests/Sa.HybridFileStorage.PostgresTests/PostgresFileStorageTests.cs +++ b/src/Tests/Sa.HybridFileStorage.PostgresTests/PostgresFileStorageTests.cs @@ -1,4 +1,4 @@ -using Sa.HybridFileStorage.Domain; +using Sa.HybridFileStorage.Domain; using System.Text; namespace Sa.HybridFileStorage.PostgresTests; @@ -31,6 +31,7 @@ public async Task UploadFileAsync() // Assert Assert.NotNull(result); Assert.NotEmpty(result.FileId); + Assert.StartsWith("pg://files/1/", result.FileId); object? v = await fixture.DataSource.ExecuteScalar("SELECT COUNT(*) FROM public.files WHERE id = @id", cmd => cmd.Parameters.Add(new("id", result.FileId)), fixture.CancellationToken); var count = (long)v!;