Fixed and closed #494: PluginPack now compiled as separate assemblies. Also implemented Updater plugin to silently replace PluginPack.dll with new DLLs.

This commit is contained in:
Dario Solera 2010-05-02 09:13:57 +00:00
parent 0a54e553cc
commit c89e576368
26 changed files with 905 additions and 936 deletions

View file

@ -1,373 +0,0 @@

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary>
/// Implements a formatter provider that counts download of files and attachments.
/// </summary>
public class DownloadCounter : IFormatterProviderV30 {
private static readonly DateTime DefaultStartDate = new DateTime(2009, 1, 1);
private const string CountPlaceholder = "#count#";
private const string DailyPlaceholder = "#daily#";
private const string WeeklyPlaceholder = "#weekly#";
private const string MonthlyPlaceholder = "#monthly#";
private IHostV30 _host;
private string _config;
private bool _enableLogging = true;
private static readonly ComponentInformation Info = new ComponentInformation("Download Counter Plugin", "Threeplicate Srl", "3.0.1.471", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/DownloadCounter.txt");
private static readonly Regex XmlRegex = new Regex(@"\<countDownloads(.+?)\>(.+?)\<\/countDownloads\>",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
/// <summary>
/// Specifies whether or not to execute Phase 1.
/// </summary>
public bool PerformPhase1 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 3.
/// </summary>
public bool PerformPhase3 {
get { return true; }
}
/// <summary>
/// Gets the execution priority of the provider (0 lowest, 100 highest).
/// </summary>
public int ExecutionPriority {
get { return 50; }
}
/// <summary>
/// Performs a Formatting phase.
/// </summary>
/// <param name="raw">The raw content to Format.</param>
/// <param name="context">The Context information.</param>
/// <param name="phase">The Phase.</param>
/// <returns>The Formatted content.</returns>
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
// <countDownloads pattern="..."[ startDate="yyyy/mm/dd"]>
// <file name="..."[ provider="..."] />
// <attachment name="..." page="..."[ provider="..."] />
// </countDownloads>
// All downloads are grouped together
// Pattern placeholders: #COUNT#, #DAILY#, #WEEKLY#, #MONTHLY# (case insensitive)
// Pattern example: "Downloaded #COUNT# times (#MONTHLY#/month)!"
// StartDate omitted -> 2009/01/01
// Provider omitted -> default
// File/attachment/page not found -> ignored
StringBuilder buffer = new StringBuilder(raw);
KeyValuePair<int, string> block = FindAndRemoveFirstOccurrence(buffer);
while(block.Key != -1) {
string blockHash = "DownCount-" + block.Value.ToString();
string result = null;
if(System.Web.HttpContext.Current != null) {
result = System.Web.HttpContext.Current.Cache[blockHash] as string;
}
if(result == null) {
XmlDocument doc = new XmlDocument();
doc.LoadXml(block.Value);
string pattern;
DateTime startDate;
GetRootAttributes(doc, out pattern, out startDate);
double downloads = CountAllDownloads(doc);
double timeSpanInDays = (DateTime.Now - startDate).TotalDays;
int dailyDownloads = (int)Math.Round(downloads / timeSpanInDays);
int weeklyDownloads = (int)Math.Round(downloads / (timeSpanInDays / 7D));
int monthlyDownloads = (int)Math.Round(downloads / (timeSpanInDays / 30D));
result = BuildResult(pattern, (int)downloads, dailyDownloads, weeklyDownloads, monthlyDownloads);
if(System.Web.HttpContext.Current != null) {
System.Web.HttpContext.Current.Cache.Add(blockHash, result, null, DateTime.Now.AddMinutes(10),
System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
}
}
buffer.Insert(block.Key, result);
block = FindAndRemoveFirstOccurrence(buffer);
}
return buffer.ToString();
}
/// <summary>
/// Builds the result.
/// </summary>
/// <param name="pattern">The result pattern.</param>
/// <param name="downloads">The downloads.</param>
/// <param name="daily">The daily downloads.</param>
/// <param name="weekly">The weekly downloads.</param>
/// <param name="monthly">The monthly downloads.</param>
/// <returns>The result.</returns>
private static string BuildResult(string pattern, int downloads, int daily, int weekly, int monthly) {
StringBuilder buffer = new StringBuilder(pattern);
ReplacePlaceholder(buffer, CountPlaceholder, downloads.ToString());
ReplacePlaceholder(buffer, DailyPlaceholder, daily.ToString());
ReplacePlaceholder(buffer, WeeklyPlaceholder, weekly.ToString());
ReplacePlaceholder(buffer, MonthlyPlaceholder, monthly.ToString());
return buffer.ToString();
}
/// <summary>
/// Replaces a placeholder with its value.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="placeholder">The placeholder.</param>
/// <param name="value">The value.</param>
private static void ReplacePlaceholder(StringBuilder buffer, string placeholder, string value) {
int index = -1;
do {
index = buffer.ToString().ToLowerInvariant().IndexOf(placeholder);
if(index != -1) {
buffer.Remove(index, placeholder.Length);
buffer.Insert(index, value);
}
} while(index != -1);
}
/// <summary>
/// Gets the root attributes.
/// </summary>
/// <param name="doc">The XML document.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="startDate">The start date/time.</param>
private static void GetRootAttributes(XmlDocument doc, out string pattern, out DateTime startDate) {
XmlNodeList root = doc.GetElementsByTagName("countDownloads");
pattern = TryGetAttribute(root[0], "pattern");
string startDateTemp = TryGetAttribute(root[0], "startDate");
if(!DateTime.TryParseExact(startDateTemp, "yyyy'/'MM'/'dd", null, System.Globalization.DateTimeStyles.AssumeLocal, out startDate)) {
startDate = DefaultStartDate;
}
}
/// <summary>
/// Tries to get the value of an attribute.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="attribute">The name of the attribute.</param>
/// <returns>The value of the attribute or <c>null</c> if no value is available.</returns>
private static string TryGetAttribute(XmlNode node, string attribute) {
XmlAttribute attr = node.Attributes[attribute];
if(attr != null) return attr.Value;
else return null;
}
/// <summary>
/// Counts all the downloads.
/// </summary>
/// <param name="doc">The XML document.</param>
/// <returns>The download count.</returns>
private int CountAllDownloads(XmlDocument doc) {
XmlNodeList files = doc.GetElementsByTagName("file");
XmlNodeList attachments = doc.GetElementsByTagName("attachment");
int count = 0;
foreach(XmlNode node in files) {
string name = TryGetAttribute(node, "name");
string provider = TryGetAttribute(node, "provider");
count += CountDownloads(name, provider);
}
foreach(XmlNode node in attachments) {
string name = TryGetAttribute(node, "name");
string page = TryGetAttribute(node, "page");
string provider = TryGetAttribute(node, "provider");
count += CountDownloads(name, page, provider);
}
return count;
}
/// <summary>
/// Counts the downloads of a file.
/// </summary>
/// <param name="fullFilePath">The full file path.</param>
/// <param name="providerName">The provider or <c>null</c> or <b>string.Empty</b>.</param>
/// <returns>The downloads.</returns>
private int CountDownloads(string fullFilePath, string providerName) {
if(string.IsNullOrEmpty(fullFilePath)) return 0;
IFilesStorageProviderV30 provider = GetProvider(providerName);
if(provider == null) return 0;
if(!fullFilePath.StartsWith("/")) fullFilePath = "/" + fullFilePath;
string directory = StDirectoryInfo.GetDirectory(fullFilePath);
StFileInfo[] files = _host.ListFiles(new StDirectoryInfo(directory, provider));
fullFilePath = fullFilePath.ToLowerInvariant();
foreach(StFileInfo file in files) {
if(file.FullName.ToLowerInvariant() == fullFilePath) {
return file.RetrievalCount;
}
}
LogWarning("File " + provider.GetType().FullName + fullFilePath + " not found");
return 0;
}
/// <summary>
/// Counts the downloads of a file.
/// </summary>
/// <param name="attachmentName">The name of the attachment.</param>
/// <param name="pageName">The full name of the page.</param>
/// <param name="providerName">The provider or <c>null</c> or <b>string.Empty</b>.</param>
/// <returns>The downloads.</returns>
private int CountDownloads(string attachmentName, string pageName, string providerName) {
if(string.IsNullOrEmpty(attachmentName)) return 0;
if(string.IsNullOrEmpty(pageName)) return 0;
PageInfo page = _host.FindPage(pageName);
if(page == null) {
LogWarning("Page " + pageName + " not found");
return 0;
}
IFilesStorageProviderV30 provider = GetProvider(providerName);
if(provider == null) return 0;
StFileInfo[] attachments = _host.ListPageAttachments(page);
attachmentName = attachmentName.ToLowerInvariant();
foreach(StFileInfo attn in attachments) {
if(attn.FullName.ToLowerInvariant() == attachmentName) {
return attn.RetrievalCount;
}
}
LogWarning("Attachment " + provider.GetType().FullName + "(" + pageName + ") " + attachmentName + " not found");
return 0;
}
/// <summary>
/// Gets the specified provider or the default one.
/// </summary>
/// <param name="provider">The provider.</param>
/// <returns></returns>
private IFilesStorageProviderV30 GetProvider(string provider) {
if(string.IsNullOrEmpty(provider)) provider = _host.GetSettingValue(SettingName.DefaultFilesStorageProvider);
provider = provider.ToLowerInvariant();
IFilesStorageProviderV30[] all = _host.GetFilesStorageProviders(true);
foreach(IFilesStorageProviderV30 prov in all) {
if(prov.GetType().FullName.ToLowerInvariant() == provider) return prov;
}
LogWarning("Provider " + provider + " not found");
return null;
}
/// <summary>
/// Finds and removes the first occurrence of the XML markup.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>The index-content data.</returns>
private static KeyValuePair<int, string> FindAndRemoveFirstOccurrence(StringBuilder buffer) {
Match match = XmlRegex.Match(buffer.ToString());
if(match.Success) {
buffer.Remove(match.Index, match.Length);
return new KeyValuePair<int, string>(match.Index, match.Value);
}
return new KeyValuePair<int, string>(-1, null);
}
/// <summary>
/// Logs a warning.
/// </summary>
/// <param name="message">The message.</param>
private void LogWarning(string message) {
if(_enableLogging) {
_host.LogEntry(message, LogEntryType.Warning, null, this);
}
}
/// <summary>
/// Prepares the title of an item for display (always during phase 3).
/// </summary>
/// <param name="title">The input title.</param>
/// <param name="context">The context information.</param>
/// <returns>The prepared title (no markup allowed).</returns>
public string PrepareTitle(string title, ContextInformation context) {
return title;
}
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
public void Init(IHostV30 host, string config) {
this._host = host;
this._config = config != null ? config : "";
if(this._config.ToLowerInvariant() == "nolog") _enableLogging = false;
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
// Nothing to do
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return Info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return "Specify <i>nolog</i> for disabling warning log messages for non-existent files or attachments."; }
}
}
}

View file

@ -1,149 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using System.Text.RegularExpressions;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary>
/// Implements a footnotes plugin.
/// </summary>
public class Footnotes : IFormatterProviderV30 {
// Kindly contributed by Jens Felsner
private static readonly ComponentInformation info = new ComponentInformation("Footnotes Plugin", "Threeplicate Srl", "3.0.1.471", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/Footnotes.txt");
private static readonly Regex ReferencesRegex = new Regex("(<[ ]*references[ ]*/[ ]*>|<[ ]*references[ ]*>.*?<[ ]*/[ ]*references[ ]*>)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RefRegex = new Regex("<[ ]*ref[ ]*>.*?<[ ]*/[ ]*ref[ ]*>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RefRemovalRegex = new Regex("(<[ ]*ref[ ]*>|<[ ]*/[ ]*ref[ ]*>)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private IHostV30 host = null;
private string config = "";
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <exception cref="ArgumentNullException">If <paramref name="host"/> or <paramref name="config"/> are <c>null</c>.</exception>
/// <exception cref="InvalidConfigurationException">If <paramref name="config"/> is not valid or is incorrect.</exception>
public void Init(IHostV30 host, string config) {
this.host = host;
this.config = config != null ? config : "";
}
// Replaces the first occurence of 'find' in 'input' with 'replace'
private static string ReplaceFirst(string input, string find, string replace) {
return input.Substring(0, input.IndexOf(find)) + replace + input.Substring(input.IndexOf(find) + find.Length);
}
/// <summary>
/// Performs a Formatting phase.
/// </summary>
/// <param name="raw">The raw content to Format.</param>
/// <param name="context">The Context information.</param>
/// <param name="phase">The Phase.</param>
/// <returns>The Formatted content.</returns>
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
// Match all <ref>*</ref>
MatchCollection mc = RefRegex.Matches(raw);
// No ref-tag found, nothing to do
if(mc.Count == 0) return raw;
// No references tag
if(ReferencesRegex.Matches(raw).Count == 0) {
return raw + "<br/><span style=\"color: #FF0000;\">Reference Error! Missing element &lt;references/&gt;</span>";
}
string output = raw;
string ref_string = "<table class=\"footnotes\">";
int footnoteCounter = 0;
// For each <ref>...</ref> replace it with Footnote, append it to ref-section
foreach(Match m in mc) {
footnoteCounter++;
output = ReplaceFirst(output, m.Value, "<a id=\"refnote" + footnoteCounter.ToString() + "\" href=\"#footnote" + footnoteCounter.ToString() + "\"><sup>" + footnoteCounter.ToString() + "</sup></a>");
ref_string += "<tr><td><a id=\"footnote" + footnoteCounter.ToString() + "\" href=\"#refnote" + footnoteCounter.ToString() + "\"><sup>" + footnoteCounter.ToString() + "</sup></a></td><td>" + RefRemovalRegex.Replace(m.Value, "") + "</td></tr>";
}
ref_string += "</table>";
// Replace <reference/> with ref-section
output = ReferencesRegex.Replace(output, ref_string);
return output;
}
#region IFormatterProviderV30 Member
/// <summary>
/// Specifies whether or not to execute Phase 1.
/// </summary>
public bool PerformPhase1 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get { return true; }
}
/// <summary>
/// Specifies whether or not to execute Phase 3.
/// </summary>
public bool PerformPhase3 {
get { return false; }
}
/// <summary>
/// Gets the execution priority of the provider (0 lowest, 100 highest).
/// </summary>
public int ExecutionPriority {
get { return 50; }
}
/// <summary>
/// Prepares the title of an item for display (always during phase 3).
/// </summary>
/// <param name="title">The input title.</param>
/// <param name="context">The context information.</param>
/// <returns>The prepared title (no markup allowed).</returns>
public string PrepareTitle(string title, ContextInformation context) {
return title;
}
#endregion
#region IProviderV30 Member
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return null; }
}
#endregion
}
}

View file

@ -1,147 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary>
/// Implements a Formatter Provider that allows to write multi-language content in Wiki Pages.
/// </summary>
public class MultilanguageContentPlugin : IFormatterProviderV30 {
private IHostV30 host;
private string config;
private ComponentInformation info = new ComponentInformation("Multilanguage Content Plugin", "Threeplicate Srl", "3.0.1.471", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/Multilanguage.txt");
private string defaultLanguage = "en-us";
private bool displayWarning = false;
private const string DivStyle = "padding: 2px; margin-bottom: 10px; font-size: 11px; background-color: #FFFFC4; border: solid 1px #DDDDDD;";
private const string StandardMessage = @"<div style=""" + DivStyle + @""">The content of this Page is localized in your language. To change the language settings, please go to the <a href=""Language.aspx"">Language Selection</a> page.</div>";
private const string NotLocalizedMessage = @"<div style=""" + DivStyle + @""">The content of this Page is <b>not</b> available in your language, and it is displayed in the default language of the Wiki. To change the language settings, please go to the <a href=""Language.aspx"">Language Selection</a> page.</div>";
/// <summary>
/// Specifies whether or not to execute Phase 1.
/// </summary>
public bool PerformPhase1 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 3.
/// </summary>
public bool PerformPhase3 {
get { return true; }
}
/// <summary>
/// Performs a Formatting phase.
/// </summary>
/// <param name="raw">The raw content to Format.</param>
/// <param name="context">The Context information.</param>
/// <param name="phase">The Phase.</param>
/// <returns>The Formatted content.</returns>
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
string result = ExtractLocalizedContent(context.Language, raw); // Try to load localized content
bool notLocalized = false;
bool noLocalization = false;
if(result == null) {
result = ExtractLocalizedContent(defaultLanguage, raw); // Load content in the default language
notLocalized = true;
noLocalization = false;
}
if(result == null) {
result = raw; // The Page is not localized, return all the content
notLocalized = false;
noLocalization = true;
}
if(displayWarning && !noLocalization && context.Page != null) {
if(notLocalized) return NotLocalizedMessage + result;
else return StandardMessage + result;
}
else return result;
}
private string ExtractLocalizedContent(string language, string content) {
string head = "\\<" + language + "\\>", tail = "\\<\\/" + language + "\\>";
Regex regex = new Regex(head + "(.+?)" + tail, RegexOptions.IgnoreCase | RegexOptions.Singleline);
Match match = regex.Match(content);
StringBuilder sb = new StringBuilder(1000);
while(match.Success) {
sb.Append(match.Groups[1].Value);
match = regex.Match(content, match.Index + match.Length);
}
if(sb.Length > 0) return sb.ToString();
else return null;
}
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
public void Init(IHostV30 host, string config) {
this.host = host;
this.config = config != null ? config : "";
defaultLanguage = host.GetSettingValue(SettingName.DefaultLanguage);
displayWarning = config.ToLowerInvariant().Equals("display warning");
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() { }
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return info; }
}
/// <summary>
/// Gets the execution priority of the provider (0 lowest, 100 highest).
/// </summary>
public int ExecutionPriority {
get { return 50; }
}
/// <summary>
/// Prepares the title of an item for display (always during phase 3).
/// </summary>
/// <param name="title">The input title.</param>
/// <param name="context">The context information.</param>
/// <returns>The prepared title (no markup allowed).</returns>
public string PrepareTitle(string title, ContextInformation context) {
string result = ExtractLocalizedContent(context.Language, title); // Try to load localized content
if(context.ForIndexing || result == null) {
result = ExtractLocalizedContent(defaultLanguage, title); // Load content in the default language
}
if(result == null) {
result = title; // The Page is not localized, return all the content
}
return result;
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return "Specify 'display warning' to notify the user of the available content languages."; }
}
}
}

View file

@ -1,798 +0,0 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using ScrewTurn.Wiki.SearchEngine;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary>
/// Implements a sandbox plugin for pages.
/// </summary>
public class PagesSandbox {
private IHostV30 host;
private string config;
private static readonly ComponentInformation info = new ComponentInformation("Pages Sandbox", "Threeplicate Srl", "3.0.1.471", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/PagesSandbox.txt");
private List<NamespaceInfo> allNamespaces = new List<NamespaceInfo>(5);
private List<PageInfo> allPages;
private Dictionary<PageInfo, PageContent> allContents;
private Dictionary<PageContent, List<PageContent>> allBackups;
private Dictionary<PageInfo, PageContent> allDrafts;
private List<CategoryInfo> allCategories;
private uint freeDocumentId = 1;
private uint freeWordId = 1;
private IInMemoryIndex index;
/// <summary>
/// Gets a namespace.
/// </summary>
/// <param name="name">The name of the namespace (cannot be <c>null</c> or empty).</param>
/// <returns>The <see cref="T:NamespaceInfo"/>, or <c>null</c> if no namespace is found.</returns>
public NamespaceInfo GetNamespace(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty");
return allNamespaces.Find(n => n.Name == name);
}
/// <summary>
/// Gets all the sub-namespaces.
/// </summary>
/// <returns>The sub-namespaces, sorted by name.</returns>
public NamespaceInfo[] GetNamespaces() {
lock(this) {
return allNamespaces.ToArray();
}
}
/// <summary>
/// Adds a new namespace.
/// </summary>
/// <param name="name">The name of the namespace.</param>
/// <returns>The correct <see cref="T:NamespaceInfo"/> object.</returns>
public NamespaceInfo AddNamespace(string name) {
throw new NotImplementedException();
/*if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
lock(this) {
if(GetNamespace(name) != null) return null;
// This does not compile unless PagesSandbox implements IPagesStorageProviderV30
NamespaceInfo newSpace = new NamespaceInfo(name, this, null);
allNamespaces.Add(newSpace);
return newSpace;
}*/
}
/// <summary>
/// Renames a namespace.
/// </summary>
/// <param name="nspace">The namespace to rename.</param>
/// <param name="newName">The new name of the namespace.</param>
/// <returns>The correct <see cref="T:NamespaceInfo"/> object.</returns>
public NamespaceInfo RenameNamespace(NamespaceInfo nspace, string newName) {
if(nspace == null) throw new ArgumentNullException("nspace");
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
lock(this) {
if(GetNamespace(newName) != null) return null;
nspace.Name = newName;
return nspace;
}
}
/// <summary>
/// Sets the default page of a namespace.
/// </summary>
/// <param name="nspace">The namespace of which to set the default page.</param>
/// <param name="page">The page to use as default page, or <c>null</c>.</param>
/// <returns>The correct <see cref="T:NamespaceInfo"/> object.</returns>
public NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) {
if(nspace == null) throw new ArgumentNullException("nspace");
lock(this) {
nspace.DefaultPage = page;
return nspace;
}
}
/// <summary>
/// Removes a namespace.
/// </summary>
/// <param name="nspace">The namespace to remove.</param>
/// <returns><c>true</c> if the namespace is removed, <c>false</c> otherwise.</returns>
public bool RemoveNamespace(NamespaceInfo nspace) {
if(nspace == null) throw new ArgumentNullException("nspace");
lock(this) {
return allNamespaces.Remove(nspace);
}
}
/// <summary>
/// Moves a page from its namespace into another.
/// </summary>
/// <param name="page">The page to move.</param>
/// <param name="destination">The destination namespace (<c>null</c> for the root).</param>
/// <param name="copyCategories">A value indicating whether to copy the page categories in the destination
/// namespace, if not already available.</param>
/// <returns>The correct instance of <see cref="T:PageInfo"/>.</returns>
public PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories) {
throw new NotImplementedException();
}
/// <summary>
/// Gets a category.
/// </summary>
/// <param name="fullName">The full name of the category.</param>
/// <returns>The <see cref="T:CategoryInfo"/>, or <c>null</c> if no category is found.</returns>
public CategoryInfo GetCategory(string fullName) {
throw new NotImplementedException();
}
/// <summary>
/// Gets all the Categories in a namespace.
/// </summary>
/// <param name="nspace">The namespace.</param>
/// <returns>All the Categories in the namespace, sorted by name.</returns>
public CategoryInfo[] GetCategories(NamespaceInfo nspace) {
throw new NotImplementedException();
}
/// <summary>
/// Gets all the categories of a page.
/// </summary>
/// <param name="page">The page.</param>
/// <returns>The categories, sorted by name.</returns>
public CategoryInfo[] GetCategoriesForPage(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Adds a Category.
/// </summary>
/// <param name="nspace">The target namespace (<c>null</c> for the root).</param>
/// <param name="name">The Category name.</param>
/// <returns>The correct CategoryInfo object.</returns>
/// <remarks>The moethod should set category's Pages to an empty array.</remarks>
public CategoryInfo AddCategory(string nspace, string name) {
throw new NotImplementedException();
}
/// <summary>
/// Renames a Category.
/// </summary>
/// <param name="category">The Category to rename.</param>
/// <param name="newName">The new Name.</param>
/// <returns>The correct CategoryInfo object.</returns>
public CategoryInfo RenameCategory(CategoryInfo category, string newName) {
throw new NotImplementedException();
}
/// <summary>
/// Removes a Category.
/// </summary>
/// <param name="category">The Category to remove.</param>
/// <returns>True if the Category has been removed successfully.</returns>
public bool RemoveCategory(CategoryInfo category) {
throw new NotImplementedException();
}
/// <summary>
/// Merges two Categories.
/// </summary>
/// <param name="source">The source Category.</param>
/// <param name="destination">The destination Category.</param>
/// <returns>The correct <see cref="T:CategoryInfo"/> object.</returns>
/// <remarks>The <b>destination</b> Category remains, while the <b>source</b> Category is deleted, and all its Pages re-bound
/// in the <b>destination</b> Category.</remarks>
public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) {
throw new NotImplementedException();
}
/// <summary>
/// Performs a search in the index.
/// </summary>
/// <param name="parameters">The search parameters.</param>
/// <returns>The results.</returns>
public SearchResultCollection PerformSearch(SearchParameters parameters) {
throw new NotImplementedException();
}
/// <summary>
/// Rebuilds the search index.
/// </summary>
public void RebuildIndex() {
throw new NotImplementedException();
}
/// <summary>
/// Gets some statistics about the search engine index.
/// </summary>
/// <param name="documentCount">The total number of documents.</param>
/// <param name="wordCount">The total number of unique words.</param>
/// <param name="occurrenceCount">The total number of word-document occurrences.</param>
/// <param name="size">The approximated size, in bytes, of the search engine index.</param>
public void GetIndexStats(out int documentCount, out int wordCount, out int occurrenceCount, out long size) {
throw new NotImplementedException();
}
/// <summary>
/// Gets a value indicating whether the search engine index is corrupted and needs to be rebuilt.
/// </summary>
public bool IsIndexCorrupted {
get { throw new NotImplementedException(); }
}
/// <summary>
/// Gets a page.
/// </summary>
/// <param name="fullName">The full name of the page.</param>
/// <returns>The <see cref="T:PageInfo"/>, or <c>null</c> if no page is found.</returns>
public PageInfo GetPage(string fullName) {
throw new NotImplementedException();
}
/// <summary>
/// Gets all the Pages in a namespace.
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>All the Pages in the namespace, sorted by name.</returns>
public PageInfo[] GetPages(NamespaceInfo nspace) {
throw new NotImplementedException();
}
/// <summary>
/// Gets all the pages in a namespace that are bound to zero categories.
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The pages, sorted by name.</returns>
public PageInfo[] GetUncategorizedPages(NamespaceInfo nspace) {
throw new NotImplementedException();
}
/// <summary>
/// Gets the Content of a Page.
/// </summary>
/// <param name="page">The Page.</param>
/// <returns>The Page Content object.</returns>
public PageContent GetContent(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Gets the content of a draft of a Page.
/// </summary>
/// <param name="page">The Page.</param>
/// <returns>The draft, or <c>null</c> if no draft exists.</returns>
public PageContent GetDraft(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Deletes a draft of a Page.
/// </summary>
/// <param name="page">The page.</param>
/// <returns><c>true</c> if the draft is deleted, <c>false</c> otherwise.</returns>
public bool DeleteDraft(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Gets the Backup/Revision numbers of a Page.
/// </summary>
/// <param name="page">The Page to get the Backups of.</param>
/// <returns>The Backup/Revision numbers.</returns>
public int[] GetBackups(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Gets the Content of a Backup of a Page.
/// </summary>
/// <param name="page">The Page to get the backup of.</param>
/// <param name="revision">The Backup/Revision number.</param>
/// <returns>The Page Backup.</returns>
public PageContent GetBackupContent(PageInfo page, int revision) {
throw new NotImplementedException();
}
/// <summary>
/// Forces to overwrite or create a Backup.
/// </summary>
/// <param name="content">The Backup content.</param>
/// <param name="revision">The revision.</param>
/// <returns>True if the Backup has been created successfully.</returns>
public bool SetBackupContent(PageContent content, int revision) {
throw new NotImplementedException();
}
/// <summary>
/// Adds a Page.
/// </summary>
/// <param name="nspace">The target namespace (<c>null</c> for the root).</param>
/// <param name="name">The Page Name.</param>
/// <param name="creationDateTime">The creation Date/Time.</param>
/// <returns>The correct PageInfo object or null.</returns>
/// <remarks>This method should <b>not</b> create the content of the Page.</remarks>
public PageInfo AddPage(string nspace, string name, DateTime creationDateTime) {
throw new NotImplementedException();
}
/// <summary>
/// Renames a Page.
/// </summary>
/// <param name="page">The Page to rename.</param>
/// <param name="newName">The new Name.</param>
/// <returns>The correct <see cref="T:PageInfo"/> object.</returns>
public PageInfo RenamePage(PageInfo page, string newName) {
throw new NotImplementedException();
}
/// <summary>
/// Modifies the Content of a Page.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="title">The Title of the Page.</param>
/// <param name="username">The Username.</param>
/// <param name="dateTime">The Date/Time.</param>
/// <param name="comment">The Comment of the editor, about this revision.</param>
/// <param name="content">The Page Content.</param>
/// <param name="keywords">The keywords, usually used for SEO.</param>
/// <param name="description">The description, usually used for SEO.</param>
/// <param name="saveMode">The save mode for this modification.</param>
/// <returns><c>true</c> if the Page has been modified successfully, <c>false</c> otherwise.</returns>
/// <remarks>If <b>saveMode</b> equals <b>Draft</b> and a draft already exists, it is overwritten.</remarks>
public bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, string[] keywords, string description, SaveMode saveMode) {
throw new NotImplementedException();
}
/// <summary>
/// Performs the rollback of a Page to a specified revision.
/// </summary>
/// <param name="page">The Page to rollback.</param>
/// <param name="revision">The Revision to rollback the Page to.</param>
/// <returns><c>true</c> if the rollback succeeded, <c>false</c> otherwise.</returns>
public bool RollbackPage(PageInfo page, int revision) {
throw new NotImplementedException();
}
/// <summary>
/// Deletes the Backups of a Page, up to a specified revision.
/// </summary>
/// <param name="page">The Page to delete the backups of.</param>
/// <param name="revision">The newest revision to delete (newer revision are kept) or -1 to delete all the Backups.</param>
/// <returns><c>true</c> if the deletion succeeded, <c>false</c> otherwise.</returns>
public bool DeleteBackups(PageInfo page, int revision) {
throw new NotImplementedException();
}
/// <summary>
/// Removes a Page.
/// </summary>
/// <param name="page">The Page to remove.</param>
/// <returns>True if the Page is removed successfully.</returns>
public bool RemovePage(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Binds a Page with one or more Categories.
/// </summary>
/// <param name="page">The Page to bind.</param>
/// <param name="categories">The Categories to bind the Page with.</param>
/// <returns>True if the binding succeeded.</returns>
/// <remarks>After a successful operation, the Page is bound with all and only the categories passed as argument.</remarks>
public bool RebindPage(PageInfo page, string[] categories) {
throw new NotImplementedException();
}
/// <summary>
/// Gets the Page Messages.
/// </summary>
/// <param name="page">The Page.</param>
/// <returns>The list of the <b>first-level</b> Messages, containing the replies properly nested, sorted by date/time.</returns>
public Message[] GetMessages(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Gets the total number of Messages in a Page Discussion.
/// </summary>
/// <param name="page">The Page.</param>
/// <returns>The number of messages.</returns>
public int GetMessageCount(PageInfo page) {
throw new NotImplementedException();
}
/// <summary>
/// Removes all messages for a page and stores the new messages.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="messages">The new messages to store.</param>
/// <returns><c>true</c> if the messages are stored, <c>false</c> otherwise.</returns>
public bool BulkStoreMessages(PageInfo page, Message[] messages) {
throw new NotImplementedException();
}
/// <summary>
/// Adds a new Message to a Page.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="username">The Username.</param>
/// <param name="subject">The Subject.</param>
/// <param name="dateTime">The Date/Time.</param>
/// <param name="body">The Body.</param>
/// <param name="parent">The Parent Message ID, or -1.</param>
/// <returns>True if the Message is added successfully.</returns>
public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) {
throw new NotImplementedException();
}
/// <summary>
/// Removes a Message.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="id">The ID of the Message to remove.</param>
/// <param name="removeReplies">A value specifying whether or not to remove the replies.</param>
/// <returns>True if the Message is removed successfully.</returns>
public bool RemoveMessage(PageInfo page, int id, bool removeReplies) {
throw new NotImplementedException();
}
/// <summary>
/// Modifies a Message.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="id">The ID of the Message to modify.</param>
/// <param name="username">The Username.</param>
/// <param name="subject">The Subject.</param>
/// <param name="dateTime">The Date/Time.</param>
/// <param name="body">The Body.</param>
/// <returns>True if the Message is modified successfully.</returns>
public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) {
throw new NotImplementedException();
}
/// <summary>
/// Gets all the Navigation Paths in a Namespace.
/// </summary>
/// <param name="nspace">The Namespace.</param>
/// <returns>All the Navigation Paths, sorted by name.</returns>
public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) {
throw new NotImplementedException();
}
/// <summary>
/// Adds a new Navigation Path.
/// </summary>
/// <param name="nspace">The target namespace (<c>null</c> for the root).</param>
/// <param name="name">The Name of the Path.</param>
/// <param name="pages">The Pages array.</param>
/// <returns>The correct <see cref="T:NavigationPath"/> object.</returns>
public NavigationPath AddNavigationPath(string nspace, string name, PageInfo[] pages) {
throw new NotImplementedException();
}
/// <summary>
/// Modifies an existing navigation path.
/// </summary>
/// <param name="path">The navigation path to modify.</param>
/// <param name="pages">The new pages array.</param>
/// <returns>The correct <see cref="T:NavigationPath"/> object.</returns>
public NavigationPath ModifyNavigationPath(NavigationPath path, PageInfo[] pages) {
throw new NotImplementedException();
}
/// <summary>
/// Removes a Navigation Path.
/// </summary>
/// <param name="path">The navigation path to remove.</param>
/// <returns><c>true</c> if the path is removed, <c>false</c> otherwise.</returns>
public bool RemoveNavigationPath(NavigationPath path) {
throw new NotImplementedException();
}
/// <summary>
/// Gets all the snippets.
/// </summary>
/// <returns>All the snippets, sorted by name.</returns>
public Snippet[] GetSnippets() {
throw new NotImplementedException();
}
/// <summary>
/// Adds a new snippet.
/// </summary>
/// <param name="name">The name of the snippet.</param>
/// <param name="content">The content of the snippet.</param>
/// <returns>The correct <see cref="T:Snippet"/> object.</returns>
public Snippet AddSnippet(string name, string content) {
throw new NotImplementedException();
}
/// <summary>
/// Modifies an existing snippet.
/// </summary>
/// <param name="name">The name of the snippet to modify.</param>
/// <param name="content">The content of the snippet.</param>
/// <returns>The correct <see cref="T:Snippet"/> object.</returns>
public Snippet ModifySnippet(string name, string content) {
throw new NotImplementedException();
}
/// <summary>
/// Removes a new Snippet.
/// </summary>
/// <param name="name">The Name of the Snippet to remove.</param>
/// <returns><c>true</c> if the snippet is removed, <c>false</c> otherwise.</returns>
public bool RemoveSnippet(string name) {
throw new NotImplementedException();
}
/// <summary>
/// Gets all the content templates.
/// </summary>
/// <returns>All the content templates, sorted by name.</returns>
public ContentTemplate[] GetContentTemplates() {
throw new NotImplementedException();
}
/// <summary>
/// Adds a new content template.
/// </summary>
/// <param name="name">The name of template.</param>
/// <param name="content">The content of the template.</param>
/// <returns>The correct <see cref="T:ContentTemplate"/> object.</returns>
public ContentTemplate AddContentTemplate(string name, string content) {
throw new NotImplementedException();
}
/// <summary>
/// Modifies an existing content template.
/// </summary>
/// <param name="name">The name of the template to modify.</param>
/// <param name="content">The content of the template.</param>
/// <returns>The correct <see cref="T:ContentTemplate"/> object.</returns>
public ContentTemplate ModifyContentTemplate(string name, string content) {
throw new NotImplementedException();
}
/// <summary>
/// Removes a content template.
/// </summary>
/// <param name="name">The name of the template to remove.</param>
/// <returns><c>true</c> if the template is removed, <c>false</c> otherwise.</returns>
public bool RemoveContentTemplate(string name) {
throw new NotImplementedException();
}
/// <summary>
/// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it.
/// </summary>
public bool ReadOnly {
get { throw new NotImplementedException(); }
}
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
public void Init(IHostV30 host, string config) {
if(host == null) throw new ArgumentNullException("host");
if(config == null) throw new ArgumentNullException("config");
this.host = host;
this.config = config;
allPages = new List<PageInfo>(50);
allContents = new Dictionary<PageInfo, PageContent>(50);
allBackups = new Dictionary<PageContent, List<PageContent>>(50);
allDrafts = new Dictionary<PageInfo, PageContent>(5);
allCategories = new List<CategoryInfo>(10);
// Prepare search index
index = new StandardIndex();
index.SetBuildDocumentDelegate(BuildDocumentHandler);
index.IndexChanged += index_IndexChanged;
}
private void index_IndexChanged(object sender, IndexChangedEventArgs e) {
lock(this) {
if(e.Change == IndexChangeType.DocumentAdded) {
List<WordId> newWords = new List<WordId>(e.ChangeData.Words.Count);
foreach(DumpedWord w in e.ChangeData.Words) {
newWords.Add(new WordId(w.Text, freeWordId));
freeWordId++;
}
e.Result = new IndexStorerResult(freeDocumentId, newWords);
freeDocumentId++;
}
else e.Result = null;
}
}
/// <summary>
/// Handles the construction of an <see cref="T:IDocument" /> for the search engine.
/// </summary>
/// <param name="dumpedDocument">The input dumped document.</param>
/// <returns>The resulting <see cref="T:IDocument" />.</returns>
private IDocument BuildDocumentHandler(DumpedDocument dumpedDocument) {
if(dumpedDocument.TypeTag == PageDocument.StandardTypeTag) {
string pageName = PageDocument.GetPageName(dumpedDocument.Name);
PageInfo page = GetPage(pageName);
if(page == null) return null;
else return new PageDocument(page, dumpedDocument, TokenizeContent);
}
else if(dumpedDocument.TypeTag == MessageDocument.StandardTypeTag) {
string pageFullName;
int id;
MessageDocument.GetMessageDetails(dumpedDocument.Name, out pageFullName, out id);
PageInfo page = GetPage(pageFullName);
if(page == null) return null;
else return new MessageDocument(page, id, dumpedDocument, TokenizeContent);
}
else return null;
}
/// <summary>
/// Tokenizes page content.
/// </summary>
/// <param name="content">The content to tokenize.</param>
/// <returns>The tokenized words.</returns>
private static WordInfo[] TokenizeContent(string content) {
WordInfo[] words = SearchEngine.Tools.Tokenize(content);
return words;
}
/// <summary>
/// Indexes a page.
/// </summary>
/// <param name="content">The content of the page.</param>
/// <returns>The number of indexed words, including duplicates.</returns>
private int IndexPage(PageContent content) {
lock(this) {
string documentName = PageDocument.GetDocumentName(content.PageInfo);
DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title),
PageDocument.StandardTypeTag, content.LastModified);
// Store the document
// The content should always be prepared using IHost.PrepareForSearchEngineIndexing()
return index.StoreDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent),
content.Keywords, host.PrepareContentForIndexing(content.PageInfo, content.Content), null);
}
}
/// <summary>
/// Removes a page from the search engine index.
/// </summary>
/// <param name="content">The content of the page to remove.</param>
private void UnindexPage(PageContent content) {
lock(this) {
string documentName = PageDocument.GetDocumentName(content.PageInfo);
DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title),
PageDocument.StandardTypeTag, content.LastModified);
index.RemoveDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent), null);
}
}
/// <summary>
/// Indexes a message.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="id">The message ID.</param>
/// <param name="subject">The subject.</param>
/// <param name="dateTime">The date/time.</param>
/// <param name="body">The body.</param>
/// <returns>The number of indexed words, including duplicates.</returns>
private int IndexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body) {
lock(this) {
// Trim "RE:" to avoid polluting the search engine index
if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim();
string documentName = MessageDocument.GetDocumentName(page, id);
DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject),
MessageDocument.StandardTypeTag, dateTime);
// Store the document
// The content should always be prepared using IHost.PrepareForSearchEngineIndexing()
return index.StoreDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null,
host.PrepareContentForIndexing(null, body), null);
}
}
/// <summary>
/// Indexes a message tree.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="root">The tree root.</param>
private void IndexMessageTree(PageInfo page, Message root) {
IndexMessage(page, root.ID, root.Subject, root.DateTime, root.Body);
foreach(Message reply in root.Replies) {
IndexMessageTree(page, reply);
}
}
/// <summary>
/// Removes a message from the search engine index.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="id">The message ID.</param>
/// <param name="subject">The subject.</param>
/// <param name="dateTime">The date/time.</param>
/// <param name="body">The body.</param>
/// <returns>The number of indexed words, including duplicates.</returns>
private void UnindexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body) {
lock(this) {
// Trim "RE:" to avoid polluting the search engine index
if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim();
string documentName = MessageDocument.GetDocumentName(page, id);
DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject),
MessageDocument.StandardTypeTag, DateTime.Now);
index.RemoveDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null);
}
}
/// <summary>
/// Removes a message tree from the search engine index.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="root">The tree root.</param>
private void UnindexMessageTree(PageInfo page, Message root) {
UnindexMessage(page, root.ID, root.Subject, root.DateTime, root.Body);
foreach(Message reply in root.Replies) {
UnindexMessageTree(page, reply);
}
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
// Nothing do to
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return null; }
}
}
}

View file

@ -68,13 +68,8 @@
<Compile Include="..\AssemblyVersion.cs">
<Link>AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Footnotes.cs" />
<Compile Include="RssFeedDisplay.cs" />
<Compile Include="DownloadCounter.cs" />
<Compile Include="MultilanguageContentPlugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PagesSandbox.cs" />
<Compile Include="UnfuddleTickets.cs" />
<Compile Include="Updater.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PluginFramework\PluginFramework.csproj">
@ -103,9 +98,6 @@
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\UnfuddleTickets.xsl" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View file

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="html"/>
<xsl:template match="/">
<div id="UnfuddleTicketsDiv">
<!-- Highest -->
<xsl:if test="count(/root/ticket-report/groups/group/title[text() = 'Highest']/parent::group/tickets) &gt; 0">
<h2 class="separator">Priority: Highest</h2>
</xsl:if>
<xsl:apply-templates select="/root/ticket-report/groups/group/title[text() = 'Highest']/parent::group/tickets"/>
<!-- High -->
<xsl:if test="count(/root/ticket-report/groups/group/title[text() = 'High']/parent::group/tickets) &gt; 0">
<h2 class="separator">Priority: High</h2>
</xsl:if>
<xsl:apply-templates select="/root/ticket-report/groups/group/title[text() = 'High']/parent::group/tickets"/>
<!-- Normal -->
<xsl:if test="count(/root/ticket-report/groups/group/title[text() = 'Normal']/parent::group/tickets) &gt; 0">
<h2 class="separator">Priority: Normal</h2>
</xsl:if>
<xsl:apply-templates select="/root/ticket-report/groups/group/title[text() = 'Normal']/parent::group/tickets"/>
<!-- Low -->
<xsl:if test="count(/root/ticket-report/groups/group/title[text() = 'Low']/parent::group/tickets) &gt; 0">
<h2 class="separator">Priority: Low</h2>
</xsl:if>
<xsl:apply-templates select="/root/ticket-report/groups/group/title[text() = 'Low']/parent::group/tickets"/>
<!-- Lowest -->
<xsl:if test="count(/root/ticket-report/groups/group/title[text() = 'Lowest']/parent::group/tickets) &gt; 0">
<h2 class="separator">Priority: Lowest</h2>
</xsl:if>
<xsl:apply-templates select="/root/ticket-report/groups/group/title[text() = 'Lowest']/parent::group/tickets"/>
</div>
</xsl:template>
<xsl:template match="/root/ticket-report/groups/group/tickets" priority="5">
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<thead>
<tr class="tableheader">
<th style="white-space: nowrap; text-align: left; width: 50px;">
#
</th>
<th style="white-space: nowrap; text-align: left;">
Summary
</th>
<th style="white-space: nowrap; text-align: left; width: 80px;">
Status
</th>
<th style="white-space: nowrap;width: 70px;">
Priority
</th>
<th style="white-space: nowrap; text-align: left;width: 120px;">
Milestone
</th>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="ticket"/>
</tbody>
</table>
<br />
<br />
</xsl:template>
<xsl:template match="/root/ticket-report/groups/group/tickets/ticket">
<tr>
<xsl:if test="position() mod 2 != 1">
<xsl:attribute name="class">priority_{priority} tablerowalternate</xsl:attribute>
</xsl:if>
<xsl:if test="position() mod 2 != 0">
<xsl:attribute name="class">priority_{priority} tablerow</xsl:attribute>
</xsl:if>
<xsl:variable name="priority" select="priority"/>
<td class='priority_{priority}' style="text-align: left;">
<xsl:value-of select="number"/>
</td>
<td class='priority_{priority}'>
<xsl:value-of select="summary" disable-output-escaping="yes"/>
</td>
<td class='priority_{priority}'>
<xsl:variable name="c" select="substring(status,1,1)"/>
<xsl:value-of select="translate($c,'abcdefghijklmnopqrst','ABCDEFGHIJKLMNOPQRST')"/>
<xsl:value-of select="substring-after(status,$c)"/>
</td>
<td class='priority_{priority}' style="text-align: center;">
<xsl:choose>
<xsl:when test="priority = '5'">Highest</xsl:when>
<xsl:when test="priority = '4'">High</xsl:when>
<xsl:when test="priority = '3'">Normal</xsl:when>
<xsl:when test="priority = '2'">Low</xsl:when>
<xsl:when test="priority = '1'">Lowest</xsl:when>
</xsl:choose>
</td>
<td class='priority_{priority}'>
<xsl:variable name="milestoneID" select="milestone-id"/>
<xsl:if test="$milestoneID = ''">
None
</xsl:if>
<xsl:if test="$milestoneID != ''">
<xsl:value-of select="/root/milestones/milestone/id[text()=$milestoneID]/parent::milestone/title"/>
</xsl:if>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>

View file

@ -1,316 +0,0 @@

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using ScrewTurn.Wiki.PluginFramework;
using System.Net;
using System.IO;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary>
/// Implements a formatter provider that counts download of files and attachments.
/// </summary>
public class RssFeedDisplay : IFormatterProviderV30 {
private IHostV30 _host;
private string _config;
private bool _enableLogging = true;
private static readonly ComponentInformation Info = new ComponentInformation("RSS Feed Display Plugin", "Threeplicate Srl", "3.0.2.528", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/RssFeedDisplay.txt");
private static readonly Regex RssRegex = new Regex(@"{(RSS|Twitter):(.+?)(\|(.+?))?}",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
/// <summary>
/// Specifies whether or not to execute Phase 1.
/// </summary>
public bool PerformPhase1 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 3.
/// </summary>
public bool PerformPhase3 {
get { return true; }
}
/// <summary>
/// Gets the execution priority of the provider (0 lowest, 100 highest).
/// </summary>
public int ExecutionPriority {
get { return 50; }
}
/// <summary>
/// Performs a Formatting phase.
/// </summary>
/// <param name="raw">The raw content to Format.</param>
/// <param name="context">The Context information.</param>
/// <param name="phase">The Phase.</param>
/// <returns>The Formatted content.</returns>
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
// {RSS:FeedAddress}
// FeedAddress not found -> ignored
StringBuilder buffer = new StringBuilder(raw);
try {
KeyValuePair<int, Match> block = FindAndRemoveFirstOccurrence(buffer);
while(block.Key != -1) {
string blockHash = block.Value.ToString();
string result = null;
if(System.Web.HttpContext.Current != null) {
result = System.Web.HttpContext.Current.Cache[blockHash] as string;
}
if(result == null) {
bool isTwitter = block.Value.Groups[1].Value.ToLowerInvariant() == "twitter";
int entries = 1;
bool newWindow = true;
int words = 350;
if(block.Value.Groups.Count > 3) {
AnalyzeSettings(block.Value.Groups[4].Value, out entries, out newWindow, out words);
}
if(isTwitter) {
result = @"<div class=""twitterfeed"">";
}
else {
result = @"<div class=""rssfeed"">";
}
XmlDocument feedXml = GetXml(block.Value.Groups[2].Value);
XmlNode node = feedXml.DocumentElement;
for(int i = 0; i < entries; i++) {
XmlNode itemTitle = node.SelectNodes("/rss/channel/item/title")[i];
if(itemTitle != null) {
XmlNode itemLink = node.SelectNodes("/rss/channel/item/link")[i];
XmlNode itemContent = node.SelectNodes("/rss/channel/item/description")[i];
string itemContentStr = StripHtml(itemContent.InnerText);
itemContentStr = (itemContentStr.Length > words && itemContentStr.Substring(words - 3, 5) != "[...]") ? itemContentStr.Substring(0, itemContentStr.IndexOf(" ", words - 5) + 1) + " [...]" : itemContentStr;
if(itemContentStr.Length <= 1) itemContentStr = StripHtml(itemContent.InnerText);
if(isTwitter) {
string tweet = itemTitle.InnerText;
tweet = tweet.Substring(tweet.IndexOf(":") + 2);
result += @"<div class=""tweet"">
<a href=""" + itemLink.InnerText + @""" title=""Go to this Tweet""";
if(newWindow) result += @" target=""_blank""";
result += @">" + tweet + @"</a>
</div>";
}
else {
result += @"<div class=""rssentry"">
<span class=""rsstitle"">
<a href=""" + itemLink.InnerText + @""" title=""" + itemTitle.InnerText + @"""";
if(newWindow) result += @" target=""_blank""";
result += @">" + itemTitle.InnerText + @"</a>
</span>
<br />
<span class=""rsscontent"">" + itemContentStr + @"</span>
</div>";
}
}
}
result += @"</div>";
if(System.Web.HttpContext.Current != null) {
System.Web.HttpContext.Current.Cache.Add(blockHash, result, null, DateTime.Now.AddMinutes(60),
System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
}
}
buffer.Insert(block.Key, result);
block = FindAndRemoveFirstOccurrence(buffer);
}
}
catch(Exception ex) {
LogWarning(string.Format("Exception occurred: {0}", ex.Message));
}
return buffer.ToString();
}
/// <summary>
/// Analizes the settings string.
/// </summary>
/// <param name="settingString">The setting string.</param>
/// <param name="entries">The number of entries.</param>
/// <param name="newWindow">The newWindow value.</param>
/// <param name="words">The max number of words.</param>
private void AnalyzeSettings(string settingString, out int entries, out bool newWindow, out int words) {
entries = 1;
newWindow = true;
words = 350;
String[] settings = settingString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach(string set in settings) {
string key = set.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries)[0].Trim().ToLowerInvariant();
string value = set.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries)[1].Trim().ToLowerInvariant();
if(key == "entries") {
try {
entries = Int32.Parse(value);
}
catch(ArgumentNullException) {
throw new ArgumentNullException("entries setting could not be null.");
//LogWarning("entries setting could not be null.");
}
catch(FormatException) {
throw new FormatException("entries setting is not a valid integer.");
}
}
else if(key == "newwindow") {
if(value == "true" || value == "1" || value == "yes") {
newWindow = true;
}
else if(value == "false" || value == "0" || value == "no") {
newWindow = false;
}
else {
throw new FormatException("newWindow setting is not a valid value. Use: true/false or 1/0 or yes/no.");
}
}
else if(key == "words") {
try {
words = Int32.Parse(value);
}
catch(ArgumentNullException) {
throw new ArgumentNullException("words setting could not be null.");
}
catch(FormatException) {
throw new FormatException("words setting is not a valid integer.");
}
}
}
}
/// <summary>
/// Produces an API call, then returns the results as an Xml Document
/// </summary>
/// <param name="Url">The Url to the specific API call</param>
/// <returns></returns>
private XmlDocument GetXml(string Url) {
try {
var results = new XmlDocument();
Url = string.Format("{0}", Url);
var request = WebRequest.Create(Url);
var response = request.GetResponse();
using(var reader = new StreamReader(response.GetResponseStream())) {
var xmlString = reader.ReadToEnd();
try {
results.LoadXml(xmlString);
}
catch {
LogWarning("Received Unexpected Response from server.");
}
}
return results;
}
catch(Exception ex) {
LogWarning(string.Format("Exception occurred: {0}", ex.Message));
return null;
}
}
/// <summary>
/// Finds and removes the first occurrence of the custom tag.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>The index->content data.</returns>
private static KeyValuePair<int, Match> FindAndRemoveFirstOccurrence(StringBuilder buffer) {
Match match = RssRegex.Match(buffer.ToString());
if(match.Success) {
buffer.Remove(match.Index, match.Length);
return new KeyValuePair<int, Match>(match.Index, match);
}
return new KeyValuePair<int, Match>(-1, null);
}
/// <summary>
/// Removes all HTML markup from a string.
/// </summary>
/// <param name="content">The string.</param>
/// <returns>The result.</returns>
private static string StripHtml(string content) {
if(string.IsNullOrEmpty(content)) return "";
StringBuilder sb = new StringBuilder(Regex.Replace(content, "<[^>]*>", " "));
sb.Replace("&nbsp;", "");
sb.Replace(" ", " ");
return sb.ToString();
}
/// <summary>
/// Prepares the title of an item for display (always during phase 3).
/// </summary>
/// <param name="title">The input title.</param>
/// <param name="context">The context information.</param>
/// <returns>The prepared title (no markup allowed).</returns>
public string PrepareTitle(string title, ContextInformation context) {
return title;
}
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
public void Init(IHostV30 host, string config) {
this._host = host;
this._config = config != null ? config : "";
if(this._config.ToLowerInvariant() == "nolog") _enableLogging = false;
}
/// <summary>
/// Logs a warning.
/// </summary>
/// <param name="message">The message.</param>
private void LogWarning(string message) {
if(_enableLogging) {
_host.LogEntry(message, LogEntryType.Warning, null, this);
}
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
// Nothing to do
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return Info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return "Specify <i>nolog</i> for disabling warning log messages."; }
}
}
}

View file

@ -1,292 +0,0 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using System.Xml;
using System.Xml.Xsl;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary>
/// Implements a formatter that display tickets from Unfuddle.
/// </summary>
public class UnfuddleTickets : IFormatterProviderV30 {
private const string ConfigHelpHtmlValue = "Config consists of three lines:<br/><i>&lt;Url&gt;</i> - The base url to the Unfuddle API (i.e. http://account_name.unfuddle.com/api/v1/projects/project_ID)<br/><i>&lt;Username&gt;</i> - The username to the unfuddle account to use for authentication<br/><i>&lt;Password&gt;</i> - The password to the unfuddle account to use for authentication<br/>";
private const string LoadErrorMessage = "Unable to load ticket report at this time.";
private static readonly Regex UnfuddleRegex = new Regex(@"{unfuddle}", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly ComponentInformation Info = new ComponentInformation("Unfuddle Tickets Plugin", "Threeplicate Srl", "3.0.1.471", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/UnfuddleTickets.txt");
private string _config;
private IHostV30 _host;
private string _baseUrl;
private string _username;
private string _password;
#region IFormatterProviderV30 Members
/// <summary>
/// Specifies whether or not to execute Phase 1.
/// </summary>
public bool PerformPhase1 {
get {
return false;
}
}
/// <summary>
/// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get {
return false;
}
}
/// <summary>
/// Specifies whether or not to execute Phase 3.
/// </summary>
public bool PerformPhase3 {
get {
return true;
}
}
/// <summary>
/// Gets the execution priority of the provider (0 lowest, 100 highest).
/// </summary>
public int ExecutionPriority {
get {
return 50;
}
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get {
return Info;
}
}
/// <summary>
/// Performs a Formatting phase.
/// </summary>
/// <param name="raw">The raw content to Format.</param>
/// <param name="context">The Context information.</param>
/// <param name="phase">The Phase.</param>
/// <returns>The Formatted content.</returns>
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
var buffer = new StringBuilder(raw);
var block = FindAndRemoveFirstOccurrence(buffer);
if(block.Key != -1) {
string unfuddleTickets = null;
if(HttpContext.Current != null)
unfuddleTickets = HttpContext.Current.Cache["UnfuddleTicketsStore"] as string;
if(string.IsNullOrEmpty(unfuddleTickets))
unfuddleTickets = LoadUnfuddleTicketsFromWeb();
if(string.IsNullOrEmpty(unfuddleTickets))
unfuddleTickets = LoadErrorMessage;
do {
buffer.Insert(block.Key, unfuddleTickets);
block = FindAndRemoveFirstOccurrence(buffer);
} while(block.Key != -1);
}
return buffer.ToString();
}
/// <summary>
/// Prepares the title of an item for display (always during phase 3).
/// </summary>
/// <param name="title">The input title.</param>
/// <param name="context">The context information.</param>
/// <returns>The prepared title (no markup allowed).</returns>
public string PrepareTitle(string title, ContextInformation context) {
return title;
}
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
public void Init(IHostV30 host, string config) {
_host = host;
_config = config ?? string.Empty;
var configEntries = _config.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
if(configEntries.Length != 3)
throw new InvalidConfigurationException("Configuration missing required parameters.");
_baseUrl = configEntries[0];
if(_baseUrl.EndsWith("/"))
_baseUrl = _baseUrl.Substring(0, _baseUrl.Length - 1);
_username = configEntries[1];
_password = configEntries[2];
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
// Nothing to do
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get {
return ConfigHelpHtmlValue;
}
}
#endregion
#region Private Methods
/// <summary>
/// Finds and removes the first occurrence of the custom tag.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>The index->content data.</returns>
private static KeyValuePair<int, string> FindAndRemoveFirstOccurrence(StringBuilder buffer) {
Match match = UnfuddleRegex.Match(buffer.ToString());
if(match.Success) {
buffer.Remove(match.Index, match.Length);
return new KeyValuePair<int, string>(match.Index, match.Value);
}
return new KeyValuePair<int, string>(-1, null);
}
/// <summary>
/// Builds an xml document from API calls to Unfuddle.com then runs them through an Xslt to format them.
/// </summary>
/// <returns>An html string that contains the tables to display the ticket information, or null</returns>
private string LoadUnfuddleTicketsFromWeb() {
var xml = BuildXmlFromApiCalls();
if(xml == null)
return null;
var settings = new XsltSettings {
EnableScript = true,
EnableDocumentFunction = true
};
var xsl = new XslCompiledTransform(true);
using(var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.PluginPack.Resources.UnfuddleTickets.xsl"))) {
xsl.Load(reader, settings, new XmlUrlResolver());
}
string results;
using(var sw = new StringWriter()) {
using(var xnr = new XmlNodeReader(xml)) {
xsl.Transform(xnr, null, sw);
}
results = sw.ToString();
}
HttpContext.Current.Cache.Add("UnfuddleTicketsStore", results, null, DateTime.Now.AddMinutes(10),
Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
return results;
}
/// <summary>
/// Builds 3 Xml Documents, the first two are lookups for Milestone, and People information, the second is the
/// ticket information.
/// </summary>
/// <returns></returns>
private XmlDocument BuildXmlFromApiCalls() {
var milestones = GetXml("/milestones", _username, _password);
if(milestones == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null;
}
var people = GetXml("/people", _username, _password);
if(people == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null;
}
var tickets = GetXml("/ticket_reports/dynamic?sort_by=priority&sort_direction=DESC&conditions_string=status-neq-closed&group_by=priority&fields_string=number,priority,summary,milestone,status,version", _username, _password);
if(tickets == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null;
}
var results = new XmlDocument();
results.AppendChild(results.CreateXmlDeclaration("1.0", "UTF-8", string.Empty));
var element = results.CreateElement("root");
results.AppendChild(element);
element.AppendChild(results.ImportNode(milestones.ChildNodes[1], true));
element.AppendChild(results.ImportNode(people.ChildNodes[1], true));
element.AppendChild(results.ImportNode(tickets.ChildNodes[1], true));
return results;
}
/// <summary>
/// Produces an API call, then returns the results as an Xml Document
/// </summary>
/// <param name="Url">The Url to the specific API call</param>
/// <param name="Username">An unfuddle account username</param>
/// <param name="Password">The password to above unfuddle account</param>
/// <returns></returns>
private XmlDocument GetXml(string Url, string Username, string Password) {
try {
var results = new XmlDocument();
Url = string.Format("{0}{1}", _baseUrl, Url);
var request = WebRequest.Create(Url);
request.Credentials = new NetworkCredential(Username, Password);
var response = request.GetResponse();
using(var reader = new StreamReader(response.GetResponseStream())) {
var xmlString = reader.ReadToEnd();
try {
results.LoadXml(xmlString);
}
catch {
LogWarning("Received Unexpected Response from Unfuddle Server.");
}
}
return results;
}
catch(Exception ex) {
LogWarning(string.Format("Exception occurred: {0}", ex.Message));
return null;
}
}
/// <summary>
/// Logs a warning.
/// </summary>
/// <param name="message">The message.</param>
private void LogWarning(string message) {
_host.LogEntry(message, LogEntryType.Warning, null, this);
}
#endregion
}
}

172
PluginPack/Updater.cs Normal file
View file

@ -0,0 +1,172 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using System.Net;
using System.IO;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary>
/// Plugin with the sole purpose of removing PluginPack.dll in favor of separate DLLs.
/// </summary>
public class Updater : IFormatterProviderV30 {
private static bool AlreadyRun = false;
private static readonly ComponentInformation _info = new ComponentInformation("Updater Plugin", "Threeplicate Srl", "3.0.2.538", "http://www.screwturn.eu", null);
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <exception cref="ArgumentNullException">If <paramref name="host"/> or <paramref name="config"/> are <c>null</c>.</exception>
/// <exception cref="InvalidConfigurationException">If <paramref name="config"/> is not valid or is incorrect.</exception>
public void Init(IHostV30 host, string config) {
if(host == null) throw new ArgumentNullException("host");
if(config == null) throw new ArgumentNullException("config");
if(AlreadyRun) return;
// 1. Delete PluginPack.dll
// 2. Download all other DLLs
string root = "http://www.screwturn.eu/Version/PluginPack/";
string[] dllNames = new string[] {
"DownloadCounterPlugin.dll",
"FootnotesPlugin.dll",
"MultilanguageContentPlugin.dll",
"RssFeedDisplayPlugin.dll",
"UnfuddleTicketsPlugin.dll"
};
string[] providerNames = new string[] {
"ScrewTurn.Wiki.Plugins.PluginPack.DownloadCounter",
"ScrewTurn.Wiki.Plugins.PluginPack.Footnotes",
"ScrewTurn.Wiki.Plugins.PluginPack.MultilanguageContentPlugin",
"ScrewTurn.Wiki.Plugins.PluginPack.RssFeedDisplay",
"ScrewTurn.Wiki.Plugins.PluginPack.UnfuddleTickets",
};
Dictionary<string, byte[]> assemblies = new Dictionary<string, byte[]>(dllNames.Length);
try {
foreach(string dll in dllNames) {
host.LogEntry("Downloading " + dll, LogEntryType.General, null, this);
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(root + dll);
req.AllowAutoRedirect = true;
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
if(resp.StatusCode == HttpStatusCode.OK) {
BinaryReader reader = new BinaryReader(resp.GetResponseStream());
byte[] content = reader.ReadBytes((int)resp.ContentLength);
reader.Close();
assemblies.Add(dll, content);
}
else {
throw new InvalidOperationException("Response status code for " + dll + ":" + resp.StatusCode.ToString());
}
}
foreach(string dll in dllNames) {
host.GetSettingsStorageProvider().StorePluginAssembly(dll, assemblies[dll]);
}
foreach(string dll in dllNames) {
LoadProvider(dll);
}
host.GetSettingsStorageProvider().DeletePluginAssembly("PluginPack.dll");
AlreadyRun = true;
}
catch(Exception ex) {
host.LogEntry("Error occurred during automatic DLL updating with Updater Plugin\n" + ex.ToString(), LogEntryType.Error, null, this);
}
}
private void LoadProvider(string dll) {
Type loader = Type.GetType("ScrewTurn.Wiki.ProviderLoader, ScrewTurn.Wiki.Core");
var method = loader.GetMethod("LoadFromAuto");
method.Invoke(null, new[] { dll });
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
}
/// <summary>
/// Specifies whether or not to execute Phase 1.
/// </summary>
public bool PerformPhase1 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get { return false; }
}
/// <summary>
/// Specifies whether or not to execute Phase 3.
/// </summary>
public bool PerformPhase3 {
get { return false; }
}
/// <summary>
/// Gets the execution priority of the provider (0 lowest, 100 highest).
/// </summary>
public int ExecutionPriority {
get { return 50; }
}
/// <summary>
/// Performs a Formatting phase.
/// </summary>
/// <param name="raw">The raw content to Format.</param>
/// <param name="context">The Context information.</param>
/// <param name="phase">The Phase.</param>
/// <returns>The Formatted content.</returns>
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
return raw;
}
/// <summary>
/// Prepares the title of an item for display (always during phase 3).
/// </summary>
/// <param name="title">The input title.</param>
/// <param name="context">The context information.</param>
/// <returns>The prepared title (no markup allowed).</returns>
public string PrepareTitle(string title, ContextInformation context) {
return title;
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return _info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return null; }
}
}
}