Files
EnglewoodLAB/Assets/Scripts/UVC/Data/UserSetting.cs

302 lines
13 KiB
C#

#nullable enable
using Cysharp.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UVC.Data.Core;
using UVC.Extention;
namespace UVC.Data
{
public class UserSetting : OrderedDictionary<string, object>
{
// 사용자 데이터를 저장하는 컬렉션입니다.
private static Dictionary<string, UserSetting> _userDatas = new Dictionary<string, UserSetting>();
public static IReadOnlyDictionary<string, UserSetting> UserDatas => _userDatas;
/// <summary>
/// 사용자 정의 키를 지정된 세팅에 연결합니다.
/// </summary>
/// <remarks>동일한 키를 가진 세팅가 이미 있는 경우 새
/// 마스크로 대체됩니다.</remarks>
/// <param name="key">세팅의 고유 식별자입니다. <see langword="null"/>이거나 비어 있을 수 없습니다.</param>
/// <param name="setting">지정된 키와 연결할 <see cref="UserSetting"/> 객체입니다. <see langword="null"/>일 수 없습니다.</param>
public static void AddSetting(string key, UserSetting setting)
{
if (string.IsNullOrEmpty(key) || setting == null) return;
_userDatas[key] = setting;
}
/// <summary>
/// 지정된 키와 연관된 사용자 마스크를 제거합니다.
/// </summary>
/// <remarks> <paramref name="key"/>가 null이거나 비어 있으면 메서드는 아무 작업도 수행하지 않습니다.
///</remarks>
/// <param name="key">제거할 사용자 마스크를 식별하는 키입니다. null이거나 비어 있으면 안 됩니다.</param>
public static void RemoveSetting(string key)
{
if (string.IsNullOrEmpty(key)) return;
_userDatas.Remove(key);
}
/// <summary>
/// 지정된 키와 연관된 <see cref="UserSetting"/>를 검색합니다.
/// </summary>
/// <param name="key">연관된 <see cref="UserSetting"/>를 찾는 데 사용되는 키입니다. null이거나 비어 있을 수 없습니다.</param>
/// <returns>지정된 키와 연관된 <see cref="UserSetting"/> 또는 키를 찾을 수 없거나 null이거나 비어 있는 경우 <see langword="null"/>을 반환합니다.
///</returns>
public static UserSetting? Get(string key)
{
if (string.IsNullOrEmpty(key)) return null;
_userDatas.TryGetValue(key, out var setting);
return setting;
}
/// <summary>
/// 내부 컬렉션에서 모든 사용자 세팅을 지웁니다.
/// </summary>
/// <remarks>이 메서드는 사용자 세팅 컬렉션에서 모든 항목을 제거하고 빈 상태로 재설정합니다.
/// 일반적으로 새 마스크를 적용하기 전에 이전에 적용된 마스크를 지우는 데 사용됩니다.
///</remarks>
public static void ClearSetting()
{
_userDatas.Clear();
}
/// <summary>
/// 애플리케이션의 영구 데이터 디렉터리(AppData/LocalLow/Company Name/Product Name)에서 사용자 마스크 데이터를 로드합니다.
/// </summary>
/// <remarks>이 메서드는 애플리케이션의 영구 데이터 경로에 있는 "user" 하위 디렉터리에서 JSON 파일을 검색합니다.
/// 각 JSON 파일을 읽고 그 내용을 사용하여 <see cref="UserSetting"/>
/// 객체를 생성한 후 애플리케이션의 사용자 마스크 컬렉션에 추가합니다. 이 메서드는 메인 스레드를 차단하지 않도록 백그라운드
/// 스레드에서 실행됩니다.</remarks>
/// <returns></returns>
public static async UniTask LoadFromAppData()
{
//C:\Users\[user name]\AppData\LocalLow\[company name]\[product name]
string persistentDataPath = UnityEngine.Application.persistentDataPath;
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 스레드 사용 불가. 메인 스레드에서 파일 작업 수행하되 프레임 양보로 스톨 방지.
string folderPath = System.IO.Path.Combine(persistentDataPath, "user");
DirectoryInfo directory = new DirectoryInfo(folderPath);
if (!directory.Exists) return;
FileInfo[] files = directory.GetFiles("*.json");
foreach (FileInfo file in files)
{
// 프레임 양보
await UniTask.Yield();
string filePath = file.FullName;
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
if (string.IsNullOrEmpty(fileName)) continue;
string jsonString = System.IO.File.ReadAllText(filePath);
AddSetting(fileName, new UserSetting(jsonString));
}
#else
await UniTask.RunOnThreadPool(() =>
{
string folderPath = System.IO.Path.Combine(persistentDataPath, "user");
DirectoryInfo directory = new DirectoryInfo(folderPath);
if (!directory.Exists) return;
FileInfo[] files = directory.GetFiles("*.json");
foreach (FileInfo file in files)
{
string filePath = file.FullName;
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
if (string.IsNullOrEmpty(fileName)) continue;
string jsonString = System.IO.File.ReadAllText(filePath);
AddSetting(fileName, new UserSetting(jsonString));
}
});
#endif
}
/// <summary>
/// 사용자 세팅을 애플리케이션의 영구 데이터 경로(AppData/LocalLow/Company Name/Product Name)에 저장합니다.
/// </summary>
/// <remarks>이 메서드는 모든 사용자 세팅을 반복하며 각 마스크를 JSON 파일로 저장합니다.
/// 애플리케이션의 영구 데이터 경로에 있는 "user" 하위 디렉터리에 디렉터리가 없으면
/// 생성됩니다. 각 파일의 이름은 해당 키와 ".json" 확장자를 사용하여 지정됩니다. 저장 과정에서 발생하는 모든 오류는
/// Unity 콘솔에 기록됩니다.</remarks>
/// <returns></returns>
public static async UniTask SaveToAppData()
{
string persistentDataPath = UnityEngine.Application.persistentDataPath;
#if UNITY_WEBGL && !UNITY_EDITOR
foreach (var kv in _userDatas)
{
await UniTask.Yield(); // 긴 루프 스톨 방지
string key = kv.Key;
if (string.IsNullOrEmpty(key)) continue;
if (!_userDatas.TryGetValue(key, out var setting)) continue;
try
{
string folderPath = Path.Combine(persistentDataPath, "user");
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
string filePath = Path.Combine(folderPath, $"{key}.json");
File.WriteAllText(filePath, setting.ToJsonString());
}
catch (Exception ex)
{
Debug.LogError($"SaveToAppData Error(WebGL): {ex.Message}");
}
}
#else
await UniTask.RunOnThreadPool(() =>
{
foreach (var keyValue in _userDatas)
{
string key = keyValue.Key;
if (string.IsNullOrEmpty(key)) continue;
if (!_userDatas.TryGetValue(key, out var setting)) return;
try
{
string folderPath = System.IO.Path.Combine(persistentDataPath, "user");
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
string filePath = System.IO.Path.Combine(folderPath, $"{key}.json");
string jsonString = setting.ToJsonString();
System.IO.File.WriteAllText(filePath, jsonString);
}
catch (Exception ex)
{
Debug.LogError($"SaveToAppData Error: {ex.Message}");
}
}
});
#endif
}
public static UserSetting? FromDataObject(DataObject dataObject)
{
if (dataObject == null) return null;
UserSetting setting = new UserSetting();
foreach (var item in dataObject)
{
setting[item.Key] = item.Key;
}
return setting;
}
public UserSetting() { }
public UserSetting(string jsonString)
{
if (string.IsNullOrEmpty(jsonString)) return;
JObject jObj = JObject.Parse(jsonString);
foreach (var property in jObj.Properties())
{
this[property.Name] = ConvertJTokenToObject(property);
}
}
public UserSetting(JObject jObj)
{
if (jObj == null) return;
foreach (var property in jObj.Properties())
{
this[property.Name] = ConvertJTokenToObject(property.Value);
}
}
/// <summary>
/// JToken을 실제 객체로 변환하는 헬퍼 메서드
/// </summary>
/// <param name="token">변환할 JToken 객체</param>
/// <returns>변환된 .NET 객체</returns>
/// <remarks>
/// 이 메서드는 JToken의 타입에 따라 적절한 .NET 타입의 객체로 변환합니다.
/// 객체는 DataObject로, 배열은 DataArray로 변환되며, 기본 타입은 해당하는 .NET 타입으로 변환됩니다.
/// </remarks>
private object ConvertJTokenToObject(JToken token)
{
if (token == null) return null;
switch (token.Type)
{
case JTokenType.Property:
JProperty prop = (JProperty)token;
return ConvertJTokenToObject(prop.Value);
case JTokenType.Object:
return new UserSetting((JObject)token);
case JTokenType.Array:
JArray array = (JArray)token;
List<UserSetting> settings = new List<UserSetting>();
if (array.All(item => item.Type == JTokenType.Object))
{
foreach (var item in array)
{
settings.Add(new UserSetting((JObject)item));
}
}
return settings;
case JTokenType.Integer:
return token.ToObject<int>();
case JTokenType.Float:
return token.ToObject<double>();
case JTokenType.String:
return token.ToObject<string>();
case JTokenType.Boolean:
return token.ToObject<bool>();
case JTokenType.Date:
return token.ToObject<DateTime>();
case JTokenType.Null:
return null;
default:
return token.ToObject<object>();
}
}
/// <summary>
/// JObject 타입과의 호환성을 위한 메서드입니다.
/// Dictionary에 저장된 모든 속성을 JObject 형식으로 반환합니다.
/// </summary>
/// <returns>현재 UserSetting의 내용을 담은 JObject</returns>
public JObject ToJObject()
{
JObject result = new JObject();
foreach (var pair in this)
{
if (pair.Value is UserSetting setting)
{
result[pair.Key] = ((UserSetting)pair.Value).ToJObject();
}
else
{
result[pair.Key] = JToken.FromObject(pair.Value);
}
}
return result;
}
/// <summary>
/// JObject에서 속성을 가져오기 위한 확장 메서드입니다.
/// </summary>
/// <param name="jObj">속성을 추출할 JObject</param>
/// <returns>JObject의 모든 속성</returns>
private IEnumerable<KeyValuePair<string, JToken>> GetProperties(JObject jObj)
{
foreach (var property in jObj.Properties())
{
yield return new KeyValuePair<string, JToken>(property.Name, property.Value);
}
}
/// <summary>
/// Json 문자열로 변환합니다.
/// </summary>
/// <returns></returns>
public string ToJsonString()
{
return ToJObject().ToString(Newtonsoft.Json.Formatting.Indented);
}
}
}