diff --git a/Stars Assistant/App.axaml.cs b/Stars Assistant/App.axaml.cs index f023837..37fa10b 100644 --- a/Stars Assistant/App.axaml.cs +++ b/Stars Assistant/App.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Markup.Xaml; using StarsAssistant.ViewModels; using StarsAssistant.Views; using Splat; +using StarsAssistant.Services; namespace StarsAssistant; @@ -18,15 +19,21 @@ public partial class App : Application { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + Game game = new(desktop.Args); + game.RegisterServicesForGame(); + desktop.MainWindow = new MainWindow { DataContext = new MainWindowViewModel(), }; + + base.OnFrameworkInitializationCompleted(); + + game.StartBackgroundServices(); + } + else + { + throw new InvalidOperationException("This app supports only Desktop mode."); } - - var csvloader = Locator.Current.GetService()!; - csvloader.StartPlanetCSVWatcher(); - - base.OnFrameworkInitializationCompleted(); } -} \ No newline at end of file +} diff --git a/Stars Assistant/CSV/Planet.cs b/Stars Assistant/CSV/Planet.cs index 3e7ce24..099401d 100644 --- a/Stars Assistant/CSV/Planet.cs +++ b/Stars Assistant/CSV/Planet.cs @@ -13,7 +13,7 @@ public class Planet public string? Owner { get; set; } [Index(2)] - public string? StarbaseType { get; set; } + public string StarbaseType { get; set; } = String.Empty; [Index(3)] public int ReportAge { get; set; } = 0; diff --git a/Stars Assistant/Model/Planet.cs b/Stars Assistant/Model/Planet.cs index 75c2c57..b7ab232 100644 --- a/Stars Assistant/Model/Planet.cs +++ b/Stars Assistant/Model/Planet.cs @@ -14,7 +14,7 @@ public class Planet public string? OwnerId { get; set; } - public string? StarbaseType { get; set; } + public string StarbaseType { get; set; } = String.Empty; public int ReportAge { get; set; } = 0; diff --git a/Stars Assistant/Program.cs b/Stars Assistant/Program.cs index 27c4a9e..7db0e56 100644 --- a/Stars Assistant/Program.cs +++ b/Stars Assistant/Program.cs @@ -25,37 +25,6 @@ sealed class Program var logger = new ConsoleLogger() { Level = LogLevel.Debug }; Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger)); - bool newGame = false; - string dbPath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/GOINGTH.sqlite"; - Services.Game gameSvc; - - using (StarsDatabase starsDB = new(dbPath)) - { - if (Path.Exists(dbPath)) - { - Model.Game dbGame = starsDB.Game.First(); - gameSvc = new Services.Game(dbGame); - } - else - { - starsDB.Database.EnsureCreated(); - gameSvc = new() - { - BaseName = "GOINGTH", - GamePath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/" - }; - gameSvc.SaveToDatabase(starsDB); - newGame = true; - } - } - - Locator.CurrentMutable.RegisterConstant(gameSvc, typeof(Services.Game)); - Locator.CurrentMutable.RegisterConstant(new Services.CSVDataLoader(), typeof(Services.CSVDataLoader)); - Locator.CurrentMutable.Register(() => new StarsDatabase(gameSvc.DatabaseFileName), typeof(StarsDatabase)); - - if (newGame) - __createTestData(); - BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); } @@ -68,93 +37,4 @@ sealed class Program .LogToTrace() .UseReactiveUI(); - - public static void __createTestData () - { - using var db = Locator.Current.GetService()!; - - // Note: This sample requires the database to be created before running. - // Console.WriteLine($"Database path: {db.DbPath}."); - - Race r = new() - { - Name = "Atlantis", - PlayerRace = true, - PlayerFileId = 1, - ColonistsPerResource = 1000, - GrowthRatePercent = 19, - PRT = PRT.Other, - HasOBRM = true, - FactoryCost3 = false, - FactoryNumberPer10k = 8, - FactoryResCost = 8, - FactoryResPer10 = 15, - MineResCost = 3, - MineMineralsPer10 = 10, - MineNumberPer10k = 10 - }; - db.Add(r); - db.SaveChanges(); - - var loader = new CSV.PlanetLoader(); - loader.ImportForRace(Services.Game.Player); - } } - - -/* -using Microsoft.EntityFrameworkCore; -using CsvHelper.Configuration; -using CsvHelper; -using StarsAssistant.model; - - -using var db = new StarsDatabase("stars.sqlite"); - -db.Database.EnsureDeleted(); -db.Database.EnsureCreated(); - -// Note: This sample requires the database to be created before running. -Console.WriteLine($"Database path: {db.DbPath}."); - -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", Encoding.Latin1)) -using (var csv = new CsvReader(reader, config)) -{ - List records = csv.GetRecords().ToList(); - foreach (Planet p in records) - { - db.Add(p); - } - db.SaveChanges(); -} -*/ - -/* -// Create -Console.WriteLine("Inserting a new blog"); -db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" }); -db.SaveChanges(); - -// Read -Console.WriteLine("Querying for a blog"); -var blog = db.Blogs - .OrderBy(b => b.BlogId) - .First(); - -// Update -Console.WriteLine("Updating the blog and adding a post"); -blog.Url = "https://devblogs.microsoft.com/dotnet"; -blog.Posts.Add( - new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" }); -db.SaveChanges(); - -// Delete -// Console.WriteLine("Delete the blog"); -// db.Remove(blog); -// db.SaveChanges(); -*/ \ No newline at end of file diff --git a/Stars Assistant/Services/CSVDataLoader.cs b/Stars Assistant/Services/CSVDataLoader.cs index 92687da..408d39f 100644 --- a/Stars Assistant/Services/CSVDataLoader.cs +++ b/Stars Assistant/Services/CSVDataLoader.cs @@ -16,30 +16,58 @@ public partial class CSVDataLoader : IEnableLogger /// protected Services.Game Game = Locator.Current.GetService()!; + /// + /// Regex to match fs watcher results to Stars file types. + /// + /// Precompiled RegEx [GeneratedRegex(@".*\.(?[pf])(?\d)+$")] private static partial Regex MyRegex(); + /// + /// RegEx Instance to match Stars file types. + /// protected Regex FileTypeRegEx = MyRegex(); + /// + /// Instance of the FSWatcher, to which we subscribe. + /// + protected IObservable Watcher; + + /// + /// Active subscription to FsWatcher. + /// + protected IDisposable? Subscription; + + /// + /// Construct the instance and prepare our FS Watcher. + /// public CSVDataLoader() - { - - } - - public void StartPlanetCSVWatcher() { // string[] filters = { "*.p*", "*.f*", "*.map" }; - - var watcher = FsWatcher + Watcher = FsWatcher .ObserveFileSystem(Game.GamePath, [ Game.PlanetFileSearchPattern ]) .ThrottleAndDistinct(2, RxApp.TaskpoolScheduler) .Log(this, $"{DateTime.Now.ToLongTimeString()} FsEvent", fsEvent => $"{fsEvent.FullPath} {fsEvent.ChangeType}") .ObserveOn(RxApp.TaskpoolScheduler); - - watcher.Subscribe(fsEvent => this.LoadPlanetFile(fsEvent.FullPath)); } - protected void LoadPlanetFile(string fileName) + /// + /// Start the CSV watcher, and capture our disposable, so that we can shut + /// down the watcher if needed. + /// + public void StartCSVWatcher() + { + if (Subscription != null) + throw new InvalidOperationException("CSV Watcher is active, can't start it again."); + + Subscription = Watcher.Subscribe(fsEvent => this.LoadFile(fsEvent.FullPath)); + } + + /// + /// Subscription to File Processing, called by our subscription to the watcher. + /// + /// The File to process + protected void LoadFile(string fileName) { Match m = FileTypeRegEx.Match(fileName); if (! m.Success) @@ -50,9 +78,18 @@ public partial class CSVDataLoader : IEnableLogger string type = m.Groups["type"].Value; string player = m.Groups["player"].Value; - this.Log().Debug($"Got file type {type} for player {player}"); - var loader = new CSV.PlanetLoader(); - loader.ImportForRace(Services.Game.Player); + + switch (type) + { + case "p": + var loader = new CSV.PlanetLoader(); + loader.ImportForRace(Services.Game.Player); + break; + + default: + this.Log().Warn($"Planet loader got unknown file type ${type}. Ignoring file."); + break; + } } } diff --git a/Stars Assistant/Services/Game.cs b/Stars Assistant/Services/Game.cs index 1585cc0..795aa54 100644 --- a/Stars Assistant/Services/Game.cs +++ b/Stars Assistant/Services/Game.cs @@ -1,5 +1,6 @@ using System.Reflection.PortableExecutable; using Splat; +using StarsAssistant.Model; namespace StarsAssistant.Services; @@ -22,28 +23,28 @@ public class Game } /// - /// Save this record in the database. Uses the service locator to access the - /// database unless you specify an instance. This is needed during initial - /// game creation, where the services are not yet established. + /// Helper to construct the game from a command line. /// - /// Optional DB instance if services are yet unavailable. - public void SaveToDatabase(Model.StarsDatabase? db = null) + /// The command line to parse. + public Game(string[]? args) { - db ??= Locator.Current.GetService()!; - - Model.Game? dbGame = db.Game.FirstOrDefault(); - if (dbGame == null) - { - dbGame = new Model.Game(); - db.Add(dbGame); - db.SaveChanges(); - } - dbGame.GamePath = GamePath; - dbGame.BaseName = BaseName; - db.Update(dbGame); - db.SaveChanges(); - } + GamePath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/"; + BaseName = "GOINGTH"; + string dbPath = DatabaseFileName; + using StarsDatabase starsDB = new(dbPath); + if (Path.Exists(dbPath)) + { + Model.Game dbGame = starsDB.Game.First(); + } + else + { + starsDB.Database.EnsureCreated(); + SaveToDatabase(starsDB); + __doCreateTestData = true; + } + } + /// /// The base path in which all game files reside. /// @@ -68,9 +69,9 @@ public class Game /// /// Internal helper to lazily load the current player from the DB. /// - private readonly static LazyLazyPlayer = new ( () => { - using var db = Locator.Current.GetService()!; - Model.Race result = db.Race + private readonly static LazyLazyPlayer = new ( () => { + using var db = Locator.Current.GetService()!; + Race result = db.Race .Where(r => r.PlayerRace == true) .First(); return result; @@ -79,13 +80,99 @@ public class Game /// /// Get the Race object for the current player, lazy initialized. /// - public static Model.Race Player => LazyPlayer.Value; + public static Race Player => LazyPlayer.Value; /// /// 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}"); + public string PlanetFileForRace (Race r) => Path.Combine(GamePath, $"{BaseName}.p{r.PlayerFileId}"); + + /// + /// Save this record in the database. Uses the service locator to access the + /// database unless you specify an instance. This is needed during initial + /// game creation, where the services are not yet established. + /// + /// Optional DB instance if services are yet unavailable. + public void SaveToDatabase(StarsDatabase? db = null) + { + db ??= Locator.Current.GetService()!; + + Model.Game? dbGame = db.Game.FirstOrDefault(); + if (dbGame == null) + { + dbGame = new Model.Game(); + db.Add(dbGame); + db.SaveChanges(); + } + dbGame.GamePath = GamePath; + dbGame.BaseName = BaseName; + db.Update(dbGame); + db.SaveChanges(); + } + + /// + /// Registers all services depending on this Game, in the order required. + /// This is called once the framework is initialized but before the main + /// window is active and has its models. + /// + public void RegisterServicesForGame() + { + Locator.CurrentMutable.RegisterConstant(this, typeof(Services.Game)); + Locator.CurrentMutable.RegisterConstant(new CSVDataLoader(), typeof(CSVDataLoader)); + Locator.CurrentMutable.Register(() => new StarsDatabase(DatabaseFileName), typeof(StarsDatabase)); + + // TESTING HELPER + if (__doCreateTestData) + __createTestData(); + } + + /// + /// Starts all required background servies, so that we are in normal UI + /// operation mode. This is called after the UI is up and running. + /// + public void StartBackgroundServices() + { + CSVDataLoader csvloader = Locator.Current.GetService()!; + csvloader.StartCSVWatcher(); + } + + + private bool __doCreateTestData = false; + + /// + /// TESTING HELPER + /// + private void __createTestData () + { + using var db = Locator.Current.GetService()!; + + // Note: This sample requires the database to be created before running. + // Console.WriteLine($"Database path: {db.DbPath}."); + + Race r = new() + { + Name = "Atlantis", + PlayerRace = true, + PlayerFileId = 1, + ColonistsPerResource = 1000, + GrowthRatePercent = 19, + PRT = PRT.Other, + HasOBRM = true, + FactoryCost3 = false, + FactoryNumberPer10k = 8, + FactoryResCost = 8, + FactoryResPer10 = 15, + MineResCost = 3, + MineMineralsPer10 = 10, + MineNumberPer10k = 10 + }; + db.Add(r); + db.SaveChanges(); + + var loader = new CSV.PlanetLoader(); + loader.ImportForRace(Services.Game.Player); + } }