-
-
Notifications
You must be signed in to change notification settings - Fork 231
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6309 from bitfoundation/develop
Version 8.4.0 (#6307)
- Loading branch information
Showing
82 changed files
with
1,685 additions
and
343 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 => { }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
``` |
Oops, something went wrong.