diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022RaceReplaySource.cs b/GamesDat/Telemetry/Sources/Formula1/F12022RaceReplaySource.cs new file mode 100644 index 0000000..652116a --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022RaceReplaySource.cs @@ -0,0 +1,47 @@ +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// File watcher source for F1 2022 race replay files. + /// Monitors: Documents\My Games\F1 22\replays + /// + public class F12022RaceReplaySource : F1RaceReplaySourceBase + { + public F12022RaceReplaySource(FileWatcherOptions options) + : base(ApplyDefaults(options)) + { + } + + public F12022RaceReplaySource(string? customPath = null) + : base(CreateDefaultOptions(customPath ?? GetDefaultReplayPath())) + { + } + + public static string GetDefaultReplayPath() => GetReplayPathForYear("22"); + + /// + /// Ensure options has a path set, defaulting to F1 22 if not provided + /// + private static FileWatcherOptions EnsurePath(FileWatcherOptions options) + { + if (string.IsNullOrEmpty(options.Path)) + { + return new FileWatcherOptions + { + Path = GetDefaultReplayPath(), + Patterns = options.Patterns, + IncludeSubdirectories = options.IncludeSubdirectories, + DebounceDelay = options.DebounceDelay + }; + } + return options; + } + + /// + /// Apply F1 22-specific defaults. Used by test discovery. + /// + private static new FileWatcherOptions ApplyDefaults(FileWatcherOptions options) + { + return F1RaceReplaySourceBase.ApplyDefaults(EnsurePath(options)); + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023RaceReplaySource.cs b/GamesDat/Telemetry/Sources/Formula1/F12023RaceReplaySource.cs new file mode 100644 index 0000000..d46325e --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023RaceReplaySource.cs @@ -0,0 +1,47 @@ +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// File watcher source for F1 2023 race replay files. + /// Monitors: Documents\My Games\F1 23\replays + /// + public class F12023RaceReplaySource : F1RaceReplaySourceBase + { + public F12023RaceReplaySource(FileWatcherOptions options) + : base(ApplyDefaults(options)) + { + } + + public F12023RaceReplaySource(string? customPath = null) + : base(CreateDefaultOptions(customPath ?? GetDefaultReplayPath())) + { + } + + public static string GetDefaultReplayPath() => GetReplayPathForYear("23"); + + /// + /// Ensure options has a path set, defaulting to F1 23 if not provided + /// + private static FileWatcherOptions EnsurePath(FileWatcherOptions options) + { + if (string.IsNullOrEmpty(options.Path)) + { + return new FileWatcherOptions + { + Path = GetDefaultReplayPath(), + Patterns = options.Patterns, + IncludeSubdirectories = options.IncludeSubdirectories, + DebounceDelay = options.DebounceDelay + }; + } + return options; + } + + /// + /// Apply F1 23-specific defaults. Used by test discovery. + /// + private static new FileWatcherOptions ApplyDefaults(FileWatcherOptions options) + { + return F1RaceReplaySourceBase.ApplyDefaults(EnsurePath(options)); + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024RaceReplaySource.cs b/GamesDat/Telemetry/Sources/Formula1/F12024RaceReplaySource.cs new file mode 100644 index 0000000..a685f40 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024RaceReplaySource.cs @@ -0,0 +1,47 @@ +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// File watcher source for F1 2024 race replay files. + /// Monitors: Documents\My Games\F1 24\replays + /// + public class F12024RaceReplaySource : F1RaceReplaySourceBase + { + public F12024RaceReplaySource(FileWatcherOptions options) + : base(ApplyDefaults(options)) + { + } + + public F12024RaceReplaySource(string? customPath = null) + : base(CreateDefaultOptions(customPath ?? GetDefaultReplayPath())) + { + } + + public static string GetDefaultReplayPath() => GetReplayPathForYear("24"); + + /// + /// Ensure options has a path set, defaulting to F1 24 if not provided + /// + private static FileWatcherOptions EnsurePath(FileWatcherOptions options) + { + if (string.IsNullOrEmpty(options.Path)) + { + return new FileWatcherOptions + { + Path = GetDefaultReplayPath(), + Patterns = options.Patterns, + IncludeSubdirectories = options.IncludeSubdirectories, + DebounceDelay = options.DebounceDelay + }; + } + return options; + } + + /// + /// Apply F1 24-specific defaults. Used by test discovery. + /// + private static new FileWatcherOptions ApplyDefaults(FileWatcherOptions options) + { + return F1RaceReplaySourceBase.ApplyDefaults(EnsurePath(options)); + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025RaceReplaySource.cs b/GamesDat/Telemetry/Sources/Formula1/F12025RaceReplaySource.cs new file mode 100644 index 0000000..905e589 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025RaceReplaySource.cs @@ -0,0 +1,47 @@ +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// File watcher source for F1 2025 race replay files. + /// Monitors: Documents\My Games\F1 25\replays + /// + public class F12025RaceReplaySource : F1RaceReplaySourceBase + { + public F12025RaceReplaySource(FileWatcherOptions options) + : base(ApplyDefaults(options)) + { + } + + public F12025RaceReplaySource(string? customPath = null) + : base(CreateDefaultOptions(customPath ?? GetDefaultReplayPath())) + { + } + + public static string GetDefaultReplayPath() => GetReplayPathForYear("25"); + + /// + /// Ensure options has a path set, defaulting to F1 25 if not provided + /// + private static FileWatcherOptions EnsurePath(FileWatcherOptions options) + { + if (string.IsNullOrEmpty(options.Path)) + { + return new FileWatcherOptions + { + Path = GetDefaultReplayPath(), + Patterns = options.Patterns, + IncludeSubdirectories = options.IncludeSubdirectories, + DebounceDelay = options.DebounceDelay + }; + } + return options; + } + + /// + /// Apply F1 25-specific defaults. Used by test discovery. + /// + private static new FileWatcherOptions ApplyDefaults(FileWatcherOptions options) + { + return F1RaceReplaySourceBase.ApplyDefaults(EnsurePath(options)); + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RaceReplaySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RaceReplaySource.cs new file mode 100644 index 0000000..97ffaec --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1RaceReplaySource.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Linq; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// File watcher source for F1 race replay files. + /// Defaults to F1 25, but can auto-detect the latest installed F1 game. + /// For monitoring specific years or multiple years simultaneously, use year-specific sources: + /// F12025RaceReplaySource, F12024RaceReplaySource, F12023RaceReplaySource, F12022RaceReplaySource + /// + public class F1RaceReplaySource : F1RaceReplaySourceBase + { + public F1RaceReplaySource(FileWatcherOptions options) + : base(ApplyDefaults(options)) + { + } + + /// + /// Ensure options has a path set, defaulting to F1 25 if not provided + /// + private static FileWatcherOptions EnsurePath(FileWatcherOptions options) + { + if (string.IsNullOrEmpty(options.Path)) + { + return new FileWatcherOptions + { + Path = F12025RaceReplaySource.GetDefaultReplayPath(), + Patterns = options.Patterns, + IncludeSubdirectories = options.IncludeSubdirectories, + DebounceDelay = options.DebounceDelay + }; + } + return options; + } + + /// + /// Apply F1 defaults. Used by test discovery. + /// + private static new FileWatcherOptions ApplyDefaults(FileWatcherOptions options) + { + return F1RaceReplaySourceBase.ApplyDefaults(EnsurePath(options)); + } + + /// + /// Create F1 replay source with optional custom path and detection strategy + /// + /// Custom replay folder path. If provided, autoDetectLatestInstalled is ignored. + /// If true and customPath is null, auto-detects the latest installed F1 game (checks 25, 24, 23, 22). If false, defaults to F1 25. + public F1RaceReplaySource(string? customPath = null, bool autoDetectLatestInstalled = false) + : base(CreateDefaultOptions(ResolveReplayPath(customPath, autoDetectLatestInstalled))) + { + } + + /// + /// Get the default F1 replay folder path (F1 25) + /// + public static string GetDefaultReplayPath() => F12025RaceReplaySource.GetDefaultReplayPath(); + + /// + /// Auto-detect the latest installed F1 game's replay folder. + /// Checks F1 25, F1 24, F1 23, F1 22 in order and returns the first that exists. + /// Note: This checks for folder existence, which may include cases where the game + /// is uninstalled but replay folders remain. + /// + public static string GetLatestInstalledReplayPath() + { + var years = new[] { "25", "24", "23", "22" }; + var existingPath = years + .Select(GetReplayPathForYear) + .FirstOrDefault(Directory.Exists); + + // Default to F1 25 even if it doesn't exist + return existingPath ?? GetReplayPathForYear("25"); + } + + private static string ResolveReplayPath(string? customPath, bool autoDetectLatestInstalled) + { + if (customPath != null) + { + return customPath; + } + + if (autoDetectLatestInstalled) + { + return GetLatestInstalledReplayPath(); + } + + return GetDefaultReplayPath(); + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RaceReplaySourceBase.cs b/GamesDat/Telemetry/Sources/Formula1/F1RaceReplaySourceBase.cs new file mode 100644 index 0000000..f459d5f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1RaceReplaySourceBase.cs @@ -0,0 +1,77 @@ +using System; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// Abstract base class for F1 race replay file sources across different game years. + /// Provides common functionality for monitoring F1 replay directories. + /// + public abstract class F1RaceReplaySourceBase : FileWatcherSourceBase + { + /// + /// The default debounce delay used by FileWatcherOptions (1 second). + /// Note: This mirrors the default in FileWatcherOptions.DebounceDelay. + /// If that default changes, this constant should be updated to match. + /// + private static readonly TimeSpan LibraryDefaultDebounceDelay = TimeSpan.FromSeconds(1); + + /// + /// The F1-specific debounce delay (2 seconds) + /// + private static readonly TimeSpan F1DebounceDelay = TimeSpan.FromSeconds(2); + + protected F1RaceReplaySourceBase(FileWatcherOptions options) : base(options) + { + } + + /// + /// Build the full replay path for a specific F1 game year + /// + protected static string GetReplayPathForYear(string gameYear) + { + var documentsFolder = Environment.GetFolderPath( + Environment.SpecialFolder.MyDocuments, + Environment.SpecialFolderOption.DoNotVerify); + return System.IO.Path.Combine(documentsFolder, "My Games", $"F1 {gameYear}", "replays"); + } + + /// + /// Create default FileWatcherOptions for F1 replay monitoring + /// + protected static FileWatcherOptions CreateDefaultOptions(string path) + { + return new FileWatcherOptions + { + Path = path, + Patterns = new[] { "*.frr" }, + IncludeSubdirectories = false, + DebounceDelay = F1DebounceDelay + }; + } + + /// + /// Apply F1-specific defaults to options. Requires Path to be non-empty. + /// + protected static FileWatcherOptions ApplyDefaults(FileWatcherOptions options) + { + if (string.IsNullOrEmpty(options.Path)) + { + throw new ArgumentException("FileWatcherOptions.Path cannot be null or empty. Provide a path before calling ApplyDefaults.", nameof(options)); + } + + return new FileWatcherOptions + { + Path = options.Path, + Patterns = options.Patterns == null || options.Patterns.Length == 0 + ? new[] { "*.frr" } + : options.Patterns, + IncludeSubdirectories = options.IncludeSubdirectories, + // Check for both default (00:00:00, used when constructed via string path) + // and LibraryDefaultDebounceDelay (1s, used when constructed via options without explicit delay) + DebounceDelay = options.DebounceDelay == default || options.DebounceDelay == LibraryDefaultDebounceDelay + ? F1DebounceDelay + : options.DebounceDelay + }; + } + } +}