Deep review of the solution, performed alongside the screenshot-fallback work and the coverage-driven unit-test expansion. All findings below have been addressed; each item keeps its original analysis and records how it was fixed. Items that could not be fixed at the source are marked with the reason instead.
Review scope: SimpleZipDrive/Core, SimpleZipDrive/Mounting, SimpleZipDrive/Views,
SimpleZipDrive/Services, SimpleZipDrive/App.axaml.cs, SimpleZipDrive/Program.cs,
SimpleZipDrive/FuseSharp, SimpleZipDrive/7zip.
Status key: [FIXED] implemented, [PARTIAL] partially implemented / documented limitation, [MITIGATED] root cause constrained, [NOT APPLICABLE] analysis was inaccurate.
-
[FIXED] The 7z fallback cannot work on x64 builds — the shipped native library is 32-bit. The SharpSevenZip package and the native
7z.dll/7z_arm64.dllwere removed entirely. The fallback now runs the bundled 7-Zip command-line executable (7-Zip 26.03):7za.exeon Windows (x64 and arm64), statically linked7zzson Linux (x64 and arm64), and the universal7zzon macOS. No native library is loaded into the process, so the architecture mismatch cannot occur; the csproj selects the binary byRuntimeIdentifierandSevenZipFallbacklocates it next to the app at runtime (setting the Unix execute bit if needed).TryExtractEntryruns7z x -sowith stdin closed (no password-prompt hangs),-spdfor literal names, and maps entries through7z l -sltso case, separator and backslash-stored names are handled. The 7-Zip license ships as7zip-license.txt. Note: the standalone7za.exesupports 7z/xz/lzma/cab/zip/gzip/bzip2/Z/tar but not RAR; Linux/macOS use the full7zz, which can also read RAR. -
[FIXED] Disk-cache extraction reads the shared archive stream without the archive lock.
Core/ZipFileSystemCore.cs— the call toExtractEntryToDiskinOpenDiskCachedStreamis now wrapped inlock (_archiveLock), matching the in-memory path, so concurrent extractions can no longer interleave seeks on the single shared source stream. -
[FIXED] The SevenZip fallback for disk-cached entries is dead code.
Core/ZipFileSystemCore.cs—ExtractEntryToDiskdeletes the temp file on failure, so the fallback now opens it withFileMode.Create(creates/truncates) instead ofFileMode.Truncate(which threwFileNotFoundException). The same change was applied toTryFallbackExtraction. -
[FIXED]
MountService.MountAsyncreplaces the active backend without unmounting it.Mounting/MountService.cs—MountAsyncis now async and, before attaching the new backend, awaits the previous backend'sUnmountAsync, unsubscribes from itsMountStatusChangedevent and disposes it. A synchronously failing new mount is detached viaDetachBackendso a failed backend is never left attached.
-
[FIXED] Dokan/WinFsp unmount "grace delay" never runs — the token is cancelled first.
Mounting/Dokan/DokanMountService.cs,Mounting/WinFsp/WinFspMountService.cs— the cancel-then-delay sequence was replaced with a plainawait Task.Delay(500)for the grace period, so the driver now actually gets time to drain pending callbacks before the core is disposed. -
[FIXED] FUSE mount leaks the archive stream and temp mount folder when the core constructor fails.
Mounting/Fuse/FuseMountService.cs— the mount points are registered before the core is created (soCleanupAfterUnmountcan remove the temp directory), and the opened file stream is disposed in the failure path becauseZipFileSystemCoredoes not take ownership of it when its constructor throws. -
[FIXED] FUSE unmount is a no-op while the session is starting, and cleanup can dispose the core while FUSE is still running.
Mounting/Fuse/FuseMountService.cs— an_unmountRequestedflag is set byUnmountAsync/Dispose;MountAsyncaborts cleanly if the flag is set before the thread starts, andOnMountedstops the session immediately if it is set.UnmountAsync/Disposeonly callCleanupAfterUnmountonce the session thread has actually exited; otherwise the thread's ownfinallyperforms the cleanup, so the core is never disposed under live FUSE callbacks. -
[FIXED]
DecompressEntryToBufferuses a fixed-sizeMemoryStreamdespite its comment.Core/ZipFileSystemCore.cs— uses a growableMemoryStream(capacity)and returns the internal buffer viaTryGetBufferwhen no growth occurred (no copy, same peak memory); oversized decompression now grows the stream instead of throwingNotSupportedException. -
[FIXED] A transient memory-cache failure permanently blacklists an entry.
Core/ZipFileSystemCore.cs— when in-memory decompression fails and the 7z fallback is unavailable, the entry is now routed to the disk cache instead of being blacklisted. The entry is only marked failed when both extraction paths fail. -
[FIXED] The per-mount temp directory is leaked when
ZipFileSystemCoreconstruction fails.Core/ZipFileSystemCore.cs— the constructor's catch block removes the just-created temp directory and clears its registration (newZipFsHelpers.ClearCurrentTempDirectory). -
[FIXED]
ListDirectoryuses the raw entry key, breaking backslash-separated names on Unix.Core/ZipFileSystemCore.cs— aGetFileNameOnlyhelper splits on both/and\on every platform. -
[FIXED]
StatsService.ReportStatsAsyncnever disposes theHttpResponseMessage.Core/Services/StatsService.cs— the response is now disposed withusing. -
[FIXED]
IsExtractionFailurebypasses the compression-library guard it documents.Core/ZipFileSystemCore.cs— only data-format exceptions (ZlibException,ZstdException,DataError) are unconditionally extraction failures; broad exception types (ArgumentException,NullReferenceException,InvalidOperationException, ...) count only whenIsCompressionLibraryExceptionconfirms they originated inside a compression library. -
[FIXED]
UserNotificationService's browser-failure handling is unreachable.Services/ShellHelper.csnow returnstrue/falsefromOpenUrl/OpenFolderinstead of swallowing the outcome, andCore/Services/UserNotificationService.csbranches on the returned value to show the "Could not open browser" fallback dialog and log accurately. -
[FIXED]
UpdateService's quiet timeout handling does not match reality.Core/Services/UpdateService.cs— aTaskCanceledException(which is howHttpClient.Timeoutexpiry surfaces) that is not caller cancellation is now logged quietly; explicit caller cancellation during shutdown is handled separately. -
[FIXED]
MainWindowscreenshot failure message always claimed a write-permission problem.Views/MainWindow.axaml.cs— the actualScreenshotResult.ErrorMessage/exception message is shown. -
[FIXED]
ScreenshotServicehad no writable-location fallback and returned a misleading result.Core/Services/ScreenshotService.cs— falls back to%LOCALAPPDATA%\SimpleZipDrive\Screenshot, returns the real exception message, returns a nullFilePathon failure and disposes the render bitmap when rendering throws.
-
[FIXED]
LogTextWriter.WriteLine()logged the literal stringSystem.Char[].Core/Logging/LogTextWriter.cs— usesnew string(CoreNewLine). -
[FIXED]
LogTextWriter.DisposethrewChannelClosedExceptionwhen called twice.Core/Logging/LogTextWriter.cs— guarded with a_disposedflag. -
[FIXED] Each bug-report POST leaks its
HttpRequestMessage/StringContent.Core/ErrorLogger.cs— both are now disposed withusing. -
[FIXED] The bug-report payload is never truncated despite the documented 4000-char API limit.
Core/ErrorLogger.cs—PostBugReportAsynctruncates the message field to 4000 characters (ending with...) before serializing. -
[FIXED]
ErrorLogger.ErrorLogFilePathis dead andWriteToCriticalLogreports a write it never performs.Core/ErrorLogger.cs—WriteToCriticalLognow appends the fatal entry toErrorLogFilePathbest-effort (and still writes to console), so the property is used and the message is accurate. -
[PARTIAL]
XisoArchive/ZarArchivereportArchiveType.Tar, andZarArchiveEntry.CompressedSizereports the uncompressed size.ZarArchiveEntry.CompressedSizenow returns 0 (unknown), which is the documented SharpCompress convention; ZArchiveSharp exposes no per-entry compressed size. TheArchiveType.Tarmapping cannot be fixed: SharpCompress'ArchiveTypeenum has no XISO or ZAR values, and nothing in the application branches onIArchive.Type(archives are identified by the extension-basedZipFileSystemCore.ArchiveTypestring). Both properties now carry XML remarks documenting this. -
[FIXED] A transient
SevenZipFallbackinitialization failure disables the fallback for the whole mount.Core/SevenZipFallback.cs— a failed initialization leaves the entry map unset and retries on the next call, up to three attempts, before giving up. -
[FIXED]
ErrorLoggerStatic.ReportSilentException'ssilentparameter documentation contradicts the implementation.Core/ErrorLoggerStatic.cs— the parameter is documented as retained for backwards compatibility with no behavioral effect. -
[FIXED] Stale
UpdateServiceXML doc about a "Core assembly";StatsServiceuses the entry assembly instead.Core/Services/UpdateService.cs— the doc now describes the containing assembly correctly.Core/Services/StatsService.csnow usestypeof(StatsService).Assembly, so the reported application id/version are the application's even under a test runner (matching UpdateService). -
[FIXED]
WinFspMountService.IsAvailablereports "not installed" for load failures.Mounting/WinFsp/WinFspMountService.cs— a genuine "not installed" is only reported when no install directory can be found; otherwise the reason explains that the installed native DLL could not be loaded (corrupted installation or architecture mismatch). -
[FIXED]
CurrentArchivePathis left stale after failed mounts.Mounting/WinFsp/WinFspMountService.cs,Mounting/Dokan/DokanMountService.cs— the early assignment inMountAsyncwas removed; both backends now setCurrentArchivePathonly when the mount succeeds. -
[FIXED] Unreachable
catch (OperationCanceledException)inWinFspMountService.UnmountAsync.Mounting/WinFsp/WinFspMountService.cs— the dead clause was removed. -
[FIXED]
DisposeleavesIsMounted/CurrentMountPointstale in Dokan and WinFsp. BothDisposeimplementations now resetIsMounted,CurrentMountPointandCurrentArchivePath, matching FUSE'sCleanupAfterUnmount. -
[FIXED] FUSE's archive-open retry blocks the UI thread.
Mounting/Fuse/FuseMountService.cs—OpenArchiveFileStreamAsyncawaitsTask.Delayinstead ofThread.Sleep, so the UI stays responsive. -
[FIXED] Dead catch around
ShellHelper.OpenFolderinMainWindow.UpdateMountStatus.Views/MainWindow.axaml.cs— the return value ofShellHelper.OpenFolderis checked and logged when it fails. -
[FIXED] Screenshot file names can collide within the same millisecond.
Core/Services/ScreenshotService.cs—GetUniqueFilePathappends a numeric suffix when a file with the same timestamp already exists.
-
[MITIGATED] Tests share the static
ServiceProvideracross parallel collections.LogTextWriternow accepts an optionalILoggingServiceand theLogTextWriterTestspass a recording service directly instead of registering it globally, eliminating the concrete cross-talk withZipFileSystemCore's staticLogMessagelookup.ZipFileSystemCore.LogMessageremains a staticServiceProviderlookup by design (it is called from static backend helpers); nothing in the suite depends on a globally registered logger any more. -
[FIXED] Several tests write to the developer's real settings file.
AppSettingsexposesSettingsFilePath(and aSIMPLEZIPDRIVE_SETTINGS_DIRenvironment override for the directory). The "Settings file" test collection now uses a newSettingsFileFixturethat redirectsAppSettings.SettingsFilePathto a per-run temporary file;Save_WritesValidJsonasserts against that redirected path. Only the file is redirected soZipFsHelpers.BaseTempPath(a static snapshot of the directory) keeps its expected shape. -
[FIXED] The 7z extraction tests are conditional by necessity. The per-RID 7-Zip CLI binary is copied into the test output, so the positive
SevenZipFallbackTestsassertions run for real on every supported platform/RID (Windows, Linux and macOS). The tests assert availability of the bundled executable and cover content extraction, backslash-separated names, wildcard characters in names ([/]), unknown entries and disposed instances. -
[FIXED] Leftover build-artifact directories and unignored test output.
SimpleZipDrive.Core/objandSimpleZipDrive_WinFsp/objwere deleted;TestResults/was added to.gitignore.
- [FIXED] Console redirection race.
DokanPrefixedLoggerTests.CustomPrefix_IsAppliedleft a disposedStringWriterasConsole.Out, which brokeXISOSharp's static logger and other concurrent tests (Cannot write to a closed TextWriter). The test now restores the console in afinally, andDokanPrefixedLoggerTests,XisoArchiveTestsandXisoEntryReaderTestsshare a serialized[Collection("Console redirection")]. - [FIXED]
WinFspDiagnosticLoggerTestsnegative assertions. The two "no[]/[null]" tests asserted over the whole shared diagnostic file, which concurrent tests append to through the static Serilog pipeline; they now locate their own operation line with a helper and assert on that.
SimpleZipDrive/7zip/ contains the 7-Zip 26.03 console binaries
(https://github.com/ip7z/7zip/releases/tag/26.03); the csproj ships exactly one per
RuntimeIdentifier and copies License.txt as 7zip-license.txt.
| File | SHA-256 |
|---|---|
win-x64/7za.exe |
edbee35370e14030e4c785cf88200f42dc651c1eb4217c1e3963c38a12f099b0 |
win-arm64/7za.exe |
c26764813a01b9714687f29c94412401f2041852634e291c59d48484432e834b |
linux-x64/7zzs |
eab4c8d7f193e3d6d3237370bbcaa879a160a3f1dc82202207e27baeab79b6ac |
linux-arm64/7zzs |
277907bc627633ec344757fe47699856cbb6e37f75cbc310d37d62cfacdd73b2 |
osx/7zz (universal) |
74b0910e50ea44d9760a57fada2192cfd530ba8bffbe7b47c412a464b796cabf |
License.txt (combined Windows + Linux/macOS texts) |
4b29a373c9eee142edf852c88d0f8e781ec8d3594611ef28f9b758058e7e74da |
To update: download the new release packages from the 7-Zip GitHub releases, replace the
five binaries, refresh License.txt and the hashes above, and re-run the test suite.
A second review of everything committed after acffb16f found and fixed additional issues:
- FUSE self-deadlock: unmount requested before the
initcallback returned calledStop()from the FUSE loop thread; the mount-point wake-up poke could only be served by that same blocked thread. The callback now uses a poke-freeRequestExit(). - FUSE lifecycle races: unmount during startup leaked the core/file stream;
Disposecould clean up concurrently with a starting mount; a timed-out unmount was reported as success;Stop()could freeze the UI for ~1 s. All four addressed inFuseMountService. - Resolver latch:
FuseInterop.RegisterResolverset its flag before registering, so a transient failure disabled resolution permanently. - Extraction fallback: any extraction exception now triggers the 7-Zip fallback (data
corruption can surface as a BCL exception); fallback results use the normalized cache key
(no repeated extraction per handle);
CreateSecureTempFilerefuses to run after dispose;ClearCurrentTempDirectoryonly clears its own registration. - Dialogs:
MessageBoxWindownow setsIsDefault/IsCancel; modal dialogs are serialized throughModalDialogHost, use a non-racy cancellation-token lifetime, and pick the active window as owner. - Shutdown:
UpdateServicere-checks cancellation before showing the update prompt;LogTextWriterno longer disposes its token source while the processing task is running. - Error reporting: WinFsp assembly-load failures and missing application paths remain suppressed; unrelated assembly/path failures are reported again.
- Screenshot: the unique-name loop no longer returns an existing
_999file. - Packaging:
winfsp-msil.dllexcluded from Linux/macOS publishes; combined7zip-license.txt; Unix 7-Zip binaries committed as100755; local packaging on Windows skips non-Windows RIDs instead of producing bundles without the executable bit.
A third pass over the commits after acffb16f found two regressions that the test suite could
not catch; both are fixed:
- Bundle corruption in the zip host-byte patch.
scripts/package-release.ps1scanned the whole archive byte stream for the central-directory signaturePK\x01\x02and overwrote the following "version made by" host byte. Compressed entry data can contain the same byte sequence, so a false positive silently corrupted the shipped executable or library. The patch now locates the end-of-central-directory record and walks only the real central-directory headers (verified by re-extracting a bundle that contains the signature inside a payload: byte-for-byte identical, all headers patched to Unix). - Backend disposal blocked the UI thread.
Mounting/MountService.csdisposed the previous backend directly on the UI thread after every unmount and failed mount; Dokan's and WinFsp'sDisposesleep 500 ms to let the driver drain callbacks, so the interface froze for that long. Disposal now runs on a background thread, and the facade publishes the new backend only after the previous one has been unmounted and released (so a concurrent unmount can never target the new, not-yet-mounted instance). - Bogus "libfuse3 not found" report from Windows (bug report #68046). A persisted
MountBackend.Fusevalue on Windows reached the startup warning before normalization was applied, and the message was not recognized as an environment condition, so the bug-report API received "libfuse3 not found: FUSE is only available on Linux and macOS." The startup check andMountService.ResolveBackendnow normalize the persisted backend for the current platform first, andErrorLogger.IsUserErrortreats missing/unsupported filesystem drivers (Dokan, WinFsp, libfuse3, macFUSE, "only available on ...") as environment conditions.
- The suite now contains 1454 tests (101 added in the screenshot/test batch) with 0 build warnings; it was run ten consecutive times green before the 7-Zip CLI change and is re-run after each subsequent change.
- The screenshot service already existed (registered at
App.axaml.cs:170, invoked on F8 atViews/MainWindow.axaml.cs:252-284); an earlier batch added the AppData fallback, the real error reporting and tests.