diff --git a/Stars Assistant/CSV/PlanetLoader.cs b/Stars Assistant/CSV/PlanetLoader.cs new file mode 100644 index 0000000..496d27a --- /dev/null +++ b/Stars Assistant/CSV/PlanetLoader.cs @@ -0,0 +1,87 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using CsvHelper.Configuration; +using CsvHelper; +using System.Globalization; +using Splat; + +namespace StarsAssistant.CSV; + +public class PlanetLoader +{ + /// + /// Reference to the game metadata, retrieved by DI + /// + protected Services.Game Game = Locator.Current.GetService()!; + + /// + /// CSV Configuration to use for importing Planet files. + /// + protected CsvConfiguration CsvConfig; + + /// + /// Construction + /// + public PlanetLoader() + { + CreateConfig(); + } + + /// + /// Creates the CSV configuration for planets. + /// + [MemberNotNull(nameof(CsvConfig))] + protected void CreateConfig() + { + CsvConfig = CsvConfiguration.FromAttributes(CultureInfo.InvariantCulture); + CsvConfig.Delimiter = "\t"; + CsvConfig.Escape = '\0'; + CsvConfig.MissingFieldFound = null; + } + + /// + /// Import the planet file for the given Race. + /// + /// Import File + public void ImportForRace(Model.Race race) + { + using (var db = Locator.Current.GetService()!) + using (var reader = new StreamReader(Game.PlanetFileForRace(race), System.Text.Encoding.Latin1)) + using (var csv = new CsvReader(reader, CsvConfig)) + { + List records = csv.GetRecords().ToList(); + var planetByPlayer = from csvp in records + group csvp by csvp.Owner into byOwners + select byOwners; + + foreach (var owner in planetByPlayer) + { + Model.Race? r; + if (owner.Key == race.Name) + { + r = race; + } + else + { + r = db.Race.Find(owner.Key); + if (r == null) + { + r = new() + { + Name = owner.Key + }; + db.Add(r); + } + } + + foreach (Planet csvp in owner) + { + Model.Planet p = new Model.Planet{ Name = csvp.Name }; + csvp.UpdateDbPlanet(p); + db.Add(p); + } + db.SaveChanges(); + } + } + } +} diff --git a/Stars Assistant/Helpers/RxExtensions.cs b/Stars Assistant/Helpers/RxExtensions.cs index 92df866..e8d4a17 100644 --- a/Stars Assistant/Helpers/RxExtensions.cs +++ b/Stars Assistant/Helpers/RxExtensions.cs @@ -35,4 +35,23 @@ public static class RxExtensions return src.Buffer(zeroCrossings); } + /// + /// Throttle a FileSystemObserver and eliminate all duplicates. + /// + /// + /// Curtesy of https://endjin.com/blog/2024/05/observe-file-system-changes-with-rx-dotnet + /// + /// An observer created with ObserveFileSystem + /// Throttling window, defaults to 1 second. + /// Scheduler to user for throttling, default is set by Quiescent + /// A throttled observer with duplicate elimination. + public static IObservable> ThrottleAndDistinct ( + this IObservable watcher, + int inactivitySeconds = 1, + IScheduler? scheduler = null) + { + return watcher + .Quiescent(TimeSpan.FromSeconds(inactivitySeconds), scheduler) + .Select(changes => changes.DistinctBy(x => (x.ChangeType, x.FullPath))); + } } diff --git a/Stars Assistant/Model/Game.cs b/Stars Assistant/Model/Game.cs deleted file mode 100644 index 0222ce9..0000000 --- a/Stars Assistant/Model/Game.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Globalization; - -namespace StarsAssistant.Model; - -public class Game (string gamePath, string baseName) -{ - /// - /// The base path in which all game files reside. - /// - public string GamePath { get; private set; } = gamePath; - - /// - /// The base name without extensions of your game, inside the GamePath folder. - /// All dependant files are resolved using this name. - /// - public string BaseName { get; private set; } = baseName; - - /// - /// The number of the player, for example identifying the pxx planet file. - /// - public int PlayerId { get; private set; } - - /// - /// Combine into the DatabaseName - /// - public string DatabaseName => Path.Combine(GamePath, $"{BaseName}.sqlite"); - - /// - /// Search for Planet files using this pattern, will give you all pxx files. - /// - public string PlanetFileSearchPattern => Path.Combine(GamePath, $"{BaseName}.p*"); - -} diff --git a/Stars Assistant/Model/Race.cs b/Stars Assistant/Model/Race.cs index c6fc157..c954fec 100644 --- a/Stars Assistant/Model/Race.cs +++ b/Stars Assistant/Model/Race.cs @@ -22,6 +22,8 @@ public class Race public bool PlayerRace { get; set; } = false; + public int? PlayerFileId { get; set; } + public int? ColonistsPerResource { get; set; } public int? GrowthRatePercent { get; set; } diff --git a/Stars Assistant/Model/StarsDatabase.cs b/Stars Assistant/Model/StarsDatabase.cs index 58bc17d..0181803 100644 --- a/Stars Assistant/Model/StarsDatabase.cs +++ b/Stars Assistant/Model/StarsDatabase.cs @@ -37,7 +37,7 @@ public class StarsDatabase(string DbPath) : DbContext /// /// The record with all game metadata, will only contain a single line at all times. /// - public DbSet Game { get; set; } + // public DbSet Game { get; set; } #endregion } \ No newline at end of file diff --git a/Stars Assistant/Program.cs b/Stars Assistant/Program.cs index 22a9089..db574d2 100644 --- a/Stars Assistant/Program.cs +++ b/Stars Assistant/Program.cs @@ -21,11 +21,16 @@ sealed class Program public static void Main(string[] args) { ModeDetector.OverrideModeDetector(Splat.ModeDetection.Mode.Run); - - Locator.CurrentMutable.Register( - () => new StarsDatabase("stars.sqlite"), - typeof(StarsDatabase) - ); + + Services.Game g = new() + { + BaseName = "GOINGTH", + GamePath = "/home/torben/goingth/" + }; + + Locator.CurrentMutable.RegisterConstant(g, typeof(Services.Game)); + Locator.CurrentMutable.RegisterConstant(new Services.CSVDataLoader(), typeof(Services.CSVDataLoader)); + Locator.CurrentMutable.Register(() => new StarsDatabase(g.DatabaseFileName), typeof(StarsDatabase)); __createTestData(); @@ -44,6 +49,7 @@ sealed class Program public static void __createTestData () { + Services.Game game = Locator.Current.GetService()!; using var db = Locator.Current.GetService()!; db.Database.EnsureDeleted(); @@ -56,6 +62,7 @@ sealed class Program { Name = "Atlantis", PlayerRace = true, + PlayerFileId = 1, ColonistsPerResource = 1000, GrowthRatePercent = 19, PRT = PRT.Other, @@ -72,40 +79,8 @@ sealed class Program db.SaveChanges(); Race.Player = r; - var config = CsvConfiguration.FromAttributes(CultureInfo.InvariantCulture); - config.Delimiter = "\t"; - config.Escape = '\0'; - config.MissingFieldFound = null; - - using (var reader = new StreamReader("/home/torben/goingth/GOINGTH.p1", System.Text.Encoding.Latin1)) - using (var csv = new CsvReader(reader, config)) - { - List records = csv.GetRecords().ToList(); - var planetByPlayer = from csvp in records - group csvp by csvp.Owner into byOwners - select byOwners; - - foreach (var owner in planetByPlayer) - { - if (owner.Key != "Atlantis") - { - r = new() - { - Name = owner.Key - }; - db.Add(r); - db.SaveChanges(); - } - - foreach (CSV.Planet csvp in owner) - { - Planet p = new Planet{ Name = csvp.Name }; - csvp.UpdateDbPlanet(p); - db.Add(p); - } - db.SaveChanges(); - } - } + var loader = new CSV.PlanetLoader(); + loader.ImportForRace(Race.Player); } } diff --git a/Stars Assistant/Services/CSVDataLoader.cs b/Stars Assistant/Services/CSVDataLoader.cs index 14d512e..946d9ea 100644 --- a/Stars Assistant/Services/CSVDataLoader.cs +++ b/Stars Assistant/Services/CSVDataLoader.cs @@ -1,20 +1,41 @@ using System; -using CsvHelper.Configuration; -using CsvHelper; +using System.IO; +using System.Reactive.Linq; using Splat; +using StarsAssistant.Helpers; namespace StarsAssistant.Services; -public class CSVDataLoader +public class CSVDataLoader { /// /// Reference to the game metadata, retrieved by DI /// - protected Model.Game game; + protected Services.Game Game = Locator.Current.GetService()!; - public CSVDataLoader() + public void CSVDataLoader() { - game = Locator.Current.GetService()!; + } -} + + public void StartPlanetCSVWatcher() + { + // TODO: which scheduler for Throttle? + var watcher = FsWatcher + .ObserveFileSystem(Game.GamePath, new string[] { Game.PlanetFileSearchPattern }); + .ThrottleAndDistinct(2); + + /* + watcher.Subscribe(x => + { + Console.WriteLine($"{DateTime.Now.ToLongTimeString()} got {x.Count()} events:"); + + foreach (var fsEvent in x) + { + Console.WriteLine($"{DateTime.Now.ToLongTimeString()} {i++} {fsEvent.FullPath} - {fsEvent.ChangeType}"); + } + }); + */ + } +} diff --git a/Stars Assistant/Services/Game.cs b/Stars Assistant/Services/Game.cs new file mode 100644 index 0000000..019af0c --- /dev/null +++ b/Stars Assistant/Services/Game.cs @@ -0,0 +1,33 @@ +namespace StarsAssistant.Services; + +public class Game +{ + /// + /// The base path in which all game files reside. + /// + public required string GamePath { get; set; } + + /// + /// The base name without extensions of your game, inside the GamePath folder. + /// All dependant files are resolved using this name. + /// + public required string BaseName { get; set; } + + /// + /// Combine into the DatabaseName + /// + public string DatabaseFileName => Path.Combine(GamePath, $"{BaseName}.sqlite"); + + /// + /// Search for Planet files using this pattern, will give you all pxx files. + /// + public string PlanetFileSearchPattern => $"{BaseName}.p*"; + + /// + /// Get the name of a planet file for a given race. + /// + /// The race to load. + /// Fully qualified file path. + public string PlanetFileForRace (Model.Race r) => Path.Combine(GamePath, $"{BaseName}.p{r.PlayerFileId}"); + +}