Skip to content

Commit 02eeaf7

Browse files
committed
Create solution, project and unit tests
#1
1 parent 291cfb5 commit 02eeaf7

27 files changed

+1913
-0
lines changed

Plugins.Azure.Blobs.sln

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.12.35728.132
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowSynx.Plugins.Azure.Blobs", "src\FlowSynx.Plugins.Azure.Blobs.csproj", "{9AFDDAD6-E3ED-4509-86AF-AD530CC12DA6}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{69A3AC28-65DC-4139-A58F-DFB84EBF5C25}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BCF525C0-EB68-48C4-BA63-EBBAEAA9FCDC}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowSynx.Plugins.Azure.Blobs.UnitTests", "tests\FlowSynx.Plugins.Azure.Blobs.UnitTests\FlowSynx.Plugins.Azure.Blobs.UnitTests.csproj", "{AEA52545-EC33-46EF-872B-D577C711DDB1}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Release|Any CPU = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{9AFDDAD6-E3ED-4509-86AF-AD530CC12DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{9AFDDAD6-E3ED-4509-86AF-AD530CC12DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{9AFDDAD6-E3ED-4509-86AF-AD530CC12DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{9AFDDAD6-E3ED-4509-86AF-AD530CC12DA6}.Release|Any CPU.Build.0 = Release|Any CPU
24+
{AEA52545-EC33-46EF-872B-D577C711DDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25+
{AEA52545-EC33-46EF-872B-D577C711DDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{AEA52545-EC33-46EF-872B-D577C711DDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{AEA52545-EC33-46EF-872B-D577C711DDB1}.Release|Any CPU.Build.0 = Release|Any CPU
28+
EndGlobalSection
29+
GlobalSection(SolutionProperties) = preSolution
30+
HideSolutionNode = FALSE
31+
EndGlobalSection
32+
GlobalSection(NestedProjects) = preSolution
33+
{9AFDDAD6-E3ED-4509-86AF-AD530CC12DA6} = {69A3AC28-65DC-4139-A58F-DFB84EBF5C25}
34+
{AEA52545-EC33-46EF-872B-D577C711DDB1} = {BCF525C0-EB68-48C4-BA63-EBBAEAA9FCDC}
35+
EndGlobalSection
36+
EndGlobal

src/AzureBlobPlugin.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using FlowSynx.PluginCore;
2+
using FlowSynx.Plugins.Azure.Blobs.Models;
3+
using FlowSynx.PluginCore.Extensions;
4+
using FlowSynx.Plugins.Azure.Blobs.Services;
5+
6+
namespace FlowSynx.Plugins.Azure.Blobs;
7+
8+
public class AzureBlobPlugin : IPlugin
9+
{
10+
private IAzureBlobManager _manager = null!;
11+
private AzureBlobSpecifications _azureBlobSpecifications = null!;
12+
13+
public PluginMetadata Metadata
14+
{
15+
get
16+
{
17+
return new PluginMetadata
18+
{
19+
Id = Guid.Parse("7f21ba04-ea2a-4c78-a2f9-051fa05391c8"),
20+
Name = "Azure.Blobs",
21+
Description = Resources.ConnectorDescription,
22+
Version = new PluginVersion(1, 0, 0),
23+
Namespace = PluginNamespace.Connectors,
24+
Author = "FlowSynx LLC."
25+
};
26+
}
27+
}
28+
29+
public PluginSpecifications? Specifications { get; set; }
30+
public Type SpecificationsType => typeof(AzureBlobSpecifications);
31+
32+
public Task Initialize(IPluginLogger logger)
33+
{
34+
ArgumentNullException.ThrowIfNull(logger);
35+
var connection = new AzureBlobConnection();
36+
_azureBlobSpecifications = Specifications.ToObject<AzureBlobSpecifications>();
37+
var client = connection.Connect(_azureBlobSpecifications);
38+
_manager = new AzureBlobManager(logger, client, _azureBlobSpecifications.ContainerName);
39+
return Task.CompletedTask;
40+
}
41+
42+
public async Task<object?> ExecuteAsync(PluginParameters parameters, CancellationToken cancellationToken)
43+
{
44+
var operationParameter = parameters.ToObject<OperationParameter>();
45+
var operation = operationParameter.Operation;
46+
47+
switch (operation.ToLower())
48+
{
49+
case "create":
50+
await _manager.Create(parameters, cancellationToken).ConfigureAwait(false);
51+
return null;
52+
case "delete":
53+
await _manager.Delete(parameters, cancellationToken).ConfigureAwait(false);
54+
return null;
55+
case "exist":
56+
return await _manager.Exist(parameters, cancellationToken).ConfigureAwait(false);
57+
case "list":
58+
return await _manager.List(parameters, cancellationToken).ConfigureAwait(false);
59+
case "purge":
60+
await _manager.Purge(parameters, cancellationToken).ConfigureAwait(false);
61+
return null;
62+
case "read":
63+
return await _manager.Read(parameters, cancellationToken).ConfigureAwait(false);
64+
case "write":
65+
await _manager.Write(parameters, cancellationToken).ConfigureAwait(false);
66+
return null;
67+
default:
68+
throw new NotSupportedException($"Microsoft Azure Blobs plugin: Operation '{operation}' is not supported.");
69+
}
70+
}
71+
}

src/Extensions/ByteExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace FlowSynx.Plugins.Azure.Blobs.Extensions;
2+
3+
public static class ByteExtensions
4+
{
5+
public static string ToHexString(this byte[]? bytes)
6+
{
7+
return bytes == null ? string.Empty : Convert.ToHexString(bytes);
8+
}
9+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Azure.Storage.Blobs;
2+
using Azure.Storage.Blobs.Models;
3+
using FlowSynx.PluginCore;
4+
using System.Text;
5+
6+
namespace FlowSynx.Plugins.Azure.Blobs.Extensions;
7+
8+
public static class ConverterExtensions
9+
{
10+
public static async Task<PluginContext> ToContext(this BlobClient blobClient, bool? includeMetadata,
11+
CancellationToken cancellationToken)
12+
{
13+
BlobDownloadInfo download = await blobClient.DownloadAsync(cancellationToken);
14+
15+
var ms = new MemoryStream();
16+
await download.Content.CopyToAsync(ms, cancellationToken);
17+
ms.Seek(0, SeekOrigin.Begin);
18+
19+
var dataBytes = ms.ToArray();
20+
var isBinaryFile = IsBinaryFile(dataBytes);
21+
var rawData = isBinaryFile ? dataBytes : null;
22+
var content = !isBinaryFile ? Encoding.UTF8.GetString(dataBytes) : null;
23+
24+
var context = new PluginContext(blobClient.Name, "File")
25+
{
26+
RawData = rawData,
27+
Content = content,
28+
};
29+
30+
if (includeMetadata is true)
31+
{
32+
var blobProperties = await blobClient.GetPropertiesAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
33+
AddProperties(context, blobProperties);
34+
}
35+
36+
return context;
37+
}
38+
39+
private static bool IsBinaryFile(byte[] data, int sampleSize = 1024)
40+
{
41+
if (data == null || data.Length == 0)
42+
return false;
43+
44+
int checkLength = Math.Min(sampleSize, data.Length);
45+
int nonPrintableCount = data.Take(checkLength)
46+
.Count(b => (b < 8 || (b > 13 && b < 32)) && b != 9 && b != 10 && b != 13);
47+
48+
double threshold = 0.1; // 10% threshold of non-printable characters
49+
return (double)nonPrintableCount / checkLength > threshold;
50+
}
51+
52+
private static void AddProperties(PluginContext context, BlobProperties properties)
53+
{
54+
context.Metadata.Add("AccessTier", properties.AccessTier);
55+
context.Metadata.Add("AccessTierChangedOn", properties.AccessTierChangedOn);
56+
context.Metadata.Add("AccessTierInferred", properties.AccessTierInferred);
57+
context.Metadata.Add("BlobSequenceNumber", properties.BlobSequenceNumber);
58+
context.Metadata.Add("BlobType", properties.BlobType);
59+
context.Metadata.Add("CacheControl", properties.CacheControl);
60+
context.Metadata.Add("ContentDisposition", properties.ContentDisposition);
61+
context.Metadata.Add("ContentEncoding", properties.ContentEncoding);
62+
context.Metadata.Add("ContentHash", properties.ContentHash.ToHexString());
63+
context.Metadata.Add("ContentLanguage", properties.ContentLanguage);
64+
context.Metadata.Add("ContentLength", properties.ContentLength);
65+
context.Metadata.Add("ContentType", properties.ContentType);
66+
context.Metadata.Add("CopyCompletedOn", properties.CopyCompletedOn);
67+
context.Metadata.Add("CopyId", properties.CopyId);
68+
context.Metadata.Add("CopyProgress", properties.CopyProgress);
69+
context.Metadata.Add("CopySource", properties.CopySource);
70+
context.Metadata.Add("CopyStatus", properties.CopyStatus);
71+
context.Metadata.Add("CopyStatusDescription", properties.CopyStatusDescription);
72+
context.Metadata.Add("CreatedOn", properties.CreatedOn);
73+
context.Metadata.Add("DestinationSnapshot", properties.DestinationSnapshot);
74+
context.Metadata.Add("ETag", properties.ETag);
75+
context.Metadata.Add("LastModified", properties.LastModified);
76+
context.Metadata.Add("LeaseDuration", properties.LeaseDuration);
77+
context.Metadata.Add("LeaseState", properties.LeaseState);
78+
context.Metadata.Add("LeaseStatus", properties.LeaseStatus);
79+
}
80+
}

src/Extensions/StringExtensions.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Text;
2+
3+
namespace FlowSynx.Plugins.Azure.Blobs.Extensions;
4+
5+
public static class StringExtensions
6+
{
7+
public static bool IsBase64String(this string value)
8+
{
9+
if (string.IsNullOrEmpty(value) || value.Length % 4 != 0
10+
|| value.Contains(' ') || value.Contains('\t')
11+
|| value.Contains('\r') || value.Contains('\n'))
12+
return false;
13+
14+
var index = value.Length - 1;
15+
16+
if (value[index] == '=')
17+
index--;
18+
19+
if (value[index] == '=')
20+
index--;
21+
22+
for (var i = 0; i <= index; i++)
23+
if (IsInvalid(value[i]))
24+
return false;
25+
26+
return true;
27+
}
28+
29+
private static bool IsInvalid(char value)
30+
{
31+
var intValue = (int)value;
32+
switch (intValue)
33+
{
34+
case >= 48 and <= 57:
35+
case >= 65 and <= 90:
36+
case >= 97 and <= 122:
37+
return false;
38+
default:
39+
return intValue != 43 && intValue != 47;
40+
}
41+
}
42+
43+
public static byte[] ToByteArray(this string value)
44+
{
45+
return Encoding.UTF8.GetBytes(value);
46+
}
47+
48+
public static Stream ToStream(this string value)
49+
{
50+
return value.ToStream(Encoding.UTF8);
51+
}
52+
53+
public static Stream ToStream(this string value, Encoding encoding)
54+
{
55+
return new MemoryStream(encoding.GetBytes(value ?? ""));
56+
}
57+
58+
public static Stream Base64ToStream(this string value)
59+
{
60+
var bytes = Convert.FromBase64String(value);
61+
return new MemoryStream(bytes);
62+
}
63+
64+
public static byte[] Base64ToByteArray(this string value)
65+
{
66+
return Convert.FromBase64String(value);
67+
}
68+
69+
public static string ToBase64String(this string value)
70+
{
71+
var bytes = Encoding.UTF8.GetBytes(value);
72+
return Convert.ToBase64String(bytes);
73+
}
74+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.0" />
11+
<PackageReference Include="FlowSynx.PluginCore" Version="1.2.0" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<Compile Update="Resources.Designer.cs">
16+
<DesignTime>True</DesignTime>
17+
<AutoGen>True</AutoGen>
18+
<DependentUpon>Resources.resx</DependentUpon>
19+
</Compile>
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<EmbeddedResource Update="Resources.resx">
24+
<Generator>ResXFileCodeGenerator</Generator>
25+
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
26+
</EmbeddedResource>
27+
</ItemGroup>
28+
29+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using FlowSynx.PluginCore;
2+
3+
namespace FlowSynx.Plugins.Azure.Blobs.Models;
4+
5+
public class AzureBlobSpecifications : PluginSpecifications
6+
{
7+
[RequiredMember]
8+
public string AccountName { get; set; } = string.Empty;
9+
10+
[RequiredMember]
11+
public string AccountKey { get; set; } = string.Empty;
12+
13+
[RequiredMember]
14+
public string ContainerName { get; set; } = string.Empty;
15+
}

src/Models/CreateParameters.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace FlowSynx.Plugins.Azure.Blobs.Models;
2+
3+
public class CreateParameters
4+
{
5+
public string Path { get; set; } = string.Empty;
6+
}

src/Models/DeleteParameters.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace FlowSynx.Plugins.Azure.Blobs.Models;
2+
3+
public class DeleteParameters
4+
{
5+
public string Path { get; set; } = string.Empty;
6+
}

src/Models/ExistParameters.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace FlowSynx.Plugins.Azure.Blobs.Models;
2+
3+
public class ExistParameters
4+
{
5+
public string Path { get; set; } = string.Empty;
6+
}

0 commit comments

Comments
 (0)