Migrate to .NET 10 with NativeAOT - #219
Merged
Merged
Conversation
- Bass.Net and the BASS native libraries were unused since the jukebox service was deleted in 2013; all audio work goes through external ffmpeg - The WaveBoxFSEvents Xcode project and dylib existed to work around Mono's kevent-based FileSystemWatcher exhausting file descriptors on macOS; modern .NET's FileSystemWatcher is FSEvents-backed natively - The OWIN/SignalR/MvvmCross/System.Windows assemblies were an abandoned experiment with zero code references - ImageMagick interop and its x86-only native builds are replaced by ImageSharp in a later commit - Drop the stale Mono mkbundle packaging scripts, nested solutions, and the System.Configuration.Install-based Windows service installer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- Replace the VS2010-era csproj files with SDK-style projects targeting net10.0; WaveBox.Server uses the Web SDK with NativeAOT publishing enabled for win/osx/linux on x64 and arm64 - All checked-in assemblies in lib/ are replaced by NuGet packages: TagLibSharp, Mono.Nat 3.x, SQLitePCLRaw.lib.e_sqlite3, ImageSharp, Microsoft.Extensions.* - Delete the Mono-only <dllmap> config files; native library resolution now comes from per-RID NuGet runtime assets - Delete AssemblyInfo files (the SDK generates assembly metadata, and a BuildDate assembly attribute replaces the old PE-header timestamp hack) - Regenerate the solution in the new slnx format Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- Injection becomes a static locator over IServiceProvider; the 300+ Injection.Kernel.Get<T>() call sites become Injection.Get<T>() (the mechanical rewrite rides along with each file's other changes) - CoreModule/ServerModule become AddWaveBoxCore()/AddWaveBoxServer() IServiceCollection extensions with the same explicit singletons - The platform-conditional web clients (curl-shelling LinuxWebClient and WebClient-based TimedWebClient, both Mono-era workarounds) collapse into one HttpClient-backed implementation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- Rename DllImport("sqlite3") to "e_sqlite3" so the native library ships
per-RID from SQLitePCLRaw.lib.e_sqlite3 (the Mono dllmap is gone)
- Rewrite DatabaseBackup against the vendored SQLiteConnection's public
Handle, deleting the System.Data.SQLite dependency and its
private-field reflection into the connection internals; check the
sqlite3_backup_step/finish results
- Replace the Newtonsoft query-log serialization with a hand-rolled
Utf8JsonWriter scalar-array writer producing identical output
- Add trim-warning suppressions justified by ModelTypeRegistry, which
preserves all ORM-mapped types' reflection metadata under AOT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- WaveBoxService (ServiceBase dual console/service mode, Kernel32 SetConsoleCtrlHandler, Mono.Posix signal thread) is replaced by WaveBoxLifecycleService, an IHostedService; Windows service and systemd support come from the hosting packages - Static server metadata moves to ServerInfo; build date now comes from an assembly attribute instead of reading the PE header (which breaks under deterministic builds) - DetectOS drops its uname P/Invoke for OperatingSystem.IsX(); RootPath uses UserProfile because SpecialFolder.Personal means ~/Documents on modern .NET, which would have abandoned existing installs' config - DelayedOperationQueue's Thread.Abort (PlatformNotSupportedException on modern .NET) becomes cooperative CancellationTokenSource shutdown - NatService ports to Mono.Nat 3.x's async API; ZeroConfService becomes a stub (Mono.Zeroconf died with Mono) that still accepts the conf name - Status CPU metric samples Process.TotalProcessorTime instead of the Windows-only PerformanceCounter Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- The TcpListener-based HttpService and vendored HttpProcessor parser are deleted; Program.cs hosts a WebApplication with a single terminal handler covering both /api dispatch and the web UI - ApiDispatcher reproduces the legacy dispatch flow exactly: raw undecoded parameter parsing, POST body appended to the query string, session-cookie auth with sliding refresh, and HTTP 200 + error JSON for all failure modes - HttpContextProcessor implements the legacy IHttpProcessor contract over HttpContext, so all 26 API handlers run unchanged, including the transcode file-tailing stream path - Response compression middleware replaces the hand-rolled gzip; wire deltas vs the old server: correct Content-Length (no more +3-byte UTF-8 BOM), HTTP keep-alive, spec-compliant range handling - ServiceFactory and ApiHandlerFactory use explicit registration lists instead of Assembly.GetTypes() scans (required for NativeAOT); the listen port is pre-read from wavebox.conf because Kestrel's options are constructed before the database-dependent settings load Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
…-gen - [JsonProperty] becomes [JsonPropertyName] across all response DTOs and models; every serialized type is registered in the source-generated WaveBoxJsonContext (required for NativeAOT), including the runtime types placed in object-valued status/stats dictionaries - IItem/IMediaItem get [JsonPolymorphic]/[JsonDerivedType] (without a discriminator) so interface-typed members keep serializing the runtime type's full shape, matching Newtonsoft's behavior - ServerSettings drops its hand-rolled JSON comment stripper: STJ reads wavebox.conf's comments and trailing commas natively, so the file format is unchanged; the settings API's partial-update write-back and the Last.fm response handling move from AOT-fatal `dynamic` to JsonNode Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- Art resizing moves to ImageSharp (pure managed, AOT-safe): Lanczos3 aspect-fit resize plus optional Gaussian blur, always JPEG output, replacing the System.Drawing/ImageMagick dual path - Transcoders drain ffmpeg's stderr asynchronously (pipe-buffer deadlock risk), kill the entire process tree on cancel, and use current ffmpeg flags: -b:a/-q:a/-c:a, the built-in aac encoder instead of the removed libfaac, and aresample=async=1 instead of the deprecated -async; the transcode service probes for ffmpeg at startup with per-OS install hints - One cross-platform FileManager (FSEvents-backed on macOS via the built-in watcher): watchers are strongly referenced, use the maximum buffer size, recover from overflow via the Error event with a rescan, and renames also rescan the old parent directory Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- A ~90-line shim (WaveBox.Core.Logging.ILog/LogManager) keeps every logger call site's shape while writing through the host's logging pipeline; log4net 3.x itself was not AOT-safe (XML-configured reflection-instantiated appenders), and MethodBase.GetCurrentMethod() logger naming is unreliable under NativeAOT, so categories come from [CallerFilePath] or an explicit type - The remaining files in this commit carry the mechanical sweeps: logger declaration rewrites, Injection.Kernel.Get<T>() -> Injection.Get<T>(), and removal of dead Ninject/Newtonsoft/log4net usings Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
- ModelTypeRegistry roots every ORM-mapped model type so the vendored sqlite-net's reflection-based mapping keeps full metadata under trimming/NativeAOT; new mapped types must be added there - Enum.GetValues(Type) becomes the AOT-friendly generic overload - Scripts/smoke-test.sh runs the server against an isolated data directory and verifies login, status, a media scan of a generated MP3 fixture (tests/fixtures/make_fixture.py, no encoder needed), TagLib metadata parsing, and a 206 range request - GitHub Actions builds NativeAOT binaries for win/osx/linux x64+arm64 (AOT cannot cross-compile between OSes, hence the four runner types) and smoke-tests the three natively-executable ones Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrates WaveBox from .NET Framework 4.5 / Mono to .NET 10 with NativeAOT publishing for all six platform targets (win-x64/arm64, osx-x64/arm64, linux-x64/arm64). The published binary is fully self-contained — no .NET runtime install needed (~27 MB on osx-arm64, ~48 MB RSS vs ~110 MB on CoreCLR).
Major changes, in commit order
FileSystemWatcheris FSEvents-backed on macOS), OWIN/SignalR/MvvmCross DLLs, ImageMagick interopnet10.0, NuGet packages replacing all checked-in DLLse_sqlite3), database backup rewritten againstsqlite3_backup_*ServiceBase/Mono.Posix/Thread.Abort→ .NET Generic Host (Windows service + systemd support built in)-b:a, built-inaac), file watching consolidated onto hardenedFileSystemWatcherModelTypeRegistryroots ORM-mapped types (zero IL warnings), end-to-end smoke test with a generated MP3 fixture, GitHub Actions matrix building all six RIDsIntentional wire deltas vs. the old server
+3Content-Length was the BOM)Connection: close; gzip via middlewareKnown follow-ups
wavebox_log.txt) not yet reimplemented — console onlyaacTest plan
Scripts/smoke-test.shagainst the osx-arm64 AOT binary — login/session auth, status, albums, media scan + TagLib parse of generated MP3, 206 range request all pass🤖 Generated with Claude Code
https://claude.ai/code/session_01RjfMF1zrb6Z7KzexmTNjXT