Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions GamesDat.Demo.Wpf/ViewModels/FileWatcherTabViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using GamesDat.Core.Telemetry.Sources.AgeOfEmpires4;

namespace GamesDat.Demo.Wpf.ViewModels;

Expand Down Expand Up @@ -323,6 +324,33 @@ private void InitializeBuiltInSources()
null);
Sources.Add(brawlhallaSource);
}

// Add AoE IV source
try
{
var aoe4Path = AgeOfEmpires4ReplayFileSource.GetDefaultReplayPath();
System.Diagnostics.Debug.WriteLine($"AoE IV replay path: {aoe4Path}");
var aoe4Source = new FileWatcherSourceViewModel(
"Age of Empires IV",
aoe4Path,
"*.*",
() => new AgeOfEmpires4ReplayFileSource());
aoe4Source.DetectedFiles.CollectionChanged += OnSourceFilesChanged;
Sources.Add(aoe4Source);
}
catch (DirectoryNotFoundException)
{
Comment on lines +329 to +342

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

The new AoE IV block relies on catching DirectoryNotFoundException, but GetDefaultReplayPath() only builds a path string and won’t throw. As a result, the “(Not Installed)” source will never be added; consider checking Directory.Exists(aoe4Path) (or using the same PathExists logic used by FileWatcherSourceViewModel) instead of try/catch if you want to present an installed/not-installed state.

Copilot uses AI. Check for mistakes.
// Game not installed - add disabled source
var defaultPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
"Age of Empires IV", "playback");
var aoe4Source = new FileWatcherSourceViewModel(
"Age of Empires IV (Not Installed)",
defaultPath,
"*.*",
null);
Comment on lines +344 to +351

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

The fallback defaultPath in the AoE IV catch block does not match AgeOfEmpires4ReplayFileSource.GetDefaultReplayPath() (it omits the "My Games" segment). If this path is shown to users, it will be incorrect; reuse GetDefaultReplayPath() or build the same path segments here for consistency.

Copilot uses AI. Check for mistakes.
Sources.Add(aoe4Source);
}
}

private void OnSourceFilesChanged(object? sender, NotifyCollectionChangedEventArgs e)
Expand Down
17 changes: 15 additions & 2 deletions GamesDat.Tests/Helpers/FileWatcherTestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using GamesDat.Core.Telemetry.Sources;
using GamesDat.Core.Telemetry.Sources.AgeOfEmpires4;
using GamesDat.Core.Telemetry.Sources.Tekken8;

namespace GamesDat.Tests.Helpers;
Expand All @@ -12,6 +13,16 @@ namespace GamesDat.Tests.Helpers;
/// </summary>
public class FileWatcherTestData
{

/// <summary>
/// Sources we currently explicitly ignore during testing ebcause they use an

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

Typo in XML doc: “ebcause” should be “because”.

Suggested change
/// Sources we currently explicitly ignore during testing ebcause they use an
/// Sources we currently explicitly ignore during testing because they use an

Copilot uses AI. Check for mistakes.
/// overly broad pattern ("*.*") and require special handling to avoid test pollution.
/// </summary>
public static Type[] IgnoredSources = [
typeof(Tekken8ReplayFileSource),
typeof(AgeOfEmpires4ReplayFileSource)
];
Comment thread
codegefluester marked this conversation as resolved.

/// <summary>
/// Returns all discovered file watcher sources as test case data.
/// Each test case includes: Type sourceType, string[] patterns
Expand All @@ -23,9 +34,11 @@ public static IEnumerable<object[]> AllSources()

foreach (var sourceType in sources)
{
// Skip Tekken8 (uses wildcard pattern "*.*" with subdirectories - needs special handling)
if (sourceType == typeof(Tekken8ReplayFileSource))
if (IgnoredSources.Contains(sourceType))
{
Console.WriteLine($"Skipping source {sourceType.Name} in AllSources test data due to broad pattern and special handling requirements.");
continue;
}

var patterns = FileWatcherSourceDiscovery.GetExpectedPatterns(sourceType);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
Comment on lines +2 to +5

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

This file includes several unused using directives (System.Collections.Generic, System.Linq, System.Text, System.Threading.Tasks). Removing them will reduce noise and avoid analyzer warnings.

Suggested change
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

Copilot uses AI. Check for mistakes.

namespace GamesDat.Core.Telemetry.Sources.AgeOfEmpires4
{
public class AgeOfEmpires4ReplayFileSource : FileWatcherSourceBase
{
public AgeOfEmpires4ReplayFileSource(FileWatcherOptions options) : base(ApplyDefaults(options))
{
}

public AgeOfEmpires4ReplayFileSource()
: base(
path: GetDefaultReplayPath(),
pattern: "*.*",
includeSubdirectories: false,
debounceDelay: TimeSpan.FromSeconds(2)
)
{
}

public static string GetDefaultReplayPath()
{
var documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments, Environment.SpecialFolderOption.DoNotVerify);
return System.IO.Path.Combine(documentsFolder, "My Games", "Age of Empires IV", "playback");
}

/// <summary>
/// Applies default configuration options if not specified.
/// </summary>
/// <param name="options">The input options.</param>
/// <returns>Options with defaults applied.</returns>
private static FileWatcherOptions ApplyDefaults(FileWatcherOptions options)
{
return new FileWatcherOptions
{
Path = string.IsNullOrEmpty(options.Path) ? GetDefaultReplayPath() : options.Path,
Patterns = options.Patterns == null || options.Patterns.Length == 0
? ["*.*"]
: options.Patterns,
IncludeSubdirectories = options.IncludeSubdirectories,
DebounceDelay = options.DebounceDelay == default
? TimeSpan.FromSeconds(2)
: options.DebounceDelay
};
}
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Out of the box:

| Game | Replay/Demo files | Realtime data | Tested |
| -------------------------- | ------------------------- | ------------------------------- | ------ |
| Age of Empires IV | ✅ | ❌ | ⏳ |
| Assetto Corsa Competizione | ❌ | ✅ Physics, Graphics, Telemetry | ⏳ |
| Brawlhalla | ✅ | ❌ | ✅ |
| Rocket League | ✅ | ❌ | ✅ |
Expand Down
Loading