move towards auto-import

This commit is contained in:
Torben Nehmer 2024-09-17 20:20:42 +02:00
parent 227d8a11d7
commit 802180a26e
No known key found for this signature in database
8 changed files with 184 additions and 81 deletions

View File

@ -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
{
/// <summary>
/// Reference to the game metadata, retrieved by DI
/// </summary>
protected Services.Game Game = Locator.Current.GetService<Services.Game>()!;
/// <summary>
/// CSV Configuration to use for importing Planet files.
/// </summary>
protected CsvConfiguration CsvConfig;
/// <summary>
/// Construction
/// </summary>
public PlanetLoader()
{
CreateConfig();
}
/// <summary>
/// Creates the CSV configuration for planets.
/// </summary>
[MemberNotNull(nameof(CsvConfig))]
protected void CreateConfig()
{
CsvConfig = CsvConfiguration.FromAttributes<Planet>(CultureInfo.InvariantCulture);
CsvConfig.Delimiter = "\t";
CsvConfig.Escape = '\0';
CsvConfig.MissingFieldFound = null;
}
/// <summary>
/// Import the planet file for the given Race.
/// </summary>
/// <param name="race">Import File</param>
public void ImportForRace(Model.Race race)
{
using (var db = Locator.Current.GetService<Model.StarsDatabase>()!)
using (var reader = new StreamReader(Game.PlanetFileForRace(race), System.Text.Encoding.Latin1))
using (var csv = new CsvReader(reader, CsvConfig))
{
List<CSV.Planet> records = csv.GetRecords<Planet>().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();
}
}
}
}

View File

@ -35,4 +35,23 @@ public static class RxExtensions
return src.Buffer(zeroCrossings);
}
/// <summary>
/// Throttle a FileSystemObserver and eliminate all duplicates.
/// </summary>
/// <remarks>
/// Curtesy of https://endjin.com/blog/2024/05/observe-file-system-changes-with-rx-dotnet
/// </remarks>
/// <param name="watcher">An observer created with <c>ObserveFileSystem</c></param>
/// <param name="inactivitySeconds">Throttling window, defaults to 1 second.</param>
/// <param name="scheduler">Scheduler to user for throttling, default is set by <c>Quiescent</c></param>
/// <returns>A throttled observer with duplicate elimination.</returns>
public static IObservable<IEnumerable<FileSystemEventArgs>> ThrottleAndDistinct (
this IObservable<FileSystemEventArgs> watcher,
int inactivitySeconds = 1,
IScheduler? scheduler = null)
{
return watcher
.Quiescent(TimeSpan.FromSeconds(inactivitySeconds), scheduler)
.Select(changes => changes.DistinctBy(x => (x.ChangeType, x.FullPath)));
}
}

View File

@ -1,34 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace StarsAssistant.Model;
public class Game (string gamePath, string baseName)
{
/// <summary>
/// The base path in which all game files reside.
/// </summary>
public string GamePath { get; private set; } = gamePath;
/// <summary>
/// The base name without extensions of your game, inside the GamePath folder.
/// All dependant files are resolved using this name.
/// </summary>
public string BaseName { get; private set; } = baseName;
/// <summary>
/// The number of the player, for example identifying the pxx planet file.
/// </summary>
public int PlayerId { get; private set; }
/// <summary>
/// Combine into the DatabaseName
/// </summary>
public string DatabaseName => Path.Combine(GamePath, $"{BaseName}.sqlite");
/// <summary>
/// Search for Planet files using this pattern, will give you all pxx files.
/// </summary>
public string PlanetFileSearchPattern => Path.Combine(GamePath, $"{BaseName}.p*");
}

View File

@ -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; }

View File

@ -37,7 +37,7 @@ public class StarsDatabase(string DbPath) : DbContext
/// <summary>
/// The record with all game metadata, will only contain a single line at all times.
/// </summary>
public DbSet<Game> Game { get; set; }
// public DbSet<Game> Game { get; set; }
#endregion
}

View File

@ -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<Services.Game>()!;
using var db = Locator.Current.GetService<StarsDatabase>()!;
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<Planet>(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<CSV.Planet> records = csv.GetRecords<CSV.Planet>().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);
}
}

View File

@ -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
{
/// <summary>
/// Reference to the game metadata, retrieved by DI
/// </summary>
protected Model.Game game;
protected Services.Game Game = Locator.Current.GetService<Services.Game>()!;
public CSVDataLoader()
public void CSVDataLoader()
{
game = Locator.Current.GetService<Model.Game>()!;
}
}
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}");
}
});
*/
}
}

View File

@ -0,0 +1,33 @@
namespace StarsAssistant.Services;
public class Game
{
/// <summary>
/// The base path in which all game files reside.
/// </summary>
public required string GamePath { get; set; }
/// <summary>
/// The base name without extensions of your game, inside the GamePath folder.
/// All dependant files are resolved using this name.
/// </summary>
public required string BaseName { get; set; }
/// <summary>
/// Combine into the DatabaseName
/// </summary>
public string DatabaseFileName => Path.Combine(GamePath, $"{BaseName}.sqlite");
/// <summary>
/// Search for Planet files using this pattern, will give you all pxx files.
/// </summary>
public string PlanetFileSearchPattern => $"{BaseName}.p*";
/// <summary>
/// Get the name of a planet file for a given race.
/// </summary>
/// <param name="r">The race to load.</param>
/// <returns>Fully qualified file path.</returns>
public string PlanetFileForRace (Model.Race r) => Path.Combine(GamePath, $"{BaseName}.p{r.PlayerFileId}");
}