183 lines
7.3 KiB
C#
183 lines
7.3 KiB
C#
using LightlessSync.API.Dto.User;
|
|
using Microsoft.VisualBasic.FileIO;
|
|
using Prometheus;
|
|
using System.Collections.Concurrent;
|
|
using System.Globalization;
|
|
|
|
namespace LightlessSyncServer.Services;
|
|
|
|
public class LightlessCensus : IHostedService
|
|
{
|
|
private record CensusEntry(ushort WorldId, short Race, short Subrace, short Gender)
|
|
{
|
|
public static CensusEntry FromDto(CensusDataDto dto)
|
|
{
|
|
return new CensusEntry(dto.WorldId, dto.RaceId, dto.TribeId, dto.Gender);
|
|
}
|
|
}
|
|
|
|
private readonly ConcurrentDictionary<string, CensusEntry> _censusEntries = new(StringComparer.Ordinal);
|
|
private readonly Dictionary<short, string> _dcs = new();
|
|
private readonly Dictionary<short, string> _gender = new();
|
|
private readonly ILogger<LightlessCensus> _logger;
|
|
private readonly Dictionary<short, string> _races = new();
|
|
private readonly Dictionary<short, string> _tribes = new();
|
|
private readonly Dictionary<ushort, (string, short)> _worlds = new();
|
|
private Gauge? _gauge;
|
|
|
|
public LightlessCensus(ILogger<LightlessCensus> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
private bool Initialized => _gauge != null;
|
|
|
|
public void ClearStatistics(string uid)
|
|
{
|
|
if (!Initialized) return;
|
|
|
|
if (_censusEntries.Remove(uid, out var censusEntry))
|
|
{
|
|
ModifyGauge(censusEntry, increase: false);
|
|
}
|
|
}
|
|
|
|
public void PublishStatistics(string uid, CensusDataDto? censusDataDto)
|
|
{
|
|
if (!Initialized || censusDataDto == null) return;
|
|
|
|
var newEntry = CensusEntry.FromDto(censusDataDto);
|
|
|
|
if (_censusEntries.TryGetValue(uid, out var entry))
|
|
{
|
|
if (entry != newEntry)
|
|
{
|
|
ModifyGauge(entry, increase: false);
|
|
ModifyGauge(newEntry, increase: true);
|
|
_censusEntries[uid] = newEntry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_censusEntries[uid] = newEntry;
|
|
ModifyGauge(newEntry, increase: true);
|
|
}
|
|
}
|
|
|
|
public async Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogInformation("Loading XIVAPI data");
|
|
|
|
using HttpClient client = new HttpClient();
|
|
|
|
Dictionary<ushort, short> worldDcs = new();
|
|
|
|
var dcs = await client.GetStringAsync("https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/WorldDCGroupType.csv", cancellationToken).ConfigureAwait(false);
|
|
// dc: https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/WorldDCGroupType.csv
|
|
// id, name, region
|
|
|
|
using var dcsReader = new StringReader(dcs);
|
|
using var dcsParser = new TextFieldParser(dcsReader);
|
|
dcsParser.Delimiters = [","];
|
|
// read 3 lines and discard
|
|
dcsParser.ReadLine(); dcsParser.ReadLine(); dcsParser.ReadLine();
|
|
|
|
while (!dcsParser.EndOfData)
|
|
{
|
|
var fields = dcsParser.ReadFields();
|
|
var id = short.Parse(fields[0], CultureInfo.InvariantCulture);
|
|
var name = fields[1];
|
|
if (string.IsNullOrEmpty(name) || id == 0) continue;
|
|
_logger.LogInformation("DC: ID: {id}, Name: {name}", id, name);
|
|
_dcs[id] = name;
|
|
}
|
|
|
|
var worlds = await client.GetStringAsync("https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/World.csv", cancellationToken).ConfigureAwait(false);
|
|
// world: https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/World.csv
|
|
// id, internalname, name, region, usertype, datacenter, ispublic
|
|
|
|
using var worldsReader = new StringReader(worlds);
|
|
using var worldsParser = new TextFieldParser(worldsReader);
|
|
worldsParser.Delimiters = [","];
|
|
// read 3 lines and discard
|
|
worldsParser.ReadLine(); worldsParser.ReadLine(); worldsParser.ReadLine();
|
|
|
|
while (!worldsParser.EndOfData)
|
|
{
|
|
var fields = worldsParser.ReadFields();
|
|
var id = ushort.Parse(fields[0], CultureInfo.InvariantCulture);
|
|
var name = fields[1];
|
|
var dc = short.Parse(fields[5], CultureInfo.InvariantCulture);
|
|
var isPublic = bool.Parse(fields[6]);
|
|
if (!_dcs.ContainsKey(dc) || !isPublic) continue;
|
|
_worlds[id] = (name, dc);
|
|
_logger.LogInformation("World: ID: {id}, Name: {name}, DC: {dc}", id, name, dc);
|
|
}
|
|
|
|
var races = await client.GetStringAsync("https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/Race.csv", cancellationToken).ConfigureAwait(false);
|
|
// race: https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/Race.csv
|
|
// id, masc name, fem name, other crap I don't care about
|
|
|
|
using var raceReader = new StringReader(races);
|
|
using var raceParser = new TextFieldParser(raceReader);
|
|
raceParser.Delimiters = [","];
|
|
// read 3 lines and discard
|
|
raceParser.ReadLine(); raceParser.ReadLine(); raceParser.ReadLine();
|
|
|
|
while (!raceParser.EndOfData)
|
|
{
|
|
var fields = raceParser.ReadFields();
|
|
var id = short.Parse(fields[0], CultureInfo.InvariantCulture);
|
|
var name = fields[1];
|
|
if (string.IsNullOrEmpty(name) || id == 0) continue;
|
|
_races[id] = name;
|
|
_logger.LogInformation("Race: ID: {id}, Name: {name}", id, name);
|
|
}
|
|
|
|
var tribe = await client.GetStringAsync("https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/Tribe.csv", cancellationToken).ConfigureAwait(false);
|
|
// tribe: https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/Tribe.csv
|
|
// id masc name, fem name, other crap I don't care about
|
|
|
|
using var tribeReader = new StringReader(tribe);
|
|
using var tribeParser = new TextFieldParser(tribeReader);
|
|
tribeParser.Delimiters = [","];
|
|
// read 3 lines and discard
|
|
tribeParser.ReadLine(); tribeParser.ReadLine(); tribeParser.ReadLine();
|
|
|
|
while (!tribeParser.EndOfData)
|
|
{
|
|
var fields = tribeParser.ReadFields();
|
|
var id = short.Parse(fields[0], CultureInfo.InvariantCulture);
|
|
var name = fields[1];
|
|
if (string.IsNullOrEmpty(name) || id == 0) continue;
|
|
_tribes[id] = name;
|
|
_logger.LogInformation("Tribe: ID: {id}, Name: {name}", id, name);
|
|
}
|
|
|
|
_gender[0] = "Male";
|
|
_gender[1] = "Female";
|
|
|
|
_gauge = Metrics.CreateGauge("lightless_census", "lightless informational census data", new[] { "dc", "world", "gender", "race", "subrace" });
|
|
}
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private void ModifyGauge(CensusEntry censusEntry, bool increase)
|
|
{
|
|
var subraceSuccess = _tribes.TryGetValue(censusEntry.Subrace, out var subrace);
|
|
var raceSuccess = _races.TryGetValue(censusEntry.Race, out var race);
|
|
var worldSuccess = _worlds.TryGetValue(censusEntry.WorldId, out var world);
|
|
var genderSuccess = _gender.TryGetValue(censusEntry.Gender, out var gender);
|
|
if (subraceSuccess && raceSuccess && worldSuccess && genderSuccess && _dcs.TryGetValue(world.Item2, out var dc))
|
|
{
|
|
if (increase)
|
|
_gauge.WithLabels(dc, world.Item1, gender, race, subrace).Inc();
|
|
else
|
|
_gauge.WithLabels(dc, world.Item1, gender, race, subrace).Dec();
|
|
}
|
|
}
|
|
}
|