Files
HelixJump/Packages/com.merry-yellow.code-assist/Editor/Preferences/PreferenceStorageAccessor.cs
2025-12-15 17:49:08 -08:00

293 lines
8.6 KiB
C#

using System;
using System.Linq;
#if UNITY_EDITOR_WIN
using Microsoft.Win32;
using System.Text;
#elif UNITY_EDITOR_OSX
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
#elif UNITY_EDITOR_LINUX
using System.IO;
using System.Xml;
using System.Xml.Linq;
#endif
#pragma warning disable IDE0005
using Serilog = Meryel.Serilog;
#pragma warning restore IDE0005
#nullable enable
//namespace BgTools.PlayerPrefsEditor
namespace Meryel.UnityCodeAssist.Editor.Preferences
{
public abstract class PreferanceStorageAccessor
{
protected string prefPath;
protected string[] cachedData = new string[0];
protected abstract void FetchKeysFromSystem();
protected PreferanceStorageAccessor(string pathToPrefs)
{
prefPath = pathToPrefs;
}
public string[] GetKeys(bool reloadData = true)
{
if (reloadData || cachedData.Length == 0)
{
FetchKeysFromSystem();
}
return cachedData;
}
public Action? PrefEntryChangedDelegate;
protected bool ignoreNextChange = false;
public void IgnoreNextChange()
{
ignoreNextChange = true;
}
protected virtual void OnPrefEntryChanged()
{
if (ignoreNextChange)
{
ignoreNextChange = false;
return;
}
PrefEntryChangedDelegate?.Invoke();
}
public Action? StartLoadingDelegate;
public Action? StopLoadingDelegate;
public abstract void StartMonitoring();
public abstract void StopMonitoring();
public abstract bool IsMonitoring();
}
#if UNITY_EDITOR_WIN
public class WindowsPrefStorage : PreferanceStorageAccessor
{
readonly RegistryMonitor monitor;
public WindowsPrefStorage(string pathToPrefs) : base(pathToPrefs)
{
monitor = new RegistryMonitor(RegistryHive.CurrentUser, prefPath);
monitor.RegChanged += new EventHandler(OnRegChanged);
}
private void OnRegChanged(object sender, EventArgs e)
{
OnPrefEntryChanged();
}
protected override void FetchKeysFromSystem()
{
cachedData = new string[0];
using (RegistryKey rootKey = Registry.CurrentUser.OpenSubKey(prefPath))
{
if (rootKey != null)
{
cachedData = rootKey.GetValueNames();
rootKey.Close();
}
}
// Clean <key>_h3320113488 nameing
//cachedData = cachedData.Select((key) => { return key.Substring(0, key.LastIndexOf("_h", StringComparison.Ordinal)); }).ToArray();
for (int i = 0; i < cachedData.Length; i++)
{
var indexOfSuffix = cachedData[i].LastIndexOf("_h", StringComparison.Ordinal);
if (indexOfSuffix >= 0)
cachedData[i] = cachedData[i].Substring(0, indexOfSuffix);
}
EncodeAnsiInPlace();
}
public override void StartMonitoring()
{
monitor.Start();
}
public override void StopMonitoring()
{
monitor.Stop();
}
public override bool IsMonitoring()
{
return monitor.IsMonitoring;
}
private void EncodeAnsiInPlace()
{
Encoding utf8 = Encoding.UTF8;
Encoding ansi = Encoding.GetEncoding(1252);
for (int i = 0; i < cachedData.Length; i++)
{
cachedData[i] = utf8.GetString(ansi.GetBytes(cachedData[i]));
}
}
}
#elif UNITY_EDITOR_LINUX
public class LinuxPrefStorage : PreferanceStorageAccessor
{
readonly FileSystemWatcher fileWatcher;
public LinuxPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs))
{
fileWatcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(prefPath),
NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite,
Filter = "prefs"
};
fileWatcher.Changed += OnWatchedFileChanged;
}
protected override void FetchKeysFromSystem()
{
cachedData = new string[0];
if (File.Exists(prefPath))
{
XmlReaderSettings settings = new XmlReaderSettings();
XmlReader reader = XmlReader.Create(prefPath, settings);
XDocument doc = XDocument.Load(reader);
cachedData = doc.Element("unity_prefs").Elements().Select((e) => e.Attribute("name").Value).ToArray();
}
}
public override void StartMonitoring()
{
fileWatcher.EnableRaisingEvents = true;
}
public override void StopMonitoring()
{
fileWatcher.EnableRaisingEvents = false;
}
public override bool IsMonitoring()
{
return fileWatcher.EnableRaisingEvents;
}
private void OnWatchedFileChanged(object source, FileSystemEventArgs e)
{
OnPrefEntryChanged();
}
}
#elif UNITY_EDITOR_OSX
public class MacPrefStorage : PreferanceStorageAccessor
{
private readonly FileSystemWatcher fileWatcher;
private readonly DirectoryInfo prefsDirInfo;
private readonly String prefsFileNameWithoutExtension;
public MacPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs))
{
prefsDirInfo = new DirectoryInfo(Path.GetDirectoryName(prefPath));
prefsFileNameWithoutExtension = Path.GetFileNameWithoutExtension(prefPath);
fileWatcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(prefPath),
NotifyFilter = NotifyFilters.LastWrite,
Filter = Path.GetFileName(prefPath)
};
// MAC delete the old and create a new file instead of updating
fileWatcher.Created += OnWatchedFileChanged;
}
protected override void FetchKeysFromSystem()
{
// Workaround to avoid incomplete tmp phase from MAC OS
foreach (FileInfo info in prefsDirInfo.GetFiles())
{
// Check if tmp PlayerPrefs file exist
if (info.FullName.Contains(prefsFileNameWithoutExtension) && !info.FullName.EndsWith(".plist"))
{
StartLoadingDelegate?.Invoke();
return;
}
}
StopLoadingDelegate?.Invoke();
cachedData = new string[0];
if (File.Exists(prefPath))
{
string fixedPrefsPath = prefPath.Replace("\"", "\\\"").Replace("'", "\\'").Replace("`", "\\`");
var cmdStr = string.Format(@"-p '{0}'", fixedPrefsPath);
string stdOut = String.Empty;
string errOut = String.Empty;
var process = new System.Diagnostics.Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "plutil";
process.StartInfo.Arguments = cmdStr;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.OutputDataReceived += new DataReceivedEventHandler((sender, evt) => { stdOut += evt.Data + "\n"; });
process.ErrorDataReceived += new DataReceivedEventHandler((sender, evt) => { errOut += evt.Data + "\n"; });
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
MatchCollection matches = Regex.Matches(stdOut, @"(?: "")(.*)(?:"" =>.*)");
cachedData = matches.Cast<Match>().Select((e) => e.Groups[1].Value).ToArray();
}
}
public override void StartMonitoring()
{
fileWatcher.EnableRaisingEvents = true;
}
public override void StopMonitoring()
{
fileWatcher.EnableRaisingEvents = false;
}
public override bool IsMonitoring()
{
return fileWatcher.EnableRaisingEvents;
}
private void OnWatchedFileChanged(object source, FileSystemEventArgs e)
{
OnPrefEntryChanged();
}
}
#endif
}