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 { /// /// Implements a sandbox plugin for pages. /// public class PagesSandbox { private IHostV30 host; private string config; private static readonly ComponentInformation info = new ComponentInformation("Pages Sandbox", "ScrewTurn Software", "3.0.0.180", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/PagesSandbox.txt"); private List allNamespaces = new List(5); private List allPages; private Dictionary allContents; private Dictionary> allBackups; private Dictionary allDrafts; private List allCategories; private uint freeDocumentId = 1; private uint freeWordId = 1; private IInMemoryIndex index; /// /// Gets a namespace. /// /// The name of the namespace (cannot be null or empty). /// The , or null if no namespace is found. 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); } /// /// Gets all the sub-namespaces. /// /// The sub-namespaces, sorted by name. public NamespaceInfo[] GetNamespaces() { lock(this) { return allNamespaces.ToArray(); } } /// /// Adds a new namespace. /// /// The name of the namespace. /// The correct object. 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; }*/ } /// /// Renames a namespace. /// /// The namespace to rename. /// The new name of the namespace. /// The correct object. 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; } } /// /// Sets the default page of a namespace. /// /// The namespace of which to set the default page. /// The page to use as default page, or null. /// The correct object. public NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) { if(nspace == null) throw new ArgumentNullException("nspace"); lock(this) { nspace.DefaultPage = page; return nspace; } } /// /// Removes a namespace. /// /// The namespace to remove. /// true if the namespace is removed, false otherwise. public bool RemoveNamespace(NamespaceInfo nspace) { if(nspace == null) throw new ArgumentNullException("nspace"); lock(this) { return allNamespaces.Remove(nspace); } } /// /// Moves a page from its namespace into another. /// /// The page to move. /// The destination namespace (null for the root). /// A value indicating whether to copy the page categories in the destination /// namespace, if not already available. /// The correct instance of . public PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories) { throw new NotImplementedException(); } /// /// Gets a category. /// /// The full name of the category. /// The , or null if no category is found. public CategoryInfo GetCategory(string fullName) { throw new NotImplementedException(); } /// /// Gets all the Categories in a namespace. /// /// The namespace. /// All the Categories in the namespace, sorted by name. public CategoryInfo[] GetCategories(NamespaceInfo nspace) { throw new NotImplementedException(); } /// /// Gets all the categories of a page. /// /// The page. /// The categories, sorted by name. public CategoryInfo[] GetCategoriesForPage(PageInfo page) { throw new NotImplementedException(); } /// /// Adds a Category. /// /// The target namespace (null for the root). /// The Category name. /// The correct CategoryInfo object. /// The moethod should set category's Pages to an empty array. public CategoryInfo AddCategory(string nspace, string name) { throw new NotImplementedException(); } /// /// Renames a Category. /// /// The Category to rename. /// The new Name. /// The correct CategoryInfo object. public CategoryInfo RenameCategory(CategoryInfo category, string newName) { throw new NotImplementedException(); } /// /// Removes a Category. /// /// The Category to remove. /// True if the Category has been removed successfully. public bool RemoveCategory(CategoryInfo category) { throw new NotImplementedException(); } /// /// Merges two Categories. /// /// The source Category. /// The destination Category. /// The correct object. /// The destination Category remains, while the source Category is deleted, and all its Pages re-bound /// in the destination Category. public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) { throw new NotImplementedException(); } /// /// Performs a search in the index. /// /// The search parameters. /// The results. public SearchResultCollection PerformSearch(SearchParameters parameters) { throw new NotImplementedException(); } /// /// Rebuilds the search index. /// public void RebuildIndex() { throw new NotImplementedException(); } /// /// Gets some statistics about the search engine index. /// /// The total number of documents. /// The total number of unique words. /// The total number of word-document occurrences. /// The approximated size, in bytes, of the search engine index. public void GetIndexStats(out int documentCount, out int wordCount, out int occurrenceCount, out long size) { throw new NotImplementedException(); } /// /// Gets a value indicating whether the search engine index is corrupted and needs to be rebuilt. /// public bool IsIndexCorrupted { get { throw new NotImplementedException(); } } /// /// Gets a page. /// /// The full name of the page. /// The , or null if no page is found. public PageInfo GetPage(string fullName) { throw new NotImplementedException(); } /// /// Gets all the Pages in a namespace. /// /// The namespace (null for the root). /// All the Pages in the namespace, sorted by name. public PageInfo[] GetPages(NamespaceInfo nspace) { throw new NotImplementedException(); } /// /// Gets all the pages in a namespace that are bound to zero categories. /// /// The namespace (null for the root). /// The pages, sorted by name. public PageInfo[] GetUncategorizedPages(NamespaceInfo nspace) { throw new NotImplementedException(); } /// /// Gets the Content of a Page. /// /// The Page. /// The Page Content object. public PageContent GetContent(PageInfo page) { throw new NotImplementedException(); } /// /// Gets the content of a draft of a Page. /// /// The Page. /// The draft, or null if no draft exists. public PageContent GetDraft(PageInfo page) { throw new NotImplementedException(); } /// /// Deletes a draft of a Page. /// /// The page. /// true if the draft is deleted, false otherwise. public bool DeleteDraft(PageInfo page) { throw new NotImplementedException(); } /// /// Gets the Backup/Revision numbers of a Page. /// /// The Page to get the Backups of. /// The Backup/Revision numbers. public int[] GetBackups(PageInfo page) { throw new NotImplementedException(); } /// /// Gets the Content of a Backup of a Page. /// /// The Page to get the backup of. /// The Backup/Revision number. /// The Page Backup. public PageContent GetBackupContent(PageInfo page, int revision) { throw new NotImplementedException(); } /// /// Forces to overwrite or create a Backup. /// /// The Backup content. /// The revision. /// True if the Backup has been created successfully. public bool SetBackupContent(PageContent content, int revision) { throw new NotImplementedException(); } /// /// Adds a Page. /// /// The target namespace (null for the root). /// The Page Name. /// The creation Date/Time. /// The correct PageInfo object or null. /// This method should not create the content of the Page. public PageInfo AddPage(string nspace, string name, DateTime creationDateTime) { throw new NotImplementedException(); } /// /// Renames a Page. /// /// The Page to rename. /// The new Name. /// The correct object. public PageInfo RenamePage(PageInfo page, string newName) { throw new NotImplementedException(); } /// /// Modifies the Content of a Page. /// /// The Page. /// The Title of the Page. /// The Username. /// The Date/Time. /// The Comment of the editor, about this revision. /// The Page Content. /// The keywords, usually used for SEO. /// The description, usually used for SEO. /// The save mode for this modification. /// true if the Page has been modified successfully, false otherwise. /// If saveMode equals Draft and a draft already exists, it is overwritten. public bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, string[] keywords, string description, SaveMode saveMode) { throw new NotImplementedException(); } /// /// Performs the rollback of a Page to a specified revision. /// /// The Page to rollback. /// The Revision to rollback the Page to. /// true if the rollback succeeded, false otherwise. public bool RollbackPage(PageInfo page, int revision) { throw new NotImplementedException(); } /// /// Deletes the Backups of a Page, up to a specified revision. /// /// The Page to delete the backups of. /// The newest revision to delete (newer revision are kept) or -1 to delete all the Backups. /// true if the deletion succeeded, false otherwise. public bool DeleteBackups(PageInfo page, int revision) { throw new NotImplementedException(); } /// /// Removes a Page. /// /// The Page to remove. /// True if the Page is removed successfully. public bool RemovePage(PageInfo page) { throw new NotImplementedException(); } /// /// Binds a Page with one or more Categories. /// /// The Page to bind. /// The Categories to bind the Page with. /// True if the binding succeeded. /// After a successful operation, the Page is bound with all and only the categories passed as argument. public bool RebindPage(PageInfo page, string[] categories) { throw new NotImplementedException(); } /// /// Gets the Page Messages. /// /// The Page. /// The list of the first-level Messages, containing the replies properly nested, sorted by date/time. public Message[] GetMessages(PageInfo page) { throw new NotImplementedException(); } /// /// Gets the total number of Messages in a Page Discussion. /// /// The Page. /// The number of messages. public int GetMessageCount(PageInfo page) { throw new NotImplementedException(); } /// /// Removes all messages for a page and stores the new messages. /// /// The page. /// The new messages to store. /// true if the messages are stored, false otherwise. public bool BulkStoreMessages(PageInfo page, Message[] messages) { throw new NotImplementedException(); } /// /// Adds a new Message to a Page. /// /// The Page. /// The Username. /// The Subject. /// The Date/Time. /// The Body. /// The Parent Message ID, or -1. /// True if the Message is added successfully. public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) { throw new NotImplementedException(); } /// /// Removes a Message. /// /// The Page. /// The ID of the Message to remove. /// A value specifying whether or not to remove the replies. /// True if the Message is removed successfully. public bool RemoveMessage(PageInfo page, int id, bool removeReplies) { throw new NotImplementedException(); } /// /// Modifies a Message. /// /// The Page. /// The ID of the Message to modify. /// The Username. /// The Subject. /// The Date/Time. /// The Body. /// True if the Message is modified successfully. public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) { throw new NotImplementedException(); } /// /// Gets all the Navigation Paths in a Namespace. /// /// The Namespace. /// All the Navigation Paths, sorted by name. public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) { throw new NotImplementedException(); } /// /// Adds a new Navigation Path. /// /// The target namespace (null for the root). /// The Name of the Path. /// The Pages array. /// The correct object. public NavigationPath AddNavigationPath(string nspace, string name, PageInfo[] pages) { throw new NotImplementedException(); } /// /// Modifies an existing navigation path. /// /// The navigation path to modify. /// The new pages array. /// The correct object. public NavigationPath ModifyNavigationPath(NavigationPath path, PageInfo[] pages) { throw new NotImplementedException(); } /// /// Removes a Navigation Path. /// /// The navigation path to remove. /// true if the path is removed, false otherwise. public bool RemoveNavigationPath(NavigationPath path) { throw new NotImplementedException(); } /// /// Gets all the snippets. /// /// All the snippets, sorted by name. public Snippet[] GetSnippets() { throw new NotImplementedException(); } /// /// Adds a new snippet. /// /// The name of the snippet. /// The content of the snippet. /// The correct object. public Snippet AddSnippet(string name, string content) { throw new NotImplementedException(); } /// /// Modifies an existing snippet. /// /// The name of the snippet to modify. /// The content of the snippet. /// The correct object. public Snippet ModifySnippet(string name, string content) { throw new NotImplementedException(); } /// /// Removes a new Snippet. /// /// The Name of the Snippet to remove. /// true if the snippet is removed, false otherwise. public bool RemoveSnippet(string name) { throw new NotImplementedException(); } /// /// Gets all the content templates. /// /// All the content templates, sorted by name. public ContentTemplate[] GetContentTemplates() { throw new NotImplementedException(); } /// /// Adds a new content template. /// /// The name of template. /// The content of the template. /// The correct object. public ContentTemplate AddContentTemplate(string name, string content) { throw new NotImplementedException(); } /// /// Modifies an existing content template. /// /// The name of the template to modify. /// The content of the template. /// The correct object. public ContentTemplate ModifyContentTemplate(string name, string content) { throw new NotImplementedException(); } /// /// Removes a content template. /// /// The name of the template to remove. /// true if the template is removed, false otherwise. public bool RemoveContentTemplate(string name) { throw new NotImplementedException(); } /// /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. /// public bool ReadOnly { get { throw new NotImplementedException(); } } /// /// 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) { if(host == null) throw new ArgumentNullException("host"); if(config == null) throw new ArgumentNullException("config"); this.host = host; this.config = config; allPages = new List(50); allContents = new Dictionary(50); allBackups = new Dictionary>(50); allDrafts = new Dictionary(5); allCategories = new List(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 newWords = new List(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; } } /// /// Handles the construction of an for the search engine. /// /// The input dumped document. /// The resulting . 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; } /// /// Tokenizes page content. /// /// The content to tokenize. /// The tokenized words. private static WordInfo[] TokenizeContent(string content) { WordInfo[] words = SearchEngine.Tools.Tokenize(content); return words; } /// /// Indexes a page. /// /// The content of the page. /// The number of indexed words, including duplicates. 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); } } /// /// Removes a page from the search engine index. /// /// The content of the page to remove. 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); } } /// /// Indexes a message. /// /// The page. /// The message ID. /// The subject. /// The date/time. /// The body. /// The number of indexed words, including duplicates. 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); } } /// /// Indexes a message tree. /// /// The page. /// The tree root. 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); } } /// /// Removes a message from the search engine index. /// /// The page. /// The message ID. /// The subject. /// The date/time. /// The body. /// The number of indexed words, including duplicates. 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); } } /// /// Removes a message tree from the search engine index. /// /// The page. /// The tree root. 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); } } /// /// Method invoked on shutdown. /// /// This method might not be invoked in some cases. public void Shutdown() { // Nothing do to } /// /// 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; } } } }