using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.Logging; namespace Tango.Settings { /// /// Represents a settings manager capable of holding a collection of settings objects, saving and loading them using JSON. /// public class SettingsManager { #region Singleton private static object _syncLock = new object(); private static String MUTEX_NAME = Assembly.GetEntryAssembly().GetName().Name; private static SettingsManager _default; /// /// Gets the default settings manager instance. /// public static SettingsManager Default { get { lock (_syncLock) { if (_default == null) { _default = new SettingsManager(); } } return _default; } } #endregion private List _settingsCollection; private JsonSerializerSettings _jsonSettings; private LogManager _logManager; private bool _loaded; /// /// Gets or sets the settings file path. /// public String FilePath { get; protected set; } /// /// Gets or sets the settings folder. /// public String Folder { get; protected set; } /// /// Prevents a default instance of the class from being created. /// private SettingsManager() { _logManager = LogManager.Default; FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Settings", Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName) + ".json"); Folder = Path.GetDirectoryName(FilePath); Directory.CreateDirectory(Folder); _settingsCollection = new List(); _jsonSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented, Error = (sender, args) => { args.ErrorContext.Handled = true; LogManager.Default.Log(args.ErrorContext.Error.Message, LogCategory.Error); } }; _jsonSettings.Converters.Add(new StringEnumConverter(false)); } private void EnsureLoaded() { if (!_loaded) { Load(); } } /// /// Gets or creates the specified settings object type. /// /// /// public T GetOrCreate() where T : SettingsBase { try { EnsureLoaded(); } catch (Exception ex) { _logManager.Log(ex, "Error deserializing settings for " + typeof(T).Name); } var settings = _settingsCollection.SingleOrDefault(x => x.GetType() == typeof(T)) as T; if (settings == null) { _logManager.Log("Settings for " + typeof(T).Name + " were not found. Initializing default settings."); settings = Activator.CreateInstance(); settings.SaveAction = Save; _settingsCollection.Add(settings); } return settings; } /// /// Loads the settings from the . /// protected virtual void Load() { if (File.Exists(FilePath)) { _logManager.Log("Loading settings from " + FilePath + "..."); _settingsCollection = JsonConvert.DeserializeObject>(File.ReadAllText(FilePath), _jsonSettings); foreach (var settings in _settingsCollection) { settings.SaveAction = Save; } } _loaded = true; } /// /// Determines whether a settings file exists. /// public virtual bool IsFileExists() { return File.Exists(FilePath); } /// /// Saves settings. /// public virtual void Save() { EnsureLoaded(); using (var mutex = new Mutex(false, MUTEX_NAME)) { var mutexAcquired = false; try { mutexAcquired = mutex.WaitOne(5000); } catch (AbandonedMutexException) { mutexAcquired = true; } // if it wasn't acquired, it timed out, so can handle that how ever we want if (!mutexAcquired) { _logManager.Log("Error saving settings due to Mutex not acquired"); return; } // otherwise, we've acquired the mutex and should do what we need to do, // then ensure that we always release the mutex try { _logManager.Log("Saving settings to " + FilePath + "..."); String json = JsonConvert.SerializeObject(_settingsCollection, _jsonSettings); File.WriteAllText(FilePath, json); } finally { mutex.ReleaseMutex(); } } } } }