using System; using System.Collections.Generic; using System.IO; using System.Text; using ScrewTurn.Wiki.PluginFramework; using ScrewTurn.Wiki.AclEngine; namespace ScrewTurn.Wiki { /// /// Implements a Settings Storage Provider against local text pluginAssemblies. /// public class SettingsStorageProvider : ISettingsStorageProviderV30 { // Filenames: Settings, Log, RecentChanges, MetaData private const string ConfigFile = "Config.cs"; private const string LogFile = "Log.cs"; private const string RecentChangesFile = "RecentChanges3.cs"; // Old v2 format no more supported private const string AccountActivationMessageFile = "AccountActivationMessage.cs"; private const string EditNoticeFile = "EditNotice.cs"; private const string FooterFile = "Footer.cs"; private const string HeaderFile = "Header.cs"; private const string HtmlHeadFile = "HtmlHead.cs"; private const string LoginNoticeFile = "LoginNotice.cs"; private const string AccessDeniedNoticeFile = "AccessDeniedNotice.cs"; private const string RegisterNoticeFile = "RegisterNotice.cs"; private const string PageFooterFile = "PageFooter.cs"; private const string PageHeaderFile = "PageHeader.cs"; private const string PasswordResetProcedureMessageFile = "PasswordResetProcedureMessage.cs"; private const string SidebarFile = "Sidebar.cs"; private const string PageChangeMessageFile = "PageChangeMessage.cs"; private const string DiscussionChangeMessageFile = "DiscussionChangeMessage.cs"; private const string ApproveDraftMessageFile = "ApproveDraftMessage.cs"; private const string PluginsDirectory = "Plugins"; private const string PluginsStatusFile = "Status.cs"; private const string PluginsConfigDirectory = "Config"; private const string AclFile = "ACL.cs"; private const string LinksFile = "Links.cs"; private const int EstimatedLogEntrySize = 60; // bytes /// /// The name of the provider. /// public static readonly string ProviderName = "Local Settings Provider"; private readonly ComponentInformation info = new ComponentInformation(ProviderName, "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null); private IHostV30 host; private IAclManager aclManager; private AclStorerBase aclStorer; private bool bulkUpdating = false; private Dictionary configData = null; private string GetFullPath(string name) { return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), name); } private string GetFullPathForPlugin(string name) { return Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), PluginsDirectory), name); } private string GetFullPathForPluginConfig(string name) { return Path.Combine(Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), PluginsDirectory), PluginsConfigDirectory), name); } /// /// Initializes the Storage Provider. /// /// The Host of the Component. /// The Configuration data, if any. /// If host or config are null. /// If config is not valid or is incorrect. public void Init(IHostV30 host, string config) { if(host == null) throw new ArgumentNullException("host"); if(config == null) throw new ArgumentNullException("config"); this.host = host; if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) { throw new InvalidConfigurationException("Cannot write into the public directory - check permissions"); } // Create all needed pluginAssemblies if(!File.Exists(GetFullPath(LogFile))) { File.Create(GetFullPath(LogFile)).Close(); } if(!File.Exists(GetFullPath(ConfigFile))) { File.Create(GetFullPath(ConfigFile)).Close(); } if(!File.Exists(GetFullPath(RecentChangesFile))) { File.Create(GetFullPath(RecentChangesFile)).Close(); } if(!File.Exists(GetFullPath(HtmlHeadFile))) { File.Create(GetFullPath(HtmlHeadFile)).Close(); } if(!File.Exists(GetFullPath(HeaderFile))) { File.Create(GetFullPath(HeaderFile)).Close(); } if(!File.Exists(GetFullPath(SidebarFile))) { File.Create(GetFullPath(SidebarFile)).Close(); } if(!File.Exists(GetFullPath(FooterFile))) { File.Create(GetFullPath(FooterFile)).Close(); } if(!File.Exists(GetFullPath(PageHeaderFile))) { File.Create(GetFullPath(PageHeaderFile)).Close(); } if(!File.Exists(GetFullPath(PageFooterFile))) { File.Create(GetFullPath(PageFooterFile)).Close(); } if(!File.Exists(GetFullPath(AccountActivationMessageFile))) { File.Create(GetFullPath(AccountActivationMessageFile)).Close(); } if(!File.Exists(GetFullPath(PasswordResetProcedureMessageFile))) { File.Create(GetFullPath(PasswordResetProcedureMessageFile)).Close(); } if(!File.Exists(GetFullPath(EditNoticeFile))) { File.Create(GetFullPath(EditNoticeFile)).Close(); } if(!File.Exists(GetFullPath(LoginNoticeFile))) { File.Create(GetFullPath(LoginNoticeFile)).Close(); } if(!File.Exists(GetFullPath(AccessDeniedNoticeFile))) { File.Create(GetFullPath(AccessDeniedNoticeFile)).Close(); } if(!File.Exists(GetFullPath(RegisterNoticeFile))) { File.Create(GetFullPath(RegisterNoticeFile)).Close(); } if(!File.Exists(GetFullPath(PageChangeMessageFile))) { File.Create(GetFullPath(PageChangeMessageFile)).Close(); } if(!File.Exists(GetFullPath(DiscussionChangeMessageFile))) { File.Create(GetFullPath(DiscussionChangeMessageFile)).Close(); } if(!File.Exists(GetFullPath(ApproveDraftMessageFile))) { File.Create(GetFullPath(ApproveDraftMessageFile)).Close(); } if(!Directory.Exists(GetFullPath(PluginsDirectory))) { Directory.CreateDirectory(GetFullPath(PluginsDirectory)); } if(!Directory.Exists(GetFullPathForPlugin(PluginsConfigDirectory))) { Directory.CreateDirectory(GetFullPathForPlugin(PluginsConfigDirectory)); } if(!File.Exists(GetFullPathForPlugin(PluginsStatusFile))) { File.Create(GetFullPathForPlugin(PluginsStatusFile)).Close(); } if(!File.Exists(GetFullPath(LinksFile))) { File.Create(GetFullPath(LinksFile)).Close(); } LoadConfig(); // Initialize ACL Manager and Storer aclManager = new StandardAclManager(); aclStorer = new AclStorer(aclManager, GetFullPath(AclFile)); aclStorer.LoadData(); } /// /// Method invoked on shutdown. /// /// This method might not be invoked in some cases. public void Shutdown() { lock(this) { aclStorer.Dispose(); } } /// /// Gets the Information about the Provider. /// public ComponentInformation Information { get { return info; } } /// /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. /// public string ConfigHelpHtml { get { return null; } } /// /// Retrieves the value of a Setting. /// /// The name of the Setting. /// The value of the Setting, or null. /// If name is null. /// If name is empty. public string GetSetting(string name) { if(name == null) throw new ArgumentNullException("name"); if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); lock(this) { string val = null; if(configData.TryGetValue(name, out val)) return val; else return null; } } /// /// Stores the value of a Setting. /// /// The name of the Setting. /// The value of the Setting. Value cannot contain CR and LF characters, which will be removed. /// True if the Setting is stored, false otherwise. /// This method stores the Value immediately. /// If name is null. /// If name is empty. public bool SetSetting(string name, string value) { if(name == null) throw new ArgumentNullException("name"); if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); // Nulls are converted to empty strings if(value == null) value = ""; // Store, then if not bulkUpdating, dump lock(this) { configData[name] = value; if(!bulkUpdating) DumpConfig(); return true; } } /// /// Gets the all the setting values. /// /// All the settings. public IDictionary GetAllSettings() { lock(this) { Dictionary result = new Dictionary(configData.Count); foreach(KeyValuePair pair in configData) { result.Add(pair.Key, pair.Value); } return result; } } /// /// Loads configuration settings from disk. /// private void LoadConfig() { // This method should not call Log.*(...) lock(this) { configData = new Dictionary(30); string data = File.ReadAllText(GetFullPath(ConfigFile), System.Text.UTF8Encoding.UTF8); data = data.Replace("\r", ""); string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); string[] fields; for(int i = 0; i < lines.Length; i++) { lines[i] = lines[i].Trim(); // Skip comments if(lines[i].StartsWith("#")) continue; fields = new string[2]; int idx = lines[i].IndexOf("="); if(idx < 0) continue; try { // Extract key fields[0] = lines[i].Substring(0, idx).Trim(); } catch { // Unexpected string format continue; } try { // Extract value fields[1] = lines[i].Substring(idx + 1).Trim(); } catch { // Blank/invalid value? fields[1] = ""; } configData.Add(fields[0], fields[1]); } } } /// /// Dumps settings on disk. /// private void DumpConfig() { lock(this) { StringBuilder buffer = new StringBuilder(4096); string[] keys = new string[configData.Keys.Count]; configData.Keys.CopyTo(keys, 0); for(int i = 0; i < keys.Length; i++) { buffer.AppendFormat("{0} = {1}\r\n", keys[i], configData[keys[i]]); } File.WriteAllText(GetFullPath(ConfigFile), buffer.ToString()); } } /// /// Starts a Bulk update of the Settings so that a bulk of settings can be set before storing them. /// public void BeginBulkUpdate() { lock(this) { bulkUpdating = true; } } /// /// Ends a Bulk update of the Settings and stores the settings. /// public void EndBulkUpdate() { lock(this) { bulkUpdating = false; DumpConfig(); } } /// /// Sanitizes a stiring from all unfriendly characters. /// /// The input string. /// The sanitized result. private static string Sanitize(string input) { StringBuilder sb = new StringBuilder(input); sb.Replace("|", "{PIPE}"); sb.Replace("\r", ""); sb.Replace("\n", "{BR}"); sb.Replace("<", "<"); sb.Replace(">", ">"); return sb.ToString(); } /// /// Re-sanitizes a string from all unfriendly characters. /// /// The input string. /// The sanitized result. private static string Resanitize(string input) { StringBuilder sb = new StringBuilder(input); sb.Replace("<", "<"); sb.Replace(">", ">"); sb.Replace("{BR}", "\n"); sb.Replace("{PIPE}", "|"); return sb.ToString(); } /// /// Converts an to a string. /// /// The entry type. /// The corresponding string. private static string EntryTypeToString(EntryType type) { switch(type) { case EntryType.General: return "G"; case EntryType.Warning: return "W"; case EntryType.Error: return "E"; default: return "G"; } } /// /// Converts an entry type string to an . /// /// The string. /// The . private static EntryType EntryTypeParse(string value) { switch(value) { case "G": return EntryType.General; case "W": return EntryType.Warning; case "E": return EntryType.Error; default: return EntryType.General; } } /// /// Records a message to the System Log. /// /// The Log Message. /// The Type of the Entry. /// The User. /// This method should not write messages to the Log using the method IHost.LogEntry. /// This method should also never throw exceptions (except for parameter validation). /// If message or user are null. /// If message or user are empty. public void LogEntry(string message, EntryType entryType, string user) { if(message == null) throw new ArgumentNullException("message"); if(message.Length == 0) throw new ArgumentException("Message cannot be empty", "message"); if(user == null) throw new ArgumentNullException("user"); if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); lock(this) { message = Sanitize(message); user = Sanitize(user); LoggingLevel level = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), host.GetSettingValue(SettingName.LoggingLevel)); switch(level) { case LoggingLevel.AllMessages: break; case LoggingLevel.WarningsAndErrors: if(entryType != EntryType.Error && entryType != EntryType.Warning) return; break; case LoggingLevel.ErrorsOnly: if(entryType != EntryType.Error) return; break; case LoggingLevel.DisableLog: return; default: break; } FileStream fs = null; try { fs = new FileStream(GetFullPath(LogFile), FileMode.Append, FileAccess.Write, FileShare.None); } catch { return; } StreamWriter sw = new StreamWriter(fs, System.Text.UTF8Encoding.UTF8); // Type | DateTime | Message | User try { sw.Write(EntryTypeToString(entryType) + "|" + string.Format("{0:yyyy'/'MM'/'dd' 'HH':'mm':'ss}", DateTime.Now) + "|" + message + "|" + user + "\r\n"); } catch { } finally { try { sw.Close(); } catch { } } try { FileInfo fi = new FileInfo(GetFullPath(LogFile)); if(fi.Length > (long)(int.Parse(host.GetSettingValue(SettingName.MaxLogSize)) * 1024)) { CutLog((int)(fi.Length * 0.75)); } } catch { } } } /// /// Reduces the size of the Log to the specified size (or less). /// /// The size to shrink the log to (in bytes). private void CutLog(int size) { lock(this) { // Contains the log messages from the newest to the oldest List entries = new List(GetLogEntries()); FileInfo fi = new FileInfo(GetFullPath(LogFile)); int difference = (int)(fi.Length - size); int removeEntries = difference / EstimatedLogEntrySize * 2; // Double the number of removed entries in order to reduce the # of times Cut is needed int preserve = entries.Count - removeEntries; // The number of entries to be preserved // Copy the entries to preserve in a temp list List toStore = new List(); for(int i = 0; i < preserve; i++) { toStore.Add(entries[i]); } // Reverse the temp list because entries contains the log messages // starting from the newest to the oldest toStore.Reverse(); StringBuilder sb = new StringBuilder(); // Type | DateTime | Message | User foreach(LogEntry e in toStore) { sb.Append(EntryTypeToString(e.EntryType)); sb.Append("|"); sb.Append(e.DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); sb.Append("|"); sb.Append(e.Message); sb.Append("|"); sb.Append(e.User); sb.Append("\r\n"); } FileStream fs = null; try { fs = new FileStream(GetFullPath(LogFile), FileMode.Create, FileAccess.Write, FileShare.None); } catch(Exception ex) { throw new IOException("Unable to open the file: " + LogFile, ex); } StreamWriter sw = new StreamWriter(fs, System.Text.UTF8Encoding.UTF8); // Type | DateTime | Message | User try { sw.Write(sb.ToString()); } catch { } sw.Close(); } } /// /// Gets all the Log Entries, sorted by date/time (oldest to newest). /// /// The Log Entries. public LogEntry[] GetLogEntries() { lock(this) { string content = File.ReadAllText(GetFullPath(LogFile)).Replace("\r", ""); List result = new List(50); string[] lines = content.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); string[] fields; for(int i = 0; i < lines.Length; i++) { fields = lines[i].Split('|'); try { // Try/catch to avoid problems with corrupted file (raw method) result.Add(new LogEntry(EntryTypeParse(fields[0]), DateTime.Parse(fields[1]), Resanitize(fields[2]), Resanitize(fields[3]))); } catch { } } return result.ToArray(); } } /// /// Clear the Log. /// public void ClearLog() { lock(this) { FileStream fs = null; try { fs = new FileStream(GetFullPath(LogFile), FileMode.Create, FileAccess.Write, FileShare.None); } catch(Exception ex) { throw new IOException("Unable to access the file: " + LogFile, ex); } fs.Close(); } } /// /// Gets the current size of the Log, in KB. /// public int LogSize { get { lock(this) { FileInfo fi = new FileInfo(GetFullPath(LogFile)); return (int)(fi.Length / 1024); } } } /// /// Builds the full path for a meta-data item file. /// /// The tag that specifies the context. /// The file. /// The full path. private string GetFullPathForMetaDataItem(string tag, string file) { string targetFile = (!string.IsNullOrEmpty(tag) ? tag + "." : "") + file; return GetFullPath(targetFile); } private static readonly Dictionary MetaDataItemFiles = new Dictionary() { { MetaDataItem.AccountActivationMessage, AccountActivationMessageFile }, { MetaDataItem.EditNotice, EditNoticeFile }, { MetaDataItem.Footer, FooterFile }, { MetaDataItem.Header, HeaderFile }, { MetaDataItem.HtmlHead, HtmlHeadFile }, { MetaDataItem.LoginNotice, LoginNoticeFile }, { MetaDataItem.AccessDeniedNotice, AccessDeniedNoticeFile }, { MetaDataItem.RegisterNotice, RegisterNoticeFile }, { MetaDataItem.PageFooter, PageFooterFile }, { MetaDataItem.PageHeader, PageHeaderFile }, { MetaDataItem.PasswordResetProcedureMessage, PasswordResetProcedureMessageFile }, { MetaDataItem.Sidebar, SidebarFile }, { MetaDataItem.PageChangeMessage, PageChangeMessageFile }, { MetaDataItem.DiscussionChangeMessage, DiscussionChangeMessageFile }, { MetaDataItem.ApproveDraftMessage, ApproveDraftMessageFile } }; /// /// Gets a meta-data item's content. /// /// The item. /// The tag that specifies the context (usually the namespace). /// The content. public string GetMetaDataItem(MetaDataItem item, string tag) { lock(this) { string fullFile = GetFullPathForMetaDataItem(tag, MetaDataItemFiles[item]); if(!File.Exists(fullFile)) return ""; else return File.ReadAllText(fullFile); } } /// /// Sets a meta-data items' content. /// /// The item. /// The tag that specifies the context (usually the namespace). /// The content. /// true if the content is set, false otherwise. public bool SetMetaDataItem(MetaDataItem item, string tag, string content) { if(content == null) content = ""; lock(this) { File.WriteAllText(GetFullPathForMetaDataItem(tag, MetaDataItemFiles[item]), content); return true; } } /// /// Gets the change from a string. /// /// The string. /// The change. private static ScrewTurn.Wiki.PluginFramework.Change GetChange(string change) { switch(change.ToUpperInvariant()) { case "U": return ScrewTurn.Wiki.PluginFramework.Change.PageUpdated; case "D": return ScrewTurn.Wiki.PluginFramework.Change.PageDeleted; case "R": return ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack; case "N": return ScrewTurn.Wiki.PluginFramework.Change.PageRenamed; case "MP": return ScrewTurn.Wiki.PluginFramework.Change.MessagePosted; case "ME": return ScrewTurn.Wiki.PluginFramework.Change.MessageEdited; case "MD": return ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted; default: throw new NotSupportedException(); } } /// /// Gets the change string for a change. /// /// The change. /// The change string. private static string GetChangeString(ScrewTurn.Wiki.PluginFramework.Change change) { switch(change) { case ScrewTurn.Wiki.PluginFramework.Change.PageUpdated: return "U"; case ScrewTurn.Wiki.PluginFramework.Change.PageDeleted: return "D"; case ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack: return "R"; case ScrewTurn.Wiki.PluginFramework.Change.PageRenamed: return "N"; case ScrewTurn.Wiki.PluginFramework.Change.MessagePosted: return "MP"; case ScrewTurn.Wiki.PluginFramework.Change.MessageEdited: return "ME"; case ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted: return "MD"; default: throw new NotSupportedException(); } } /// /// Gets the recent changes of the Wiki. /// /// The recent Changes (oldest to newest). public RecentChange[] GetRecentChanges() { lock(this) { // Load from file string data = File.ReadAllText(GetFullPath(RecentChangesFile)).Replace("\r", ""); string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); List changes = new List(lines.Length); string[] fields; for(int i = 0; i < lines.Length; i++) { try { // Try/catch to avoid problems for corrupted file (raw method) fields = lines[i].Split('|'); ScrewTurn.Wiki.PluginFramework.Change c = GetChange(fields[5]); changes.Add(new RecentChange(Tools.UnescapeString(fields[0]), Tools.UnescapeString(fields[1]), Tools.UnescapeString(fields[2]), DateTime.Parse(fields[3]), Tools.UnescapeString(fields[4]), c, Tools.UnescapeString(fields[6]))); } catch { } } changes.Sort((x, y) => { return x.DateTime.CompareTo(y.DateTime); }); return changes.ToArray(); } } /// /// Adds a new change. /// /// The page name. /// The page title. /// The message subject (or null). /// The date/time. /// The user. /// The change. /// The description (optional). /// true if the change is saved, false otherwise. /// If page, title or user are null. /// If page, title or user are empty. public bool AddRecentChange(string page, string title, string messageSubject, DateTime dateTime, string user, ScrewTurn.Wiki.PluginFramework.Change change, string descr) { if(page == null) throw new ArgumentNullException("page"); if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); if(title == null) throw new ArgumentNullException("title"); if(title.Length == 0) throw new ArgumentException("Title cannot be empty", "title"); if(user == null) throw new ArgumentNullException("user"); if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); if(messageSubject == null) messageSubject = ""; if(descr == null) descr = ""; lock(this) { StringBuilder sb = new StringBuilder(100); sb.Append(Tools.EscapeString(page)); sb.Append("|"); sb.Append(Tools.EscapeString(title)); sb.Append("|"); sb.Append(Tools.EscapeString(messageSubject)); sb.Append("|"); sb.Append(dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); sb.Append("|"); sb.Append(Tools.EscapeString(user)); sb.Append("|"); sb.Append(GetChangeString(change)); sb.Append("|"); sb.Append(Tools.EscapeString(descr)); sb.Append("\r\n"); File.AppendAllText(GetFullPath(RecentChangesFile), sb.ToString()); // Delete old changes, if needed int max = int.Parse(host.GetSettingValue(SettingName.MaxRecentChanges)); if(GetRecentChanges().Length > max) CutRecentChanges((int)(max * 0.90)); return true; } } /// /// Reduces the size of the recent changes file to the specified size, deleting old entries. /// /// The new Size. private void CutRecentChanges(int size) { lock(this) { List changes = new List(GetRecentChanges()); if(size >= changes.Count) return; int idx = changes.Count - size + 1; StringBuilder sb = new StringBuilder(); for(int i = idx; i < changes.Count; i++) { sb.Append(Tools.EscapeString(changes[i].Page)); sb.Append("|"); sb.Append(Tools.EscapeString(changes[i].Title)); sb.Append("|"); sb.Append(Tools.EscapeString(changes[i].MessageSubject)); sb.Append("|"); sb.Append(changes[i].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); sb.Append("|"); sb.Append(Tools.EscapeString(changes[i].User)); sb.Append("|"); sb.Append(GetChangeString(changes[i].Change)); sb.Append("|"); sb.Append(Tools.EscapeString(changes[i].Description)); sb.Append("\r\n"); } File.WriteAllText(GetFullPath(RecentChangesFile), sb.ToString()); } } /// /// Lists the stored plugin assemblies. /// /// public string[] ListPluginAssemblies() { lock(this) { string[] files = Directory.GetFiles(GetFullPath(PluginsDirectory), "*.dll"); string[] result = new string[files.Length]; for(int i = 0; i < files.Length; i++) result[i] = Path.GetFileName(files[i]); return result; } } /// /// Stores a plugin's assembly, overwriting existing ones if present. /// /// The file name of the assembly, such as "Assembly.dll". /// The assembly content. /// true if the assembly is stored, false otherwise. /// If filename or assembly are null. /// If filename or assembly are empty. public bool StorePluginAssembly(string filename, byte[] assembly) { if(filename == null) throw new ArgumentNullException("filename"); if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); if(assembly == null) throw new ArgumentNullException("assembly"); if(assembly.Length == 0) throw new ArgumentException("Assembly cannot be empty", "assembly"); lock(this) { try { File.WriteAllBytes(GetFullPathForPlugin(filename), assembly); } catch(IOException) { return false; } return true; } } /// /// Retrieves a plugin's assembly. /// /// The file name of the assembly. /// The assembly content, or null. /// If filename is null. /// If filename is empty. public byte[] RetrievePluginAssembly(string filename) { if(filename == null) throw new ArgumentNullException("filename"); if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); if(!File.Exists(GetFullPathForPlugin(filename))) return null; lock(this) { try { return File.ReadAllBytes(GetFullPathForPlugin(filename)); } catch(IOException) { return null; } } } /// /// Removes a plugin's assembly. /// /// The file name of the assembly to remove, such as "Assembly.dll". /// true if the assembly is removed, false otherwise. /// If filename is null. /// If filename is empty. public bool DeletePluginAssembly(string filename) { if(filename == null) throw new ArgumentNullException(filename); if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); lock(this) { string fullName = GetFullPathForPlugin(filename); if(!File.Exists(fullName)) return false; try { File.Delete(fullName); return true; } catch(IOException) { return false; } } } /// /// Sets the status of a plugin. /// /// The Type name of the plugin. /// The plugin status. /// true if the status is stored, false otherwise. /// If typeName is null. /// If typeName is empty. public bool SetPluginStatus(string typeName, bool enabled) { if(typeName == null) throw new ArgumentNullException("typeName"); if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); lock(this) { string data = File.ReadAllText(GetFullPathForPlugin(PluginsStatusFile)).Replace("\r", ""); string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); int idx = -1; for(int i = 0; i < lines.Length; i++) { if(lines[i].Equals(typeName)) { idx = i; break; } } if(enabled) { if(idx >= 0) { StringBuilder sb = new StringBuilder(200); for(int i = 0; i < lines.Length; i++) { if(i != idx) sb.Append(lines[i] + "\r\n"); } File.WriteAllText(GetFullPathForPlugin(PluginsStatusFile), sb.ToString()); } // Else nothing to do } else { if(idx == -1) { StringBuilder sb = new StringBuilder(200); for(int i = 0; i < lines.Length; i++) { if(i != idx) sb.Append(lines[i] + "\r\n"); } sb.Append(typeName + "\r\n"); File.WriteAllText(GetFullPathForPlugin(PluginsStatusFile), sb.ToString()); } // Else nothing to do } } return true; } /// /// Gets the status of a plugin. /// /// The Type name of the plugin. /// The status (true for enabled, false for disabled), or true if no status is found. /// If typeName is null. /// If typeName is empty. public bool GetPluginStatus(string typeName) { if(typeName == null) throw new ArgumentNullException("typeName"); if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); lock(this) { string data = File.ReadAllText(GetFullPathForPlugin(PluginsStatusFile)).Replace("\r", ""); string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); for(int i = 0; i < lines.Length; i++) { if(lines[i].Equals(typeName)) return false; } return true; } } /// /// Sets the configuration of a plugin. /// /// The Type name of the plugin. /// The configuration. /// true if the configuration is stored, false otherwise. /// If typeName is null. /// If typeName is empty. public bool SetPluginConfiguration(string typeName, string config) { if(typeName == null) throw new ArgumentNullException("typeName"); if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); lock(this) { try { File.WriteAllText(GetFullPathForPluginConfig(typeName + ".cs"), config != null ? config : ""); return true; } catch(IOException) { return false; } } } /// /// Gets the configuration of a plugin. /// /// The Type name of the plugin. /// The plugin configuration, or String.Empty. /// If typeName is null. /// If typeName is empty. public string GetPluginConfiguration(string typeName) { if(typeName == null) throw new ArgumentNullException("typeName"); if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); lock(this) { string file = GetFullPathForPluginConfig(typeName + ".cs"); if(!File.Exists(file)) return ""; else { try { return File.ReadAllText(file); } catch(IOException) { return ""; } } } } /// /// Gets the ACL Manager instance. /// public IAclManager AclManager { get { lock(this) { return aclManager; } } } /// /// Stores the outgoing links of a page, overwriting existing data. /// /// The full name of the page. /// The full names of the pages that page links to. /// true if the outgoing links are stored, false otherwise. /// If page or outgoingLinks are null. /// If page or outgoingLinks are empty. public bool StoreOutgoingLinks(string page, string[] outgoingLinks) { if(page == null) throw new ArgumentNullException("page"); if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); if(outgoingLinks == null) throw new ArgumentNullException("outgoingLinks"); lock(this) { // Step 1: remove old values string[] lines = File.ReadAllLines(GetFullPath(LinksFile)); StringBuilder sb = new StringBuilder(lines.Length * 100); string testString = page + "|"; foreach(string line in lines) { if(!line.StartsWith(testString)) { sb.Append(line); sb.Append("\r\n"); } } lines = null; // Step 2: add new values sb.Append(page); sb.Append("|"); for(int i = 0; i < outgoingLinks.Length; i++) { if(outgoingLinks[i] == null) throw new ArgumentNullException("outgoingLinks", "Null element in outgoing links array"); if(outgoingLinks[i].Length == 0) throw new ArgumentException("Elements in outgoing links cannot be empty", "outgoingLinks"); sb.Append(outgoingLinks[i]); if(i != outgoingLinks.Length - 1) sb.Append("|"); } sb.Append("\r\n"); File.WriteAllText(GetFullPath(LinksFile), sb.ToString()); } return true; } /// /// Gets the outgoing links of a page. /// /// The full name of the page. /// The outgoing links. /// If page is null. /// If page is empty. public string[] GetOutgoingLinks(string page) { if(page == null) throw new ArgumentNullException("page"); if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); lock(this) { string[] lines = File.ReadAllLines(GetFullPath(LinksFile)); string testString = page + "|"; foreach(string line in lines) { if(line.StartsWith(testString)) { string[] fields = line.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); string[] result = new string[fields.Length - 1]; Array.Copy(fields, 1, result, 0, result.Length); return result; } } } // Nothing found, return empty array return new string[0]; } /// /// Gets all the outgoing links stored. /// /// The outgoing links, in a dictionary in the form page->outgoing_links. public IDictionary GetAllOutgoingLinks() { lock(this) { string[] lines = File.ReadAllLines(GetFullPath(LinksFile)); Dictionary result = new Dictionary(lines.Length); string[] fields; string[] links; foreach(string line in lines) { fields = line.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); links = new string[fields.Length - 1]; Array.Copy(fields, 1, links, 0, links.Length); result.Add(fields[0], links); } return result; } } /// /// Deletes the outgoing links of a page and all the target links that include the page. /// /// The full name of the page. /// true if the links are deleted, false otherwise. /// If page is null. /// If page is empty. public bool DeleteOutgoingLinks(string page) { if(page == null) throw new ArgumentNullException("page"); if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); lock(this) { bool removedSomething = false; IDictionary links = GetAllOutgoingLinks(); // Step 1: rename source page, if any removedSomething = links.Remove(page); // Step 2: rename all target pages, for all source pages string[] keys = new string[links.Count]; links.Keys.CopyTo(keys, 0); foreach(string key in keys) { List currentLinks = new List(links[key]); removedSomething |= currentLinks.Remove(page); links[key] = currentLinks.ToArray(); } // Step 3: save on disk, if data changed if(removedSomething) { StringBuilder sb = new StringBuilder(links.Count * 100); foreach(string key in links.Keys) { sb.Append(key); sb.Append("|"); for(int i = 0; i < links[key].Length; i++) { sb.Append(links[key][i]); if(i != links[key][i].Length - 1) sb.Append("|"); } sb.Append("\r\n"); } File.WriteAllText(GetFullPath(LinksFile), sb.ToString()); } return removedSomething; } } /// /// Updates all outgoing links data for a page rename. /// /// The old page name. /// The new page name. /// true if the data is updated, false otherwise. /// If oldName or newName are null. /// If oldName or newName are empty. public bool UpdateOutgoingLinksForRename(string oldName, string newName) { if(oldName == null) throw new ArgumentNullException("oldName"); if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); if(newName == null) throw new ArgumentNullException("newName"); if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); lock(this) { bool replacedSomething = false; IDictionary links = GetAllOutgoingLinks(); // Step 1: rename source page, if any string[] tempLinks = null; if(links.TryGetValue(oldName, out tempLinks)) { links.Remove(oldName); links.Add(newName, tempLinks); replacedSomething = true; } // Step 2: rename all target pages, for all source pages foreach(string key in links.Keys) { for(int i = 0; i < links[key].Length; i++) { if(links[key][i] == oldName) { links[key][i] = newName; replacedSomething = true; } } } // Step 3: save on disk, if data changed if(replacedSomething) { StringBuilder sb = new StringBuilder(links.Count * 100); foreach(string key in links.Keys) { sb.Append(key); sb.Append("|"); for(int i = 0; i < links[key].Length; i++) { sb.Append(links[key][i]); if(i != links[key][i].Length - 1) sb.Append("|"); } sb.Append("\r\n"); } File.WriteAllText(GetFullPath(LinksFile), sb.ToString()); } return replacedSomething; } } } }