using System;
using System.IO;
using UnityEngine;
namespace UnityEditor.SettingsManagement
{
///
/// Represents a settings repository that stores data serialized to a JSON file.
///
[Serializable]
public class FileSettingsRepository : ISettingsRepository
{
///
/// Location of where the package settings are saved under the `ProjectSettings` directory.
///
/// The folder where package settings are saved under the `ProjectSettings` directory.
protected const string k_PackageSettingsDirectory = "ProjectSettings/Packages";
///
/// Location of where the package settings are saved under the `UserSettings` directory.
///
/// Per-project user settings directory.
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;
///
/// Initializes and returns an instance of the FileSettingsRepository
/// with the serialized data location set to the specified path.
///
/// The project-relative path to save settings to.
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();
}
///
/// Sets the this repository applies to.
///
///
/// 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.
///
///
/// , meaning that this setting applies to project settings (the default);
/// or , meaning that this setting applies to user preferences.
///
///
public virtual SettingsScope scope => SettingsScope.Project;
///
/// Gets the full path to the file containing the serialized settings data.
///
/// The location stored for this repository.
///
public string path
{
get { return m_Path; }
}
///
/// Sets the name of file containing the serialized settings data.
///
/// The bare filename of the settings file.
public string name => Path.GetFileNameWithoutExtension(path);
///
/// Loads the JSON file that stores the values for this settings object.
///
/// The full path to the JSON file to load.
/// True if the file exists; false if it doesn't.
public bool TryLoadSavedJson(out string json)
{
json = string.Empty;
if (!File.Exists(path))
return false;
json = File.ReadAllText(path);
return true;
}
///
/// Saves all settings to their serialized state.
///
///
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}");
}
}
///
/// Sets a value for a settings entry with a matching key and type `T`.
///
/// The key used to identify the settings entry.
/// The value to set. This value must be serializable.
/// The type of value that this key points to.
///
public void Set(string key, T value)
{
Init();
m_Dictionary.Set(key, value);
}
///
/// Returns a value with key of type `T`, or the fallback value if no matching key is found.
///
/// The key used to identify the settings entry.
/// Specify the value of type `T` to return if the entry can't be found.
/// The type of value that this key points to.
/// The settings value if a match is found; otherwise, it returns the default (fallback) value.
///
public T Get(string key, T fallback = default(T))
{
Init();
return m_Dictionary.Get(key, fallback);
}
///
/// Determines whether this repository contains a settings entry that matches the specified key and is of type `T`.
///
/// The key used to identify the settings entry.
/// The type of value that this key points to.
/// True if a match is found for both key and type; false if no entry is found.
///
public bool ContainsKey(string key)
{
Init();
return m_Dictionary.ContainsKey(key);
}
///
/// 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`.
///
/// The key used to identify the settings entry.
/// The type of value that this key points to.
///
public void Remove(string key)
{
Init();
m_Dictionary.Remove(key);
}
}
}