Skip to content

Commit 253ec37

Browse files
committed
init
1 parent b429ed4 commit 253ec37

17 files changed

+641
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace DistributedFileStorage.Abstractions
8+
{
9+
public static class DfsExtensions
10+
{
11+
public static Task<string> Add<TMetadata>(this IDistributedFileStorage<TMetadata> dfs,
12+
Stream stream, string name, TMetadata? metadata, CancellationToken cancellationToken = default)
13+
{
14+
return dfs.Add(stream.GetEnumerator(), name, metadata, cancellationToken);
15+
}
16+
17+
public static Task<string> Add<TMetadata>(this IDistributedFileStorage<TMetadata> dfs,
18+
IAsyncEnumerable<byte[]> content, string name, TMetadata? metadata, CancellationToken cancellationToken = default)
19+
{
20+
return dfs.Add(content.GetAsyncEnumerator(cancellationToken), name, metadata, cancellationToken);
21+
}
22+
23+
public static async IAsyncEnumerator<byte[]> GetEnumerator(this Stream stream, CancellationToken cancellationToken = default)
24+
{
25+
var buffer = new byte[4096];
26+
int readCount;
27+
28+
while ((readCount = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
29+
yield return readCount < buffer.Length ? buffer.Take(readCount).ToArray() : buffer;
30+
}
31+
}
32+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace DistributedFileStorage.Abstractions
6+
{
7+
public class DfsFileInfo<TMetadata>
8+
{
9+
public DfsFileInfo(string id, string name, long length, TMetadata? metadata = default)
10+
{
11+
Id = id;
12+
Name = name;
13+
Length = length;
14+
Metadata = metadata;
15+
}
16+
17+
public string Id { get; }
18+
public string Name { get; }
19+
public long Length { get; }
20+
public TMetadata? Metadata { get; }
21+
22+
public override int GetHashCode() => Id.GetHashCode();
23+
public override bool Equals(object obj) => Id == (obj as DfsFileInfo<TMetadata>)?.Id;
24+
}
25+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<SignAssembly>True</SignAssembly>
8+
<AssemblyOriginatorKeyFile>..\DistributedFileStorage.snk</AssemblyOriginatorKeyFile>
9+
<AssemblyVersion>1.0.0</AssemblyVersion>
10+
<FileVersion>1.0.0</FileVersion>
11+
<Version>1.0.0-rc1</Version>
12+
13+
<Company></Company>
14+
<Authors>Leonid Salavatov</Authors>
15+
<Copyright>Leonid Salavatov 2021</Copyright>
16+
<PackageId>DistributedFileStorage.Abstractions</PackageId>
17+
<Product>DistributedFileStorage.Abstractions</Product>
18+
<Title>DistributedFileStorage.Abstractions</Title>
19+
<Description></Description>
20+
<PackageTags>distributed filestorage dotnet</PackageTags>
21+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
22+
<PackageProjectUrl>https://github.com/mustaddon/DistributedFileStorage</PackageProjectUrl>
23+
<RepositoryUrl>https://github.com/mustaddon/DistributedFileStorage</RepositoryUrl>
24+
<RepositoryType>git</RepositoryType>
25+
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
26+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
27+
<NeutralLanguage />
28+
<PackageReleaseNotes></PackageReleaseNotes>
29+
</PropertyGroup>
30+
31+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace DistributedFileStorage.Abstractions
6+
{
7+
public interface IDistributedFileStorage<TMetadata>
8+
{
9+
Task<string> Add(IAsyncEnumerator<byte[]> content, string name, TMetadata? metadata, CancellationToken cancellationToken = default);
10+
IAsyncEnumerable<byte[]> GetContent(string id, CancellationToken cancellationToken = default);
11+
Task<DfsFileInfo<TMetadata>> GetInfo(string id, CancellationToken cancellationToken = default);
12+
Task UpdateInfo(string id, string name, TMetadata? metadata, CancellationToken cancellationToken = default);
13+
Task Delete(string id, CancellationToken cancellationToken = default);
14+
}
15+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Newtonsoft.Json;
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace DistributedFileStorage.EntityFrameworkCore
8+
{
9+
public class DfsDatabase<TMetadata> : IDfsDatabase<TMetadata>
10+
{
11+
public DfsDatabase(DfsDbContext context, DfsDbSettings? settings = null)
12+
{
13+
_context = context;
14+
_settings = settings ?? new();
15+
}
16+
17+
readonly DfsDbContext _context;
18+
readonly DfsDbSettings _settings;
19+
20+
public Task Add(DfsDbItem<TMetadata> item, CancellationToken cancellationToken = default)
21+
{
22+
var fileInfo = new DfsDbFileInfo
23+
{
24+
Id = item.Id,
25+
Name = item.Name,
26+
Metadata = SerializeMetadata(item.Metadata),
27+
ContentId = item.Hash,
28+
};
29+
30+
var contentInfo = new DfsDbContentInfo
31+
{
32+
Id = item.Hash,
33+
Length = item.Length,
34+
Path = item.Path,
35+
};
36+
37+
return WithRetry(1, async (i) =>
38+
{
39+
_context.Entry(contentInfo).State =
40+
!await _context.DfsContentInfo.AnyAsync(x => x.Id == item.Hash, cancellationToken)
41+
? EntityState.Added : EntityState.Detached;
42+
43+
_context.Entry(fileInfo).State = EntityState.Added;
44+
45+
await _context.SaveChangesAsync(cancellationToken);
46+
});
47+
}
48+
49+
public async Task<DfsDbItem<TMetadata>> Get(string id, CancellationToken cancellationToken = default)
50+
{
51+
return Map(await _context.DfsFileInfo.AsNoTracking()
52+
.Include(x => x.Content)
53+
.SingleAsync(x => x.Id == id, cancellationToken));
54+
}
55+
56+
57+
public async Task<string?> GetPath(string hash, CancellationToken cancellationToken = default)
58+
{
59+
return (await _context.DfsContentInfo.AsNoTracking()
60+
.SingleOrDefaultAsync(x => x.Id == hash, cancellationToken))?.Path;
61+
}
62+
63+
public async Task Update(string id, string name, TMetadata? metadata, CancellationToken cancellationToken = default)
64+
{
65+
var entity = await _context.DfsFileInfo.SingleAsync(x => x.Id == id, cancellationToken);
66+
entity.Name = name;
67+
entity.Metadata = SerializeMetadata(metadata);
68+
await _context.SaveChangesAsync(cancellationToken);
69+
}
70+
71+
public async Task<DfsDbItem<TMetadata>> Delete(string id, CancellationToken cancellationToken = default)
72+
{
73+
var entity = await _context.DfsFileInfo.Include(x => x.Content).SingleAsync(x => x.Id == id, cancellationToken);
74+
75+
_context.Remove(entity);
76+
await _context.SaveChangesAsync(cancellationToken);
77+
78+
if (!await _context.DfsFileInfo.AnyAsync(x => x.ContentId == entity.ContentId, cancellationToken))
79+
{
80+
_context.Remove(entity.Content);
81+
await _context.SaveChangesAsync(cancellationToken);
82+
}
83+
84+
return Map(entity);
85+
}
86+
87+
private DfsDbItem<TMetadata> Map(DfsDbFileInfo entity)
88+
{
89+
return new()
90+
{
91+
Id = entity.Id,
92+
Name = entity.Name,
93+
Hash = entity.ContentId,
94+
Length = entity.Content?.Length ?? 0,
95+
Path = entity.Content?.Path ?? string.Empty,
96+
Metadata = DeserializeMetadata(entity.Metadata),
97+
};
98+
}
99+
100+
private async Task WithRetry(int retries, Func<int, Task> task)
101+
{
102+
for (var i = 0; i <= retries; i++)
103+
try
104+
{
105+
await task(i); return;
106+
}
107+
catch
108+
{
109+
if (i == retries) throw;
110+
}
111+
}
112+
113+
private string? SerializeMetadata(TMetadata? metadata)
114+
{
115+
if (metadata == null || metadata is string)
116+
return metadata as string;
117+
118+
return JsonConvert.SerializeObject(metadata, _settings.JsonSerializer);
119+
}
120+
121+
private TMetadata? DeserializeMetadata(string? metadata)
122+
{
123+
if (string.IsNullOrEmpty(metadata))
124+
return default;
125+
126+
if (typeof(TMetadata) == typeof(string))
127+
return (TMetadata?)(metadata as object);
128+
129+
try
130+
{
131+
return JsonConvert.DeserializeObject<TMetadata?>(metadata, _settings.JsonSerializer);
132+
}
133+
catch
134+
{
135+
return default;
136+
}
137+
}
138+
}
139+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace DistributedFileStorage.EntityFrameworkCore
4+
{
5+
public delegate void DfsDbContextConfigurator(DbContextOptionsBuilder optionsBuilder);
6+
7+
public class DfsDbContext : DbContext
8+
{
9+
public DfsDbContext(DfsDbContextConfigurator configurator)
10+
{
11+
_configurator = configurator;
12+
13+
DfsFileInfo = Set<DfsDbFileInfo>();
14+
DfsContentInfo = Set<DfsDbContentInfo>();
15+
}
16+
17+
private readonly DfsDbContextConfigurator _configurator;
18+
19+
internal DbSet<DfsDbFileInfo> DfsFileInfo { get; private set; }
20+
internal DbSet<DfsDbContentInfo> DfsContentInfo { get; private set; }
21+
22+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => _configurator(optionsBuilder);
23+
}
24+
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace DistributedFileStorage.EntityFrameworkCore
2+
{
3+
internal class DfsDbFileInfo
4+
{
5+
public string Id { get; set; } = string.Empty;
6+
public string Name { get; set; } = string.Empty;
7+
public string? Metadata { get; set; }
8+
public string ContentId { get; set; } = string.Empty;
9+
public DfsDbContentInfo? Content { get; set; }
10+
11+
public override int GetHashCode() => Id.GetHashCode();
12+
public override bool Equals(object obj) => Id == (obj as DfsDbFileInfo)?.Id;
13+
}
14+
15+
internal class DfsDbContentInfo
16+
{
17+
public string Id { get; set; } = string.Empty;
18+
public string Path { get; set; } = string.Empty;
19+
public long Length { get; set; }
20+
21+
public override int GetHashCode() => Id.GetHashCode();
22+
public override bool Equals(object obj) => Id == (obj as DfsDbContentInfo)?.Id;
23+
}
24+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Newtonsoft.Json;
2+
3+
namespace DistributedFileStorage.EntityFrameworkCore
4+
{
5+
public class DfsDbSettings
6+
{
7+
public JsonSerializerSettings JsonSerializer { get; set; } = new()
8+
{
9+
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
10+
TypeNameHandling = TypeNameHandling.Auto,
11+
};
12+
}
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace DistributedFileStorage.EntityFrameworkCore
2+
{
3+
public class DfsEfcOptions
4+
{
5+
public DfsSettings FileStorage { get; } = new();
6+
public DfsDbSettings Database { get; } = new();
7+
public DfsDbContextConfigurator DbContextConfigurator { get; set; } = static x => { };
8+
}
9+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<SignAssembly>True</SignAssembly>
8+
<AssemblyOriginatorKeyFile>..\DistributedFileStorage.snk</AssemblyOriginatorKeyFile>
9+
<AssemblyVersion>1.0.0</AssemblyVersion>
10+
<FileVersion>1.0.0</FileVersion>
11+
<Version>1.0.0-rc1</Version>
12+
13+
<Company></Company>
14+
<Authors>Leonid Salavatov</Authors>
15+
<Copyright>Leonid Salavatov 2021</Copyright>
16+
<PackageId>DistributedFileStorage.EntityFrameworkCore</PackageId>
17+
<Product>DistributedFileStorage.EntityFrameworkCore</Product>
18+
<Title>DistributedFileStorage.EntityFrameworkCore</Title>
19+
<Description></Description>
20+
<PackageTags>distributed filestorage entityframework dotnet efcore ef</PackageTags>
21+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
22+
<PackageProjectUrl>https://github.com/mustaddon/DistributedFileStorage</PackageProjectUrl>
23+
<RepositoryUrl>https://github.com/mustaddon/DistributedFileStorage</RepositoryUrl>
24+
<RepositoryType>git</RepositoryType>
25+
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
26+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
27+
<NeutralLanguage />
28+
<PackageReleaseNotes></PackageReleaseNotes>
29+
</PropertyGroup>
30+
31+
<ItemGroup>
32+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.10" />
33+
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
34+
</ItemGroup>
35+
36+
<ItemGroup>
37+
<ProjectReference Include="..\DistributedFileStorage\DistributedFileStorage.csproj" />
38+
</ItemGroup>
39+
40+
</Project>

0 commit comments

Comments
 (0)