Files
back_cantanBuilding/Assets/Scripts/Services/Tables.cs
2026-05-26 16:15:54 +08:00

581 lines
22 KiB
C#

using asap.core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Unity.SharpZipLib.Zip;
using System.Net;
using UnityEngine.Networking;
using System.Threading;
namespace cfg
{
public sealed partial class Tables
{
#if UNITY_EDITOR
public static async System.Threading.Tasks.Task<Tables> InitEditor(Action<float> progress)
{
var cwd = Directory.GetParent(Directory.GetCurrentDirectory());
var dir = Path.Combine(cwd.FullName, "cfgData");
var tables = new cfg.Tables(
tableName => SimpleJSON.JSON.Parse(File.ReadAllText(Path.Combine(dir, $"{tableName}.json")))
);
await Task.Yield();
progress.Invoke(1f);
await Task.Yield();
return tables;
}
#endif
public static async System.Threading.Tasks.Task<Tables> Init(Action<float> progress)
{
var dir = Path.Combine(Application.persistentDataPath, "cfgData");
var verFilePath = Path.Combine(dir, "conf_ver.txt");
var ver = File.ReadAllText(verFilePath).Trim();
Debug.Log($"cfg init ver {ver}");
var zipFilePath = Path.Combine(dir, $"{ver}.zip");
#if UNITY_EDITOR
UnityEngine.Assertions.Assert.IsTrue(File.Exists(zipFilePath), $"File {zipFilePath} not found");
#endif
progress.Invoke(0f);
var p = (VersionTool.PackVer.Replace("_", "") + ver).Substring(0, 8);
var map = await Task.Run<Dictionary<string, string>>(() => GetEntryContent(zipFilePath, p));
progress.Invoke(0.5f);
await Task.Yield();
var tables = new cfg.Tables(
tableName => SimpleJSON.JSON.Parse(map[tableName])
);
progress.Invoke(1f);
return tables;
}
private static Dictionary<string, string> GetEntryContent(string zipPath, string p)
{
var map = new Dictionary<string, string>();
var memoryStream = new MemoryStream();
using (var zip = new ZipFile(zipPath))
{
zip.Password = p;
foreach (ZipEntry entry in zip)
{
if (entry.Name.EndsWith(".json"))
{
using (var stream = zip.GetInputStream(entry))
{
memoryStream.SetLength(0);
memoryStream.Position = 0;
stream.CopyTo(memoryStream);
var content = Encoding.UTF8.GetString(memoryStream.ToArray());
map.Add(Path.GetFileNameWithoutExtension(entry.Name), content);
}
}
}
}
memoryStream.Close();
memoryStream.Dispose();
return map;
}
//public static async System.Threading.Tasks.Task<Tables> Init(Action<float> progress)
//{
//var rawDataMap = new Dictionary<string, string>();
//var opMap = new Dictionary<string, AsyncOperationHandle<TextAsset>>();
//var props = typeof(Tables).GetProperties(BindingFlags.Public | BindingFlags.Instance);
//var addrs = props.Select(_ => _.Name.ToLower()).ToList();
//foreach (var addr in addrs)
//{
//var op = Addressables.LoadAssetAsync<TextAsset>(addr);
//opMap.Add(addr, op);
//}
//var tasks = opMap.Values.Select(_ => _.Task);
//var total = (float)tasks.Count();
//while (true)
//{
//var completed = tasks.Count(_ => _.IsCompleted);
//if (completed == total)
//{
//progress.Invoke(1f);
//break;
//}
//else
//{
//progress.Invoke(completed / total);
//}
//await Awaiters.NextFrame;
//}
//var tables = new cfg.Tables(
//tableName => SimpleJSON.JSON.Parse(opMap[tableName].Result.text)
//);
//foreach (var kv in opMap)
//{
//Addressables.Release(kv.Value);
//}
//progress.Invoke(1f);
//return tables;
//}
//
private static void EnsureDefaultCfgFileExists(out string dir, out string persistCfgFile)
{
//Copy build-in cfg to persistentDataPath is not exists
dir = Path.Combine(Application.persistentDataPath, "cfgData");
persistCfgFile = Path.Combine(dir, "conf_ver.txt");
if (!Directory.Exists(dir))
{
Debug.Log($"create dir {dir}");
Directory.CreateDirectory(dir);
var files = BetterStreamingAssets.GetFiles("cfgData");
foreach (var file in files)
{
var savePath = Path.Combine(dir, Path.GetFileName(file));
if (File.Exists(savePath)) File.Delete(savePath);
Debug.Log($"copy {file} to {savePath}");
File.WriteAllBytes(savePath, BetterStreamingAssets.ReadAllBytes(file));
}
}
}
private static readonly List<(float vtimeout, float ztimeout)> timeoutValues = new() { (1, 3), (2, 5), (2, 5) };
public static async Task<bool> UpdateCfg(IConfig config, bool isSuccessLoadRemoteConfigFile)
{
EnsureDefaultCfgFileExists(out var dir, out var persistCfgFile);
if (!isSuccessLoadRemoteConfigFile)
return true;
var maxRetryTimes = timeoutValues.Count;
for (int retryTime = 0; retryTime < maxRetryTimes; retryTime++)
{
(var vtimeout, var ztimeout) = timeoutValues[retryTime];
if (await DoUpdateCfgUWR(config, dir, persistCfgFile, (int)MathF.Ceiling(vtimeout), (int)MathF.Ceiling(ztimeout), retryTime, maxRetryTimes))
return true;
}
return false;
}
private static async Task<bool> DoUpdateCfgUWR(
IConfig config,
string dir, string persistCfgFile,
int verFileTimeoutInSec, int cfgDataTimeoutInSec,
int retryTimes, int maxRetryTimes)
{
var stopwatch = new System.Diagnostics.Stopwatch();
string localVer = File.ReadAllText(persistCfgFile).Trim();
string remoteVer = localVer;
var staticResUrl = config.Get<string>(GConstant.K_Static_Res_URL, GConstant.V_Static_Res_URL);
var remoteVerUrl = $"{staticResUrl}/cfgData/conf_ver.txt";
using (var verReq = UnityWebRequest.Get(remoteVerUrl))
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(verFileTimeoutInSec));
var ct = cts.Token;
verReq.timeout = verFileTimeoutInSec;
verReq.SetRequestHeader("Accept", "application/json");
stopwatch.Start();
var op = verReq.SendWebRequest();
try
{
while (!op.isDone && !ct.IsCancellationRequested)
{
await Awaiters.NextFrame;
}
ct.ThrowIfCancellationRequested();
stopwatch.Stop();
if (verReq.result != UnityWebRequest.Result.Success)
{
Debug.LogWarning($"Load cfg Ver file failed {verReq.result.ToString()} {verReq.error} {retryTimes + 1}/{maxRetryTimes}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfgver");
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
e.AddContent("reason", verReq.result.ToString());
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
e.AddContent("error", verReq.error);
}
return false;
}
remoteVer = verReq.downloadHandler.text.Trim();
}
catch (OperationCanceledException)
{
stopwatch.Stop();
verReq.Abort();
Debug.LogWarning($"Load cfg Ver file cancel {verFileTimeoutInSec}s {retryTimes + 1}/{maxRetryTimes}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfgver");
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
e.AddContent("reason", "cancel");
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
e.AddContent("error", verReq.error ?? "cancel");
}
return false;
}
finally
{
cts.Dispose();
}
}
if (remoteVer == localVer) return true;
var remoteCfgUrl = $"{staticResUrl}/cfgData/{remoteVer}.zip";
var savePath = Path.Combine(dir, $"{remoteVer}.zip");
using (var cfgReq = UnityWebRequest.Get(remoteCfgUrl))
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(cfgDataTimeoutInSec));
var ct = cts.Token;
cfgReq.timeout = cfgDataTimeoutInSec;
cfgReq.downloadHandler = new DownloadHandlerFile(savePath, false);
stopwatch.Reset();
stopwatch.Restart();
var op = cfgReq.SendWebRequest();
try
{
while (!op.isDone && !ct.IsCancellationRequested)
{
await Awaiters.NextFrame;
}
ct.ThrowIfCancellationRequested();
stopwatch.Stop();
if (cfgReq.result != UnityWebRequest.Result.Success)
{
if (File.Exists(savePath)) File.Delete(savePath);
Debug.LogWarning($"Load cfg file failed {cfgReq.result.ToString()} {cfgReq.error} {retryTimes + 1}/{maxRetryTimes}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
e.AddContent("reason", cfgReq.result.ToString());
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
e.AddContent("error", cfgReq.error);
}
return false;
}
if (File.Exists(persistCfgFile)) File.Delete(persistCfgFile);
File.WriteAllText(persistCfgFile, remoteVer);
Debug.Log($"cfg is updated from {localVer} to {remoteVer}");
return true;
}
catch (OperationCanceledException)
{
cfgReq.Abort();
stopwatch.Stop();
Debug.LogWarning($"Load cfg file cancel {cfgDataTimeoutInSec}s {retryTimes + 1}/{maxRetryTimes}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
e.AddContent("reason", "cancel");
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
e.AddContent("error", cfgReq.error ?? "cancel");
}
return false;
}
finally
{
cts.Dispose();
}
}
}
/**
private static async Task<bool> DoUpdateCfg(
IConfig config,
string dir, string persistCfgFile,
int verFileTimeoutInSec, int cfgDataTimeoutInSec,
int retryTimes)
{
var stopwatch = new System.Diagnostics.Stopwatch();
var content = string.Empty;
try
{
string localVer = File.ReadAllText(persistCfgFile).Trim();
var staticResUrl = config.Get<string>(GConstant.K_Static_Res_URL, GConstant.V_Static_Res_URL);
if (staticResUrl.Equals(GConstant.V_Static_Res_URL))
{
staticResUrl = string.Format(GConstant.V_Static_Res_URL, VersionTool.PackVer);
}
var remoteVerUrl = $"{staticResUrl}/cfgData/conf_ver.txt";
content = "conf_ver";
var handler = new System.Net.Http.HttpClientHandler()
{
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; }
};
using (var httpClient = new HttpClient(handler))
{
httpClient.Timeout = TimeSpan.FromSeconds(verFileTimeoutInSec);
stopwatch.Start();
var verFileRespon = await httpClient.GetAsync(remoteVerUrl);
stopwatch.Stop();
if (verFileRespon.StatusCode != HttpStatusCode.OK)
{
var elapsed = stopwatch.ElapsedMilliseconds;
Debug.LogWarning($"Load cfg Ver file failed {verFileRespon.StatusCode.ToString()}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", elapsed);
e.AddContent("reason", verFileRespon.StatusCode.ToString());
e.AddContent("skip", retryTimes >= 2);
}
return false;
}
var remoteVer = (await verFileRespon.Content.ReadAsStringAsync()).Trim();
if (remoteVer != localVer)
{
var remoteCfgUrl = $"{staticResUrl}/cfgData/{remoteVer}.zip";
content = "cfgzip";
httpClient.Timeout = TimeSpan.FromSeconds(cfgDataTimeoutInSec);
stopwatch.Reset();
stopwatch.Start();
var remoteCfgRespon = await httpClient.GetAsync(remoteCfgUrl);
stopwatch.Stop();
if (remoteCfgRespon.StatusCode != HttpStatusCode.OK)
{
var elapsed = stopwatch.ElapsedMilliseconds;
Debug.LogWarning($"Load cfg zip file failed {remoteCfgRespon.StatusCode.ToString()}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", elapsed);
e.AddContent("reason", remoteCfgRespon.StatusCode.ToString());
e.AddContent("skip", retryTimes >= 2);
e.AddContent("content", content);
}
return false;
}
var savePath = Path.Combine(dir, $"{remoteVer}.zip");
var remoteCfg = await remoteCfgRespon.Content.ReadAsByteArrayAsync();
if (File.Exists(savePath)) File.Delete(savePath);
await File.WriteAllBytesAsync(savePath, remoteCfg);
if (File.Exists(persistCfgFile)) File.Delete(persistCfgFile);
File.WriteAllText(persistCfgFile, remoteVer);
Debug.Log($"cfg is updated from {localVer} to {remoteVer}");
}
}
return true;
}
catch (System.Net.Http.HttpRequestException httpEx)
{
if (stopwatch.IsRunning)
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
var errorMessage = $"HTTP request failed: {httpEx.Message}";
if (httpEx.InnerException != null)
{
errorMessage += $" Inner: {httpEx.InnerException.Message}";
if (httpEx.InnerException.InnerException != null)
{
errorMessage += $" InnerInner: {httpEx.InnerException.InnerException.Message}";
}
}
Debug.LogWarning(errorMessage);
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", elapsed);
e.AddContent("reason", "httpEx");
e.AddContent("error", httpEx.Message);
e.AddContent("skip", retryTimes >= 2);
e.AddContent("content", content);
}
return false;
}
catch (System.Threading.Tasks.TaskCanceledException timeoutEx)
{
if (stopwatch.IsRunning)
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
Debug.LogWarning($"Request timeout after {verFileTimeoutInSec}s: {timeoutEx.Message}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", elapsed);
e.AddContent("reason", "timeout");
e.AddContent("error", timeoutEx.Message);
e.AddContent("skip", retryTimes >= 2);
e.AddContent("content", content);
}
return false;
}
catch (System.IO.IOException ioEx)
{
if (stopwatch.IsRunning)
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
Debug.LogWarning($"IO operation failed: {ioEx.Message}");
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", elapsed);
e.AddContent("reason", "io_exception");
e.AddContent("error", ioEx.Message);
e.AddContent("skip", retryTimes >= 2);
e.AddContent("content", content);
}
return false;
}
catch (Exception exception)
{
if (stopwatch.IsRunning) stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
var errorMessage = $"Unexpected error loading cfg: {exception.Message}";
if (exception.InnerException != null)
{
errorMessage += $" Inner: {exception.InnerException.Message}";
}
Debug.LogError(errorMessage);
using (var e = GEvent.TackEvent("retry"))
{
e.AddContent("type", "cfg");
e.AddContent("duration_ms", elapsed);
e.AddContent("reason", "unexpected_error");
e.AddContent("error", exception.Message);
e.AddContent("skip", retryTimes >= 2);
e.AddContent("content", content);
}
return false;
}
}
**/
//private async Task ExtractCfg(string ver)
//{
//var zipFilePath = Path.Combine(Application.persistentDataPath, $"{ver}.zip");
//var saveDir = Path.Combine(Application.persistentDataPath, "cfgData");
//if (Directory.Exists(saveDir))
//{
//Directory.Delete(saveDir, true);
//}
//await Task.Run(() => ZipFile.ExtractToDirectory(zipFilePath, Application.persistentDataPath));
//File.Delete(zipFilePath);
//}
}
public static class TablesExtensions
{
public static cfg.MapData GetMapData(this Tables tables, int id)
{
return tables.TbMapData[id];
}
public static cfg.FishData GetFishData(this Tables tables, int id)
{
var fishData = tables.TbFishData;
if (!fishData.DataMap.ContainsKey(id))
{
return fishData.DataList[0];
}
return fishData[id];
}
public static cfg.RodData GetRodData(this Tables tables, int id)
{
var fishRodData = tables.TbRodData;
if (!fishRodData.DataMap.ContainsKey(id))
{
return fishRodData.DataList[0];
}
return fishRodData[id];
}
public static cfg.AlbumExchange GetAlbumExchange(this Tables tables, int id)
{
return tables.TbAlbumExchange.GetOrDefault(id);
}
public static cfg.Item GetItemData(this Tables tables, int id)
{
return tables.TbItem[id];
}
public static string GetItemIconName(this Tables tables, int id)
{
var item = tables.TbItem;
if (!item.DataMap.ContainsKey(id))
{
return "";
}
return item[id].Icon;
}
}
public sealed partial class Text
{
public string GetText(string code)
{
string text = json["text_" + code];
return string.IsNullOrEmpty(text) ? TextEn : text;
}
}
public sealed partial class ShopPackManager
{
public int MaxPurchaseNew
{
get
{
if (MaxPurchase == 0)
{
return 1;
}
return MaxPurchase;
}
}
}
}