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;
}
}
}
}