Persist minigame, gameevent and resource files (dat/lang) to PostgreSQL
This commit is contained in:
parent
59c2c9796e
commit
aafa4585f9
8 changed files with 286 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Plugin.ResourceLoader.Loaders;
|
||||
using Plugin.ResourceLoader.Services;
|
||||
using WingsAPI.Data.ActDesc;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsAPI.Plugins;
|
||||
|
|
@ -40,6 +41,7 @@ namespace Plugin.ResourceLoader
|
|||
services.AddSingleton<IGameDataLanguageService, InMemoryGameDataLanguageService>();
|
||||
|
||||
services.AddSingleton<IBattleEntityAlgorithmService, BattleEntityAlgorithmService>();
|
||||
services.AddHostedService<ResourceFilesPostgresSyncService>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +53,7 @@ namespace Plugin.ResourceLoader
|
|||
{
|
||||
services.AddSingleton<IResourceLoader<GenericTranslationDto>, GenericTranslationGrpcLoader>();
|
||||
services.AddSingleton<IGameLanguageService, InMemoryMultilanguageService>();
|
||||
services.AddHostedService<ResourceFilesPostgresSyncService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,11 @@
|
|||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\PhoenixLib.Caching\PhoenixLib.Caching.csproj" />
|
||||
<ProjectReference Include="..\..\PhoenixLib.Multilanguage\PhoenixLib.Multilanguage.csproj" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
|
||||
namespace Plugin.ResourceLoader.Services
|
||||
{
|
||||
public class ResourceFilesPostgresSyncService : IHostedService
|
||||
{
|
||||
private readonly ResourceLoadingConfiguration _configuration;
|
||||
|
||||
public ResourceFilesPostgresSyncService(ResourceLoadingConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("PARSER_DB_SYNC"), "false", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string host = Environment.GetEnvironmentVariable("DATABASE_IP")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP")
|
||||
?? "127.0.0.1";
|
||||
string port = Environment.GetEnvironmentVariable("DATABASE_PORT")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT")
|
||||
?? "5432";
|
||||
string db = Environment.GetEnvironmentVariable("DATABASE_NAME")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME")
|
||||
?? "game";
|
||||
string user = Environment.GetEnvironmentVariable("DATABASE_USER")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER")
|
||||
?? "postgres";
|
||||
string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD")
|
||||
?? "postgres";
|
||||
|
||||
string root = _configuration.ResourcePaths;
|
||||
string datPath = _configuration.GameDataPath;
|
||||
string langPath = _configuration.GameLanguagePath;
|
||||
|
||||
string[] datFiles = Directory.Exists(datPath)
|
||||
? Directory.GetFiles(datPath, "*", SearchOption.AllDirectories)
|
||||
: Array.Empty<string>();
|
||||
string[] langFiles = Directory.Exists(langPath)
|
||||
? Directory.GetFiles(langPath, "*", SearchOption.AllDirectories)
|
||||
: Array.Empty<string>();
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
using var tx = conn.BeginTransaction();
|
||||
|
||||
using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS resource_files (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
category TEXT NOT NULL,
|
||||
relative_path TEXT NOT NULL,
|
||||
sha256 TEXT NOT NULL,
|
||||
content BYTEA NOT NULL,
|
||||
size_bytes INT NOT NULL,
|
||||
synced_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(category, relative_path)
|
||||
);", conn, tx))
|
||||
{
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
UpsertFiles(conn, tx, root, "dat", datFiles);
|
||||
UpsertFiles(conn, tx, root, "lang", langFiles);
|
||||
|
||||
tx.Commit();
|
||||
Log.Info($"[PARSER_DB_SYNC] Synced resource_files dat={datFiles.Length} lang={langFiles.Length}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[PARSER_DB_SYNC] Failed to sync resource files", ex);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
private static void UpsertFiles(NpgsqlConnection conn, NpgsqlTransaction tx, string root, string category, string[] files)
|
||||
{
|
||||
foreach (string file in files.Where(File.Exists))
|
||||
{
|
||||
byte[] content = File.ReadAllBytes(file);
|
||||
string hash;
|
||||
using (SHA256 sha = SHA256.Create())
|
||||
{
|
||||
hash = Convert.ToHexString(sha.ComputeHash(content));
|
||||
}
|
||||
|
||||
string relative = Path.GetRelativePath(root, file).Replace('\\', '/');
|
||||
|
||||
using var cmd = new NpgsqlCommand(@"INSERT INTO resource_files(category,relative_path,sha256,content,size_bytes,synced_at)
|
||||
VALUES (@category,@path,@sha,@content,@size,NOW())
|
||||
ON CONFLICT (category, relative_path)
|
||||
DO UPDATE SET sha256=EXCLUDED.sha256, content=EXCLUDED.content, size_bytes=EXCLUDED.size_bytes, synced_at=NOW();", conn, tx);
|
||||
cmd.Parameters.AddWithValue("category", category);
|
||||
cmd.Parameters.AddWithValue("path", relative);
|
||||
cmd.Parameters.AddWithValue("sha", hash);
|
||||
cmd.Parameters.AddWithValue("content", content);
|
||||
cmd.Parameters.AddWithValue("size", content.Length);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using WingsEmu.Game.Characters.Events;
|
|||
using WingsEmu.Game.Configurations.Miniland;
|
||||
using WingsEmu.Game.Miniland;
|
||||
using WingsEmu.Game.Networking;
|
||||
using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence;
|
||||
|
||||
namespace WingsEmu.Plugins.BasicImplementations.Managers;
|
||||
|
||||
|
|
@ -20,11 +21,14 @@ public class MinigameManager : IMinigameManager
|
|||
{
|
||||
_minigameConfiguration = minigameConfiguration;
|
||||
_lockService = lockService;
|
||||
InitializePersistence();
|
||||
}
|
||||
|
||||
public async Task<bool> CanRefreshMinigamesFreeProductionPoints(long characterId) =>
|
||||
await _lockService.TryAddTemporaryLockAsync($"game:locks:minigame-refresh:{characterId}", DateTime.UtcNow.Date.AddDays(1));
|
||||
|
||||
public void InitializePersistence() => ParserDataPostgresSync.SyncMinigames(_minigameConfiguration);
|
||||
|
||||
|
||||
public MinigameScoresHolder GetScores(int minigameVnum)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.Drops;
|
||||
|
|
@ -8,6 +9,7 @@ using WingsEmu.DTOs.Maps;
|
|||
using WingsEmu.DTOs.Recipes;
|
||||
using WingsEmu.DTOs.ServerDatas;
|
||||
using WingsEmu.DTOs.Shops;
|
||||
using WingsEmu.Game.Configurations.Miniland;
|
||||
|
||||
namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence;
|
||||
|
||||
|
|
@ -492,6 +494,77 @@ VALUES (@id,@amount,@chance,@item,@map,@mon,@race,@subrace);", conn, tx);
|
|||
}
|
||||
}
|
||||
|
||||
public static void SyncMinigames(MinigameConfiguration minigameConfiguration)
|
||||
{
|
||||
if (!Enabled || minigameConfiguration == null) return;
|
||||
try
|
||||
{
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
using var tx = conn.BeginTransaction();
|
||||
|
||||
using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS minigame_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
minigame_vnum INT,
|
||||
minigame_type INT,
|
||||
minimum_level INT,
|
||||
minimum_reputation INT,
|
||||
rewards_json JSONB
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS minigame_scores_holders (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
minigame_type INT,
|
||||
scores_json JSONB
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS global_minigame_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
config_json JSONB
|
||||
);", conn, tx)) cmd.ExecuteNonQuery();
|
||||
|
||||
using (var cmd = new NpgsqlCommand("TRUNCATE TABLE minigame_config, minigame_scores_holders, global_minigame_config RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();
|
||||
|
||||
if (minigameConfiguration.Minigames != null)
|
||||
{
|
||||
foreach (Minigame m in minigameConfiguration.Minigames)
|
||||
{
|
||||
using var cmd = new NpgsqlCommand(@"INSERT INTO minigame_config(minigame_vnum,minigame_type,minimum_level,minimum_reputation,rewards_json)
|
||||
VALUES (@vnum,@type,@minLvl,@minRep,@rewards::jsonb);", conn, tx);
|
||||
cmd.Parameters.AddWithValue("vnum", m.Vnum);
|
||||
cmd.Parameters.AddWithValue("type", (int)m.Type);
|
||||
cmd.Parameters.AddWithValue("minLvl", m.MinimumLevel);
|
||||
cmd.Parameters.AddWithValue("minRep", m.MinimumReputation);
|
||||
cmd.Parameters.AddWithValue("rewards", JsonSerializer.Serialize(m.Rewards));
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
if (minigameConfiguration.ScoresHolders != null)
|
||||
{
|
||||
foreach (MinigameScoresHolder s in minigameConfiguration.ScoresHolders)
|
||||
{
|
||||
using var cmd = new NpgsqlCommand(@"INSERT INTO minigame_scores_holders(minigame_type,scores_json)
|
||||
VALUES (@type,@scores::jsonb);", conn, tx);
|
||||
cmd.Parameters.AddWithValue("type", (int)s.Type);
|
||||
cmd.Parameters.AddWithValue("scores", JsonSerializer.Serialize(s.Scores));
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = new NpgsqlCommand("INSERT INTO global_minigame_config(config_json) VALUES (@cfg::jsonb);", conn, tx))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("cfg", JsonSerializer.Serialize(minigameConfiguration.Configuration));
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
tx.Commit();
|
||||
Log.Info($"[PARSER_DB_SYNC] Synced minigames={minigameConfiguration.Minigames?.Count ?? 0} holders={minigameConfiguration.ScoresHolders?.Count ?? 0}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[PARSER_DB_SYNC] Failed to sync minigames", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SyncRecipes(IReadOnlyList<RecipeDTO> recipes)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using WingsEmu.Plugins.GameEvents.Configuration.InstantBattle;
|
|||
using WingsEmu.Plugins.GameEvents.Consumers;
|
||||
using WingsEmu.Plugins.GameEvents.Matchmaking.Matchmaker;
|
||||
using WingsEmu.Plugins.GameEvents.RecurrentJob;
|
||||
using WingsEmu.Plugins.GameEvents.Services;
|
||||
|
||||
namespace WingsEmu.Plugins.GameEvents
|
||||
{
|
||||
|
|
@ -59,6 +60,8 @@ namespace WingsEmu.Plugins.GameEvents
|
|||
{
|
||||
[GameEventType.InstantBattle] = new InstantBattleMatchmaker(s.GetService<IGlobalInstantBattleConfiguration>())
|
||||
}));
|
||||
|
||||
services.AddHostedService<GameEventConfigPostgresSyncService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsEmu.Plugins.GameEvents.Configuration.InstantBattle;
|
||||
|
||||
namespace WingsEmu.Plugins.GameEvents.Services
|
||||
{
|
||||
public class GameEventConfigPostgresSyncService : IHostedService
|
||||
{
|
||||
private readonly IGlobalInstantBattleConfiguration _configuration;
|
||||
|
||||
public GameEventConfigPostgresSyncService(IGlobalInstantBattleConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
string host = Environment.GetEnvironmentVariable("DATABASE_IP")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP")
|
||||
?? "127.0.0.1";
|
||||
string port = Environment.GetEnvironmentVariable("DATABASE_PORT")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT")
|
||||
?? "5432";
|
||||
string db = Environment.GetEnvironmentVariable("DATABASE_NAME")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME")
|
||||
?? "game";
|
||||
string user = Environment.GetEnvironmentVariable("DATABASE_USER")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER")
|
||||
?? "postgres";
|
||||
string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD")
|
||||
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD")
|
||||
?? "postgres";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
using var tx = conn.BeginTransaction();
|
||||
|
||||
using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS gameevent_instant_battle_configs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
map_id INT,
|
||||
game_event_type INT,
|
||||
config_json JSONB
|
||||
);", conn, tx)) cmd.ExecuteNonQuery();
|
||||
|
||||
using (var cmd = new NpgsqlCommand("TRUNCATE TABLE gameevent_instant_battle_configs RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();
|
||||
|
||||
var configs = (_configuration as GlobalInstantBattleConfiguration)?.Configurations ?? Enumerable.Empty<InstantBattleConfiguration>();
|
||||
|
||||
foreach (InstantBattleConfiguration c in configs)
|
||||
{
|
||||
using var cmd = new NpgsqlCommand("INSERT INTO gameevent_instant_battle_configs(map_id,game_event_type,config_json) VALUES (@map,@type,@cfg::jsonb);", conn, tx);
|
||||
cmd.Parameters.AddWithValue("map", c.MapId);
|
||||
cmd.Parameters.AddWithValue("type", (int)c.GameEventType);
|
||||
cmd.Parameters.AddWithValue("cfg", JsonSerializer.Serialize(c));
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
tx.Commit();
|
||||
Log.Info($"[PARSER_DB_SYNC] Synced instant_battle_configs={configs.Count()}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[PARSER_DB_SYNC] Failed to sync gameevents", ex);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.18" />
|
||||
<ProjectReference Include="..\..\PhoenixLib.Configuration\PhoenixLib.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\Plugin.RainbowBattle\Plugin.RainbowBattle.csproj" />
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue