using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
///
/// Implements a local cache provider. All instance members are thread-safe.
///
public class CacheProvider : ICacheProviderV30 {
private readonly ComponentInformation _info =
new ComponentInformation("Local Cache Provider", "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null);
private IHostV30 _host;
// Implements the pseudo cache
private Dictionary _pseudoCache;
// Contains the page contents
private Dictionary _pageContentCache;
// Contains the partially-formatted page content
private Dictionary _formattedContentCache;
// Records, for each page, how many times a page has been requested,
// limited to page contents (not formatted content)
private Dictionary _pageCacheUsage;
private int _onlineUsers = 0;
private List _sessions;
// Key is lowercase, invariant culture
private Dictionary _redirections;
///
/// Initializes the Storage Provider.
///
/// The Host of the Component.
/// The Configuration data, if any.
/// If host or config are null.
/// If config is not valid or is incorrect.
public void Init(IHostV30 host, string config) {
if(host == null) throw new ArgumentNullException("host");
if(config == null) throw new ArgumentNullException("config");
_host = host;
int s = int.Parse(host.GetSettingValue(SettingName.CacheSize));
// Initialize pseudo cache
_pseudoCache = new Dictionary(10);
// Initialize page content cache
_pageContentCache = new Dictionary(s);
_pageCacheUsage = new Dictionary(s);
// Initialize formatted page content cache
_formattedContentCache = new Dictionary(s);
_sessions = new List(50);
_redirections = new Dictionary(50);
}
///
/// Method invoked on shutdown.
///
/// This method might not be invoked in some cases.
public void Shutdown() {
ClearPseudoCache();
ClearPageContentCache();
}
///
/// Gets or sets the number of users online.
///
public int OnlineUsers {
get {
lock(this) {
return _onlineUsers;
}
}
set {
lock(this) {
_onlineUsers = value;
}
}
}
///
/// Gets the Information about the Provider.
///
public ComponentInformation Information {
get { return _info; }
}
///
/// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed.
///
public string ConfigHelpHtml {
get { return null; }
}
///
/// Gets the value of a Pseudo-cache item, previously stored in the cache.
///
/// The name of the item being requested.
/// The value of the item, or null if the item is not found.
/// If name is null.
/// If name is empty.
public string GetPseudoCacheValue(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
string value = null;
lock(_pseudoCache) {
_pseudoCache.TryGetValue(name, out value);
}
return value;
}
///
/// Sets the value of a Pseudo-cache item.
///
/// The name of the item being stored.
/// The value of the item. If the value is null, then the item should be removed from the cache.
/// If name is null.
/// If name is empty.
public void SetPseudoCacheValue(string name, string value) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
lock(_pseudoCache) {
if(value == null) _pseudoCache.Remove(name);
else _pseudoCache[name] = value;
}
}
///
/// Gets the Content of a Page, previously stored in cache.
///
/// The Page Info object related to the Content being requested.
/// The Page Content object, or null if the item is not found.
/// If pageInfo is null.
public PageContent GetPageContent(PageInfo pageInfo) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
PageContent value = null;
lock(_pageContentCache) {
if(_pageContentCache.TryGetValue(pageInfo, out value)) {
_pageCacheUsage[pageInfo]++;
}
}
return value;
}
///
/// Sets the Content of a Page.
///
/// The Page Info object related to the Content being stored.
/// The Content of the Page.
/// If pageInfo or content are null.
public void SetPageContent(PageInfo pageInfo, PageContent content) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(content == null) throw new ArgumentNullException("content");
lock(_pageContentCache) {
_pageContentCache[pageInfo] = content;
lock(_pageCacheUsage) {
_pageCacheUsage[pageInfo] = 0;
}
}
}
///
/// Gets the partially-formatted content (text) of a Page, previously stored in the cache.
///
/// The Page Info object related to the content being requested.
/// The partially-formatted content, or null if the item is not found.
/// If pageInfo is null.
public string GetFormattedPageContent(PageInfo pageInfo) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
string value = null;
lock(_formattedContentCache) {
_formattedContentCache.TryGetValue(pageInfo, out value);
}
return value;
}
///
/// Sets the partially-preformatted content (text) of a Page.
///
/// The Page Info object related to the content being stored.
/// The partially-preformatted content.
/// If pageInfo or content are null.
public void SetFormattedPageContent(PageInfo pageInfo, string content) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(content == null) throw new ArgumentNullException("content");
lock(_formattedContentCache) {
_formattedContentCache[pageInfo] = content;
}
}
///
/// Removes a Page from the cache.
///
/// The Page Info object related to the Page that has to be removed.
/// If pageInfo is null.
public void RemovePage(PageInfo pageInfo) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
// In this order
lock(_formattedContentCache) {
_formattedContentCache.Remove(pageInfo);
}
lock(_pageContentCache) {
_pageContentCache.Remove(pageInfo);
}
lock(_pageCacheUsage) {
_pageCacheUsage.Remove(pageInfo);
}
}
///
/// Clears the Page Content cache.
///
public void ClearPageContentCache() {
// In this order
lock(_formattedContentCache) {
_formattedContentCache.Clear();
}
lock(_pageContentCache) {
_pageContentCache.Clear();
}
lock(_pageCacheUsage) {
_pageCacheUsage.Clear();
}
}
///
/// Clears the Pseudo-Cache.
///
public void ClearPseudoCache() {
lock(_pseudoCache) {
_pseudoCache.Clear();
}
}
///
/// Reduces the size of the Page Content cache, removing the least-recently used items.
///
/// The number of Pages to remove.
/// If cutSize is less than or equal to zero.
public void CutCache(int cutSize) {
if(cutSize <= 0) throw new ArgumentOutOfRangeException("cutSize", "Cut Size should be greater than zero");
lock(_pageContentCache) {
// TODO: improve performance - now the operation is O(cache_size^2)
for(int i = 0; i < cutSize; i++) {
PageInfo key = null;
int min = int.MaxValue;
// Find the page that has been requested the least times
foreach(PageInfo p in _pageCacheUsage.Keys) {
if(_pageCacheUsage[p] < min) {
key = p;
min = _pageCacheUsage[p];
}
}
// This is necessary to avoid infinite loops
if(key == null) {
break;
}
// Remove the page from cache
RemovePage(key);
}
}
}
///
/// Gets the number of Pages whose content is currently stored in the cache.
///
public int PageCacheUsage {
get {
lock(_pageContentCache) {
return _pageContentCache.Count;
}
}
}
///
/// Gets the numer of Pages whose formatted content is currently stored in the cache.
///
public int FormatterPageCacheUsage {
get {
lock(_formattedContentCache) {
return _formattedContentCache.Count;
}
}
}
///
/// Adds or updates an editing session.
///
/// The edited Page.
/// The User who is editing the Page.
/// If page or user are null.
/// If page or user are empty.
public void RenewEditingSession(string page, string user) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
if(user == null) throw new ArgumentNullException("user");
if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user");
lock(this) {
bool found = false;
for(int i = 0; i < _sessions.Count; i++) {
if(_sessions[i].Page == page && _sessions[i].User.Equals(user)) {
_sessions[i].Renew();
found = true;
break;
}
}
if(!found) _sessions.Add(new EditingSession(page, user));
}
}
///
/// Cancels an editing session.
///
/// The Page.
/// The User.
/// If page or user are null.
/// If page or user are empty.
public void CancelEditingSession(string page, string user) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
if(user == null) throw new ArgumentNullException("user");
if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user");
lock(this) {
for(int i = 0; i < _sessions.Count; i++) {
if(_sessions[i].Page == page && _sessions[i].User.Equals(user)) {
_sessions.Remove(_sessions[i]);
break;
}
}
}
}
///
/// Finds whether a Page is being edited by a different user.
///
/// The Page.
/// The User who is requesting the status of the Page.
/// True if the Page is being edited by another User.
/// If page or currentUser are null.
/// If page or currentUser are empty.
public bool IsPageBeingEdited(string page, string currentUser) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
if(currentUser == null) throw new ArgumentNullException("currentUser");
if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser");
lock(this) {
int timeout = int.Parse(_host.GetSettingValue(SettingName.EditingSessionTimeout));
DateTime limit = DateTime.Now.AddSeconds(-(timeout + 5)); // Allow 5 seconds for network delays
for(int i = 0; i < _sessions.Count; i++) {
if(_sessions[i].Page == page && !_sessions[i].User.Equals(currentUser)) {
if(_sessions[i].LastContact.CompareTo(limit) >= 0) {
// Page is being edited
return true;
}
else {
// Lost contact
_sessions.Remove(_sessions[i]);
return false;
}
}
}
}
return false;
}
///
/// Gets the username of the user who's editing a page.
///
/// The page.
/// The username.
/// If page is null.
/// If page is empty.
public string WhosEditing(string page) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
lock(this) {
foreach(EditingSession s in _sessions) {
if(s.Page == page) return s.User;
}
}
return "";
}
///
/// Adds the redirection information for a page (overwrites the previous value, if any).
///
/// The source page.
/// The destination page.
/// If source or destination are null.
/// If source or destination are empty.
public void AddRedirection(string source, string destination) {
if(source == null) throw new ArgumentNullException("source");
if(source.Length == 0) throw new ArgumentException("Source cannot be empty", "source");
if(destination == null) throw new ArgumentNullException("destination");
if(destination.Length == 0) throw new ArgumentException("Destination cannot be empty", "destination");
lock(_redirections) {
_redirections[source.ToLowerInvariant()] = destination;
}
}
///
/// Gets the destination of a redirection.
///
/// The source page.
/// The destination page, if any, null otherwise.
/// If source is null.
/// If source is empty.
public string GetRedirectionDestination(string source) {
if(source == null) throw new ArgumentNullException("source");
if(source.Length == 0) throw new ArgumentException("Source cannot be empty", "source");
string dest = null;
lock(_redirections) {
_redirections.TryGetValue(source.ToLowerInvariant(), out dest);
}
return dest;
}
///
/// Removes a pge from both sources and destinations.
///
/// The name of the page.
/// If name is null.
/// If name is empty.
public void RemovePageFromRedirections(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
name = name.ToLowerInvariant();
lock(_redirections) {
_redirections.Remove(name);
List keysToRemove = new List(10);
foreach(KeyValuePair pair in _redirections) {
if(pair.Value.ToLowerInvariant() == name) keysToRemove.Add(pair.Key);
}
foreach(string key in keysToRemove) {
_redirections.Remove(key);
}
}
}
///
/// Clears all the redirections information.
///
public void ClearRedirections() {
lock(_redirections) {
_redirections.Clear();
}
}
}
///
/// Represents an Editing Session.
///
public class EditingSession {
private string page;
private string user;
private DateTime lastContact;
///
/// Initializes a new instance of the EditingSession class.
///
/// The edited Page.
/// The User who is editing the Page.
public EditingSession(string page, string user) {
this.page = page;
this.user = user;
lastContact = DateTime.Now;
}
///
/// Gets the edited Page.
///
public string Page {
get { return page; }
}
///
/// Gets the User.
///
public string User {
get { return user; }
}
///
/// Sets the Last Contact to now.
///
public void Renew() {
lastContact = DateTime.Now;
}
///
/// Gets the Last Contact Date/Time.
///
public DateTime LastContact {
get { return lastContact; }
}
}
}