Skip to content

feat: Import SD card logging sessions into application#375

Merged
tylerkron merged 17 commits intomainfrom
feature/sd-card-import
Mar 16, 2026
Merged

feat: Import SD card logging sessions into application#375
tylerkron merged 17 commits intomainfrom
feature/sd-card-import

Conversation

@tylerkron
Copy link
Contributor

@tylerkron tylerkron commented Feb 6, 2026

User description

Summary

  • Add SdCardSessionImporter service that parses SD card .bin log files (via core library's SdCardFileParser) and bulk-inserts them into the desktop app's SQLite database as LoggingSession + DataSample entities
  • Support three import paths: local file (no device needed), stream, and USB device download
  • Add per-file "Import" button and "Import All" button in the Device Logs tab for USB-connected devices
  • Add "Import .bin File" button in the Application Logs tab for importing local files without a device
  • Add DownloadSdCardFileAsync and DeleteSdCardFileAsync to the desktop IStreamingDevice interface
  • Add conditional local core project reference support via DaqifiCoreProjectPath MSBuild property (needed until core fix: updated menu name with correct capitalizaiton #110/chore: Update build workflow to build MSI installer #111 are published to NuGet)

Test plan

  • Import a local .bin file via "Import .bin File" button → session appears in session list → viewable in plot → exportable to CSV
  • Connect USB device → Device Logs tab → click "Import" on a file → progress shown → session appears in Application Logs
  • "Import All" imports all listed files sequentially with progress
  • Import large file → progress reporting works, memory stays bounded
  • Cancel/error during import shows appropriate dialog, no partial session left
  • Build with local core: dotnet build -p:DaqifiCoreProjectPath="path/to/Daqifi.Core.csproj"
  • Build without local core still uses NuGet package (once core 0.15+ is published)

Prerequisites

Closes #374

🤖 Generated with Claude Code


PR Type

Enhancement


Description

  • Add SdCardSessionImporter service to parse and bulk-import SD card .bin log files into SQLite database

  • Implement three import paths: local file, stream, and USB device download with progress tracking

  • Add "Import .bin File" button in Application Logs tab for local file imports

  • Add per-file "Import" and "Import All" buttons in Device Logs tab for USB device imports

  • Extend IStreamingDevice interface with DownloadSdCardFileAsync and DeleteSdCardFileAsync methods

  • Support conditional local core project reference via DaqifiCoreProjectPath MSBuild property


Diagram Walkthrough

flowchart LR
  A["SD Card .bin File"] -->|ImportFromFileAsync| B["SdCardSessionImporter"]
  C["USB Device"] -->|ImportFromDeviceAsync| B
  D["Stream"] -->|ImportFromStreamAsync| B
  B -->|Parse| E["SdCardFileParser"]
  E -->|Map to DataSample| F["SQLite Database"]
  F -->|Create LoggingSession| G["Application Logs"]
  B -->|Bulk Insert| F
Loading

File Walkthrough

Relevant files
Enhancement
SdCardSessionImporter.cs
Core SD card import service with batch processing               

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs

  • New service class implementing three import entry points:
    ImportFromFileAsync, ImportFromStreamAsync, and ImportFromDeviceAsync
  • Maps core SdCardLogEntry objects to desktop DataSample entities with
    per-channel color assignment
  • Implements batch bulk-insert logic with configurable batch size (1000
    samples) for performance
  • Supports ImportOptions for session name override, overwrite behavior,
    and post-import device file deletion
  • Provides ImportProgress class for progress reporting during import
    operations
+284/-0 
IStreamingDevice.cs
Extend interface with SD card operations                                 

Daqifi.Desktop/Device/IStreamingDevice.cs

  • Add DownloadSdCardFileAsync method to download files from device SD
    card over USB with progress tracking
  • Add DeleteSdCardFileAsync method to delete files from device SD card
  • Both methods include cancellation token support
+14/-0   
AbstractStreamingDevice.cs
Implement SD card operations in device class                         

Daqifi.Desktop/Device/AbstractStreamingDevice.cs

  • Add using statement for Daqifi.Core.Device.SdCard namespace
  • Implement DownloadSdCardFileAsync method delegating to core device
  • Implement DeleteSdCardFileAsync method delegating to core device
  • Both methods use GetCoreDeviceForSd() helper for validation
+16/-0   
DaqifiViewModel.cs
Add local file import command to main view model                 

Daqifi.Desktop/ViewModels/DaqifiViewModel.cs

  • Add ImportSdCardLogFile relay command for importing local .bin files
    via file dialog
  • Show progress overlay with sample count during import operation
  • Handle import completion by adding session to LoggingManager and
    showing success dialog
  • Implement error handling with user-friendly error messages and
    cancellation support
+54/-0   
DeviceLogsViewModel.cs
Add device file import commands with batch support             

Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs

  • Add ImportFile relay command for importing individual files from
    connected USB device
  • Add ImportAllFiles relay command for batch importing all device files
    with per-file error handling
  • Both commands show progress overlay with file name and sample count
  • Implement success/failure dialogs and add imported sessions to
    LoggingManager
  • Add logger instance for error tracking
+123/-1 
MainWindow.xaml
Add import button to application logs UI                                 

Daqifi.Desktop/MainWindow.xaml

  • Add "Import .bin File" button in Application Logs tab header with
    import icon and label
  • Button positioned on left side of toolbar alongside existing
    export/delete buttons
  • Uses ImportSdCardLogFileCommand from view model
+9/-0     
DeviceLogsView.xaml
Add import buttons to device logs UI                                         

Daqifi.Desktop/View/DeviceLogsView.xaml

  • Add "Import All" button in Device Logs header with import icon,
    positioned right of refresh button
  • Add per-file "Import" button in file list grid view as new column
  • Adjust column widths to accommodate new import button column
  • Add grid column definition for new button column
  • Minor formatting cleanup of whitespace
+46/-17 
Configuration changes
Daqifi.Desktop.csproj
Support conditional local core project reference                 

Daqifi.Desktop/Daqifi.Desktop.csproj

  • Make Daqifi.Core NuGet package reference conditional on
    DaqifiCoreProjectPath being empty
  • Add conditional ProjectReference to local core project when
    DaqifiCoreProjectPath MSBuild property is set
  • Enables development workflow with local core library without modifying
    project file
+5/-1     

Add the ability to import SD card .bin log files into the desktop app's
SQLite database for viewing, analysis, and CSV export. This implements
the desktop side of #126, wiring up the core library's SdCardFileParser
and ISdCardOperations to the desktop data model.

New SdCardSessionImporter service with three entry points:
- ImportFromFileAsync: import a local .bin file (no device needed)
- ImportFromStreamAsync: import from a stream
- ImportFromDeviceAsync: download from USB device then import

Maps core SdCardLogEntry to desktop DataSample entities (one per analog/
digital channel per sample), creates LoggingSession in SQLite, and uses
batch bulk inserts for performance.

UI additions:
- "Import .bin File" button in APPLICATION LOGS tab for local file import
- Per-file "Import" button in DEVICE LOGS file list for USB device import
- "Import All" button in DEVICE LOGS header to batch import all files
- Progress overlay during download and import operations

Also adds DownloadSdCardFileAsync and DeleteSdCardFileAsync to the
desktop IStreamingDevice interface, and conditional local core project
reference support via DaqifiCoreProjectPath MSBuild property.

Closes #374

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Feb 6, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Arbitrary file deletion

Description: The code deletes downloadResult.FilePath on cleanup without validating that the path is a
trusted temp location, so if an attacker can influence SdCardDownloadResult.FilePath
(e.g., via device/core layer), it could enable arbitrary file deletion on the host.
SdCardSessionImporter.cs [66-92]

Referred Code
// Download to temp file
var downloadResult = await device.DownloadSdCardFileAsync(fileName, null, ct);

try
{
    // Parse and import
    var parser = new SdCardFileParser();
    var logSession = await parser.ParseFileAsync(downloadResult.FilePath!, null, ct);
    var session = await ImportSessionAsync(logSession, options, progress, ct);

    // Optionally delete from device after successful import
    if (options.DeleteFromDeviceAfterImport)
    {
        await device.DeleteSdCardFileAsync(fileName, ct);
    }

    return session;
}
finally
{
    // Clean up temp file


 ... (clipped 6 lines)
Ticket Compliance
🟡
🎫 #112
🔴 Fix the device connection screen so DAQiFi devices still appear when the host PC has
multiple network interfaces (some disconnected or with no devices).
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Swallowed cleanup errors: Temp-file deletion errors are silently swallowed and downloadResult.FilePath! is
dereferenced without validation, which can hide failures and cause unhandled null-path
issues.

Referred Code
    var logSession = await parser.ParseFileAsync(downloadResult.FilePath!, null, ct);
    var session = await ImportSessionAsync(logSession, options, progress, ct);

    // Optionally delete from device after successful import
    if (options.DeleteFromDeviceAfterImport)
    {
        await device.DeleteSdCardFileAsync(fileName, ct);
    }

    return session;
}
finally
{
    // Clean up temp file
    if (downloadResult.FilePath != null && File.Exists(downloadResult.FilePath))
    {
        try { File.Delete(downloadResult.FilePath); }
        catch { /* best effort cleanup */ }
    }
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Leaks exception message: User-facing dialogs display raw ex.Message, which may expose internal details (e.g., file
paths, parser/database specifics) to end users.

Referred Code
catch (Exception ex)
{
    _appLogger.Error(ex, "Error importing SD card log file");
    await ShowMessage("Import Failed",
        $"Failed to import file: {ex.Message}",
        MessageDialogStyle.Affirmative);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing action audit: Critical actions (device file download/import and optional device deletion) are not
consistently audit-logged with actor/user context and outcome, so event reconstruction may
be incomplete.

Referred Code
public async Task<LoggingSession> ImportFromDeviceAsync(
    IStreamingDevice device,
    string fileName,
    ImportOptions? options = null,
    IProgress<ImportProgress>? progress = null,
    CancellationToken ct = default)
{
    options ??= new ImportOptions();

    // Download to temp file
    var downloadResult = await device.DownloadSdCardFileAsync(fileName, null, ct);

    try
    {
        // Parse and import
        var parser = new SdCardFileParser();
        var logSession = await parser.ParseFileAsync(downloadResult.FilePath!, null, ct);
        var session = await ImportSessionAsync(logSession, options, progress, ct);

        // Optionally delete from device after successful import
        if (options.DeleteFromDeviceAfterImport)


 ... (clipped 15 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Exception logging review: Exceptions are logged via _logger.Error(ex, ...) and may include sensitive environment
details (e.g., local paths/device identifiers) depending on underlying exception content,
which needs verification against logging policy.

Referred Code
catch (Exception ex)
{
    _logger.Error(ex, $"Error importing {file.FileName}");
    await ShowMessage("Import Failed",
        $"Failed to import {file.FileName}: {ex.Message}",
        MessageDialogStyle.Affirmative);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated file name: SD-card fileName inputs are passed to device download/delete operations without visible
validation/normalization, so it requires verification that the core/device layer prevents
path traversal or invalid name injection.

Referred Code
/// <summary>
/// Downloads a file from the device's SD card over USB to a temporary file.
/// </summary>
Task<SdCardDownloadResult> DownloadSdCardFileAsync(
    string fileName,
    IProgress<SdCardTransferProgress>? progress = null,
    CancellationToken ct = default);

/// <summary>
/// Deletes a file from the device's SD card.
/// </summary>
Task DeleteSdCardFileAsync(string fileName, CancellationToken ct = default);

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Feb 6, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent orphaned data on session overwrite

When overwriting an existing session in CreateSession, first delete all
DataSample records associated with the old session before removing the
LoggingSession entity to prevent orphaned data.

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs [200-209]

 // Check for existing session with same name
 if (options.OverwriteExistingSession)
 {
     var existing = context.Sessions.FirstOrDefault(s => s.Name == sessionName);
     if (existing != null)
     {
+        // First, delete all data samples associated with the old session
+        context.Set<DataSample>().Where(ds => ds.LoggingSessionID == existing.ID).ExecuteDelete();
+
         context.Sessions.Remove(existing);
         context.SaveChanges();
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical data integrity issue where overwriting a session would leave orphaned data, and provides an efficient solution to delete associated records before removing the session.

High
Avoid race conditions in ID generation

In CreateSession, remove the manual session ID generation (max(ID) + 1) and let
the database auto-generate the primary key to prevent race conditions during
concurrent import operations.

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs [211-221]

-// Generate new session ID (same pattern as LoggingManager.OnActiveChanged)
-var ids = context.Sessions.AsNoTracking().Select(s => s.ID).ToList();
-var newId = ids.Count > 0 ? ids.Max() + 1 : 0;
-
-var session = new LoggingSession(newId, sessionName)
+// Let the database generate the new session ID to avoid race conditions
+var session = new LoggingSession(0, sessionName) // Pass a temporary ID
 {
     SessionStart = logSession.FileCreatedDate ?? DateTime.Now
 };
 
 context.Sessions.Add(session);
-context.SaveChanges();
+context.SaveChanges(); // After this, session.ID will be populated with the DB-generated value
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential race condition in manual primary key generation and proposes a robust solution by letting the database manage auto-incrementing keys, which prevents data corruption.

Medium
Remove Task.Run wrapping

In ImportFile, remove the unnecessary Task.Run wrapper and directly await the
importer.ImportFromDeviceAsync call to simplify the code and avoid redundant
thread pool usage.

Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs [199-200]

-var session = await Task.Run(() =>
-    importer.ImportFromDeviceAsync(SelectedDevice, file.FileName, null, progress, CancellationToken.None));
+var session = await importer.ImportFromDeviceAsync(SelectedDevice, file.FileName, null, progress, CancellationToken.None);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies the redundant use of Task.Run on an already asynchronous method and recommends a cleaner, more efficient implementation by directly awaiting the async call.

Low
High-level
Inject importer service into ViewModels

Instead of creating SdCardSessionImporter instances directly in ViewModels,
define an ISdCardSessionImporter interface and use dependency injection. This
will decouple the ViewModels from the import logic, improving testability.

Examples:

Daqifi.Desktop/ViewModels/DaqifiViewModel.cs [952]
            var importer = new SdCardSessionImporter(loggingContext);
Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs [192]
            var importer = new SdCardSessionImporter(loggingContext);

Solution Walkthrough:

Before:

// In DaqifiViewModel.cs
public partial class DaqifiViewModel : ObservableObject
{
    [RelayCommand]
    private async Task ImportSdCardLogFile()
    {
        // ...
        var loggingContext = App.ServiceProvider.GetRequiredService<IDbContextFactory<LoggingContext>>();
        var importer = new SdCardSessionImporter(loggingContext);
        var session = await importer.ImportFromFileAsync(dialog.FileName, ...);
        // ...
    }
}

// In SdCardSessionImporter.cs
public class SdCardSessionImporter { /* ... implementation ... */ }

After:

// In ISdCardSessionImporter.cs (new file)
public interface ISdCardSessionImporter 
{
    Task<LoggingSession> ImportFromFileAsync(...);
    // ... other import methods
}

// In SdCardSessionImporter.cs
public class SdCardSessionImporter : ISdCardSessionImporter { /* ... */ }

// In DaqifiViewModel.cs
public partial class DaqifiViewModel : ObservableObject
{
    private readonly ISdCardSessionImporter _importer;

    public DaqifiViewModel(ISdCardSessionImporter importer, ...) 
    {
        _importer = importer;
    }

    [RelayCommand]
    private async Task ImportSdCardLogFile()
    {
        // ...
        var session = await _importer.ImportFromFileAsync(dialog.FileName, ...);
        // ...
    }
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies tight coupling where SdCardSessionImporter is directly instantiated in ViewModels, and proposing dependency injection via an interface is a significant architectural improvement that enhances testability and maintainability.

Medium
General
Use async bulk insert and transaction

In FlushBatchAsync, replace the synchronous database calls (BeginTransaction,
BulkInsert, Commit) with their asynchronous counterparts (BeginTransactionAsync,
BulkInsertAsync, CommitAsync) to ensure the operation is non-blocking.

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs [226-234]

 private async Task FlushBatchAsync(List<DataSample> batch, CancellationToken ct)
 {
     ct.ThrowIfCancellationRequested();
 
     using var context = _loggingContext.CreateDbContext();
-    using var transaction = context.Database.BeginTransaction();
-    context.BulkInsert(batch);
-    transaction.Commit();
+    await using var transaction = await context.Database.BeginTransactionAsync(ct);
+    await context.BulkInsertAsync(batch, cancellationToken: ct);
+    await transaction.CommitAsync(ct);
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that a method declared as async is performing synchronous, blocking database operations, and proposes using the proper asynchronous equivalents to improve performance and avoid thread blocking.

Medium
Avoid wrapping async methods in Task.Run

In ImportSdCardLogFile, remove the unnecessary Task.Run wrapper and directly
await the importer.ImportFromFileAsync call to avoid redundant thread pool
usage.

Daqifi.Desktop/ViewModels/DaqifiViewModel.cs [959-965]

-var session = await Task.Run(() =>
-    importer.ImportFromFileAsync(dialog.FileName, null, progress, CancellationToken.None));
+var session = await importer.ImportFromFileAsync(dialog.FileName, null, progress, CancellationToken.None);
 
 Application.Current.Dispatcher.Invoke(() =>
 {
     LoggingManager.Instance.LoggingSessions.Add(session);
 });
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies the redundant use of Task.Run on an already asynchronous method and recommends a cleaner, more efficient implementation by directly awaiting the async call.

Low
Learned
best practice
Validate inputs at boundaries

Trim and validate fileName (reject null/whitespace) before calling core APIs to
avoid hard-to-diagnose failures and inconsistent behavior.

Daqifi.Desktop/Device/AbstractStreamingDevice.cs [502-515]

 public async Task<SdCardDownloadResult> DownloadSdCardFileAsync(
     string fileName,
     IProgress<SdCardTransferProgress>? progress = null,
     CancellationToken ct = default)
 {
+    var normalizedFileName = fileName?.Trim();
+    if (string.IsNullOrWhiteSpace(normalizedFileName))
+        throw new ArgumentException("File name is required.", nameof(fileName));
+
     var coreDevice = GetCoreDeviceForSd();
-    return await coreDevice.DownloadSdCardFileAsync(fileName, progress, ct);
+    return await coreDevice.DownloadSdCardFileAsync(normalizedFileName, progress, ct);
 }
 
 public async Task DeleteSdCardFileAsync(string fileName, CancellationToken ct = default)
 {
+    var normalizedFileName = fileName?.Trim();
+    if (string.IsNullOrWhiteSpace(normalizedFileName))
+        throw new ArgumentException("File name is required.", nameof(fileName));
+
     var coreDevice = GetCoreDeviceForSd();
-    await coreDevice.DeleteSdCardFileAsync(fileName, ct);
+    await coreDevice.DeleteSdCardFileAsync(normalizedFileName, ct);
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Normalize and validate external/user-provided inputs (trim and reject whitespace-only values) at method boundaries.

Low
Log cleanup exceptions with context
Suggestion Impact:The cleanup File.Delete catch block was changed from silent swallowing to logging a warning when temp file deletion fails (though it logs only the message string rather than the exception object and full suggested context).

code diff:

         finally
         {
             // Clean up temp file
-            if (downloadResult.FilePath != null && File.Exists(downloadResult.FilePath))
-            {
-                try { File.Delete(downloadResult.FilePath); }
-                catch { /* best effort cleanup */ }
+            if (File.Exists(downloadResult.FilePath))
+            {
+                try
+                {
+                    File.Delete(downloadResult.FilePath);
+                }
+                catch (Exception ex)
+                {
+                    _logger.Warning($"Failed to clean up temp file '{downloadResult.FilePath}': {ex.Message}");
+                }
             }

Avoid swallowing cleanup exceptions silently; log the caught exception with
context (e.g., temp file path and SD fileName) to make cleanup failures
diagnosable.

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs [84-92]

 finally
 {
     // Clean up temp file
     if (downloadResult.FilePath != null && File.Exists(downloadResult.FilePath))
     {
-        try { File.Delete(downloadResult.FilePath); }
-        catch { /* best effort cleanup */ }
+        try
+        {
+            File.Delete(downloadResult.FilePath);
+        }
+        catch (Exception ex)
+        {
+            _logger.Error(ex, "Failed to delete temp SD import file (TempPath={TempPath}, SdFileName={SdFileName})",
+                downloadResult.FilePath, fileName);
+        }
     }
 }

[Suggestion processed]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Log exceptions by passing the exception object (not only message) and include concrete runtime context to aid diagnosis.

Low
  • Update

- Update Daqifi.Core NuGet from 0.14.0 to 0.15.0 across all projects
  to include SD card parsing/download types needed for import feature
- Add null safety check for downloadResult.FilePath before use
- Validate downloaded file is in expected temp directory
- Log cleanup errors instead of silently swallowing them
- Sanitize user-facing error messages to avoid leaking internal details
- Add audit logging for import, download, and delete operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tylerkron tylerkron requested a review from a team as a code owner February 7, 2026 04:55
tylerkron and others added 4 commits February 6, 2026 22:16
- Fix StartSdCardLogging sending individual channel enable commands
  instead of a combined bitmask. Each EnableAdcChannels SCPI command
  replaces the previous setting, so sending one-channel-at-a-time
  resulted in only the last channel being logged. Now builds a single
  bitmask for all active analog channels before sending.
- Suppress streaming data from appearing on the Live Graph when in
  Log to Device mode by checking Mode in OnStreamMessageReceived.
- Add diagnostic logging to SdCardSessionImporter for device config,
  first sample details, and channel discovery to aid debugging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…stics

- Changed ImportFromDeviceAsync to use ParseAsync with the original device
  filename instead of ParseFileAsync with the temp path. This fixes:
  1. Session names showing temp GUIDs instead of the original log filename
  2. Date extraction from log filenames (e.g., log_20260206_101851.bin)
- Added download result diagnostics (reported size, disk size, temp path)
- Added empty file detection with descriptive error message
- Added warning log when 0 samples are imported for debugging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Log first two samples (instead of just first) to verify timestamps
  are properly spaced after the core parser fix
- Add warning when TimestampFrequency is 0, indicating firmware may
  not include TimestampFreq in logged messages
- Include sample index in diagnostic log for easier debugging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Device firmware does not include TimestampFreq in SD card log data,
causing all imported samples to have identical timestamps.

Changes:
- Store TimestampFrequency on AbstractStreamingDevice from status messages
- Expose TimestampFrequency on IStreamingDevice interface
- Pass device's TimestampFrequency as FallbackTimestampFrequency when
  parsing SD card files via ImportFromDeviceAsync
- Add diagnostic logging for first two samples to verify timestamp spacing
- Add warning when no TimestampFrequency is available

Requires daqifi-core PR #117 (FallbackTimestampFrequency support)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tylerkron and others added 8 commits February 7, 2026 21:41
Closes daqifi/daqifi-core#137

Daqifi.Core 0.18.1 (daqifi/daqifi-core#138) adds:
- DaqifiDevice.TimestampFrequency populated from status messages
- SdCardFileListParser filters plain ERROR lines and control-char filenames
- DaqifiStreamingDevice.ContainsScpiError matches both **ERROR and ERROR

Desktop changes:
- Bump Daqifi.Core 0.15.1 → 0.18.1 across all three projects
- Bump System.IO.Ports 10.0.2 → 10.0.3 (new Core transitive requirement)
- AbstractStreamingDevice.TimestampFrequency now delegates to the
  underlying Core device instead of re-extracting from protobuf
- Remove ContainsScpiErrorEntry and IsScpiErrorLine helpers — Core's
  SdCardFileListParser now filters these before returning file lists
- Remove the Desktop-level SCPI error retry in RefreshSdCardFiles —
  Core already retries internally in GetSdCardFilesAsync
- Simplify IsImportableSdLogFileName — drop redundant IsScpiErrorLine
  and control-character guards, keep only the .bin extension filter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved conflicts by taking newer package versions from main (Daqifi.Core
0.19.1, System.IO.Ports 10.0.5, Google.Protobuf 3.34.0) and preserving both
CoreDeviceForStreaming (SD card feature) and CoreDeviceForNetworkConfiguration
(main) in AbstractStreamingDevice, SerialStreamingDevice, and DaqifiStreamingDevice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix CS1503: convert uint channelMask to binary string for
  StartSdCardLoggingAsync which expects string?
- Fix CS0246: add missing using for SdCardSessionImporter/ImportProgress
  in DaqifiViewModel
- Remove dead BuildActiveAnalogChannelMask() (superseded by inline uint
  mask in StartSdCardLogging)
- EnsureSdOperationsQuiesced now throws instead of silently stopping
  streaming, preventing unexpected data loss

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test device only overrode CoreDeviceForNetworkConfiguration, so
StopStreaming() threw when UpdateNetworkConfiguration called it with
IsStreaming=true. Expose the same RecordingCoreStreamingDevice for
both properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
StopStreaming now delegates to the Core layer, so the stop command
is recorded as core:SYSTem:StopStreamData rather than desktop:...
Update the assertion to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Core layer's StopStreaming() is a no-op when its own IsStreaming
is false. The test only set the Desktop-level flag, so the stop command
was never sent. Call StartStreaming() on the Core device (and clear
recorded commands) so that StopStreaming() flows through correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tylerkron
Copy link
Contributor Author

/agentic_review

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 16, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (8) 📎 Requirement gaps (0)

Grey Divider


Action required

1. SD methods missing XML docs📘 Rule violation ✓ Correctness
Description
New public methods on AbstractStreamingDevice are missing XML documentation comments. This
violates the requirement that all public APIs be documented.
Code

Daqifi.Desktop/Device/AbstractStreamingDevice.cs[R546-563]

+    public async Task<SdCardDownloadResult> DownloadSdCardFileAsync(
+        string fileName,
+        IProgress<SdCardTransferProgress>? progress = null,
+        CancellationToken ct = default)
+    {
+        EnsureSdOperationsQuiesced();
+
+        var coreDevice = GetCoreDeviceForSd();
+        return await coreDevice.DownloadSdCardFileAsync(fileName, progress, ct);
+    }
+
+    public async Task DeleteSdCardFileAsync(string fileName, CancellationToken ct = default)
+    {
+        EnsureSdOperationsQuiesced();
+
+        var coreDevice = GetCoreDeviceForSd();
+        await coreDevice.DeleteSdCardFileAsync(fileName, ct);
+    }
Evidence
The checklist requires XML documentation for all public methods, but these newly added public
methods do not have XML documentation comments.

CLAUDE.md
Daqifi.Desktop/Device/AbstractStreamingDevice.cs[546-563]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New public methods were added without required XML documentation comments.
## Issue Context
The compliance checklist requires XML documentation for all public APIs.
## Fix Focus Areas
- Daqifi.Desktop/Device/AbstractStreamingDevice.cs[546-563]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Importer uses sync EF 📘 Rule violation ➹ Performance
Description
The importer performs synchronous Entity Framework operations (SaveChanges, queries, and
transactions) instead of async equivalents. This can block threads and reduce UI responsiveness
during large imports.
Code

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[R284-323]

+        using var context = _loggingContext.CreateDbContext();
+
+        var sessionName = options.SessionNameOverride
+                          ?? $"SD Import - {Path.GetFileNameWithoutExtension(logSession.FileName)}";
+
+        // Check for existing session with same name
+        if (options.OverwriteExistingSession)
+        {
+            var existing = context.Sessions.FirstOrDefault(s => s.Name == sessionName);
+            if (existing != null)
+            {
+                context.Sessions.Remove(existing);
+                context.SaveChanges();
+            }
+        }
+
+        // Generate new session ID (same pattern as LoggingManager.OnActiveChanged)
+        var ids = context.Sessions.AsNoTracking().Select(s => s.ID).ToList();
+        var newId = ids.Count > 0 ? ids.Max() + 1 : 0;
+
+        var session = new LoggingSession(newId, sessionName)
+        {
+            SessionStart = logSession.FileCreatedDate ?? DateTime.Now
+        };
+
+        context.Sessions.Add(session);
+        context.SaveChanges();
+
+        return session;
+    }
+
+    private async Task FlushBatchAsync(List<DataSample> batch, CancellationToken ct)
+    {
+        ct.ThrowIfCancellationRequested();
+
+        using var context = _loggingContext.CreateDbContext();
+        using var transaction = context.Database.BeginTransaction();
+        context.BulkInsert(batch);
+        transaction.Commit();
+    }
Evidence
The checklist requires async database APIs for EF + SQLite, but the importer uses synchronous query
and save calls and a synchronous transaction/bulk insert path.

CLAUDE.md
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[284-313]
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[319-323]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new SD card import path performs synchronous EF Core operations (queries, SaveChanges, transactions/bulk insert), which violates the async database operation standard.
## Issue Context
Imports can process large datasets; blocking DB operations can freeze UI or reduce responsiveness.
## Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[282-313]
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[315-323]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Import inputs not validated 📘 Rule violation ⛨ Security
Description
User-provided inputs (filePath and fileName) are consumed without validation
(existence/format/whitespace/extension checks). This can cause crashes or unintended file access and
violates the input validation requirements.
Code

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[R28-37]

+    public async Task<LoggingSession> ImportFromFileAsync(
+        string filePath,
+        ImportOptions? options = null,
+        IProgress<ImportProgress>? progress = null,
+        CancellationToken ct = default)
+    {
+        _logger.Information($"Starting file import from '{Path.GetFileName(filePath)}'");
+        var parser = new SdCardFileParser();
+        var logSession = await parser.ParseFileAsync(filePath, null, ct);
+        return await ImportSessionAsync(logSession, options, progress, ct);
Evidence
The checklist requires validating and normalizing boundary/user inputs; the importer directly uses
filePath for parsing without checking File.Exists, extension, or normalization.

CLAUDE.md
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[28-37]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Importer boundary methods accept user/device-provided file identifiers and consume them without validation or normalization.
## Issue Context
Per compliance rules, all user inputs must be validated and normalized (e.g., trim, reject whitespace-only, check existence/format) before being used for I/O.
## Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[28-54]
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[59-85]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (5)
4. Cleanup log drops stacktrace📘 Rule violation ✓ Correctness
Description
The temp-file cleanup exception is logged using only ex.Message, which loses the stack trace. This
reduces diagnosability and violates the exception logging standard.
Code

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[R145-148]

+                catch (Exception ex)
+                {
+                    _logger.Warning($"Failed to clean up temp file '{downloadResult.FilePath}': {ex.Message}");
+                }
Evidence
The checklist requires logging exceptions with the exception object (stack trace) and context; this
catch block logs only the message string.

CLAUDE.md
CLAUDE.md
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[145-148]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Exception handling logs only `ex.Message`, which drops stack trace and weakens diagnostics.
## Issue Context
Compliance requires logging exceptions with the exception object to preserve stack traces.
## Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[145-148]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. GetResult() blocks UI 📘 Rule violation ➹ Performance
Description
StartSdCardLogging performs a blocking wait using GetAwaiter().GetResult(), which can block the
UI thread and risks deadlocks. This violates the async/await and UI responsiveness requirement.
Code

Daqifi.Desktop/Device/AbstractStreamingDevice.cs[R472-475]

        // The Core package resumes StartSdCardLoggingAsync continuations on the caller's
        // synchronization context. Running it on the thread pool prevents UI deadlocks.
-            Task.Run(() => coreDevice.StartSdCardLoggingAsync(channelMask: analogChannelMask)).GetAwaiter().GetResult();
+            var channelMaskString = Convert.ToString((long)analogChannelMask, 2);
+            Task.Run(() => coreDevice.StartSdCardLoggingAsync(channelMask: channelMaskString)).GetAwaiter().GetResult();
Evidence
The checklist prohibits blocking waits (e.g., .Result, .Wait()) and expects async/await for I/O;
this code blocks while waiting for an async SD operation to complete.

CLAUDE.md
Daqifi.Desktop/Device/AbstractStreamingDevice.cs[472-475]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A blocking wait (`GetAwaiter().GetResult()`) is used around async SD card logging start, which can block the UI thread.
## Issue Context
Compliance requires async/await for I/O and forbids blocking waits on UI-sensitive code paths.
## Fix Focus Areas
- Daqifi.Desktop/Device/AbstractStreamingDevice.cs[472-475]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. ADC mask mis-sent 🐞 Bug ✓ Correctness
Description
StartSdCardLogging sends EnableAdcChannels using a decimal string via the desktop transport, even
though SD logging configuration is expected to be owned by Core using a combined binary mask; this
can momentarily apply an incorrect channel mask or cause redundant/misordered channel configuration.
Code

Daqifi.Desktop/Device/AbstractStreamingDevice.cs[R463-467]

+            if (analogChannelMask != 0)
+            {
+                SendMessage(ScpiMessageProducer.EnableAdcChannels(
+                    analogChannelMask.ToString(CultureInfo.InvariantCulture)));
+            }
Evidence
The desktop wrapper sends EnableAdcChannels with analogChannelMask formatted as a decimal number,
while the SD logging path also passes a binary string mask to Core. The unit test codifies the
intended behavior that Core should receive the single combined binary mask for SD logging channels,
implying the desktop should not be issuing its own (potentially differently-formatted) mask.

Daqifi.Desktop/Device/AbstractStreamingDevice.cs[440-476]
Daqifi.Desktop.Test/Device/AbstractStreamingDeviceTests.cs[565-590]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`StartSdCardLogging()` currently sends `EnableAdcChannels` from the desktop layer using a decimal-encoded mask, while Core SD logging is expected to own channel-mask configuration using a combined binary mask. This duplication/mismatch can apply the wrong channel mask or introduce ordering issues.
### Issue Context
Unit tests indicate the desired behavior: Core should receive a single combined binary mask for SD logging channels.
### Fix Focus Areas
- Daqifi.Desktop/Device/AbstractStreamingDevice.cs[463-476]
- Daqifi.Desktop.Test/Device/AbstractStreamingDeviceTests.cs[565-590]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Partial import persisted 🐞 Bug ⛯ Reliability
Description
SdCardSessionImporter persists a new LoggingSession before inserting samples and flushes samples in
separate transactions without rollback/cleanup, so exceptions or cancellations can leave
empty/partial sessions and partial sample sets in SQLite.
Code

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[R181-323]

+        // Create the logging session in the database
+        var session = CreateSession(logSession, options);
+
+        if ((config?.TimestampFrequency ?? 0) == 0)
+        {
+            _logger.Warning(
+                "No TimestampFrequency found in SD card file. " +
+                "Timestamps may not be properly reconstructed. " +
+                "Device firmware may not include TimestampFreq in logged messages.");
+        }
+
+        // Bulk-insert samples
+        var batch = new List<DataSample>();
+        long samplesProcessed = 0;
+        var sampleIndex = 0;
+
+        await foreach (var entry in logSession.Samples.WithCancellation(ct))
+        {
+            // Log first and second sample for diagnostics (to verify timestamps are spaced correctly)
+            if (sampleIndex < 2)
+            {
+                _logger.Information(
+                    $"Sample[{sampleIndex}]: AnalogValues.Count={entry.AnalogValues.Count}, " +
+                    $"DigitalData=0x{entry.DigitalData:X8}, Timestamp={entry.Timestamp:O}");
+            }
+
+            sampleIndex++;
+
+            // If we didn't have config, discover channel count from first entry
+            if (analogPortCount == 0 && entry.AnalogValues.Count > 0)
+            {
+                analogPortCount = entry.AnalogValues.Count;
+                _logger.Information($"Discovered {analogPortCount} analog channels from first sample");
+                AssignChannelColors(channelColors, analogPortCount, digitalPortCount);
+            }
+
+            // Create analog samples
+            for (var i = 0; i < entry.AnalogValues.Count; i++)
+            {
+                var channelName = $"AI{i}";
+                batch.Add(new DataSample
+                {
+                    LoggingSessionID = session.ID,
+                    ChannelName = channelName,
+                    DeviceName = deviceName,
+                    DeviceSerialNo = deviceSerialNo,
+                    Color = channelColors.GetValueOrDefault(channelName, "#D32F2F"),
+                    Type = ChannelType.Analog,
+                    Value = entry.AnalogValues[i],
+                    TimestampTicks = entry.Timestamp.Ticks
+                });
+            }
+
+            // Create digital samples (one per bit)
+            for (var i = 0; i < digitalPortCount; i++)
+            {
+                var channelName = $"DI{i}";
+                var bitValue = (entry.DigitalData & (1u << i)) != 0 ? 1.0 : 0.0;
+                batch.Add(new DataSample
+                {
+                    LoggingSessionID = session.ID,
+                    ChannelName = channelName,
+                    DeviceName = deviceName,
+                    DeviceSerialNo = deviceSerialNo,
+                    Color = channelColors.GetValueOrDefault(channelName, "#757575"),
+                    Type = ChannelType.Digital,
+                    Value = bitValue,
+                    TimestampTicks = entry.Timestamp.Ticks
+                });
+            }
+
+            // Flush batch when full
+            if (batch.Count >= BatchSize)
+            {
+                await FlushBatchAsync(batch, ct);
+                samplesProcessed += batch.Count;
+                batch.Clear();
+                progress?.Report(new ImportProgress(samplesProcessed, null));
+            }
+        }
+
+        // Flush remaining samples
+        if (batch.Count > 0)
+        {
+            await FlushBatchAsync(batch, ct);
+            samplesProcessed += batch.Count;
+            batch.Clear();
+            progress?.Report(new ImportProgress(samplesProcessed, null));
+        }
+
+        if (samplesProcessed == 0)
+        {
+            _logger.Warning(
+                $"No samples found in SD card file '{logSession.FileName}'. " +
+                $"DeviceConfig present: {config != null}");
+        }
+
+        _logger.Information($"Imported {samplesProcessed} samples for session '{session.Name}' (ID={session.ID})");
+        return session;
+    }
+
+    private LoggingSession CreateSession(SdCardLogSession logSession, ImportOptions options)
+    {
+        using var context = _loggingContext.CreateDbContext();
+
+        var sessionName = options.SessionNameOverride
+                          ?? $"SD Import - {Path.GetFileNameWithoutExtension(logSession.FileName)}";
+
+        // Check for existing session with same name
+        if (options.OverwriteExistingSession)
+        {
+            var existing = context.Sessions.FirstOrDefault(s => s.Name == sessionName);
+            if (existing != null)
+            {
+                context.Sessions.Remove(existing);
+                context.SaveChanges();
+            }
+        }
+
+        // Generate new session ID (same pattern as LoggingManager.OnActiveChanged)
+        var ids = context.Sessions.AsNoTracking().Select(s => s.ID).ToList();
+        var newId = ids.Count > 0 ? ids.Max() + 1 : 0;
+
+        var session = new LoggingSession(newId, sessionName)
+        {
+            SessionStart = logSession.FileCreatedDate ?? DateTime.Now
+        };
+
+        context.Sessions.Add(session);
+        context.SaveChanges();
+
+        return session;
+    }
+
+    private async Task FlushBatchAsync(List<DataSample> batch, CancellationToken ct)
+    {
+        ct.ThrowIfCancellationRequested();
+
+        using var context = _loggingContext.CreateDbContext();
+        using var transaction = context.Database.BeginTransaction();
+        context.BulkInsert(batch);
+        transaction.Commit();
+    }
Evidence
The importer creates and saves the session up front, then inserts samples in batches with
independent transactions and no surrounding transaction or failure cleanup. ViewModels also pass
CancellationToken.None, so user cancellation isn’t wired, and any operational failure can persist
partial data. LoggingManager only removes empty sessions on application load, not at import time, so
partial sessions with some samples will remain indefinitely.

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[181-269]
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[282-313]
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[315-323]
Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs[182-202]
Daqifi.Desktop/ViewModels/DaqifiViewModel.cs[1188-1214]
Daqifi.Desktop/Loggers/LoggingManager.cs[459-478]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
SD card import can leave the database in a partially-imported state (session row created, some sample batches committed) when parsing/insertion fails or is cancelled.
### Issue Context
`CreateSession()` saves immediately, and each batch insert commits independently with no top-level transaction or compensating cleanup.
### Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[156-323]
- Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs[182-295]
- Daqifi.Desktop/ViewModels/DaqifiViewModel.cs[1188-1240]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Core reference split-brain🐞 Bug ⛯ Reliability
Description
Only Daqifi.Desktop.csproj conditionally switches between a Daqifi.Core package reference and a
local project reference, while Daqifi.Desktop.DataModel and Daqifi.Desktop.IO always reference the
NuGet package, potentially producing mixed Daqifi.Core assemblies and type identity/version
conflicts when using DaqifiCoreProjectPath.
Code

Daqifi.Desktop/Daqifi.Desktop.csproj[R59-64]

+		<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+		<PackageReference Include="Daqifi.Core" Version="0.19.1" Condition="'$(DaqifiCoreProjectPath)' == ''" />
+		<PackageReference Include="EFCore.BulkExtensions" Version="9.0.2" />
<PackageReference Include="MahApps.Metro" Version="2.4.11" />
<PackageReference Include="MahApps.Metro.IconPacks" Version="6.2.1" />
<PackageReference Include="MahApps.Metro.IconPacks.Material" Version="6.2.1" />
Evidence
The desktop app can be built against a local Core project, but referenced projects still compile
against the NuGet Core package. This creates a configuration where the solution may contain both the
project-built and package-built Daqifi.Core, risking binding/type mismatches when Core types flow
across project boundaries.

Daqifi.Desktop/Daqifi.Desktop.csproj[58-90]
Daqifi.Desktop.DataModel/Daqifi.Desktop.DataModel.csproj[12-21]
Daqifi.Desktop.IO/Daqifi.Desktop.IO.csproj[11-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Building with `-p:DaqifiCoreProjectPath=...` can result in inconsistent Core references across the solution (project reference in Desktop, NuGet reference in IO/DataModel), risking mixed assemblies and type identity conflicts.
### Issue Context
The conditional Core reference was only added to the Desktop project.
### Fix Focus Areas
- Daqifi.Desktop/Daqifi.Desktop.csproj[58-90]
- Daqifi.Desktop.IO/Daqifi.Desktop.IO.csproj[11-19]
- Daqifi.Desktop.DataModel/Daqifi.Desktop.DataModel.csproj[12-21]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

9. BatchSize constant misnamed 📘 Rule violation ✓ Correctness
Description
The constant BatchSize does not follow the required SCREAMING_SNAKE_CASE convention. This reduces
consistency and violates the naming standard.
Code

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[16]

+    private const int BatchSize = 1000;
Evidence
Compliance requires constants to be named in SCREAMING_SNAKE_CASE, but the new constant is named
BatchSize.

CLAUDE.md
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[16-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new constant is not named using SCREAMING_SNAKE_CASE as required by the project&amp;amp;amp;amp;#x27;s naming conventions.
## Issue Context
The compliance checklist explicitly requires constants to use SCREAMING_SNAKE_CASE.
## Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[16-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Service locator used in commands 📘 Rule violation ⛯ Reliability
Description
The view models resolve dependencies via App.ServiceProvider and instantiate
SdCardSessionImporter inside command methods. This reduces testability and violates the dependency
injection requirement.
Code

Daqifi.Desktop/ViewModels/DaqifiViewModel.cs[R1204-1206]

+            var loggingContext = App.ServiceProvider.GetRequiredService<IDbContextFactory<LoggingContext>>();
+            var importer = new SdCardSessionImporter(loggingContext);
+
Evidence
Compliance requires dependencies to be injected; this code uses a service locator and direct
construction inside UI command handlers, making the logic harder to unit test and mock.

CLAUDE.md
Daqifi.Desktop/ViewModels/DaqifiViewModel.cs[1204-1206]
Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs[192-194]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
ViewModels use `App.ServiceProvider` (service locator) and `new SdCardSessionImporter(...)` inside command methods, reducing testability.
## Issue Context
Compliance requires using dependency injection for external services/helpers to support mocking/substitution in tests.
## Fix Focus Areas
- Daqifi.Desktop/ViewModels/DaqifiViewModel.cs[1204-1206]
- Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs[192-194]
- Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs[243-245]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Multiple public classes in file 📘 Rule violation ✓ Correctness
Description
SdCardSessionImporter.cs introduces multiple public classes in a single file. This violates the
file organization requirement and increases diff noise/maintenance overhead.
Code

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[R352-373]

+public class ImportOptions
+{
+    public bool DeleteFromDeviceAfterImport { get; set; }
+    public bool OverwriteExistingSession { get; set; }
+    public string? SessionNameOverride { get; set; }
+}
+
+public class ImportProgress
+{
+    public long SamplesProcessed { get; }
+    public long? EstimatedTotal { get; }
+
+    public double PercentComplete => EstimatedTotal is > 0
+        ? (double)SamplesProcessed / EstimatedTotal.Value * 100
+        : -1;
+
+    public ImportProgress(long samplesProcessed, long? estimatedTotal)
+    {
+        SamplesProcessed = samplesProcessed;
+        EstimatedTotal = estimatedTotal;
+    }
+}
Evidence
The code style rule requires one main class per file, but this file includes multiple public classes
(SdCardSessionImporter, ImportOptions, and ImportProgress).

CLAUDE.md
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[14-14]
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[352-373]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Multiple public classes were added to a single file, violating the project&amp;amp;amp;amp;#x27;s file organization standard.
## Issue Context
The style compliance requires one main class per file to improve readability and reduce diff noise.
## Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[352-373]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
12. Digital samples silently dropped 🐞 Bug ✓ Correctness
Description
SdCardSessionImporter imports digital samples only when DeviceConfig.DigitalPortCount is non-zero;
when the SD log lacks that metadata, all digital data is silently skipped even though DigitalData is
present in each sample.
Code

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[R169-250]

+        // Determine channel counts from config or discover from first sample
+        var analogPortCount = config?.AnalogPortCount ?? 0;
+        var digitalPortCount = config?.DigitalPortCount ?? 0;
+
+        _logger.Information(
+            $"SD card session config: AnalogPorts={analogPortCount}, DigitalPorts={digitalPortCount}, " +
+            $"Device={deviceSerialNo}, TimestampFreq={config?.TimestampFrequency ?? 0}");
+
+        // Pre-assign colors per channel
+        var channelColors = new Dictionary<string, string>();
+        AssignChannelColors(channelColors, analogPortCount, digitalPortCount);
+
+        // Create the logging session in the database
+        var session = CreateSession(logSession, options);
+
+        if ((config?.TimestampFrequency ?? 0) == 0)
+        {
+            _logger.Warning(
+                "No TimestampFrequency found in SD card file. " +
+                "Timestamps may not be properly reconstructed. " +
+                "Device firmware may not include TimestampFreq in logged messages.");
+        }
+
+        // Bulk-insert samples
+        var batch = new List<DataSample>();
+        long samplesProcessed = 0;
+        var sampleIndex = 0;
+
+        await foreach (var entry in logSession.Samples.WithCancellation(ct))
+        {
+            // Log first and second sample for diagnostics (to verify timestamps are spaced correctly)
+            if (sampleIndex < 2)
+            {
+                _logger.Information(
+                    $"Sample[{sampleIndex}]: AnalogValues.Count={entry.AnalogValues.Count}, " +
+                    $"DigitalData=0x{entry.DigitalData:X8}, Timestamp={entry.Timestamp:O}");
+            }
+
+            sampleIndex++;
+
+            // If we didn't have config, discover channel count from first entry
+            if (analogPortCount == 0 && entry.AnalogValues.Count > 0)
+            {
+                analogPortCount = entry.AnalogValues.Count;
+                _logger.Information($"Discovered {analogPortCount} analog channels from first sample");
+                AssignChannelColors(channelColors, analogPortCount, digitalPortCount);
+            }
+
+            // Create analog samples
+            for (var i = 0; i < entry.AnalogValues.Count; i++)
+            {
+                var channelName = $"AI{i}";
+                batch.Add(new DataSample
+                {
+                    LoggingSessionID = session.ID,
+                    ChannelName = channelName,
+                    DeviceName = deviceName,
+                    DeviceSerialNo = deviceSerialNo,
+                    Color = channelColors.GetValueOrDefault(channelName, "#D32F2F"),
+                    Type = ChannelType.Analog,
+                    Value = entry.AnalogValues[i],
+                    TimestampTicks = entry.Timestamp.Ticks
+                });
+            }
+
+            // Create digital samples (one per bit)
+            for (var i = 0; i < digitalPortCount; i++)
+            {
+                var channelName = $"DI{i}";
+                var bitValue = (entry.DigitalData & (1u << i)) != 0 ? 1.0 : 0.0;
+                batch.Add(new DataSample
+                {
+                    LoggingSessionID = session.ID,
+                    ChannelName = channelName,
+                    DeviceName = deviceName,
+                    DeviceSerialNo = deviceSerialNo,
+                    Color = channelColors.GetValueOrDefault(channelName, "#757575"),
+                    Type = ChannelType.Digital,
+                    Value = bitValue,
+                    TimestampTicks = entry.Timestamp.Ticks
+                });
+            }
Evidence
The importer initializes digitalPortCount from config and never updates it, but the digital-sample
loop bounds exclusively on digitalPortCount. If config is missing/zero, the loop never runs and no
digital samples are written, producing an imported session that lacks digital channels without an
error.

Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[169-215]
Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[234-250]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Digital samples are skipped entirely when the SD log session metadata does not include `DigitalPortCount`, causing silent data loss.
### Issue Context
Digital data is stored as a bitmask, so the importer must choose an explicit fallback strategy when port count is unknown.
### Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[169-250]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +284 to +323
using var context = _loggingContext.CreateDbContext();

var sessionName = options.SessionNameOverride
?? $"SD Import - {Path.GetFileNameWithoutExtension(logSession.FileName)}";

// Check for existing session with same name
if (options.OverwriteExistingSession)
{
var existing = context.Sessions.FirstOrDefault(s => s.Name == sessionName);
if (existing != null)
{
context.Sessions.Remove(existing);
context.SaveChanges();
}
}

// Generate new session ID (same pattern as LoggingManager.OnActiveChanged)
var ids = context.Sessions.AsNoTracking().Select(s => s.ID).ToList();
var newId = ids.Count > 0 ? ids.Max() + 1 : 0;

var session = new LoggingSession(newId, sessionName)
{
SessionStart = logSession.FileCreatedDate ?? DateTime.Now
};

context.Sessions.Add(session);
context.SaveChanges();

return session;
}

private async Task FlushBatchAsync(List<DataSample> batch, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();

using var context = _loggingContext.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
context.BulkInsert(batch);
transaction.Commit();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Importer uses sync ef 📘 Rule violation ➹ Performance

The importer performs synchronous Entity Framework operations (SaveChanges, queries, and
transactions) instead of async equivalents. This can block threads and reduce UI responsiveness
during large imports.
Agent Prompt
## Issue description
The new SD card import path performs synchronous EF Core operations (queries, SaveChanges, transactions/bulk insert), which violates the async database operation standard.

## Issue Context
Imports can process large datasets; blocking DB operations can freeze UI or reduce responsiveness.

## Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[282-313]
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[315-323]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +28 to +37
public async Task<LoggingSession> ImportFromFileAsync(
string filePath,
ImportOptions? options = null,
IProgress<ImportProgress>? progress = null,
CancellationToken ct = default)
{
_logger.Information($"Starting file import from '{Path.GetFileName(filePath)}'");
var parser = new SdCardFileParser();
var logSession = await parser.ParseFileAsync(filePath, null, ct);
return await ImportSessionAsync(logSession, options, progress, ct);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Import inputs not validated 📘 Rule violation ⛨ Security

User-provided inputs (filePath and fileName) are consumed without validation
(existence/format/whitespace/extension checks). This can cause crashes or unintended file access and
violates the input validation requirements.
Agent Prompt
## Issue description
Importer boundary methods accept user/device-provided file identifiers and consume them without validation or normalization.

## Issue Context
Per compliance rules, all user inputs must be validated and normalized (e.g., trim, reject whitespace-only, check existence/format) before being used for I/O.

## Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[28-54]
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[59-85]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 472 to +475
// The Core package resumes StartSdCardLoggingAsync continuations on the caller's
// synchronization context. Running it on the thread pool prevents UI deadlocks.
Task.Run(() => coreDevice.StartSdCardLoggingAsync(channelMask: analogChannelMask)).GetAwaiter().GetResult();
var channelMaskString = Convert.ToString((long)analogChannelMask, 2);
Task.Run(() => coreDevice.StartSdCardLoggingAsync(channelMask: channelMaskString)).GetAwaiter().GetResult();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. getresult() blocks ui 📘 Rule violation ➹ Performance

StartSdCardLogging performs a blocking wait using GetAwaiter().GetResult(), which can block the
UI thread and risks deadlocks. This violates the async/await and UI responsiveness requirement.
Agent Prompt
## Issue description
A blocking wait (`GetAwaiter().GetResult()`) is used around async SD card logging start, which can block the UI thread.

## Issue Context
Compliance requires async/await for I/O and forbids blocking waits on UI-sensitive code paths.

## Fix Focus Areas
- Daqifi.Desktop/Device/AbstractStreamingDevice.cs[472-475]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +463 to +467
if (analogChannelMask != 0)
{
SendMessage(ScpiMessageProducer.EnableAdcChannels(
analogChannelMask.ToString(CultureInfo.InvariantCulture)));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

6. Adc mask mis-sent 🐞 Bug ✓ Correctness

StartSdCardLogging sends EnableAdcChannels using a decimal string via the desktop transport, even
though SD logging configuration is expected to be owned by Core using a combined binary mask; this
can momentarily apply an incorrect channel mask or cause redundant/misordered channel configuration.
Agent Prompt
### Issue description
`StartSdCardLogging()` currently sends `EnableAdcChannels` from the desktop layer using a decimal-encoded mask, while Core SD logging is expected to own channel-mask configuration using a combined binary mask. This duplication/mismatch can apply the wrong channel mask or introduce ordering issues.

### Issue Context
Unit tests indicate the desired behavior: Core should receive a single combined binary mask for SD logging channels.

### Fix Focus Areas
- Daqifi.Desktop/Device/AbstractStreamingDevice.cs[463-476]
- Daqifi.Desktop.Test/Device/AbstractStreamingDeviceTests.cs[565-590]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +181 to +323
// Create the logging session in the database
var session = CreateSession(logSession, options);

if ((config?.TimestampFrequency ?? 0) == 0)
{
_logger.Warning(
"No TimestampFrequency found in SD card file. " +
"Timestamps may not be properly reconstructed. " +
"Device firmware may not include TimestampFreq in logged messages.");
}

// Bulk-insert samples
var batch = new List<DataSample>();
long samplesProcessed = 0;
var sampleIndex = 0;

await foreach (var entry in logSession.Samples.WithCancellation(ct))
{
// Log first and second sample for diagnostics (to verify timestamps are spaced correctly)
if (sampleIndex < 2)
{
_logger.Information(
$"Sample[{sampleIndex}]: AnalogValues.Count={entry.AnalogValues.Count}, " +
$"DigitalData=0x{entry.DigitalData:X8}, Timestamp={entry.Timestamp:O}");
}

sampleIndex++;

// If we didn't have config, discover channel count from first entry
if (analogPortCount == 0 && entry.AnalogValues.Count > 0)
{
analogPortCount = entry.AnalogValues.Count;
_logger.Information($"Discovered {analogPortCount} analog channels from first sample");
AssignChannelColors(channelColors, analogPortCount, digitalPortCount);
}

// Create analog samples
for (var i = 0; i < entry.AnalogValues.Count; i++)
{
var channelName = $"AI{i}";
batch.Add(new DataSample
{
LoggingSessionID = session.ID,
ChannelName = channelName,
DeviceName = deviceName,
DeviceSerialNo = deviceSerialNo,
Color = channelColors.GetValueOrDefault(channelName, "#D32F2F"),
Type = ChannelType.Analog,
Value = entry.AnalogValues[i],
TimestampTicks = entry.Timestamp.Ticks
});
}

// Create digital samples (one per bit)
for (var i = 0; i < digitalPortCount; i++)
{
var channelName = $"DI{i}";
var bitValue = (entry.DigitalData & (1u << i)) != 0 ? 1.0 : 0.0;
batch.Add(new DataSample
{
LoggingSessionID = session.ID,
ChannelName = channelName,
DeviceName = deviceName,
DeviceSerialNo = deviceSerialNo,
Color = channelColors.GetValueOrDefault(channelName, "#757575"),
Type = ChannelType.Digital,
Value = bitValue,
TimestampTicks = entry.Timestamp.Ticks
});
}

// Flush batch when full
if (batch.Count >= BatchSize)
{
await FlushBatchAsync(batch, ct);
samplesProcessed += batch.Count;
batch.Clear();
progress?.Report(new ImportProgress(samplesProcessed, null));
}
}

// Flush remaining samples
if (batch.Count > 0)
{
await FlushBatchAsync(batch, ct);
samplesProcessed += batch.Count;
batch.Clear();
progress?.Report(new ImportProgress(samplesProcessed, null));
}

if (samplesProcessed == 0)
{
_logger.Warning(
$"No samples found in SD card file '{logSession.FileName}'. " +
$"DeviceConfig present: {config != null}");
}

_logger.Information($"Imported {samplesProcessed} samples for session '{session.Name}' (ID={session.ID})");
return session;
}

private LoggingSession CreateSession(SdCardLogSession logSession, ImportOptions options)
{
using var context = _loggingContext.CreateDbContext();

var sessionName = options.SessionNameOverride
?? $"SD Import - {Path.GetFileNameWithoutExtension(logSession.FileName)}";

// Check for existing session with same name
if (options.OverwriteExistingSession)
{
var existing = context.Sessions.FirstOrDefault(s => s.Name == sessionName);
if (existing != null)
{
context.Sessions.Remove(existing);
context.SaveChanges();
}
}

// Generate new session ID (same pattern as LoggingManager.OnActiveChanged)
var ids = context.Sessions.AsNoTracking().Select(s => s.ID).ToList();
var newId = ids.Count > 0 ? ids.Max() + 1 : 0;

var session = new LoggingSession(newId, sessionName)
{
SessionStart = logSession.FileCreatedDate ?? DateTime.Now
};

context.Sessions.Add(session);
context.SaveChanges();

return session;
}

private async Task FlushBatchAsync(List<DataSample> batch, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();

using var context = _loggingContext.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
context.BulkInsert(batch);
transaction.Commit();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

7. Partial import persisted 🐞 Bug ⛯ Reliability

SdCardSessionImporter persists a new LoggingSession before inserting samples and flushes samples in
separate transactions without rollback/cleanup, so exceptions or cancellations can leave
empty/partial sessions and partial sample sets in SQLite.
Agent Prompt
### Issue description
SD card import can leave the database in a partially-imported state (session row created, some sample batches committed) when parsing/insertion fails or is cancelled.

### Issue Context
`CreateSession()` saves immediately, and each batch insert commits independently with no top-level transaction or compensating cleanup.

### Fix Focus Areas
- Daqifi.Desktop/Loggers/SdCardSessionImporter.cs[156-323]
- Daqifi.Desktop/ViewModels/DeviceLogsViewModel.cs[182-295]
- Daqifi.Desktop/ViewModels/DaqifiViewModel.cs[1188-1240]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

tylerkron and others added 3 commits March 15, 2026 20:16
… in cleanup log

- Add XML documentation to RefreshSdCardFiles, UpdateSdCardFiles,
  DownloadSdCardFileAsync, and DeleteSdCardFileAsync
- Log full exception (not just Message) when temp file cleanup fails,
  preserving the stack trace for diagnostics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Always pull Daqifi.Core from NuGet; remove the DaqifiCoreProjectPath
conditional switch and local project reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tage scaling

- Update Daqifi.Core to v0.19.2 (fixes raw ADC scaling, timestamp
  reconstruction, and ConfigurationOverride merge behavior)
- SdCardSessionImporter.ImportFromDeviceAsync now builds a
  ConfigurationOverride from the connected device's channel data
  (calibration, resolution, port range, internal scale) so the parser
  can convert raw ADC values to real voltages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

📊 Code Coverage Report

Summary

Summary
Generated on: 3/16/2026 - 3:40:12 AM
Coverage date: 3/16/2026 - 3:39:45 AM - 3/16/2026 - 3:40:08 AM
Parser: MultiReport (4x Cobertura)
Assemblies: 3
Classes: 106
Files: 137
Line coverage: 19.1% (1093 of 5700)
Covered lines: 1093
Uncovered lines: 4607
Coverable lines: 5700
Total lines: 17674
Branch coverage: 17.8% (368 of 2059)
Covered branches: 368
Total branches: 2059
Method coverage: Feature is only available for sponsors

Coverage

DAQiFi - 18.8%
Name Line Branch
DAQiFi 18.8% 17.7%
Daqifi.Desktop.App 2.3% 0%
Daqifi.Desktop.Channel.AbstractChannel 40.9% 27.7%
Daqifi.Desktop.Channel.AnalogChannel 58.7% 25%
Daqifi.Desktop.Channel.Channel 11.5% 0%
Daqifi.Desktop.Channel.ChannelColorManager 100% 100%
Daqifi.Desktop.Channel.DataSample 90.4%
Daqifi.Desktop.Channel.DigitalChannel 65.2% 25%
Daqifi.Desktop.Commands.CompositeCommand 0% 0%
Daqifi.Desktop.Commands.HostCommands 0%
Daqifi.Desktop.Commands.WeakEventHandlerManager 0% 0%
Daqifi.Desktop.Configuration.FirewallConfiguration 90.6% 66.6%
Daqifi.Desktop.Configuration.WindowsFirewallWrapper 64% 68.4%
Daqifi.Desktop.ConnectionManager 43% 43.1%
Daqifi.Desktop.Converters.BoolToActiveStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToConnectionStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToStatusColorConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToColorConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToUsbConverter 0% 0%
Daqifi.Desktop.Converters.InvertedBoolToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.ListToStringConverter 0% 0%
Daqifi.Desktop.Converters.NotNullToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.OxyColorToBrushConverter 0% 0%
Daqifi.Desktop.Converters.StringRightConverter 0% 0%
Daqifi.Desktop.Device.AbstractStreamingDevice 46.1% 43.4%
Daqifi.Desktop.Device.DeviceMessage 0%
Daqifi.Desktop.Device.Firmware.BootloaderSessionStreamingDeviceAdapter 0% 0%
Daqifi.Desktop.Device.Firmware.WifiPromptDelayProcessRunner 0% 0%
Daqifi.Desktop.Device.NativeMethods 100%
Daqifi.Desktop.Device.SerialDevice.SerialStreamingDevice 27.6% 30.8%
Daqifi.Desktop.Device.WiFiDevice.DaqifiStreamingDevice 40.9% 39.4%
Daqifi.Desktop.DialogService.DialogService 0% 0%
Daqifi.Desktop.DialogService.ServiceLocator 0% 0%
Daqifi.Desktop.DuplicateDeviceCheckResult 100%
Daqifi.Desktop.Exporter.OptimizedLoggingSessionExporter 29.7% 32.9%
Daqifi.Desktop.Exporter.SampleData 0%
Daqifi.Desktop.Helpers.BooleanConverter`1 0% 0%
Daqifi.Desktop.Helpers.BooleanToInverseBoolConverter 0% 0%
Daqifi.Desktop.Helpers.BooleanToVisibilityConverter 0%
Daqifi.Desktop.Helpers.EnumDescriptionConverter 100% 100%
Daqifi.Desktop.Helpers.IntToVisibilityConverter 0% 0%
Daqifi.Desktop.Helpers.MyMultiValueConverter 0%
Daqifi.Desktop.Helpers.NaturalSortHelper 100% 100%
Daqifi.Desktop.Helpers.VersionHelper 98.2% 66.2%
Daqifi.Desktop.Logger.DatabaseLogger 0% 0%
Daqifi.Desktop.Logger.LoggedSeriesLegendItem 0% 0%
Daqifi.Desktop.Logger.LoggingContext 0%
Daqifi.Desktop.Logger.LoggingManager 0% 0%
Daqifi.Desktop.Logger.LoggingSession 26.6% 0%
Daqifi.Desktop.Logger.PlotLogger 0% 0%
Daqifi.Desktop.Logger.SummaryLogger 0% 0%
Daqifi.Desktop.Logger.TimestampGapDetector 100% 100%
Daqifi.Desktop.Loggers.ImportOptions 0%
Daqifi.Desktop.Loggers.ImportProgress 0% 0%
Daqifi.Desktop.Loggers.SdCardSessionImporter 0% 0%
Daqifi.Desktop.MainWindow 0% 0%
Daqifi.Desktop.Migrations.InitialSQLiteMigration 0%
Daqifi.Desktop.Migrations.LoggingContextModelSnapshot 0%
Daqifi.Desktop.Models.AddProfileModel 0%
Daqifi.Desktop.Models.DaqifiSettings 84% 90%
Daqifi.Desktop.Models.DebugDataCollection 6.6% 0%
Daqifi.Desktop.Models.DebugDataModel 0% 0%
Daqifi.Desktop.Models.Notifications 0%
Daqifi.Desktop.Models.SdCardFile 0% 0%
Daqifi.Desktop.Services.WindowsPrincipalAdminChecker 0%
Daqifi.Desktop.Services.WpfMessageBoxService 0%
Daqifi.Desktop.UpdateVersion.VersionNotification 0% 0%
Daqifi.Desktop.View.AddChannelDialog 0% 0%
Daqifi.Desktop.View.AddProfileConfirmationDialog 0% 0%
Daqifi.Desktop.View.AddprofileDialog 0% 0%
Daqifi.Desktop.View.ConnectionDialog 0% 0%
Daqifi.Desktop.View.DebugWindow 0% 0%
Daqifi.Desktop.View.DeviceLogsView 0% 0%
Daqifi.Desktop.View.DuplicateDeviceDialog 0% 0%
Daqifi.Desktop.View.ErrorDialog 0% 0%
Daqifi.Desktop.View.ExportDialog 0% 0%
Daqifi.Desktop.View.FirmwareDialog 0% 0%
Daqifi.Desktop.View.Flyouts.ChannelsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.DevicesFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.FirmwareFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.LiveGraphFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.LoggedSessionFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.NotificationsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.SummaryFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.UpdateProfileFlyout 0% 0%
Daqifi.Desktop.View.SelectColorDialog 0% 0%
Daqifi.Desktop.View.SettingsDialog 0% 0%
Daqifi.Desktop.View.SuccessDialog 0% 0%
Daqifi.Desktop.ViewModels.AddChannelDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.AddProfileConfirmationDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.AddProfileDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.ConnectionDialogViewModel 21.6% 19.5%
Daqifi.Desktop.ViewModels.DaqifiViewModel 16.2% 10%
Daqifi.Desktop.ViewModels.DeviceLogsViewModel 0% 0%
Daqifi.Desktop.ViewModels.DeviceSettingsViewModel 0% 0%
Daqifi.Desktop.ViewModels.DuplicateDeviceDialogViewModel 0%
Daqifi.Desktop.ViewModels.ErrorDialogViewModel 0%
Daqifi.Desktop.ViewModels.ExportDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.FirmwareDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.SelectColorDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.SettingsViewModel 0%
Daqifi.Desktop.ViewModels.SuccessDialogViewModel 85.7%
Daqifi.Desktop.WindowViewModelMapping.IWindowViewModelMappingsContract 0%
Daqifi.Desktop.WindowViewModelMapping.WindowViewModelMappings 0%
Daqifi.Desktop.Common - 39.3%
Name Line Branch
Daqifi.Desktop.Common 39.3% 27.7%
Daqifi.Desktop.Common.Loggers.AppLogger 36.8% 27.7%
Daqifi.Desktop.Common.Loggers.NoOpLogger 75%
Daqifi.Desktop.IO - 100%
Name Line Branch
Daqifi.Desktop.IO 100% ****
Daqifi.Desktop.IO.Messages.MessageEventArgs`1 100%

Coverage report generated by ReportGeneratorView full report in build artifacts

@tylerkron tylerkron merged commit 2431bce into main Mar 16, 2026
2 checks passed
@tylerkron tylerkron deleted the feature/sd-card-import branch March 16, 2026 03:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Import SD card logging sessions into application

1 participant