| title | Services Reference |
|---|---|
| nav_order | 8 |
All classes live in BatchConvertToCHD/Services/. Namespaces are BatchConvertToCHD.Services unless noted.
internal static class AppHttpClient (AppHttpClient.cs:13)
Thread-safe singleton HttpClient used by BugReportService, StatsService, and UpdateService.
internal static HttpClient Client— double-checked locking; builds aSocketsHttpHandlerwith:- TLS 1.2 + TLS 1.3 only (
EnabledSslProtocols), PooledConnectionLifetime = 10 minutes,- default header
Accept: application/json.
- TLS 1.2 + TLS 1.3 only (
ServerCertificateValidationCallback(:50):- clean chain → accept;
- name mismatch (
RemoteCertificateNameMismatch) → warn ("may be caused by a proxy or firewall intercepting the connection") and accept; - any other error (expired, revoked, chain) → warn and reject.
internal static void Dispose()— disposes client+handler; the nextClientaccess rebuilds them. Called fromApp_Exit.
internal class ArchiveService : IDisposable (ArchiveService.cs:23)
Decompresses archives and compressed images for the conversion pipeline.
ArchiveService(string sevenZipExePath, bool isSevenZipAvailable) — the app passes Path.Combine(baseDirectory, AppConfig.SevenZipExeName) plus whether the file exists.
| Member | Purpose |
|---|---|
ExtractCsoAsync(originalCsoPath, tempOutputIsoPath, tempDirectoryRoot, onLog, token) |
Decompresses a .cso/.ciso to an ISO via CSOSharp (CsoFile.Open → ExtractToIso). Returns (Success, FilePath, TempDir, ErrorMessage). |
ExtractArchiveAsync(originalArchivePath, tempDirectoryRoot, onLog, token) |
Dispatches by extension: .zip → ExtractZipWith7ZaFallbackAsync; .7z → ExtractSevenZipArchiveAsync; .rar → ExtractRarArchive. Returns (Success, List<string> FilePaths, TempDir, ErrorMessage). |
ExtractArchiveWithFallback<TArchive>(...) (static) |
Shared SharpCompress extraction with temp-copy fallback; TArchive : IArchive, IDisposable. |
IsMultiPartRarError(Exception) (static) |
True for SharpCompress multi-part RAR messages. |
IsNetworkUnavailableError(Exception) (static) |
True for network-unavailable messages (disconnected drive). |
- Pre-extraction disk check (
CheckTempDiskSpace): estimates the uncompressed size (sum of ZIP entry lengths; otherwise the archive file size) and requiresestimated + max(est/10, 100 MB)free, else extraction is refused with a clear message. - Zip-slip protection: every entry destination must be under the normalized output directory, otherwise
SecurityException("Attempted to extract file outside of the target directory."). - 7za.exe fallback: zip/7z failures (except cancellation) fall back to
7za x "<archive>" -o"<output>" -ywhen the exe is available; RAR has no fallback. 7za exit code 2 or "Is not archive"/"Cannot open" output →InvalidDataException"archive is invalid or corrupt". - Retries: ZIP open/entry writes retry 3 times on
IOException/UnauthorizedAccessExceptionwithattempt * 1000 mssleeps; SharpCompress temp-copy fallback covers locked source files. - Error categorization (converted to user-facing messages): unsupported ZIP compression method (Deflate64/LZMA/PPMd — re-compress advice), corrupt/incomplete archive, encrypted archive (
CryptographicException), missing multi-part RAR volume, disk full (HResult-2147024784/-2147024783), locked file, network unavailable. - Post-extraction scan: collects files matching
PrimaryTargetExtensionsSet(.cue/.iso/.img/.gdi/.toc/.raw/.ccd/.mds/.isz); if none and.binfiles exist, a(Track N)set becomes a multi-FILE cue (TrackBinCueBuilder), otherwise a MODE2/2352 auto-cue is generated for the largest bin (BinCueGenerator); if nothing supported → "No supported primary files found in archive." - Companion filtering:
InputFileFilter.RemoveCompanionDataFilesAsyncdrops raw images that a descriptor in the archive already covers, so a cue/bin or CloneCD set inside an archive converts once through its descriptor rather than once per file with both attempts aimed at the same output name. .ecmis not a primary target, so an archive containing only.ecmfiles still reports nothing supported. Loose.ecmfiles convert normally.- Cancellation: observed throughout;
OperationCanceledExceptionis rethrown.
internal class BugReportApiSink : ILogEventSink (BugReportApiSink.cs:13)
Serilog sink that forwards Warning and above log events to BugReportService.SendBugReportAsync.
- Ignores
LogEventLevel.Informationand below. - Drops messages matching
BugReportService.IsExcludedFromBugReport. - Flood control: a static interlocked flag allows only one in-flight send; the flag is released in a
ContinueWith(ExecuteSynchronously)continuation. Fire-and-forget.
internal class BugReportService (BugReportService.cs:14)
Client for the PureLogicCode BugReport API. Full details and the exclusion list in Bug Reporting System.
SendBugReportAsync(message, ex?, token)— POSTs{ message (formatted report), applicationName, version, userInfo (Environment.UserName), environment, stackTrace }with headerX-API-KEY; returns whether the server accepted it. Excluded messages returnfalsewithout any HTTP call; send failures are logged at Debug and swallowed.IsExcludedFromBugReport(message)— case-insensitive substring match against 38 known-noise patterns.BuildFormattedReport— builds the=== Environment Details ===/=== Error Details ===/=== Exception Details ===sections (inner-exception chain, max depth 5).
internal sealed class FileWatcherService : IDisposable (FileWatcherService.cs:12)
Explains why a file disappeared mid-batch.
StartWatching(folderPath)—FileSystemWatcherwithIncludeSubdirectories=true,NotifyFilter = FileName | DirectoryName, 64 KB internal buffer; handles Deleted/Renamed/Created/Error; tolerates bad paths and disconnected drives.StopWatching()/Dispose().GetContextForMissingFile(filePath)— returns a human-readable diagnostic: not watched, folder inaccessible, deleted at time, renamed from/to, created-then-gone, or never observed. History is capped at 1,000 events (FIFO); buffer overflow clears history to avoid stale diagnostics.- Supporting types:
FileEventRecord { Timestamp, EventType, RelatedName },FileWatchEventType { Deleted, RenamedFrom, RenamedTo, Created }.
The watcher is started when the user picks the conversion input folder and is consumed in ProcessSingleFileForConversionAsync when a selected file is gone.
internal static class LegacyCleanupService (LegacyCleanupService.cs:10)
Runs once at startup on a background thread and deletes leftovers from older versions next to the executable:
- Folders:
logs,Resources,Screenshot - Files:
maxcso.exe,psxpackager.exe
All failures are silently ignored (files may be in use).
internal class ScreenshotService (ScreenshotService.cs:16)
Captures the foreground window via GDI (GetForegroundWindow → GetWindowRect → BitBlt) and saves a PNG:
- Location:
%LocalAppData%\BatchConvertToCHD\screenshots(created on demand). - Filename:
screenshot_yyyy-MM-dd_HH-mm-ss-fff.png. TakeScreenshot()returns the saved path, ornull(no foreground window / zero-size / failure).- Triggered by the global F8 hotkey (see User Guide).
internal class StatsService (StatsService.cs:8)
Records anonymous usage statistics once per launch.
RecordUsageAsync()— POSTs{ applicationId, version }withAuthorization: Bearer {apiKey}tohttps://www.purelogiccode.com/ApplicationStats/stats.- HTTP 429 →
Logger.Debug("Usage statistics rate-limited (HTTP 429) - this is expected behavior")— treated as expected, no retry, no bug report. - Other non-success statuses →
Logger.Information(below the bug-report threshold). - Network errors →
Logger.Debugwith the exception, silently swallowed.
internal class UpdateService (UpdateService.cs:13)
Checks GitHub for new releases at startup.
CheckForNewVersionAsync(onLog, onStatusUpdate, onBugReport)— wrapper; core overload takes(HttpClient, Version? currentVersion, ...)for testing.- Flow:
- GET the release URL for each configured source in order (
AppConfig.GitHubApiLatestReleaseUrls: primaryhttps://api.github.com/repos/purelogiccode/BatchConvertToCHD/releases/latest, then fallbackhttps://api.github.com/repos/drpetersonfernandes/BatchConvertToCHD/releases/latest) with a User-Agent. 404/5xx/network failures move to the next source; rate limits (403/429) skip the check entirely because they are per-IP and shared by both URLs. - 403/429 → "GitHub API rate limit exceeded. Skipping update check." — no bug report.
- 5xx → "Update check skipped: GitHub server error." — no bug report.
- Deserialize
GitHubRelease(tag_name,html_url,name,body,prerelease,draft). - Skip draft/prerelease/empty tags.
- Compare versions (
TryNormalizeVersions— 4-part versions with-1parts normalized to 0;ParseVersionFromTagstrips prefixes likev/release/versionand leading non-digits). - Newer version → Dispatcher message box ("A new version ... Would you like to go to the download page?"); on Yes opens
html_url. If the browser fails, the URL is copied to the clipboard (with its own bug-report path on failure) and a "Browser Launch Failed" dialog shows the URL. - Network/SSL errors → logged, no bug report. HTTP errors with a status code and generic exceptions → logged and reported via
onBugReport.
- GET the release URL for each configured source in order (
TryNormalizeVersions/ParseVersionFromTagareinternal staticand heavily unit-tested.