/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2013 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using GreenshotPlugin; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using System.Threading; namespace Greenshot.IniFile { public class IniConfig { private const string INI_EXTENSION = ".ini"; private const string DEFAULTS_POSTFIX = "-defaults"; private const string FIXED_POSTFIX = "-fixed"; /// /// A lock object for the ini file saving /// private static object iniLock = new object(); /// /// As the ini implementation is kept someone generic, for reusing, this holds the name of the application /// private static string applicationName = null; /// /// As the ini implementation is kept someone generic, for reusing, this holds the name of the configuration /// private static string configName = null; private static string configFolderPath = null; /// /// A Dictionary with all the sections stored by section name /// private static Dictionary sectionMap = new Dictionary(); /// /// A Dictionary with the properties for a section stored by section name /// private static Dictionary> sections = new Dictionary>(); /// /// A Dictionary with the fixed-properties for a section stored by section name /// private static Dictionary> fixedProperties = null; /// /// Is the configuration portable (meaning we don't store it in the AppData directory) /// private static bool portable = false; public static bool IsPortable { get { return portable; } } public static bool AllowSave = true; /// /// Checks if we initialized the ini /// public static bool isInitialized { get { return applicationName != null && configName != null && sectionMap.Count > 0; } } /// /// Initialize the ini config /// /// /// public static void Init(string appName, string confName, string configFolder) { applicationName = appName; configName = confName; configFolderPath = configFolder; if (AllowSave) { Reload(); WatchConfigFile(true); } } /// /// Default init /// public static void Init(string configFolder) { AssemblyProductAttribute[] assemblyProductAttributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false) as AssemblyProductAttribute[]; if (assemblyProductAttributes.Length > 0) { string productName = assemblyProductAttributes[0].Product; LOG.InfoFormat("Using ProductName {0}", productName); Init(productName, "GreenshotImageEditor", configFolder); } else { throw new InvalidOperationException("Assembly ProductName not set."); } } /// /// Enable watching the configuration /// /// private static void WatchConfigFile(bool sendEvents) { /*string iniLocation = CreateIniLocation(configName + INI_EXTENSION); // Wait with watching until the file is there if (Directory.Exists(Path.GetDirectoryName(iniLocation))) { if (watcher == null) { //LOG.DebugFormat("Starting FileSystemWatcher for {0}", iniLocation); // Monitor the ini file watcher = new FileSystemWatcher(); watcher.Path = Path.GetDirectoryName(iniLocation); watcher.Filter = "greenshot.ini"; watcher.NotifyFilter = NotifyFilters.LastWrite; watcher.Changed += new FileSystemEventHandler(ConfigFileChanged); } } if (watcher != null) { watcher.EnableRaisingEvents = sendEvents; }*/ } /// /// Called by the filesystem watcher when a file is changed. /// /// /// private static void ConfigFileChanged(object source, FileSystemEventArgs e) { /*string iniLocation = CreateIniLocation(configName + INI_EXTENSION); if (iniLocation.Equals(e.FullPath)) { LOG.InfoFormat("Config file {0} was changed, reloading", e.FullPath); // Try to reread the configuration int retries = 10; bool configRead = false; while (!configRead && retries != 0) { try { IniConfig.Reload(); configRead = true; } catch (IOException) { retries--; Thread.Sleep(100); } } if (configRead && IniChanged != null) { IniChanged.Invoke(source, e); } }*/ } /// /// Create the location of the configuration file /// private static string CreateIniLocation(string configFilename) { if (applicationName == null || configName == null) { throw new InvalidOperationException("IniConfig.Init not called!"); } /*string iniFilePath = null; string applicationStartupPath = ""; try { applicationStartupPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); } catch (Exception exception) { LOG.WarnFormat("Problem retrieving the AssemblyLocation: {0} (Designer mode?)", exception.Message); applicationStartupPath = @"."; } string pafPath = Path.Combine(applicationStartupPath, @"App\" + applicationName); if (portable || !portableCheckMade) { if (!portable) { LOG.Info("Checking for portable mode."); portableCheckMade = true; if (Directory.Exists(pafPath)) { portable = true; LOG.Info("Portable mode active!"); } } if (portable) { string pafConfigPath = Path.Combine(applicationStartupPath, @"Data\Settings"); try { if (!Directory.Exists(pafConfigPath)) { Directory.CreateDirectory(pafConfigPath); } iniFilePath = Path.Combine(pafConfigPath, configFilename); } catch (Exception e) { LOG.InfoFormat("Portable mode NOT possible, couldn't create directory '{0}'! Reason: {1}", pafConfigPath, e.Message); } } } if (iniFilePath == null) { // check if file is in the same location as started from, if this is the case // we will use this file instead of the Applicationdate folder // Done for Feature Request #2741508 iniFilePath = Path.Combine(applicationStartupPath, configFilename); if (!File.Exists(iniFilePath)) { string iniDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), applicationName); if (!Directory.Exists(iniDirectory)) { Directory.CreateDirectory(iniDirectory); } iniFilePath = Path.Combine(iniDirectory, configFilename); } } LOG.InfoFormat("Using ini file {0}", iniFilePath); return iniFilePath;*/ if (AllowSave) { if (!Directory.Exists(configFolderPath)) { Directory.CreateDirectory(configFolderPath); } return Path.Combine(configFolderPath, configFilename); } return null; } /// /// Reload the Ini file /// public static void Reload() { // Clear the current properties sections = new Dictionary>(); // Load the defaults Read(CreateIniLocation(configName + DEFAULTS_POSTFIX + INI_EXTENSION)); // Load the normal Read(CreateIniLocation(configName + INI_EXTENSION)); // Load the fixed settings fixedProperties = Read(CreateIniLocation(configName + FIXED_POSTFIX + INI_EXTENSION)); foreach (IniSection section in sectionMap.Values) { try { section.Fill(PropertiesForSection(section)); FixProperties(section); } catch (Exception ex) { string sectionName = "unknown"; if (section != null && section.IniSectionAttribute != null && section.IniSectionAttribute.Name != null) { sectionName = section.IniSectionAttribute.Name; } LOG.WarnFormat("Problem reading the ini section {0}", sectionName); LOG.Warn("Exception", ex); } } } /// /// This "fixes" the properties of the section, meaning any properties in the fixed file can't be changed. /// /// IniSection private static void FixProperties(IniSection section) { // Make properties unchangable if (fixedProperties != null) { Dictionary fixedPropertiesForSection = null; if (fixedProperties.TryGetValue(section.IniSectionAttribute.Name, out fixedPropertiesForSection)) { foreach (string fixedPropertyKey in fixedPropertiesForSection.Keys) { if (section.Values.ContainsKey(fixedPropertyKey)) { section.Values[fixedPropertyKey].IsFixed = true; } } } } } /// /// Read the ini file into the Dictionary /// /// Path & Filename of ini file private static Dictionary> Read(string iniLocation) { if (!File.Exists(iniLocation)) { LOG.Info("Can't find file: " + iniLocation); return null; } LOG.InfoFormat("Loading ini-file: {0}", iniLocation); //LOG.Info("Reading ini-properties from file: " + iniLocation); Dictionary> newSections = IniReader.read(iniLocation, Encoding.UTF8); // Merge the newly loaded properties to the already available foreach (string section in newSections.Keys) { Dictionary newProperties = newSections[section]; if (!sections.ContainsKey(section)) { // This section is not yet loaded, simply add the complete section sections.Add(section, newProperties); } else { // Overwrite or add every property from the newly loaded section to the available one Dictionary currentProperties = sections[section]; foreach (string propertyName in newProperties.Keys) { string propertyValue = newProperties[propertyName]; if (currentProperties.ContainsKey(propertyName)) { // Override current value as we are loading in a certain order which insures the default, current and fixed currentProperties[propertyName] = propertyValue; } else { // Add "new" value currentProperties.Add(propertyName, propertyValue); } } } } return newSections; } public static IEnumerable IniSectionNames { get { foreach (string sectionName in sectionMap.Keys) { yield return sectionName; } } } /// /// Method used for internal tricks... /// /// /// public static IniSection GetIniSection(string sectionName) { IniSection returnValue = null; sectionMap.TryGetValue(sectionName, out returnValue); return returnValue; } /// /// A generic method which returns an instance of the supplied type, filled with it's configuration /// /// Filled instance of IniSection type which was supplied public static T GetIniSection() where T : IniSection { T section; Type iniSectionType = typeof(T); string sectionName = IniSection.GetIniSectionAttribute(iniSectionType).Name; if (sectionMap.ContainsKey(sectionName)) { //LOG.Debug("Returning pre-mapped section " + sectionName); section = (T)sectionMap[sectionName]; } else { // Create instance of this type section = (T)Activator.CreateInstance(iniSectionType); // Store for later save & retrieval sectionMap.Add(sectionName, section); section.Fill(PropertiesForSection(section)); FixProperties(section); } if (section.IsDirty) { LOG.DebugFormat("Section {0} is marked dirty, saving!", sectionName); Save(); } return section; } /// /// Get the raw properties for a section /// /// /// public static Dictionary PropertiesForSection(IniSection section) { Type iniSectionType = section.GetType(); string sectionName = section.IniSectionAttribute.Name; // Get the properties for the section Dictionary properties = null; if (sections.ContainsKey(sectionName)) { properties = sections[sectionName]; } else { sections.Add(sectionName, new Dictionary()); properties = sections[sectionName]; } return properties; } /// /// Save the ini file /// public static void Save() { if (AllowSave) { bool acquiredLock = false; try { acquiredLock = Monitor.TryEnter(iniLock, TimeSpan.FromMilliseconds(200)); if (acquiredLock) { // Code that accesses resources that are protected by the lock. string iniLocation = CreateIniLocation(configName + INI_EXTENSION); try { SaveInternally(iniLocation); } catch (Exception ex) { LOG.Error("A problem occured while writing the configuration file to: " + iniLocation); LOG.Error(ex); } } else { // Code to deal with the fact that the lock was not acquired. LOG.Warn("A second thread tried to save the ini-file, we blocked as the first took too long."); } } finally { if (acquiredLock) { Monitor.Exit(iniLock); } } } } /// /// The real save implementation, this first disables the watch otherwise we would be informed of our own changes. /// /// private static void SaveInternally(string iniLocation) { WatchConfigFile(false); try { LOG.Info("Saving configuration to: " + iniLocation); if (!Directory.Exists(Path.GetDirectoryName(iniLocation))) { Directory.CreateDirectory(Path.GetDirectoryName(iniLocation)); } using (TextWriter writer = new StreamWriter(iniLocation, false, Encoding.UTF8)) { foreach (IniSection section in sectionMap.Values) { // Only save EditorConfiguration if (section != null && section.IniSectionAttribute != null && section.IniSectionAttribute.Name == "Editor") { section.Write(writer, false); // Add empty line after section writer.WriteLine(); section.IsDirty = false; } } writer.WriteLine(); // Write left over properties foreach (string sectionName in sections.Keys) { // Check if the section is one that is "registered", if so skip it! if (!sectionMap.ContainsKey(sectionName)) { writer.WriteLine("; The section {0} hasn't been 'claimed' since the last start of Greenshot, therefor it doesn't have additional information here!", sectionName); writer.WriteLine("; The reason could be that the section {0} just hasn't been used, a plugin has an error and can't claim it or maybe the whole section {0} is obsolete.", sectionName); // Write section name writer.WriteLine("[{0}]", sectionName); Dictionary properties = sections[sectionName]; // Loop and write properties foreach (string propertyName in properties.Keys) { writer.WriteLine("{0}={1}", propertyName, properties[propertyName]); } writer.WriteLine(); } } } } finally { try { WatchConfigFile(true); } catch (Exception ex) { LOG.Error("A problem occured while enabling the WatchConfigFile: ", ex); } } } } }