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