You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
8.3 KiB
C#

using System;
using System.IO;
using UnityEngine;
namespace UnityEditor.SettingsManagement
{
/// <summary>
/// Represents a settings repository that stores data serialized to a JSON file.
/// </summary>
[Serializable]
public class FileSettingsRepository : ISettingsRepository
{
/// <summary>
/// Location of where the package settings are saved under the `ProjectSettings` directory.
/// </summary>
/// <returns>The folder where package settings are saved under the `ProjectSettings` directory.</returns>
protected const string k_PackageSettingsDirectory = "ProjectSettings/Packages";
/// <summary>
/// Location of where the package settings are saved under the `UserSettings` directory.
/// </summary>
/// <returns>Per-project user settings directory. </returns>
protected const string k_UserProjectSettingsDirectory = "UserSettings/Packages";
const bool k_PrettyPrintJson = true;
bool m_Initialized;
string m_Path;
[SerializeField]
SettingsDictionary m_Dictionary = new SettingsDictionary();
Hash128 m_JsonHash;
/// <summary>
/// Initializes and returns an instance of the FileSettingsRepository
/// with the serialized data location set to the specified path.
/// </summary>
/// <param name="path">The project-relative path to save settings to.</param>
public FileSettingsRepository(string path)
{
m_Path = path;
m_Initialized = false;
AssemblyReloadEvents.beforeAssemblyReload += Save;
EditorApplication.quitting += Save;
}
void Init()
{
if (m_Initialized)
return;
m_Initialized = true;
if (TryLoadSavedJson(out string json))
{
m_Dictionary = null;
m_JsonHash = Hash128.Compute(json);
EditorJsonUtility.FromJsonOverwrite(json, this);
}
if (m_Dictionary == null)
m_Dictionary = new SettingsDictionary();
}
/// <summary>
/// Sets the <see cref="SettingsScope"/> this repository applies to.
/// </summary>
/// <remarks>
/// By default, this repository implementation is relevant to the Project scope, but any implementations
/// that override this method can choose to store this serialized data at a user scope instead.
/// </remarks>
/// <value>
/// <see cref="SettingsScope.Project"/>, meaning that this setting applies to project settings (the default);
/// or <see cref="SettingsScope.User"/>, meaning that this setting applies to user preferences.
/// </value>
/// <seealso cref="ISettingsRepository.scope"/>
public virtual SettingsScope scope => SettingsScope.Project;
/// <summary>
/// Gets the full path to the file containing the serialized settings data.
/// </summary>
/// <value>The location stored for this repository.</value>
/// <seealso cref="ISettingsRepository.path"/>
public string path
{
get { return m_Path; }
}
/// <summary>
/// Sets the name of file containing the serialized settings data.
/// </summary>
/// <value>The bare filename of the settings file.</value>
public string name => Path.GetFileNameWithoutExtension(path);
/// <summary>
/// Loads the JSON file that stores the values for this settings object.
/// </summary>
/// <param name="json">The full path to the JSON file to load.</param>
/// <returns>True if the file exists; false if it doesn't.</returns>
public bool TryLoadSavedJson(out string json)
{
json = string.Empty;
if (!File.Exists(path))
return false;
json = File.ReadAllText(path);
return true;
}
/// <summary>
/// Saves all settings to their serialized state.
/// </summary>
/// <seealso cref="ISettingsRepository.Save"/>
public void Save()
{
Init();
if (!File.Exists(path))
{
var directory = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(directory))
{
Debug.LogError(
$"Settings file {name} is saved to an invalid path: {path}. Settings will not be saved.");
return;
}
Directory.CreateDirectory(directory);
}
string json = EditorJsonUtility.ToJson(this, k_PrettyPrintJson);
// While unlikely, a hash collision is possible. Always test the actual saved contents before early exit.
if (m_JsonHash == Hash128.Compute(json)
&& TryLoadSavedJson(out string existing)
&& existing.Equals(json))
return;
#if UNITY_2019_3_OR_NEWER
// AssetDatabase.IsOpenForEdit can be a very slow synchronous blocking call when Unity is connected to
// Perforce Version Control. Especially if it's called repeatedly with every EditorGUI redraw.
if (File.Exists(path) && !AssetDatabase.IsOpenForEdit(path))
{
if (!AssetDatabase.MakeEditable(path))
{
Debug.LogWarning($"Could not save package settings to {path}");
return;
}
}
#endif
try
{
m_JsonHash = Hash128.Compute(json);
File.WriteAllText(path, json);
}
catch (UnauthorizedAccessException)
{
Debug.LogWarning($"Could not save package settings to {path}");
}
}
/// <summary>
/// Sets a value for a settings entry with a matching key and type `T`.
/// </summary>
/// <param name="key">The key used to identify the settings entry.</param>
/// <param name="value">The value to set. This value must be serializable.</param>
/// <typeparam name="T">The type of value that this key points to.</typeparam>
/// <seealso cref="ISettingsRepository.Set{T}"/>
public void Set<T>(string key, T value)
{
Init();
m_Dictionary.Set<T>(key, value);
}
/// <summary>
/// Returns a value with key of type `T`, or the fallback value if no matching key is found.
/// </summary>
/// <param name="key">The key used to identify the settings entry.</param>
/// <param name="fallback">Specify the value of type `T` to return if the entry can't be found.</param>
/// <typeparam name="T">The type of value that this key points to.</typeparam>
/// <returns>The settings value if a match is found; otherwise, it returns the default (fallback) value.</returns>
/// <seealso cref="ISettingsRepository.Get{T}"/>
public T Get<T>(string key, T fallback = default(T))
{
Init();
return m_Dictionary.Get<T>(key, fallback);
}
/// <summary>
/// Determines whether this repository contains a settings entry that matches the specified key and is of type `T`.
/// </summary>
/// <param name="key">The key used to identify the settings entry.</param>
/// <typeparam name="T">The type of value that this key points to.</typeparam>
/// <returns>True if a match is found for both key and type; false if no entry is found.</returns>
/// <seealso cref="ISettingsRepository.ContainsKey{T}"/>
public bool ContainsKey<T>(string key)
{
Init();
return m_Dictionary.ContainsKey<T>(key);
}
/// <summary>
/// Removes a key-value pair from the settings repository. This method identifies the settings entry to remove
/// by matching the specified key for a value of type `T`.
/// </summary>
/// <param name="key">The key used to identify the settings entry.</param>
/// <typeparam name="T">The type of value that this key points to.</typeparam>
/// <seealso cref="ISettingsRepository.Remove{T}"/>
public void Remove<T>(string key)
{
Init();
m_Dictionary.Remove<T>(key);
}
}
}