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 {
///
/// Implements a formatter provider that counts download of files and attachments.
///
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", "ScrewTurn Software", "3.0.0.206", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/DownloadCounter.txt");
private static readonly Regex XmlRegex = new Regex(@"\(.+?)\<\/countDownloads\>",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
///
/// Specifies whether or not to execute Phase 1.
///
public bool PerformPhase1 {
get { return false; }
}
///
/// Specifies whether or not to execute Phase 2.
///
public bool PerformPhase2 {
get { return false; }
}
///
/// Specifies whether or not to execute Phase 3.
///
public bool PerformPhase3 {
get { return true; }
}
///
/// Gets the execution priority of the provider (0 lowest, 100 highest).
///
public int ExecutionPriority {
get { return 50; }
}
///
/// Performs a Formatting phase.
///
/// The raw content to Format.
/// The Context information.
/// The Phase.
/// The Formatted content.
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
//
//
//
//
// 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 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();
}
///
/// Builds the result.
///
/// The result pattern.
/// The downloads.
/// The daily downloads.
/// The weekly downloads.
/// The monthly downloads.
/// The result.
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();
}
///
/// Replaces a placeholder with its value.
///
/// The buffer.
/// The placeholder.
/// The value.
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);
}
///
/// Gets the root attributes.
///
/// The XML document.
/// The pattern.
/// The start date/time.
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;
}
}
///
/// Tries to get the value of an attribute.
///
/// The node.
/// The name of the attribute.
/// The value of the attribute or null if no value is available.
private static string TryGetAttribute(XmlNode node, string attribute) {
XmlAttribute attr = node.Attributes[attribute];
if(attr != null) return attr.Value;
else return null;
}
///
/// Counts all the downloads.
///
/// The XML document.
/// The download count.
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;
}
///
/// Counts the downloads of a file.
///
/// The full file path.
/// The provider or null or string.Empty.
/// The downloads.
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;
}
///
/// Counts the downloads of a file.
///
/// The name of the attachment.
/// The full name of the page.
/// The provider or null or string.Empty.
/// The downloads.
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;
}
///
/// Gets the specified provider or the default one.
///
/// The provider.
///
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;
}
///
/// Finds and removes the first occurrence of the XML markup.
///
/// The buffer.
/// The index-content data.
private static KeyValuePair FindAndRemoveFirstOccurrence(StringBuilder buffer) {
Match match = XmlRegex.Match(buffer.ToString());
if(match.Success) {
buffer.Remove(match.Index, match.Length);
return new KeyValuePair(match.Index, match.Value);
}
return new KeyValuePair(-1, null);
}
///
/// Logs a warning.
///
/// The message.
private void LogWarning(string message) {
if(_enableLogging) {
_host.LogEntry(message, LogEntryType.Warning, null, this);
}
}
///
/// Prepares the title of an item for display (always during phase 3).
///
/// The input title.
/// The context information.
/// The prepared title (no markup allowed).
public string PrepareTitle(string title, ContextInformation context) {
return title;
}
///
/// Initializes the Storage Provider.
///
/// The Host of the Component.
/// The Configuration data, if any.
/// If the configuration string is not valid, the methoud should throw a .
public void Init(IHostV30 host, string config) {
this._host = host;
this._config = config != null ? config : "";
if(this._config.ToLowerInvariant() == "nolog") _enableLogging = false;
}
///
/// Method invoked on shutdown.
///
/// This method might not be invoked in some cases.
public void Shutdown() {
// Nothing to do
}
///
/// 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 "Specify nolog for disabling warning log messages for non-existent files or attachments."; }
}
}
}