Skip to content

Commit

Permalink
Merge pull request #6309 from bitfoundation/develop
Browse files Browse the repository at this point in the history
Version 8.4.0 (#6307)
  • Loading branch information
msynk authored Dec 14, 2023
2 parents b6e8f4c + 4f584c9 commit b12a5a0
Show file tree
Hide file tree
Showing 82 changed files with 1,685 additions and 343 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/nuget.org.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ jobs:
- name: dotnet pack Butil
run: dotnet pack src/Butil/Bit.Butil/Bit.Butil.csproj --output . --configuration Release

- name: dotnet build Besql
run: dotnet build src/Besql/Bit.Besql/Bit.Besql.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false
- name: dotnet pack Besql
run: dotnet pack src/Besql/Bit.Besql/Bit.Besql.csproj --output . --configuration Release

- name: dotnet build CodeAnalyzers
run: dotnet build src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false

- name: dotnet pack CodeAnalyzer
run: dotnet pack src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj --output . --configuration Release

Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/prerelease.nuget.org.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,16 @@ jobs:
- name: dotnet pack Butil
run: dotnet pack src/Butil/Bit.Butil/Bit.Butil.csproj --output . --configuration Release

- name: dotnet build Besql
run: dotnet build src/Besql/Bit.Besql/Bit.Besql.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false
- name: dotnet pack Besql
run: dotnet pack src/Besql/Bit.Besql/Bit.Besql.csproj --output . --configuration Release

- name: dotnet build CodeAnalyzers
run: dotnet build src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false

- name: dotnet pack CodeAnalyzer
run: dotnet pack src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj --output . --configuration Release

- name: build SourceGenerators nupkg file
run: dotnet build src/SourceGenerators/Bit.SourceGenerators/Bit.SourceGenerators.csproj --configuration Release

Expand Down
25 changes: 25 additions & 0 deletions src/Besql/Bit.Besql.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34321.82
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bit.Besql", "Bit.Besql\Bit.Besql.csproj", "{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A4474E62-EBBF-4C5C-B256-EDA41E47EDC3}
EndGlobalSection
EndGlobal
131 changes: 131 additions & 0 deletions src/Besql/Bit.Besql/BesqlDbContextFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;

namespace Bit.Besql;

public class BesqlDbContextFactory<TContext> : DbContextFactory<TContext>
where TContext : DbContext
{
private static readonly IDictionary<Type, string> FileNames = new Dictionary<Type, string>();

private readonly IBesqlStorage cache;
private Task<int>? startupTask = null;
private int lastStatus = -2;

public BesqlDbContextFactory(
IBesqlStorage cache,
IServiceProvider serviceProvider,
DbContextOptions<TContext> options,
IDbContextFactorySource<TContext> factorySource)
: base(serviceProvider, options, factorySource)
{
this.cache = cache;
startupTask = RestoreAsync();
}

private static string Filename => FileNames[typeof(TContext)];

private static string BackupFile => $"{BesqlDbContextFactory<TContext>.Filename}_bak";

public static void Reset() => FileNames.Clear();

public static string? GetFilenameForType() =>
FileNames.ContainsKey(typeof(TContext)) ? FileNames[typeof(TContext)] : null;

public override async Task<TContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
{
await CheckForStartupTaskAsync();

var ctx = await base.CreateDbContextAsync(cancellationToken);

ctx.SavedChanges += SyncDbToCacheAsync;

return ctx;
}

private async Task DoSwap(string source, string target)
{
await using var src = new SqliteConnection($"Data Source={source}");
await using var tgt = new SqliteConnection($"Data Source={target}");

await src.OpenAsync();
await tgt.OpenAsync();

src.BackupDatabase(tgt);

await tgt.CloseAsync();
await src.CloseAsync();
}

private async Task<string> GetFilename()
{
await using var ctx = await base.CreateDbContextAsync();
var filename = "filenotfound.db";
var type = ctx.GetType();
if (FileNames.TryGetValue(type, out var value))
{
return value;
}

var cs = ctx.Database.GetConnectionString();

if (cs != null)
{
var file = cs.Split(';').Select(s => s.Split('='))
.Select(split => new
{
key = split[0].ToLowerInvariant(),
value = split[1],
})
.Where(kv => kv.key.Contains("data source") ||
kv.key.Contains("datasource") ||
kv.key.Contains("filename"))
.Select(kv => kv.value)
.FirstOrDefault();
if (file != null)
{
filename = file;
}
}

FileNames.Add(type, filename);
return filename;
}

private async Task CheckForStartupTaskAsync()
{
if (startupTask != null)
{
lastStatus = await startupTask;
startupTask?.Dispose();
startupTask = null;
}
}

private async void SyncDbToCacheAsync(object sender, SavedChangesEventArgs e)
{
var ctx = (TContext)sender;
await ctx.Database.CloseConnectionAsync();
await CheckForStartupTaskAsync();
if (e.EntitiesSavedCount > 0)
{
// unique to avoid conflicts. Is deleted after caching.
var backupName = $"{BesqlDbContextFactory<TContext>.BackupFile}-{Guid.NewGuid().ToString().Split('-')[0]}";
await DoSwap(BesqlDbContextFactory<TContext>.Filename, backupName);
lastStatus = await cache.SyncDb(backupName);
}
}

private async Task<int> RestoreAsync()
{
var filename = $"{await GetFilename()}_bak";
lastStatus = await cache.SyncDb(filename);
if (lastStatus == 0)
{
await DoSwap(filename, FileNames[typeof(TContext)]);
}

return lastStatus;
}
}
32 changes: 32 additions & 0 deletions src/Besql/Bit.Besql/Bit.Besql.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<Import Project="../../Bit.Build.props" />

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>


<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>
29 changes: 29 additions & 0 deletions src/Besql/Bit.Besql/BrowserCacheBesqlStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.JSInterop;

namespace Bit.Besql;

public sealed class BrowserCacheBesqlStorage : IAsyncDisposable, IBesqlStorage
{
private readonly Lazy<Task<IJSObjectReference>> moduleTask;

public BrowserCacheBesqlStorage(IJSRuntime jsRuntime)
{
moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
"import", "./_content/Bit.Besql/browserCache.js").AsTask()!);
}

public async ValueTask DisposeAsync()
{
if (moduleTask.IsValueCreated)
{
var module = await moduleTask.Value;
await module.DisposeAsync();
}
}

public async Task<int> SyncDb(string filename)
{
var module = await moduleTask.Value;
return await module.InvokeAsync<int>("synchronizeDbWithCache", filename);
}
}
6 changes: 6 additions & 0 deletions src/Besql/Bit.Besql/IBesqlStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Bit.Besql;

public interface IBesqlStorage
{
Task<int> SyncDb(string filename);
}
43 changes: 43 additions & 0 deletions src/Besql/Bit.Besql/IServiceCollectionBesqlExtentions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Bit.Besql;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection;

public static class IServiceCollectionBesqlExtentions
{
public static IServiceCollection AddBesqlDbContextFactory<TContext>(
this IServiceCollection services,
Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction)
where TContext : DbContext
{
if (OperatingSystem.IsBrowser())
{
services.TryAddScoped<IBesqlStorage, BrowserCacheBesqlStorage>();
services.AddDbContextFactory<TContext, BesqlDbContextFactory<TContext>>(
optionsAction ?? ((s, p) => { }), ServiceLifetime.Scoped);
}
else
{
services.AddDbContextFactory<TContext>(
optionsAction ?? ((s, p) => { }), ServiceLifetime.Scoped);
}

return services;
}

public static IServiceCollection AddBesqlDbContextFactory<TContext>(
this IServiceCollection services,
Action<DbContextOptionsBuilder>? optionsAction)
where TContext : DbContext
{
return services.AddBesqlDbContextFactory<TContext>((s, p) => optionsAction?.Invoke(p));
}

public static IServiceCollection AddBesqlDbContextFactory<TContext>(
this IServiceCollection services)
where TContext : DbContext
{
return services.AddBesqlDbContextFactory<TContext>(options => { });
}
}
63 changes: 63 additions & 0 deletions src/Besql/Bit.Besql/wwwroot/browserCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export async function synchronizeDbWithCache(file) {

window.sqlitedb = window.sqlitedb || {
init: false,
cache: await caches.open('Bit-Besql')
};

const db = window.sqlitedb;

const backupPath = `/${file}`;
const cachePath = `/data/cache/${file.substring(0, file.indexOf('_bak'))}`;

if (!db.init) {

db.init = true;

const resp = await db.cache.match(cachePath);

if (resp && resp.ok) {

const res = await resp.arrayBuffer();

if (res) {
console.log(`Restoring ${res.byteLength} bytes.`);
window.Module.FS.writeFile(backupPath, new Uint8Array(res));
return 0;
}
}
return -1;
}

if (window.Module.FS.analyzePath(backupPath).exists) {

const waitFlush = new Promise((done, _) => {
setTimeout(done, 10);
});

await waitFlush;

const data = window.Module.FS.readFile(backupPath);

const blob = new Blob([data], {
type: 'application/octet-stream',
ok: true,
status: 200
});

const headers = new Headers({
'content-length': blob.size
});

const response = new Response(blob, {
headers
});

await db.cache.put(cachePath, response);

window.Module.FS.unlink(backupPath);

return 1;
}
return -1;
}
17 changes: 17 additions & 0 deletions src/Besql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## bit entity framework core sqlite (bit Besql)

How to use `Bit.Besql`:

The usage of `Bit.Besql` is exactly the same as the regular usage of `Microsoft.EntityFrameworkCore.Sqlite` with [IDbContextFactory](https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-ef-core?view=aspnetcore-8.0#new-dbcontext-instances).

To get start, simply install `Bit.Besql` and use `services.AddBesqlDbContextFactory` instead of `services.AddDbContextFactory`.

Note: Don't use `IDbContextFactory` in `OnInitialized` because it relies on `IJSRuntime`. Use `OnAfterRender` instead.

In order to download sqlite db file from browser cache storage in blazor WebAssembly run the followings in browser console:
```js
const cache = await caches.open('Bit-Besql');
const resp = await cache.match('/data/cache/Boilerplate-ClientDb.db');
const blob = await resp.blob();
URL.createObjectURL(blob);
```
Loading

0 comments on commit b12a5a0

Please sign in to comment.