Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8aaea7e
fix(ingestion): prevent directory deletion and preserve cover images …
sphildreth May 25, 2026
4d2b5a0
feat(ingestion): add detailed scan metrics and bypass negative cache …
sphildreth May 25, 2026
a33d38c
docs: update changelog with scan metrics and ingestion fixes
sphildreth May 25, 2026
a7cbe68
feat(artist): treat iTunes ID as trusted artist identity
sphildreth May 25, 2026
f52d705
feat(scanning): remove source sidecar metadata after staging
sphildreth May 25, 2026
00d6ecb
fix(scanning): track converted files during staging to prevent leftov…
sphildreth May 25, 2026
4c8f9e7
feat(scanning): add performance reporting, compound artist fallbacks,…
sphildreth May 25, 2026
e239909
fix(scanning): skip compound artist fallback during forced revalidation
sphildreth May 25, 2026
a192c31
feat(scanning): add revalidation deferral and live scan progress
sphildreth May 25, 2026
a92b1b1
feat(scanning): report scan warnings and suppress ATL stack traces
sphildreth May 25, 2026
8810b32
feat(scanning): add source stability checks, residue cleanup, and dee…
sphildreth May 25, 2026
c41143e
fix(scanning): bound external provider result limits and fix iTunes d…
sphildreth May 25, 2026
d6d7557
fix(scanning): harden NFO parsing and optimize MusicBrainz search
sphildreth May 25, 2026
2e884ef
feat(search): parallelize image search engines
sphildreth May 28, 2026
d9be3c3
chore(release): bump version to 2.1.2 and update dependencies
sphildreth Jun 9, 2026
b498fc3
feat(search): introduce ArtistAlias model and expand DecentDB import/…
sphildreth Jun 9, 2026
38b78d8
chore(provider): validate DecentDB 2.10.0, update query probes, and d…
sphildreth Jun 10, 2026
77f78e9
chore(provider): validate DecentDB 2.11.0, update query probes, and d…
sphildreth Jun 12, 2026
268f538
chore(provider): validate DecentDB 2.12.0, update query probes, and d…
sphildreth Jun 13, 2026
8a69ff3
chore(provider): validate DecentDB 2.13.1, complete DDB-002/003, and …
sphildreth Jun 15, 2026
2f90f46
chore(provider): validate DecentDB 2.14.0 and implement warm-up service
sphildreth Jun 15, 2026
fdb1ba6
chore(provider): remove unsupported aliases-by-artist query from Dece…
sphildreth Jun 15, 2026
c29bc3f
chore(release): bump version to 2.1.3 across components and update ch…
sphildreth Jun 15, 2026
00b55f4
refactor(metadata): replace IdSharpMetaTag with NativeId3MetaTag and …
sphildreth Jun 15, 2026
257fa49
Add DecentDB migration guide and enhance health checks
sphildreth Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/docker/Dockerfile.container-scan
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
FROM mcr.microsoft.com/dotnet/aspnet:10.0.4
ARG DOTNET_ASPNET_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0
FROM ${DOTNET_ASPNET_IMAGE}

WORKDIR /app

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ jobs:
Jwt__Audience: "test"
security__secretKey: "testsecretkeytestsecretkeytestsecretkey"
QuartzDisabled: "true"
run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings coverage.runsettings --results-directory coverage/results
# Parallel solution-level VSTest with coverage can return a non-zero exit after successful test summaries.
run: dotnet test --no-build -m:1 --verbosity normal --collect:"XPlat Code Coverage" --settings coverage.runsettings --results-directory coverage/results
- name: Generate coverage report
run: |
dotnet tool install -g dotnet-reportgenerator-globaltool
Expand Down
51 changes: 41 additions & 10 deletions .github/workflows/sca-container-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ jobs:
permissions:
contents: read
security-events: write
env:
SCAN_BASE_IMAGE: mcr.microsoft.com/dotnet/aspnet:10.0

steps:
- name: Checkout repository
Expand All @@ -58,18 +60,47 @@ jobs:
- name: Publish application for scanning
run: dotnet publish src/Melodee.Blazor/Melodee.Blazor.csproj -c Release -o scan/publish --self-contained false -p:PublishTrimmed=false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Pull Docker base image for scanning
run: |
set -euo pipefail

for attempt in 1 2 3 4 5; do
if docker pull "$SCAN_BASE_IMAGE"; then
exit 0
fi

delay=$((attempt * 20))
echo "Base image pull failed on attempt ${attempt}; retrying in ${delay}s..."
sleep "$delay"
done

docker pull "$SCAN_BASE_IMAGE"

- name: Build Docker image for scanning
uses: docker/build-push-action@v5
with:
context: .
file: .github/docker/Dockerfile.container-scan
load: true
tags: melodee:latest
cache-from: type=gha
cache-to: type=gha,mode=max
run: |
set -euo pipefail

for attempt in 1 2 3; do
if docker build \
--pull=false \
--build-arg DOTNET_ASPNET_IMAGE="$SCAN_BASE_IMAGE" \
-f .github/docker/Dockerfile.container-scan \
-t melodee:latest \
.; then
exit 0
fi

delay=$((attempt * 20))
echo "Docker image build failed on attempt ${attempt}; retrying in ${delay}s..."
sleep "$delay"
done

docker build \
--pull=false \
--build-arg DOTNET_ASPNET_IMAGE="$SCAN_BASE_IMAGE" \
-f .github/docker/Dockerfile.container-scan \
-t melodee:latest \
.

- name: Run Trivy Vulnerability Scanner
uses: aquasecurity/trivy-action@master
Expand Down
4 changes: 0 additions & 4 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
<Project>
<PropertyGroup>
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
<!-- Suppress NU1902 for SixLabors.ImageSharp (v3.1.10) which is a transitive dependency of IdSharp.Tagging.
Melodee no longer directly references or uses ImageSharp; all image processing is done via SkiaSharp.
This warning cannot be resolved without upgrading or replacing IdSharp.Tagging. -->
<NoWarn>$(NoWarn);NU1902</NoWarn>
</PropertyGroup>
</Project>
99 changes: 49 additions & 50 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<PackageVersion Include="Asp.Versioning.Http" Version="10.0.0" />
<PackageVersion Include="Asp.Versioning.Mvc" Version="10.0.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.0.0" />
<PackageVersion Include="AuralizeBlazor" Version="3.0.4" />
<PackageVersion Include="BCrypt.Net-Next" Version="4.2.0" />
<PackageVersion Include="Blazored.SessionStorage" Version="2.4.0" />
<PackageVersion Include="bunit" Version="2.7.2" />
Expand All @@ -18,106 +17,106 @@
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
<PackageVersion Include="CsvHelper" Version="33.1.0" />
<PackageVersion Include="Dapper" Version="2.1.79" />
<PackageVersion Include="DecentDB.EntityFrameworkCore" Version="2.7.0" />
<PackageVersion Include="DecentDB.EntityFrameworkCore.NodaTime" Version="2.7.0" />
<PackageVersion Include="DecentDB.AdoNet" Version="2.13.1" />
<PackageVersion Include="DecentDB.EntityFrameworkCore" Version="2.13.1" />
<PackageVersion Include="DecentDB.EntityFrameworkCore.NodaTime" Version="2.13.1" />
<PackageVersion Include="Fastenshtein" Version="1.0.12" />
<PackageVersion Include="FFMpegCore" Version="5.4.0" />
<PackageVersion Include="FluentAssertions" Version="8.10.0" />
<PackageVersion Include="FluentValidation" Version="12.1.1" />
<PackageVersion Include="Google.Apis.Auth" Version="1.74.0" />
<PackageVersion Include="Google.Apis.Auth" Version="1.75.0" />
<PackageVersion Include="Hashids.net" Version="1.7.0" />
<PackageVersion Include="Hqub.Last.fm" Version="2.5.1" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="HtmlSanitizer" Version="9.1.923-beta" />
<PackageVersion Include="IdGen" Version="3.0.7" />
<PackageVersion Include="IdSharp.Common" Version="2.0.3" />
<PackageVersion Include="IdSharp.Tagging" Version="2.0.3" />
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.4" />
<PackageVersion Include="Jint" Version="4.9.2" />
<PackageVersion Include="Jint" Version="4.10.0" />
<PackageVersion Include="K4os.Hash.xxHash" Version="1.0.8" />
<PackageVersion Include="LiteDB" Version="5.0.21" />
<PackageVersion Include="Mapster" Version="10.0.7" />
<PackageVersion Include="Markdig" Version="1.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Http" Version="2.3.10" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.10" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
<PackageVersion Include="Mapster" Version="10.0.8" />
<PackageVersion Include="Markdig" Version="1.3.0" />
<PackageVersion Include="MessagePack" Version="3.1.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.11" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.18.0" />
<PackageVersion Include="Microsoft.Net.Http.Headers" Version="10.0.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageVersion Include="MailKit" Version="4.16.0" />
<PackageVersion Include="MimeKit" Version="4.16.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
<PackageVersion Include="Microsoft.Net.Http.Headers" Version="10.0.9" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageVersion Include="MailKit" Version="4.17.0" />
<PackageVersion Include="MimeKit" Version="4.17.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="NBomber" Version="6.4.1" />
<PackageVersion Include="NodaTime" Version="3.3.2" />
<PackageVersion Include="NodaTime.Testing" Version="3.3.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="10.0.1" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="10.0.2" />
<PackageVersion Include="PSC.Blazor.Components.MarkdownEditor" Version="10.0.9" />
<PackageVersion Include="Polly" Version="8.6.6" />
<PackageVersion Include="Polly.Core" Version="8.6.6" />
<PackageVersion Include="Polly" Version="8.7.0" />
<PackageVersion Include="Polly.Core" Version="8.7.0" />
<PackageVersion Include="Quartz.AspNetCore" Version="3.18.1" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
<PackageVersion Include="Quartz.Jobs" Version="3.18.1" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.18.1" />
<PackageVersion Include="Quartz.Serialization.SystemTextJson" Version="3.18.1" />
<PackageVersion Include="Radzen.Blazor" Version="10.4.4" />
<PackageVersion Include="Radzen.Blazor" Version="10.4.9" />
<PackageVersion Include="Rebus" Version="8.9.2" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.7.2" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.14.14" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.16.3" />
<PackageVersion Include="Serilog" Version="4.3.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageVersion Include="Serilog.Settings.AppSettings" Version="3.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="10.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="10.0.1" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="SerilogTimings" Version="3.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="SkiaSharp" Version="3.119.2" />
<PackageVersion Include="SkiaSharp" Version="3.119.4" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.2" />
<PackageVersion Include="SmartFormat" Version="3.6.1" />
<PackageVersion Include="Spectre.Console" Version="0.55.2" />
<PackageVersion Include="Spectre.Console" Version="0.57.0" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.55.0" />
<PackageVersion Include="Spectre.Console.Json" Version="0.55.2" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.55.2" />
<PackageVersion Include="Spectre.Console.Json" Version="0.57.0" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.57.0" />
<PackageVersion Include="SpotifyAPI.Web" Version="7.4.2" />
<PackageVersion Include="Sqids" Version="3.2.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
<PackageVersion Include="System.IO.Hashing" Version="10.0.8" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
<PackageVersion Include="System.IO.Hashing" Version="10.0.9" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.2" />
<PackageVersion Include="System.Reactive" Version="6.1.0" />
<PackageVersion Include="System.Resources.Extensions" Version="10.0.7" />
<PackageVersion Include="System.ServiceModel.Syndication" Version="10.0.8" />
<PackageVersion Include="System.ServiceModel.Syndication" Version="10.0.9" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="z440.atl.core" Version="7.13.0" />
<PackageVersion Include="z440.atl.core" Version="7.15.3" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
</ItemGroup>
</Project>
</Project>
4 changes: 4 additions & 0 deletions benchmarks/Melodee.Benchmarks/Melodee.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="DecentDB.AdoNet" />
<PackageReference Include="NBomber" />
<PackageReference Include="MessagePack">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
Expand Down
18 changes: 6 additions & 12 deletions benchmarks/Melodee.Benchmarks/MusicBrainzImportBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,15 @@ public async Task<int> ImportWithDefaultSettings()
await context.Database.EnsureCreatedAsync();

var importer = new DecentDBStreamingMusicBrainzImporter(_logger);
await importer.ImportAsync(context, _testDataPath, null, CancellationToken.None);

var count = await context.Artists.CountAsync();
var summary = await importer.ImportAsync(context, _testDataPath, null, CancellationToken.None);

try
{
File.Delete(dbFile);
}
catch { }

return count;
return summary.Artists;
}

[Benchmark]
Expand All @@ -107,17 +105,15 @@ public async Task<int> ImportWithLargerDataset()
await context.Database.EnsureCreatedAsync();

var importer = new DecentDBStreamingMusicBrainzImporter(_logger);
await importer.ImportAsync(context, _testDataPath, null, CancellationToken.None);

var count = await context.Artists.CountAsync();
var summary = await importer.ImportAsync(context, _testDataPath, null, CancellationToken.None);

try
{
File.Delete(dbFile);
}
catch { }

return count;
return summary.Artists;
}

[Benchmark]
Expand All @@ -133,18 +129,16 @@ public async Task<int> ImportWithProgressTracking()
await context.Database.EnsureCreatedAsync();

var importer = new DecentDBStreamingMusicBrainzImporter(_logger);
await importer.ImportAsync(context, _testDataPath,
var summary = await importer.ImportAsync(context, _testDataPath,
(phase, current, total, msg) => { }, CancellationToken.None);

var count = await context.Artists.CountAsync();

try
{
File.Delete(dbFile);
}
catch { }

return count;
return summary.Artists;
}
}

Expand Down
Loading
Loading