using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using ScrewTurn.Wiki.SearchEngine;
namespace ScrewTurn.Wiki.Plugins.SqlCommon {
///
/// Implements a base class for a SQL pages storage provider.
///
public abstract class SqlPagesStorageProviderBase : SqlStorageProviderBase, IPagesStorageProviderV30 {
private const int MaxStatementsInBatch = 20;
private const int FirstRevision = 0;
private const int CurrentRevision = -1;
private const int DraftRevision = -100;
private IIndex index;
private bool alwaysGenerateDocument = false;
///
/// 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 new void Init(IHostV30 host, string config) {
base.Init(host, config);
index = new SqlIndex(new IndexConnector(GetWordFetcher, GetSize, GetCount, ClearIndex, DeleteDataForDocument, SaveDataForDocument, TryFindWord));
}
#region Index and Search Engine
///
/// Sets test flags (to be used only for tests).
///
/// A value indicating whether to always generate a result when resolving a document,
/// even when the page does not exist.
public void SetFlags(bool alwaysGenerateDocument) {
this.alwaysGenerateDocument = alwaysGenerateDocument;
}
///
/// Gets a word fetcher.
///
/// The word fetcher.
private IWordFetcher GetWordFetcher() {
return new SqlWordFetcher(GetCommandBuilder().GetConnection(connString), TryFindWord);
}
///
/// Gets the search index (only used for testing purposes).
///
public IIndex Index {
get { return index; }
}
///
/// Performs a search in the index.
///
/// The search parameters.
/// The results.
/// If is null.
public SearchResultCollection PerformSearch(SearchParameters parameters) {
if(parameters == null) throw new ArgumentNullException("parameters");
return index.Search(parameters);
}
///
/// Rebuilds the search index.
///
public void RebuildIndex() {
index.Clear(null);
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
List allNamespaces = new List(GetNamespaces());
allNamespaces.Add(null);
int indexedElements = 0;
foreach(NamespaceInfo nspace in allNamespaces) {
foreach(PageInfo page in GetPages(transaction, nspace)) {
IndexPage(GetContent(page), transaction);
indexedElements++;
foreach(Message msg in GetMessages(transaction, page)) {
IndexMessageTree(page, msg, transaction);
indexedElements++;
}
// Every 10 indexed documents, commit the transaction to
// reduce the number of database locks
if(indexedElements >= 10) {
CommitTransaction(transaction);
indexedElements = 0;
connection = builder.GetConnection(connString);
transaction = BeginTransaction(connection);
}
}
}
CommitTransaction(transaction);
}
///
/// Gets a value indicating whether the search engine index is corrupted and needs to be rebuilt.
///
public bool IsIndexCorrupted {
get { return false; }
}
///
/// Handles the construction of an for the search engine.
///
/// The input dumped document.
/// The resulting .
private IDocument BuildDocument(DumpedDocument dumpedDocument) {
if(alwaysGenerateDocument) {
return new DummyDocument() {
ID = dumpedDocument.ID,
Name = dumpedDocument.Name,
Title = dumpedDocument.Title,
TypeTag = dumpedDocument.TypeTag,
DateTime = dumpedDocument.DateTime
};
}
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;
}
// Extremely dirty way for testing the search engine in combination with alwaysGenerateDocument
private class DummyDocument : IDocument {
public uint ID { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string TypeTag { get; set; }
public DateTime DateTime { get; set; }
public WordInfo[] Tokenize(string content) {
return ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(content);
}
}
///
/// 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) {
documentCount = index.TotalDocuments;
wordCount = index.TotalWords;
occurrenceCount = index.TotalOccurrences;
size = GetSize();
}
///
/// Gets the approximate size, in bytes, of the search engine index.
///
private long GetSize() {
// 1. Size of documents: 8 + 2*20 + 2*30 + 2*1 + 8 = 118 bytes
// 2. Size of words: 8 + 2*8 = 24 bytes
// 3. Size of mappings: 8 + 8 + 2 + 2 + 1 = 21 bytes
// 4. Size = Size * 2
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
QueryBuilder queryBuilder = new QueryBuilder(builder);
long size = 0;
string query = queryBuilder.SelectCountFrom("IndexDocument");
DbCommand command = builder.GetCommand(connection, query, new List());
int rows = ExecuteScalar(command, -1, false);
if(rows == -1) return 0;
size += rows * 118;
query = queryBuilder.SelectCountFrom("IndexWord");
command = builder.GetCommand(connection, query, new List());
rows = ExecuteScalar(command, -1, false);
if(rows == -1) return 0;
size += rows * 24;
query = queryBuilder.SelectCountFrom("IndexWordMapping");
command = builder.GetCommand(connection, query, new List());
rows = ExecuteScalar(command, -1, false);
if(rows == -1) return 0;
size += rows * 21;
CloseConnection(connection);
return size * 2;
}
///
/// Gets the number of elements in the index.
///
/// The type of elements.
/// The number of elements.
private int GetCount(IndexElementType element) {
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
QueryBuilder queryBuilder = new QueryBuilder(builder);
int count = 0;
string elemName = "";
if(element == IndexElementType.Documents) elemName = "IndexDocument";
else if(element == IndexElementType.Words) elemName = "IndexWord";
else if(element == IndexElementType.Occurrences) elemName = "IndexWordMapping";
else throw new NotSupportedException("Unsupported element type");
string query = queryBuilder.SelectCountFrom(elemName);
DbCommand command = builder.GetCommand(connection, query, new List());
count = ExecuteScalar(command, -1, true);
return count;
}
///
/// Deletes all data associated to a document.
///
/// The document.
/// A state object passed from the index (can be null or a ).
private void DeleteDataForDocument(IDocument document, object state) {
// 1. Delete all data related to a document
// 2. Delete all words that have no more mappings
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("IndexDocument");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "DocName");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "DocName", document.Name));
string subQuery = queryBuilder.SelectFrom("IndexWordMapping", new string[] { "Word" });
subQuery = queryBuilder.GroupBy(subQuery, new string[] { "Word" });
string query2 = queryBuilder.DeleteFrom("IndexWord");
query2 = queryBuilder.WhereNotInSubquery(query2, "IndexWord", "Id", subQuery);
query = queryBuilder.AppendForBatch(query, query2);
DbCommand command = null;
if(state != null) command = builder.GetCommand((DbTransaction)state, query, parameters);
else command = builder.GetCommand(connString, query, parameters);
// Close only if state is null
ExecuteNonQuery(command, state == null);
}
///
/// Saves data for a new document.
///
/// The document.
/// The content words.
/// The title words.
/// The keywords.
/// A state object passed from the index (can be null or a ).
/// The number of stored occurrences.
private int SaveDataForDocument(IDocument document, WordInfo[] content, WordInfo[] title, WordInfo[] keywords, object state) {
// 1. Insert document
// 2. Insert all new words
// 3. Load all word IDs
// 4. Insert mappings
// On error, return without rolling back if state != null, rollback otherwise
// On completion, commit if state == null
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
DbTransaction transaction = null;
if(state != null) transaction = (DbTransaction)state;
else {
DbConnection connection = builder.GetConnection(connString);
transaction = BeginTransaction(connection);
}
uint freeDocumentId = GetFreeElementId(IndexElementType.Documents, transaction);
uint freeWordId = GetFreeElementId(IndexElementType.Words, transaction);
// Insert the document
string query = queryBuilder.InsertInto("IndexDocument",
new string[] { "Id", "Name", "Title", "TypeTag", "DateTime" },
new string[] { "Id", "Name", "Title", "TypeTag", "DateTime" });
List parameters = new List(5);
parameters.Add(new Parameter(ParameterType.Int32, "Id", (int)freeDocumentId));
parameters.Add(new Parameter(ParameterType.String, "Name", document.Name));
parameters.Add(new Parameter(ParameterType.String, "Title", document.Title));
parameters.Add(new Parameter(ParameterType.String, "TypeTag", document.TypeTag));
parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", document.DateTime));
DbCommand command = builder.GetCommand(transaction, query, parameters);
if(ExecuteNonQuery(command, false) != 1) {
if(state == null) RollbackTransaction(transaction);
return -1;
}
document.ID = freeDocumentId;
List allWords = new List(content.Length + title.Length + keywords.Length);
allWords.AddRange(content);
allWords.AddRange(title);
allWords.AddRange(keywords);
List existingWords = new List(allWords.Count / 2);
Dictionary wordIds = new Dictionary(1024);
// Try to blindly insert all words (assumed to be lowercase and clean from diacritics)
query = queryBuilder.InsertInto("IndexWord", new string[] { "Id", "Text" }, new string[] { "Id", "Text" });
parameters = new List(2);
parameters.Add(new Parameter(ParameterType.Int32, "Id", 0));
parameters.Add(new Parameter(ParameterType.String, "Text", ""));
foreach(WordInfo word in allWords) {
parameters[0].Value = (int)freeWordId;
parameters[1].Value = word.Text;
command = builder.GetCommand(transaction, query, parameters);
if(ExecuteNonQuery(command, false, false) == 1) {
wordIds.Add(word.Text, freeWordId);
freeWordId++;
}
else {
existingWords.Add(word);
}
}
// Load IDs of all existing words
query = queryBuilder.SelectFrom("IndexWord", new string[] { "Id" });
query = queryBuilder.Where(query, "Text", WhereOperator.Equals, "Text");
parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Text", ""));
foreach(WordInfo word in existingWords) {
parameters[0].Value = word.Text;
command = builder.GetCommand(transaction, query, parameters);
int id = ExecuteScalar(command, -1, false);
if(id == -1) {
if(state == null) RollbackTransaction(transaction);
return -1;
}
if(!wordIds.ContainsKey(word.Text)) {
wordIds.Add(word.Text, (uint)id);
}
else if(wordIds[word.Text] != (uint)id) throw new InvalidOperationException("Word ID mismatch");
}
// Insert all mappings
query = queryBuilder.InsertInto("IndexWordMapping",
new string[] { "Word", "Document", "FirstCharIndex", "WordIndex", "Location" },
new string[] { "Word", "Document", "FirstCharIndex", "WordIndex", "Location" });
parameters = new List(5);
parameters.Add(new Parameter(ParameterType.Int32, "Word", 0));
parameters.Add(new Parameter(ParameterType.Int32, "Document", (int)freeDocumentId));
parameters.Add(new Parameter(ParameterType.Int16, "FirstCharIndex", 0));
parameters.Add(new Parameter(ParameterType.Int16, "WordIndex", 0));
parameters.Add(new Parameter(ParameterType.Byte, "Location", 0));
foreach(WordInfo word in allWords) {
parameters[0].Value = (int)wordIds[word.Text];
parameters[1].Value = (int)freeDocumentId;
parameters[2].Value = (short)word.FirstCharIndex;
parameters[3].Value = (short)word.WordIndex;
parameters[4].Value = word.Location.Location;
command = builder.GetCommand(transaction, query, parameters);
if(ExecuteNonQuery(command, false) != 1) {
if(state == null) RollbackTransaction(transaction);
return -1;
}
}
if(state == null) CommitTransaction(transaction);
return allWords.Count;
}
///
/// Gets a free element ID from the database.
///
/// The element type.
/// The current database transaction.
/// The free element ID.
private uint GetFreeElementId(IndexElementType element, DbTransaction transaction) {
if(element == IndexElementType.Occurrences) throw new ArgumentException("Element cannot be Occurrences", "element");
string table = element == IndexElementType.Documents ? "IndexDocument" : "IndexWord";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom(table, new string[] { "Id" });
query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Desc });
DbCommand command = builder.GetCommand(transaction, query, new List());
int id = ExecuteScalar(command, -1, false);
if(id == -1) return 0;
else return (uint)id + 1;
}
///
/// Tries to load all data related to a word from the database.
///
/// The word text.
/// The returned word.
/// An open database connection.
/// true if the word is found, false otherwise.
private bool TryFindWord(string text, out Word word, DbConnection connection) {
// 1. Find word - if not found, return
// 2. Read all raw word mappings
// 3. Read all documents (unique)
// 4. Build result data structure
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("IndexWord", new string[] { "Id" });
query = queryBuilder.Where(query, "Text", WhereOperator.Equals, "Text");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Text", text));
DbCommand command = builder.GetCommand(connection, query, parameters);
int wordId = ExecuteScalar(command, -1, false);
if(wordId == -1) {
word = null;
return false;
}
// Read all raw mappings
query = queryBuilder.SelectFrom("IndexWordMapping");
query = queryBuilder.Where(query, "Word", WhereOperator.Equals, "WordId");
parameters = new List(1);
parameters.Add(new Parameter(ParameterType.Int32, "WordId", wordId));
command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command, false);
List mappings = new List(2048);
while(reader != null && reader.Read()) {
mappings.Add(new DumpedWordMapping((uint)wordId,
(uint)(int)reader["Document"],
(ushort)(short)reader["FirstCharIndex"], (ushort)(short)reader["WordIndex"],
(byte)reader["Location"]));
}
CloseReader(reader);
if(mappings.Count == 0) {
word = null;
return false;
}
// Find all documents
query = queryBuilder.SelectFrom("IndexDocument");
query = queryBuilder.Where(query, "Id", WhereOperator.Equals, "DocId");
parameters = new List(1);
parameters.Add(new Parameter(ParameterType.Int32, "DocId", 0));
Dictionary documents = new Dictionary(64);
foreach(DumpedWordMapping map in mappings) {
uint docId = map.DocumentID;
if(documents.ContainsKey(docId)) continue;
parameters[0].Value = (int)docId;
command = builder.GetCommand(connection, query, parameters);
reader = ExecuteReader(command, false);
if(reader != null && reader.Read()) {
DumpedDocument dumpedDoc = new DumpedDocument(docId,
reader["Name"] as string, reader["Title"] as string,
reader["TypeTag"] as string,
(DateTime)reader["DateTime"]);
IDocument document = BuildDocument(dumpedDoc);
if(document != null) documents.Add(docId, document);
}
CloseReader(reader);
}
OccurrenceDictionary occurrences = new OccurrenceDictionary(mappings.Count);
foreach(DumpedWordMapping map in mappings) {
if(!occurrences.ContainsKey(documents[map.DocumentID])) {
occurrences.Add(documents[map.DocumentID], new SortedBasicWordInfoSet(2));
}
occurrences[documents[map.DocumentID]].Add(new BasicWordInfo(
map.FirstCharIndex, map.WordIndex, WordLocation.GetInstance(map.Location)));
}
word = new Word((uint)wordId, text, occurrences);
return true;
}
///
/// Clears the index.
///
/// A state object passed from the index.
private void ClearIndex(object state) {
// state can be null, depending on when the method is called
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("IndexWordMapping");
query = queryBuilder.AppendForBatch(query, queryBuilder.DeleteFrom("IndexWord"));
query = queryBuilder.AppendForBatch(query, queryBuilder.DeleteFrom("IndexDocument"));
DbCommand command = null;
if(state == null) command = builder.GetCommand(connString, query, new List());
else command = builder.GetCommand((DbTransaction)state, query, new List());
ExecuteNonQuery(command, state == 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 page content.
/// The current transaction.
/// The number of indexed words, including duplicates.
private int IndexPage(PageContent content, DbTransaction transaction) {
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()
int count = index.StoreDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent),
content.Keywords, host.PrepareContentForIndexing(content.PageInfo, content.Content), transaction);
if(count == 0 && content.Content.Length > 0) {
host.LogEntry("Indexed 0 words for page " + content.PageInfo.FullName + ": possible index corruption. Please report this error to the developers",
LogEntryType.Warning, null, this);
}
return count;
}
///
/// Removes a page from the search engine index.
///
/// The content of the page to remove.
/// The current transaction.
private void UnindexPage(PageContent content, DbTransaction transaction) {
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), transaction);
}
///
/// Indexes a message tree.
///
/// The page.
/// The root message.
/// The current transaction.
private void IndexMessageTree(PageInfo page, Message root, DbTransaction transaction) {
IndexMessage(page, root.ID, root.Subject, root.DateTime, root.Body, transaction);
foreach(Message reply in root.Replies) {
IndexMessageTree(page, reply, transaction);
}
}
///
/// Indexes a message.
///
/// The page.
/// The message ID.
/// The subject.
/// The date/time.
/// The body.
/// The current transaction.
/// The number of indexed words, including duplicates.
private int IndexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body, DbTransaction transaction) {
// 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()
int count = index.StoreDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null,
host.PrepareContentForIndexing(null, body), transaction);
if(count == 0 && body.Length > 0) {
host.LogEntry("Indexed 0 words for message " + page.FullName + ":" + id.ToString() + ": possible index corruption. Please report this error to the developers",
LogEntryType.Warning, null, this);
}
return count;
}
///
/// Removes a message tree from the search engine index.
///
/// The page.
/// The tree root.
/// The current transaction.
private void UnindexMessageTree(PageInfo page, Message root, DbTransaction transaction) {
UnindexMessage(page, root.ID, root.Subject, root.DateTime, root.Body, transaction);
foreach(Message reply in root.Replies) {
UnindexMessageTree(page, reply, transaction);
}
}
///
/// Removes a message from the search engine index.
///
/// The page.
/// The message ID.
/// The subject.
/// The date/time.
/// The body.
/// The current transaction.
/// The number of indexed words, including duplicates.
private void UnindexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body, DbTransaction transaction) {
// 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), transaction);
}
#endregion
#region IPagesStorageProvider Members
///
/// Gets a namespace.
///
/// A database transaction.
/// The name of the namespace (cannot be null or empty).
/// The , or null if no namespace is found.
private NamespaceInfo GetNamespace(DbTransaction transaction, string name) {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
// select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name = and (Namespace.DefaultPage is null or Page.Namespace = )
string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" });
query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.Equals, "Name1");
query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false);
query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.Equals, "Name2", false, true);
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name1", name));
parameters.Add(new Parameter(ParameterType.String, "Name2", name));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
NamespaceInfo result = null;
if(reader.Read()) {
string realName = reader["Namespace_Name"] as string;
string page = GetNullableColumn(reader, "Namespace_DefaultPage", null);
PageInfo defaultPage = string.IsNullOrEmpty(page) ? null :
new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]);
result = new NamespaceInfo(realName, this, defaultPage);
}
CloseReader(reader);
return result;
}
else return null;
}
///
/// Gets a namespace.
///
/// A database connection.
/// The name of the namespace (cannot be null or empty).
/// The , or null if no namespace is found.
private NamespaceInfo GetNamespace(DbConnection connection, string name) {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
// select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name = and (Namespace.DefaultPage is null or Page.Namespace = )
string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" });
query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.Equals, "Name1");
query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false);
query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.Equals, "Name2", false, true);
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name1", name));
parameters.Add(new Parameter(ParameterType.String, "Name2", name));
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
NamespaceInfo result = null;
if(reader.Read()) {
string realName = reader["Namespace_Name"] as string;
string page = GetNullableColumn(reader, "Namespace_DefaultPage", null);
PageInfo defaultPage = string.IsNullOrEmpty(page) ? null :
new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]);
result = new NamespaceInfo(realName, this, defaultPage);
}
CloseReader(reader);
return result;
}
else return null;
}
///
/// Gets a namespace.
///
/// The name of the namespace (cannot be null or empty).
/// The , or null if no namespace is found.
/// If is null.
/// If is empty.
public NamespaceInfo GetNamespace(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
NamespaceInfo nspace = GetNamespace(connection, name);
CloseConnection(connection);
return nspace;
}
///
/// Gets all the sub-namespaces.
///
/// The sub-namespaces, sorted by name.
public NamespaceInfo[] GetNamespaces() {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
// select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name <> '' and (Namespace.DefaultPage is null or Page.Namespace <> '')
string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" });
query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.NotEquals, "Empty1");
query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false);
query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.NotEquals, "Empty2", false, true);
query = queryBuilder.OrderBy(query, new[] { "Namespace_Name" }, new[] { Ordering.Asc });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Empty1", ""));
parameters.Add(new Parameter(ParameterType.String, "Empty2", ""));
DbCommand command = builder.GetCommand(connString, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(10);
while(reader.Read()) {
string realName = reader["Namespace_Name"] as string;
string page = GetNullableColumn(reader, "Namespace_DefaultPage", null);
PageInfo defaultPage = string.IsNullOrEmpty(page) ? null :
new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]);
// The query returns duplicate entries if the main page of two or more namespaces have the same name
if(result.Find(n => { return n.Name.Equals(realName); }) == null) {
result.Add(new NamespaceInfo(realName, this, defaultPage));
}
}
CloseReader(command, reader);
return result.ToArray();
}
else return null;
}
///
/// Adds a new namespace.
///
/// The name of the namespace.
/// The correct object.
/// If is null.
/// If is empty.
public NamespaceInfo AddNamespace(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.InsertInto("Namespace", new string[] { "Name" }, new string[] { "Name" });
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
DbCommand command = builder.GetCommand(connString, query, parameters);
int rows = ExecuteNonQuery(command);
if(rows == 1) return new NamespaceInfo(name, this, null);
else return null;
}
///
/// Renames a namespace.
///
/// The namespace to rename.
/// The new name of the namespace.
/// The correct object.
/// If or are null.
/// If is empty.
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");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(GetNamespace(transaction, nspace.Name) == null) {
RollbackTransaction(transaction);
return null;
}
foreach(PageInfo page in GetPages(transaction, nspace)) {
PageContent content = GetContent(transaction, page, CurrentRevision);
if(content != null) {
UnindexPage(content, transaction);
}
Message[] messages = GetMessages(transaction, page);
if(messages != null) {
foreach(Message msg in messages) {
UnindexMessageTree(page, msg, transaction);
}
}
}
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.Update("Namespace", new string[] { "Name" }, new string[] { "NewName" });
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "NewName", newName));
parameters.Add(new Parameter(ParameterType.String, "OldName", nspace.Name));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows > 0) {
NamespaceInfo result = GetNamespace(transaction, newName);
foreach(PageInfo page in GetPages(transaction, result)) {
PageContent content = GetContent(transaction, page, CurrentRevision);
if(content != null) {
IndexPage(content, transaction);
}
Message[] messages = GetMessages(transaction, page);
if(messages != null) {
foreach(Message msg in messages) {
IndexMessageTree(page, msg, transaction);
}
}
}
CommitTransaction(transaction);
return result;
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// 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.
/// If is null.
public NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) {
if(nspace == null) throw new ArgumentNullException("nspace");
// Namespace existence is verified by the affected rows (should be 1)
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(page != null && GetPage(transaction, page.FullName) == null) {
RollbackTransaction(transaction);
return null;
}
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.Update("Namespace", new string[] { "DefaultPage" }, new string[] { "DefaultPage" });
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
List parameters = new List(2);
if(page == null) parameters.Add(new Parameter(ParameterType.String, "DefaultPage", DBNull.Value));
else parameters.Add(new Parameter(ParameterType.String, "DefaultPage", NameTools.GetLocalName(page.FullName)));
parameters.Add(new Parameter(ParameterType.String, "Name", nspace.Name));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == 1) {
CommitTransaction(transaction);
return new NamespaceInfo(nspace.Name, this, page);
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// Removes a namespace.
///
/// The namespace to remove.
/// true if the namespace is removed, false otherwise.
/// If is null.
public bool RemoveNamespace(NamespaceInfo nspace) {
if(nspace == null) throw new ArgumentNullException("nspace");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
foreach(PageInfo page in GetPages(transaction, nspace)) {
PageContent content = GetContent(transaction, page, CurrentRevision);
UnindexPage(content, transaction);
foreach(Message msg in GetMessages(transaction, page)) {
UnindexMessageTree(page, msg, transaction);
}
}
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Namespace");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Name", nspace.Name));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows > 0) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return rows > 0;
}
///
/// Determines whether a page is the default page of its namespace.
///
/// A database transaction.
/// The page.
/// true if the page is the default page, false otherwise.
private bool IsDefaultPage(DbTransaction transaction, PageInfo page) {
string nspaceName = NameTools.GetNamespace(page.FullName);
if(string.IsNullOrEmpty(nspaceName)) return false;
NamespaceInfo nspace = GetNamespace(transaction, nspaceName);
if(nspace == null) return false;
else {
if(nspace.DefaultPage != null) return new PageNameComparer().Compare(nspace.DefaultPage, page) == 0;
else return false;
}
}
///
/// 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 .
/// If is null.
public PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories) {
if(page == null) throw new ArgumentNullException("page");
// Check:
// 1. Same namespace - ROOT, SUB (explicit check)
// 2. Destination existence (update query affects 0 rows because it would break a FK)
// 3. Page existence in target (update query affects 0 rows because it would break a FK)
// 4. Page is default page of its namespace (explicit check)
string destinationName = destination != null ? destination.Name : "";
string sourceName = null;
string pageName = null;
NameTools.ExpandFullName(page.FullName, out sourceName, out pageName);
if(sourceName == null) sourceName = "";
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(destinationName.ToLowerInvariant() == sourceName.ToLowerInvariant()) return null;
if(IsDefaultPage(transaction, page)) {
RollbackTransaction(transaction);
return null;
}
PageContent currentContent = GetContent(transaction, page, CurrentRevision);
if(currentContent != null) {
UnindexPage(currentContent, transaction);
foreach(Message msg in GetMessages(transaction, page)) {
UnindexMessageTree(page, msg, transaction);
}
}
CategoryInfo[] currCategories = GetCategories(transaction, sourceName == "" ? null : GetNamespace(transaction, sourceName));
// Remove bindings
RebindPage(transaction, page, new string[0]);
string[] newCategories = new string[0];
if(copyCategories) {
// Retrieve categories for page
// Copy missing ones in destination
string lowerPageName = page.FullName.ToLowerInvariant();
List pageCategories = new List(10);
foreach(CategoryInfo cat in currCategories) {
if(Array.Find(cat.Pages, (s) => { return s.ToLowerInvariant() == lowerPageName; }) != null) {
pageCategories.Add(NameTools.GetLocalName(cat.FullName));
}
}
// Create categories into destination without checking existence (AddCategory will return null)
string tempName = destinationName == "" ? null : destinationName;
newCategories = new string[pageCategories.Count];
for(int i = 0; i < pageCategories.Count; i++) {
CategoryInfo added = AddCategory(tempName, pageCategories[i]);
newCategories[i] = added.FullName;
}
}
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.Update("Page", new string[] { "Namespace" }, new string[] { "Destination" });
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Source");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Destination", destinationName));
parameters.Add(new Parameter(ParameterType.String, "Name", pageName));
parameters.Add(new Parameter(ParameterType.String, "Source", sourceName));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows > 0) {
PageInfo result = new PageInfo(NameTools.GetFullName(destinationName, pageName), this, page.CreationDateTime);
// Re-bind categories
if(copyCategories) {
bool rebound = RebindPage(transaction, result, newCategories);
if(!rebound) {
RollbackTransaction(transaction);
return null;
}
}
PageContent newContent = GetContent(transaction, result, CurrentRevision);
IndexPage(newContent, transaction);
foreach(Message msg in GetMessages(transaction, result)) {
IndexMessageTree(result, msg, transaction);
}
CommitTransaction(transaction);
return result;
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// Gets a category.
///
/// A database transaction.
/// The full name of the category.
/// The , or null if no category is found.
private CategoryInfo GetCategory(DbTransaction transaction, string fullName) {
string nspace = null;
string name = null;
NameTools.ExpandFullName(fullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin,
new string[] { "Name", "Namespace" }, new string[] { "Page" });
query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Category", "Name", WhereOperator.Equals, "Name");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.String, "Name", name));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
CategoryInfo result = null;
List pages = new List(50);
while(reader.Read()) {
if(result == null) result = new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["Category_Name"] as string), this);
if(!IsDBNull(reader, "CategoryBinding_Page")) {
pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string));
}
}
CloseReader(reader);
if(result != null) result.Pages = pages.ToArray();
return result;
}
else return null;
}
///
/// Gets a category.
///
/// A database connection.
/// The full name of the category.
/// The , or null if no category is found.
private CategoryInfo GetCategory(DbConnection connection, string fullName) {
string nspace = null;
string name = null;
NameTools.ExpandFullName(fullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin,
new string[] { "Name", "Namespace" }, new string[] { "Page" });
query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Category", "Name", WhereOperator.Equals, "Name");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.String, "Name", name));
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
CategoryInfo result = null;
List pages = new List(50);
while(reader.Read()) {
if(result == null) result = new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["Category_Name"] as string), this);
if(!IsDBNull(reader, "CategoryBinding_Page")) {
pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string));
}
}
CloseReader(reader);
if(result != null) result.Pages = pages.ToArray();
return result;
}
else return null;
}
///
/// Gets a category.
///
/// The full name of the category.
/// The , or null if no category is found.
/// If is null.
/// If is empty.
public CategoryInfo GetCategory(string fullName) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
CategoryInfo category = GetCategory(connection, fullName);
CloseConnection(connection);
return category;
}
///
/// Gets all the Categories in a namespace.
///
/// A database transaction.
/// The namespace.
/// All the Categories in the namespace. The array is not sorted.
private CategoryInfo[] GetCategories(DbTransaction transaction, NamespaceInfo nspace) {
string nspaceName = nspace != null ? nspace.Name : "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin,
new string[] { "Name", "Namespace" }, new string[] { "Page" });
query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new string[] { "Category_Name", "CategoryBinding_Page" }, new Ordering[] { Ordering.Asc, Ordering.Asc });
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(20);
List pages = new List(50);
string prevName = "|||";
string name = null;
while(reader.Read()) {
name = reader["Category_Name"] as string;
if(name != prevName) {
if(prevName != "|||") {
result[result.Count - 1].Pages = pages.ToArray();
pages.Clear();
}
result.Add(new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, name), this));
}
prevName = name;
if(!IsDBNull(reader, "CategoryBinding_Page")) {
pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string));
}
}
CloseReader(reader);
if(result.Count > 0) result[result.Count - 1].Pages = pages.ToArray();
return result.ToArray();
}
else return null;
}
///
/// Gets all the Categories in a namespace.
///
/// A database connection.
/// The namespace.
/// All the Categories in the namespace. The array is not sorted.
private CategoryInfo[] GetCategories(DbConnection connection, NamespaceInfo nspace) {
string nspaceName = nspace != null ? nspace.Name : "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin,
new string[] { "Name", "Namespace" }, new string[] { "Page" });
query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new string[] { "Category_Name", "CategoryBinding_Page" }, new Ordering[] { Ordering.Asc, Ordering.Asc });
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(20);
List pages = new List(50);
string prevName = "|||";
string name = null;
while(reader.Read()) {
name = reader["Category_Name"] as string;
if(name != prevName) {
if(prevName != "|||") {
result[result.Count - 1].Pages = pages.ToArray();
pages.Clear();
}
result.Add(new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, name), this));
}
prevName = name;
if(!IsDBNull(reader, "CategoryBinding_Page")) {
pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string));
}
}
CloseReader(reader);
if(result.Count > 0) result[result.Count - 1].Pages = pages.ToArray();
return result.ToArray();
}
else return null;
}
///
/// Gets all the Categories in a namespace.
///
/// The namespace.
/// All the Categories in the namespace, sorted by name.
public CategoryInfo[] GetCategories(NamespaceInfo nspace) {
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
CategoryInfo[] categories = GetCategories(connection, nspace);
CloseConnection(connection);
return categories;
}
///
/// Gets all the categories of a page.
///
/// The page.
/// The categories, sorted by name.
/// If is null.
public CategoryInfo[] GetCategoriesForPage(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string nspace, pageName;
NameTools.ExpandFullName(page.FullName, out nspace, out pageName);
if(nspace == null) nspace = "";
string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin,
new string[] { "Name", "Namespace" }, new string[] { "Page" });
query = queryBuilder.Where(query, "CategoryBinding", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "CategoryBinding", "Page", WhereOperator.Equals, "Page");
query = queryBuilder.OrderBy(query, new[] { "Category_Name" }, new[] { Ordering.Asc });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.String, "Page", pageName));
DbCommand command = builder.GetCommand(connString, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(20);
List pages = new List(50);
string prevName = "|||";
string name = null;
while(reader.Read()) {
name = reader["Category_Name"] as string;
if(name != prevName) {
if(prevName != "|||") {
result[result.Count - 1].Pages = pages.ToArray();
pages.Clear();
}
result.Add(new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, name), this));
}
prevName = name;
if(!IsDBNull(reader, "CategoryBinding_Page")) {
pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string));
}
}
CloseReader(command, reader);
if(result.Count > 0) result[result.Count - 1].Pages = pages.ToArray();
return result.ToArray();
}
else return null;
}
///
/// Adds a Category.
///
/// The target namespace (null for the root).
/// The Category name.
/// The correct CategoryInfo object.
/// The method should set category's Pages to an empty array.
/// If is null.
/// If is empty.
public CategoryInfo AddCategory(string nspace, string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
string query = QueryBuilder.NewQuery(builder).InsertInto("Category", new string[] { "Name", "Namespace" }, new string[] { "Name", "Namespace" });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(connString, query, parameters);
int rows = ExecuteNonQuery(command);
if(rows == 1) return new CategoryInfo(NameTools.GetFullName(nspace, name), this);
else return null;
}
///
/// Renames a Category.
///
/// The Category to rename.
/// The new Name.
/// The correct CategoryInfo object.
/// If or are null.
/// If is empty.
public CategoryInfo RenameCategory(CategoryInfo category, string newName) {
if(category == null) throw new ArgumentNullException("category");
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
string nspace = null;
string name = null;
NameTools.ExpandFullName(category.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.Update("Category", new string[] { "Name" }, new string[] { "NewName" });
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "NewName", newName));
parameters.Add(new Parameter(ParameterType.String, "OldName", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows > 0) {
CategoryInfo result = GetCategory(transaction, NameTools.GetFullName(nspace, newName));
CommitTransaction(transaction);
return result;
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// Removes a Category.
///
/// A database transaction.
/// The Category to remove.
/// True if the Category has been removed successfully.
private bool RemoveCategory(DbTransaction transaction, CategoryInfo category) {
string nspace = null;
string name = null;
NameTools.ExpandFullName(category.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Category");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows > 0;
}
///
/// Removes a Category.
///
/// A database connection.
/// The Category to remove.
/// True if the Category has been removed successfully.
private bool RemoveCategory(DbConnection connection, CategoryInfo category) {
string nspace = null;
string name = null;
NameTools.ExpandFullName(category.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Category");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows > 0;
}
///
/// Removes a Category.
///
/// The Category to remove.
/// True if the Category has been removed successfully.
/// If is null.
public bool RemoveCategory(CategoryInfo category) {
if(category == null) throw new ArgumentNullException("category");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
bool removed = RemoveCategory(connection, category);
CloseConnection(connection);
return removed;
}
///
/// 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.
/// If or are null.
public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) {
if(source == null) throw new ArgumentNullException("source");
if(destination == null) throw new ArgumentNullException("destination");
// 1. Check for same namespace
// 2. Load all pages in source
// 3. Load all pages in destination
// 4. Merge lists in memory
// 5. Delete all destination bindings
// 6. Delete source cat
// 7. Insert new bindings stored in memory
string sourceNs = NameTools.GetNamespace(source.FullName);
string destinationNs = NameTools.GetNamespace(destination.FullName);
// If one is null and the other not null, fail
if(sourceNs == null && destinationNs != null || sourceNs != null && destinationNs == null) return null;
else {
// Both non-null or both null
if(sourceNs != null) {
// Both non-null, check names
NamespaceInfo tempSource = new NamespaceInfo(sourceNs, this, null);
NamespaceInfo tempDest = new NamespaceInfo(destinationNs, this, null);
// Different names, fail
if(new NamespaceComparer().Compare(tempSource, tempDest) != 0) return null;
}
// else both null, OK
}
string nspace = sourceNs != null ? sourceNs : "";
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
CategoryInfo actualSource = GetCategory(transaction, source.FullName);
CategoryInfo actualDestination = GetCategory(transaction, destination.FullName);
if(actualSource == null) {
RollbackTransaction(transaction);
return null;
}
if(actualDestination == null) {
RollbackTransaction(transaction);
return null;
}
string destinationName = NameTools.GetLocalName(actualDestination.FullName);
string[] mergedPages = MergeArrays(actualSource.Pages, actualDestination.Pages);
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("CategoryBinding");
query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Category", WhereOperator.Equals, "Category");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.String, "Category", destinationName));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == -1) {
RollbackTransaction(transaction);
return null;
}
if(!RemoveCategory(transaction, source)) {
RollbackTransaction(transaction);
return null;
}
string finalQuery = "";
parameters = new List(MaxStatementsInBatch * 3);
rows = 0;
int count = 1;
string countString;
foreach(string page in mergedPages) {
// This batch is executed in small chunks (MaxStatementsInBatch) to avoid exceeding DB's max batch length/size
countString = count.ToString();
query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" },
new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString });
finalQuery = queryBuilder.AppendForBatch(finalQuery, query);
parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
parameters.Add(new Parameter(ParameterType.String, "Category" + countString, destinationName));
parameters.Add(new Parameter(ParameterType.String, "Page" + countString, NameTools.GetLocalName(page)));
count++;
if(count == MaxStatementsInBatch) {
// Batch is complete -> execute
command = builder.GetCommand(transaction, finalQuery, parameters);
rows += ExecuteNonQuery(command, false);
count = 1;
finalQuery = "";
parameters.Clear();
}
}
if(finalQuery.Length > 0) {
// Execute remaining queries, if any
command = builder.GetCommand(transaction, finalQuery, parameters);
rows += ExecuteNonQuery(command, false);
}
if(rows == mergedPages.Length) {
CommitTransaction(transaction);
CategoryInfo result = new CategoryInfo(actualDestination.FullName, this);
result.Pages = mergedPages;
return result;
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// Merges two arrays of strings.
///
/// The first array.
/// The second array.
/// The merged array.
private static string[] MergeArrays(string[] array1, string[] array2) {
List result = new List(array1.Length + array2.Length);
// A) BinarySearch is O(log n), but Insert is O(n) (+ QuickSort which is O(n*log n))
// B) A linear search is O(n), and Add is O(1) (given that the list is already big enough)
// --> B is faster, even when result approaches a size of array1.Length + array2.Length
StringComparer comp = StringComparer.OrdinalIgnoreCase;
result.AddRange(array1);
foreach(string value in array2) {
if(result.Find(x => { return comp.Compare(x, value) == 0; }) == null) {
result.Add(value);
}
}
return result.ToArray();
}
///
/// Gets a page.
///
/// A database transaction.
/// The full name of the page.
/// The , or null if no page is found.
private PageInfo GetPage(DbTransaction transaction, string fullName) {
string nspace, name;
NameTools.ExpandFullName(fullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Page");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
PageInfo result = null;
if(reader.Read()) {
result = new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string),
this, (DateTime)reader["CreationDateTime"]);
}
CloseReader(reader);
return result;
}
else return null;
}
///
/// Gets a page.
///
/// A database connection.
/// The full name of the page.
/// The , or null if no page is found.
private PageInfo GetPage(DbConnection connection, string fullName) {
string nspace, name;
NameTools.ExpandFullName(fullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Page");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
PageInfo result = null;
if(reader.Read()) {
result = new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string),
this, (DateTime)reader["CreationDateTime"]);
}
CloseReader(reader);
return result;
}
else return null;
}
///
/// Gets a page.
///
/// The full name of the page.
/// The , or null if no page is found.
/// If is null.
/// If is empty.
public PageInfo GetPage(string fullName) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
PageInfo page = GetPage(connection, fullName);
CloseConnection(connection);
return page;
}
///
/// Gets all the Pages in a namespace.
///
/// A database transaction.
/// The namespace (null for the root).
/// All the Pages in the namespace. The array is not sorted.
private PageInfo[] GetPages(DbTransaction transaction, NamespaceInfo nspace) {
string nspaceName = nspace != null ? nspace.Name : "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Page");
query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new[] { "Name" }, new[] { Ordering.Asc });
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(100);
while(reader.Read()) {
result.Add(new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string),
this, (DateTime)reader["CreationDateTime"]));
}
CloseReader(reader);
return result.ToArray();
}
else return null;
}
///
/// Gets all the Pages in a namespace.
///
/// A database connection.
/// The namespace (null for the root).
/// All the Pages in the namespace. The array is not sorted.
private PageInfo[] GetPages(DbConnection connection, NamespaceInfo nspace) {
string nspaceName = nspace != null ? nspace.Name : "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Page");
query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));
query = queryBuilder.OrderBy(query, new[] { "Name" }, new[] { Ordering.Asc });
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(100);
while(reader.Read()) {
result.Add(new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string),
this, (DateTime)reader["CreationDateTime"]));
}
CloseReader(reader);
return result.ToArray();
}
else return null;
}
///
/// Gets all the Pages in a namespace.
///
/// The namespace (null for the root).
/// All the Pages in the namespace. The array is not sorted.
public PageInfo[] GetPages(NamespaceInfo nspace) {
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
PageInfo[] pages = GetPages(connection, nspace);
CloseConnection(connection);
return pages;
}
///
/// 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) {
string nspaceName = nspace != null ? nspace.Name : "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Page", "CategoryBinding", "Name", "Page", Join.LeftJoin);
query = queryBuilder.Where(query, "CategoryBinding", "Category", WhereOperator.IsNull, null);
query = queryBuilder.AndWhere(query, "Page", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new[] { "Name" }, new[] { Ordering.Asc });
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));
DbCommand command = builder.GetCommand(connString, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(100);
while(reader.Read()) {
result.Add(new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string),
this, (DateTime)reader["CreationDateTime"]));
}
CloseReader(command, reader);
return result.ToArray();
}
else return null;
}
///
/// Gets the content of a specific revision of a page.
///
/// A database connection.
/// The page.
/// The revision.
/// The content.
private PageContent GetContent(DbConnection connection, PageInfo page, int revision) {
// Internal version to work with GetContent, GetBackupContent, GetDraft
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string name, nspace;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
string query = queryBuilder.SelectFrom("PageContent", "PageKeyword", new string[] { "Page", "Namespace", "Revision" }, new string[] { "Page", "Namespace", "Revision" }, Join.LeftJoin,
new string [] { "Title", "User", "LastModified", "Comment", "Content", "Description" }, new string[] { "Keyword" });
query = queryBuilder.Where(query, "PageContent", "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "PageContent", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "PageContent", "Revision", WhereOperator.Equals, "Revision");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Revision", (short)revision));
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
PageContent result = null;
string title = null, user = null, comment = null, content = null, description = null;
DateTime dateTime = DateTime.MinValue;
List keywords = new List(10);
while(reader.Read()) {
if(title == null) {
title = reader["PageContent_Title"] as string;
user = reader["PageContent_User"] as string;
dateTime = (DateTime)reader["PageContent_LastModified"];
comment = GetNullableColumn(reader, "PageContent_Comment", "");
content = reader["PageContent_Content"] as string;
description = GetNullableColumn(reader, "PageContent_Description", null);
}
if(!IsDBNull(reader, "PageKeyword_Keyword")) {
keywords.Add(reader["PageKeyword_Keyword"] as string);
}
}
if(title != null) {
result = new PageContent(page, title, user, dateTime, comment, content, keywords.ToArray(), description);
}
CloseReader(reader);
return result;
}
else return null;
}
///
/// Gets the content of a specific revision of a page.
///
/// A database transaction.
/// The page.
/// The revision.
/// The content.
private PageContent GetContent(DbTransaction transaction, PageInfo page, int revision) {
// Internal version to work with GetContent, GetBackupContent, GetDraft
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string name, nspace;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
string query = queryBuilder.SelectFrom("PageContent", "PageKeyword", new string[] { "Page", "Namespace", "Revision" }, new string[] { "Page", "Namespace", "Revision" }, Join.LeftJoin,
new string[] { "Title", "User", "LastModified", "Comment", "Content", "Description" }, new string[] { "Keyword" });
query = queryBuilder.Where(query, "PageContent", "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "PageContent", "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "PageContent", "Revision", WhereOperator.Equals, "Revision");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Revision", (short)revision));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
PageContent result = null;
string title = null, user = null, comment = null, content = null, description = null;
DateTime dateTime = DateTime.MinValue;
List keywords = new List(10);
while(reader.Read()) {
if(title == null) {
title = reader["PageContent_Title"] as string;
user = reader["PageContent_User"] as string;
dateTime = (DateTime)reader["PageContent_LastModified"];
comment = GetNullableColumn(reader, "PageContent_Comment", "");
content = reader["PageContent_Content"] as string;
description = GetNullableColumn(reader, "PageContent_Description", null);
}
if(!IsDBNull(reader, "PageKeyword_Keyword")) {
keywords.Add(reader["PageKeyword_Keyword"] as string);
}
}
if(title != null) {
result = new PageContent(page, title, user, dateTime, comment, content, keywords.ToArray(), description);
}
CloseReader(reader);
return result;
}
else return null;
}
///
/// Gets the Content of a Page.
///
/// The Page.
/// The Page Content object, null if the page does not exist or is null,
/// or an empty instance if the content could not be retrieved ().
public PageContent GetContent(PageInfo page) {
if(page == null) return null;
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
PageContent content = GetContent(connection, page, CurrentRevision);
CloseConnection(connection);
return content;
}
///
/// Gets the content of a draft of a Page.
///
/// The Page.
/// The draft, or null if no draft exists.
/// If is null.
public PageContent GetDraft(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
PageContent content = GetContent(connection, page, DraftRevision);
CloseConnection(connection);
return content;
}
///
/// Deletes a draft of a Page.
///
/// The page.
/// true if the draft is deleted, false otherwise.
/// If is null.
public bool DeleteDraft(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
bool deleted = DeleteContent(connection, page, DraftRevision);
CloseConnection(connection);
return deleted;
}
///
/// Gets the Backup/Revision numbers of a Page.
///
/// A database transaction.
/// The Page to get the Backups of.
/// The Backup/Revision numbers.
private int[] GetBackups(DbTransaction transaction, PageInfo page) {
if(GetPage(transaction, page.FullName) == null) {
return null;
}
string name, nspace;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("PageContent", new string[] { "Revision" });
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "Revision");
query = queryBuilder.OrderBy(query, new[] { "Revision" }, new[] { Ordering.Asc });
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Revision", FirstRevision));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(100);
while(reader.Read()) {
result.Add((short)reader["Revision"]);
}
CloseReader(reader);
return result.ToArray();
}
else return null;
}
///
/// Gets the Backup/Revision numbers of a Page.
///
/// A database connection.
/// The Page to get the Backups of.
/// The Backup/Revision numbers.
private int[] GetBackups(DbConnection connection, PageInfo page) {
if(GetPage(connection, page.FullName) == null) {
return null;
}
string name, nspace;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("PageContent", new string[] { "Revision" });
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "Revision");
query = queryBuilder.OrderBy(query, new[] { "Revision" }, new[] { Ordering.Asc });
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Revision", FirstRevision));
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(100);
while(reader.Read()) {
result.Add((short)reader["Revision"]);
}
CloseReader(reader);
return result.ToArray();
}
else return null;
}
///
/// Gets the Backup/Revision numbers of a Page.
///
/// The Page to get the Backups of.
/// The Backup/Revision numbers.
/// If is null.
public int[] GetBackups(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
int[] revisions = GetBackups(connection, page);
CloseConnection(connection);
return revisions;
}
///
/// Gets the Content of a Backup of a Page.
///
/// The Page to get the backup of.
/// The Backup/Revision number.
/// The Page Backup.
/// If is null.
/// If is less than zero.
public PageContent GetBackupContent(PageInfo page, int revision) {
if(page == null) throw new ArgumentNullException("page");
if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Invalid Revision");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
PageContent content = GetContent(connection, page, revision);
CloseConnection(connection);
return content;
}
///
/// Stores the content for a revision.
///
/// A database transaction.
/// The content.
/// The revision.
/// true if the content is stored, false otherwise.
private bool SetContent(DbTransaction transaction, PageContent content, int revision) {
string name, nspace;
NameTools.ExpandFullName(content.PageInfo.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.InsertInto("PageContent",
new string[] { "Page", "Namespace", "Revision", "Title", "User", "LastModified", "Comment", "Content", "Description" },
new string[] { "Page", "Namespace", "Revision", "Title", "User", "LastModified", "Comment", "Content", "Description" });
List parameters = new List(9);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Revision", revision));
parameters.Add(new Parameter(ParameterType.String, "Title", content.Title));
parameters.Add(new Parameter(ParameterType.String, "User", content.User));
parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", content.LastModified));
if(!string.IsNullOrEmpty(content.Comment)) parameters.Add(new Parameter(ParameterType.String, "Comment", content.Comment));
else parameters.Add(new Parameter(ParameterType.String, "Comment", DBNull.Value));
parameters.Add(new Parameter(ParameterType.String, "Content", content.Content));
if(!string.IsNullOrEmpty(content.Description)) parameters.Add(new Parameter(ParameterType.String, "Description", content.Description));
else parameters.Add(new Parameter(ParameterType.String, "Description", DBNull.Value));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows != 1) return false;
if(content.Keywords.Length > 0) {
parameters = new List(content.Keywords.Length * 4);
string fullQuery = "";
int count = 0;
string countString;
foreach(string kw in content.Keywords) {
countString = count.ToString();
query = queryBuilder.InsertInto("PageKeyword", new string[] { "Page", "Namespace", "Revision", "Keyword" },
new string[] { "Page" + countString, "Namespace" + countString, "Revision" + countString, "Keyword" + countString });
fullQuery = queryBuilder.AppendForBatch(fullQuery, query);
parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name));
parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Revision" + countString, revision));
parameters.Add(new Parameter(ParameterType.String, "Keyword" + countString, kw));
count++;
}
command = builder.GetCommand(transaction, fullQuery, parameters);
rows = ExecuteNonQuery(command, false);
return rows == content.Keywords.Length;
}
else return true;
}
///
/// Deletes a revision of a page content.
///
/// A database transaction.
/// The page.
/// The revision.
/// true if the content ir deleted, false otherwise.
private bool DeleteContent(DbTransaction transaction, PageInfo page, int revision) {
string name, nspace;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("PageContent");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Revision", WhereOperator.Equals, "Revision");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.String, "Revision", revision));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows > 0;
}
///
/// Deletes a revision of a page content.
///
/// A database connection.
/// The page.
/// The revision.
/// true if the content ir deleted, false otherwise.
private bool DeleteContent(DbConnection connection, PageInfo page, int revision) {
string name, nspace;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("PageContent");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Revision", WhereOperator.Equals, "Revision");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.String, "Revision", revision));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows > 0;
}
///
/// Forces to overwrite or create a Backup.
///
/// The Backup content.
/// The revision.
/// True if the Backup has been created successfully.
/// If is null.
/// If is less than zero.
public bool SetBackupContent(PageContent content, int revision) {
if(content == null) throw new ArgumentNullException("content");
if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Invalid Revision");
// 1. DeletebBackup, if any
// 2. Set new content
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
DeleteContent(transaction, content.PageInfo, revision);
bool set = SetContent(transaction, content, revision);
if(set) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return set;
}
///
/// 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.
/// If is null.
/// If is empty.
public PageInfo AddPage(string nspace, string name, DateTime creationDateTime) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
string query = QueryBuilder.NewQuery(builder).InsertInto("Page", new string[] { "Name", "Namespace", "CreationDateTime" },
new string[] { "Name", "Namespace", "CreationDateTime" });
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.DateTime, "CreationDateTime", creationDateTime));
DbCommand command = builder.GetCommand(connString, query, parameters);
int rows = ExecuteNonQuery(command);
if(rows == 1) {
return new PageInfo(NameTools.GetFullName(nspace, name), this, creationDateTime);
}
else return null;
}
///
/// Renames a Page.
///
/// The Page to rename.
/// The new Name.
/// The correct object.
/// If or are null.
/// If is empty.
public PageInfo RenamePage(PageInfo page, string newName) {
if(page == null) throw new ArgumentNullException("page");
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
// Check
// 1. Page is default page of its namespace
// 2. New name already exists
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(GetPage(transaction, page.FullName) == null) {
RollbackTransaction(transaction);
return null;
}
if(IsDefaultPage(transaction, page)) {
RollbackTransaction(transaction);
return null;
}
if(GetPage(transaction, NameTools.GetFullName(NameTools.GetNamespace(page.FullName), NameTools.GetLocalName(newName))) != null) {
RollbackTransaction(transaction);
return null;
}
PageContent currentContent = GetContent(transaction, page, CurrentRevision);
UnindexPage(currentContent, transaction);
foreach(Message msg in GetMessages(transaction, page)) {
UnindexMessageTree(page, msg, transaction);
}
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
CategoryInfo[] currCategories = GetCategories(transaction, nspace == "" ? null : GetNamespace(transaction, nspace));
string lowerPageName = page.FullName.ToLowerInvariant();
List pageCategories = new List(10);
foreach(CategoryInfo cat in currCategories) {
if(Array.Find(cat.Pages, (s) => { return s.ToLowerInvariant() == lowerPageName; }) != null) {
pageCategories.Add(NameTools.GetLocalName(cat.FullName));
}
}
RebindPage(transaction, page, new string[0]);
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.Update("Page", new string[] { "Name" }, new string[] { "NewName" });
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "NewName", newName));
parameters.Add(new Parameter(ParameterType.String, "OldName", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows > 0) {
PageInfo result = new PageInfo(NameTools.GetFullName(nspace, newName), this, page.CreationDateTime);
RebindPage(transaction, result, pageCategories.ToArray());
PageContent newContent = GetContent(transaction, result, CurrentRevision);
IndexPage(newContent, transaction);
foreach(Message msg in GetMessages(transaction, result)) {
IndexMessageTree(result, msg, transaction);
}
CommitTransaction(transaction);
return result;
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// 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.
/// If , , or are null.
/// If or are empty.
public bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, string[] keywords, string description, SaveMode saveMode) {
if(page == null) throw new ArgumentNullException("page");
if(title == null) throw new ArgumentNullException("title");
if(title.Length == 0) throw new ArgumentException("Title cannot be empty", "title");
if(username == null) throw new ArgumentNullException("username");
if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
if(content == null) throw new ArgumentNullException("content"); // content can be empty
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
PageContent currentContent = GetContent(transaction, page, CurrentRevision);
PageContent pageContent = new PageContent(page, title, username, dateTime, comment, content, keywords != null ? keywords : new string[0], description);
switch(saveMode) {
case SaveMode.Backup:
// Do backup (if there is something to backup), delete current version (if any), store new version
if(currentContent != null) UnindexPage(currentContent, transaction);
Backup(transaction, page);
DeleteContent(transaction, page, CurrentRevision);
bool done1 = SetContent(transaction, pageContent, CurrentRevision);
if(done1) IndexPage(pageContent, transaction);
if(done1) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return done1;
case SaveMode.Normal:
// Delete current version (if any), store new version
if(currentContent != null) UnindexPage(currentContent, transaction);
DeleteContent(transaction, page, CurrentRevision);
bool done2 = SetContent(transaction, pageContent, CurrentRevision);
if(done2) IndexPage(pageContent, transaction);
if(done2) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return done2;
case SaveMode.Draft:
// Delete current draft (if any), store new draft
DeleteContent(transaction, page, DraftRevision);
bool done3 = SetContent(transaction, pageContent, DraftRevision);
if(done3) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return done3;
default:
RollbackTransaction(transaction);
throw new NotSupportedException();
}
}
///
/// Backs up the content of a page.
///
/// A database transaction.
/// The page.
/// true if the backup is performed, false otherwise.
private bool Backup(DbTransaction transaction, PageInfo page) {
PageContent currentContent = GetContent(transaction, page, CurrentRevision);
if(currentContent != null) {
// Insert a new revision
int[] backups = GetBackups(transaction, page);
if(backups == null) return false;
int revision = backups.Length > 0 ? backups[backups.Length - 1] + 1 : FirstRevision;
bool set = SetContent(transaction, currentContent, revision);
return set;
}
else return false;
}
///
/// 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.
/// If is null.
/// If is less than zero.
public bool RollbackPage(PageInfo page, int revision) {
if(page == null) throw new ArgumentNullException("page");
if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Invalid Revision");
// 1. Load specific revision's content
// 2. Modify page with loaded content, performing backup
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
PageContent targetContent = GetContent(transaction, page, revision);
if(targetContent == null) {
RollbackTransaction(transaction);
return false;
}
UnindexPage(GetContent(transaction, page, CurrentRevision), transaction);
bool done = Backup(transaction, page);
if(!done) {
RollbackTransaction(transaction);
return false;
}
done = DeleteContent(transaction, page, CurrentRevision);
if(!done) {
RollbackTransaction(transaction);
return false;
}
done = SetContent(transaction, targetContent, CurrentRevision);
if(!done) {
RollbackTransaction(transaction);
return false;
}
IndexPage(targetContent, transaction);
CommitTransaction(transaction);
return true;
}
///
/// 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) o -1 to delete all the Backups.
/// true if the deletion succeeded, false otherwise.
/// If is null.
/// If is less than -1.
public bool DeleteBackups(PageInfo page, int revision) {
if(page == null) throw new ArgumentNullException("page");
if(revision < -1) throw new ArgumentOutOfRangeException("revision", "Invalid Revision");
// 1. Retrieve target content (revision-1 = first kept revision)
// 2. Replace the current content (delete, store)
// 3. Delete all older revisions up to the specified on (included) "N-m...N"
// 4. Re-number remaining revisions starting from FirstRevision (zero) to revision-1 (don't re-number revs -1, -100)
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(GetPage(transaction, page.FullName) == null) {
RollbackTransaction(transaction);
return false;
}
int[] baks = GetBackups(transaction, page);
if(baks.Length > 0 && revision > baks[baks.Length - 1]) {
RollbackTransaction(transaction);
return true;
}
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("PageContent");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
if(revision != -1) query = queryBuilder.AndWhere(query, "Revision", WhereOperator.LessThanOrEqualTo, "Revision");
query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "FirstRevision");
List parameters = new List(4);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
if(revision != -1) parameters.Add(new Parameter(ParameterType.Int16, "Revision", revision));
parameters.Add(new Parameter(ParameterType.Int16, "FirstRevision", FirstRevision));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == -1) {
RollbackTransaction(transaction);
return false;
}
if(revision != -1) {
int revisionDelta = revision + 1;
query = queryBuilder.UpdateIncrement("PageContent", "Revision", -revisionDelta);
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "FirstRevision");
parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "FirstRevision", FirstRevision));
command = builder.GetCommand(transaction, query, parameters);
rows = ExecuteNonQuery(command, false);
if(rows > 0) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return rows >= 0;
}
else {
CommitTransaction(transaction);
return true;
}
}
///
/// Removes a Page.
///
/// The Page to remove.
/// True if the Page is removed successfully.
/// If is null.
public bool RemovePage(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(IsDefaultPage(transaction, page)) {
RollbackTransaction(transaction);
return false;
}
PageContent currentContent = GetContent(transaction, page, CurrentRevision);
if(currentContent != null) {
UnindexPage(currentContent, transaction);
foreach(Message msg in GetMessages(transaction, page)) {
UnindexMessageTree(page, msg, transaction);
}
}
RebindPage(transaction, page, new string[0]);
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Page");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows > 0) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return rows > 0;
}
///
/// Binds a Page with one or more Categories.
///
/// A database transaction.
/// 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.
private bool RebindPage(DbTransaction transaction, PageInfo page, string[] categories) {
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("CategoryBinding");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows < 0) return false;
if(categories.Length > 0) {
string finalQuery = "";
parameters = new List(categories.Length * 3);
int count = 0;
string countString;
foreach(string cat in categories) {
countString = count.ToString();
query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" },
new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString });
finalQuery = queryBuilder.AppendForBatch(finalQuery, query);
parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
parameters.Add(new Parameter(ParameterType.String, "Category" + countString, NameTools.GetLocalName(cat)));
parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name));
count++;
}
command = builder.GetCommand(transaction, finalQuery, parameters);
rows = ExecuteNonQuery(command, false);
return rows == categories.Length;
}
else return true;
}
///
/// Binds a Page with one or more Categories.
///
/// A database connection.
/// 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.
private bool RebindPage(DbConnection connection, PageInfo page, string[] categories) {
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("CategoryBinding");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows < 0) return false;
if(categories.Length > 0) {
string finalQuery = "";
parameters = new List(categories.Length * 3);
int count = 0;
string countString;
foreach(string cat in categories) {
countString = count.ToString();
query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" },
new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString });
finalQuery = queryBuilder.AppendForBatch(finalQuery, query);
parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
parameters.Add(new Parameter(ParameterType.String, "Category" + countString, NameTools.GetLocalName(cat)));
parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name));
count++;
}
command = builder.GetCommand(connection, finalQuery, parameters);
rows = ExecuteNonQuery(command, false);
return rows == categories.Length;
}
else return true;
}
///
/// 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.
/// If or are null.
public bool RebindPage(PageInfo page, string[] categories) {
if(page == null) throw new ArgumentNullException("page");
if(categories == null) throw new ArgumentNullException("categories");
foreach(string cat in categories) {
if(cat == null) throw new ArgumentNullException("categories");
if(cat.Length == 0) throw new ArgumentException("Category item cannot be empty", "categories");
}
// 1. Delete old bindings
// 2. Store new bindings
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
bool rebound = RebindPage(connection, page, categories);
CloseConnection(connection);
return rebound;
}
///
/// Gets the Page Messages.
///
/// A database transaction.
/// The Page.
/// The list of the first-level Messages, containing the replies properly nested, sorted by date/time.
private Message[] GetMessages(DbTransaction transaction, PageInfo page) {
if(GetPage(transaction, page.FullName) == null) return null;
// 1. Load all messages in memory in a dictionary id->message
// 2. Build tree using ParentID
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Message", new string[] { "Id", "Parent", "Username", "Subject", "DateTime", "Body" });
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new string[] { "DateTime", "Id" }, new Ordering[] { Ordering.Asc, Ordering.Asc });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
Dictionary allMessages = new Dictionary(50);
List ids = new List(50);
List parents = new List(50);
while(reader.Read()) {
Message msg = new Message((short)reader["Id"], reader["Username"] as string, reader["Subject"] as string,
(DateTime)reader["DateTime"], reader["Body"] as string);
ids.Add((short)msg.ID);
// Import from V2: parent = -1, otherwise null
if(!IsDBNull(reader, "Parent")) {
short par = (short)reader["Parent"];
if(par >= 0) parents.Add(par);
else parents.Add(null);
}
else parents.Add(null);
allMessages.Add((short)msg.ID, msg);
}
CloseReader(reader);
// Add messages to their parents and build the top-level messages list
List result = new List(20);
for(int i = 0; i < ids.Count; i++) {
short? currentParent = parents[i];
short currentId = ids[i];
if(currentParent.HasValue) {
List replies = new List(allMessages[currentParent.Value].Replies);
replies.Add(allMessages[currentId]);
allMessages[currentParent.Value].Replies = replies.ToArray();
}
else result.Add(allMessages[currentId]);
}
return result.ToArray();
}
else return null;
}
///
/// Gets the Page Messages.
///
/// A database connection.
/// The Page.
/// The list of the first-level Messages, containing the replies properly nested, sorted by date/time.
private Message[] GetMessages(DbConnection connection, PageInfo page) {
if(GetPage(connection, page.FullName) == null) return null;
// 1. Load all messages in memory in a dictionary id->message
// 2. Build tree using ParentID
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("Message", new string[] { "Id", "Parent", "Username", "Subject", "DateTime", "Body" });
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new string[] { "DateTime", "Id" }, new Ordering[] { Ordering.Asc, Ordering.Asc });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(connection, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
Dictionary allMessages = new Dictionary(50);
List ids = new List(50);
List parents = new List(50);
while(reader.Read()) {
Message msg = new Message((short)reader["Id"], reader["Username"] as string, reader["Subject"] as string,
(DateTime)reader["DateTime"], reader["Body"] as string);
ids.Add((short)msg.ID);
// Import from V2: parent = -1, otherwise null
if(!IsDBNull(reader, "Parent")) {
short par = (short)reader["Parent"];
if(par >= 0) parents.Add(par);
else parents.Add(null);
}
else parents.Add(null);
allMessages.Add((short)msg.ID, msg);
}
CloseReader(reader);
// Add messages to their parents and build the top-level messages list
List result = new List(20);
for(int i = 0; i < ids.Count; i++) {
short? currentParent = parents[i];
short currentId = ids[i];
if(currentParent.HasValue) {
List replies = new List(allMessages[currentParent.Value].Replies);
replies.Add(allMessages[currentId]);
allMessages[currentParent.Value].Replies = replies.ToArray();
}
else result.Add(allMessages[currentId]);
}
return result.ToArray();
}
else return null;
}
///
/// Gets the Page Messages.
///
/// The Page.
/// The list of the first-level Messages, containing the replies properly nested, sorted by date/time.
/// If is null.
public Message[] GetMessages(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
Message[] messages = GetMessages(connection, page);
CloseConnection(connection);
return messages;
}
///
/// Gets the total number of Messages in a Page Discussion.
///
/// The Page.
/// The number of messages.
/// If is null.
public int GetMessageCount(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
if(GetPage(connection, page.FullName) == null) {
CloseConnection(connection);
return -1;
}
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectCountFrom("Message");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(connection, query, parameters);
int count = ExecuteScalar(command, 0);
return count;
}
///
/// 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.
/// If or are null.
public bool BulkStoreMessages(PageInfo page, Message[] messages) {
if(page == null) throw new ArgumentNullException("page");
if(messages == null) throw new ArgumentNullException("messages");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(GetPage(transaction, page.FullName) == null) {
RollbackTransaction(transaction);
return false;
}
foreach(Message msg in GetMessages(transaction, page)) {
UnindexMessageTree(page, msg, transaction);
}
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Message");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
ExecuteNonQuery(command, false);
List allMessages;
List parents;
UnTreeMessages(messages, out allMessages, out parents, -1);
string finalQuery = "";
int count = 1;
string countString;
parameters = new List(MaxStatementsInBatch * 8);
int rowsDone = 0;
for(int i = 0; i < allMessages.Count; i++) {
// Execute the batch in smaller chunks
Message msg = allMessages[i];
int parent = parents[i];
countString = count.ToString();
query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" },
new string[] { "Page" + countString, "Namespace" + countString, "Id" + countString, "Parent" + countString, "Username" + countString, "Subject" + countString, "DateTime" + countString, "Body" + countString });
parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name));
parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Id" + countString, (short)msg.ID));
if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, parent));
else parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, DBNull.Value));
parameters.Add(new Parameter(ParameterType.String, "Username" + countString, msg.Username));
parameters.Add(new Parameter(ParameterType.String, "Subject" + countString, msg.Subject));
parameters.Add(new Parameter(ParameterType.DateTime, "DateTime" + countString, msg.DateTime));
parameters.Add(new Parameter(ParameterType.String, "Body" + countString, msg.Body));
finalQuery = queryBuilder.AppendForBatch(finalQuery, query);
count++;
if(count == MaxStatementsInBatch) {
command = builder.GetCommand(transaction, finalQuery, parameters);
rowsDone += ExecuteNonQuery(command, false);
finalQuery = "";
count = 1;
parameters.Clear();
}
}
if(finalQuery.Length > 0) {
command = builder.GetCommand(transaction, finalQuery, parameters);
rowsDone += ExecuteNonQuery(command, false);
}
if(rowsDone == allMessages.Count) {
foreach(Message msg in messages) {
IndexMessageTree(page, msg, transaction);
}
CommitTransaction(transaction);
return true;
}
else {
RollbackTransaction(transaction);
return false;
}
}
///
/// Deconstructs a tree of messages and converts it into a flat list.
///
/// The input tree.
/// The resulting flat message list.
/// The list of parent IDs.
/// The current parent ID.
private static void UnTreeMessages(Message[] messages, out List flatList, out List parents, int parent) {
flatList = new List(20);
parents = new List(20);
flatList.AddRange(messages);
for(int i = 0; i < messages.Length; i++) {
parents.Add(parent);
}
foreach(Message msg in messages) {
List temp;
List tempParents;
UnTreeMessages(msg.Replies, out temp, out tempParents, msg.ID);
flatList.AddRange(temp);
parents.AddRange(tempParents);
}
}
///
/// 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.
/// If , , or are null.
/// If or are empty.
/// If is less than -1.
public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) {
if(page == null) throw new ArgumentNullException("page");
if(username == null) throw new ArgumentNullException("username");
if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
if(body == null) throw new ArgumentNullException("body"); // body can be empty
if(parent < -1) throw new ArgumentOutOfRangeException("parent", "Invalid Parent Message ID");
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(parent != -1 && FindMessage(GetMessages(transaction, page), parent) == null) {
RollbackTransaction(transaction);
return false;
}
QueryBuilder queryBuilder = new QueryBuilder(builder);
short freeId = -1;
string query = queryBuilder.SelectFrom("Message", new string[] { "Id" });
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Desc });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
freeId = ExecuteScalar(command, -1, false);
if(freeId == -1) freeId = 0;
else freeId++;
query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" },
new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" });
parameters = new List(8);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Id", freeId));
if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent", parent));
else parameters.Add(new Parameter(ParameterType.Int16, "Parent", DBNull.Value));
parameters.Add(new Parameter(ParameterType.String, "Username", username));
parameters.Add(new Parameter(ParameterType.String, "Subject", subject));
parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime));
parameters.Add(new Parameter(ParameterType.String, "Body", body));
command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == 1) {
IndexMessage(page, freeId, subject, dateTime, body, transaction);
CommitTransaction(transaction);
return true;
}
else {
RollbackTransaction(transaction);
return false;
}
}
///
/// Finds a Message in a Message tree.
///
/// The Message tree.
/// The ID of the Message to find.
/// The Message or null.
/// The method is recursive.
private static Message FindMessage(IEnumerable messages, int id) {
Message result = null;
foreach(Message msg in messages) {
if(msg.ID == id) {
result = msg;
}
if(result == null) {
result = FindMessage(msg.Replies, id);
}
if(result != null) break;
}
return result;
}
///
/// Finds the anchestor/parent of a Message.
///
/// The Messages.
/// The Message ID.
/// The anchestor Message or null.
private static Message FindAnchestor(IEnumerable messages, int id) {
Message result = null;
foreach(Message msg in messages) {
for(int k = 0; k < msg.Replies.Length; k++) {
if(msg.Replies[k].ID == id) {
result = msg;
break;
}
if(result == null) {
result = FindAnchestor(msg.Replies, id);
}
}
if(result != null) break;
}
return result;
}
///
/// Removes a Message.
///
/// A database transaction.
/// 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.
private bool RemoveMessage(DbTransaction transaction, PageInfo page, int id, bool removeReplies) {
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
Message[] messages = GetMessages(transaction, page);
if(messages == null) return false;
Message message = FindMessage(messages, id);
if(message == null) return false;
Message parent = FindAnchestor(messages, id);
int parentId = parent != null ? parent.ID : -1;
UnindexMessage(page, message.ID, message.Subject, message.DateTime, message.Body, transaction);
if(removeReplies) {
// Recursively remove all replies BEFORE removing parent (depth-first)
foreach(Message reply in message.Replies) {
if(!RemoveMessage(transaction, page, reply.ID, true)) return false;
}
}
// Remove this message
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Message");
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id");
List parameters = new List(3);
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Id", (short)id));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(!removeReplies && rows == 1) {
// Update replies' parent id
query = queryBuilder.Update("Message", new string[] { "Parent" }, new string[] { "NewParent" });
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Parent", WhereOperator.Equals, "OldParent");
parameters = new List(4);
if(parentId != -1) parameters.Add(new Parameter(ParameterType.Int16, "NewParent", parentId));
else parameters.Add(new Parameter(ParameterType.Int16, "NewParent", DBNull.Value));
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "OldParent", (short)id));
command = builder.GetCommand(transaction, query, parameters);
rows = ExecuteNonQuery(command, false);
}
return rows > 0;
}
///
/// 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.
/// If is null.
/// If is less than zero.
public bool RemoveMessage(PageInfo page, int id, bool removeReplies) {
if(page == null) throw new ArgumentNullException("page");
if(id < 0) throw new ArgumentOutOfRangeException("id", "Invalid ID");
// 1. If removeReplies, recursively delete all messages with parent == id
// Else remove current message, updating all replies' parent id (set to this message's parent or to NULL)
// 2. If removeReplies, unindex the whole message tree
// Else unindex only this message
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
bool done = RemoveMessage(transaction, page, id, removeReplies);
if(done) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return done;
}
///
/// 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.
/// If , , or are null.
/// If is less than zero.
/// If or are empty.
public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) {
if(page == null) throw new ArgumentNullException("page");
if(id < 0) throw new ArgumentOutOfRangeException("id", "Invalid Message ID");
if(username == null) throw new ArgumentNullException("username");
if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
if(body == null) throw new ArgumentNullException("body"); // body can be empty
string nspace, name;
NameTools.ExpandFullName(page.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
Message[] messages = GetMessages(transaction, page);
if(messages == null) {
RollbackTransaction(transaction);
return false;
}
Message oldMessage = FindMessage(messages, id);
if(oldMessage == null) {
RollbackTransaction(transaction);
return false;
}
UnindexMessage(page, oldMessage.ID, oldMessage.Subject, oldMessage.DateTime, oldMessage.Body, transaction);
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.Update("Message", new string[] { "Username", "Subject", "DateTime", "Body" }, new string[] { "Username", "Subject", "DateTime", "Body" });
query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id");
List parameters = new List(7);
parameters.Add(new Parameter(ParameterType.String, "Username", username));
parameters.Add(new Parameter(ParameterType.String, "Subject", subject));
parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime));
parameters.Add(new Parameter(ParameterType.String, "Body", body));
parameters.Add(new Parameter(ParameterType.String, "Page", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
parameters.Add(new Parameter(ParameterType.Int16, "Id", id));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == 1) {
IndexMessage(page, id, subject, dateTime, body, transaction);
CommitTransaction(transaction);
return true;
}
else {
RollbackTransaction(transaction);
return false;
}
}
///
/// Gets all the Navigation Paths in a Namespace.
///
/// The Namespace.
/// All the Navigation Paths, sorted by name.
public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) {
string nspaceName = nspace != null ? nspace.Name : "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.SelectFrom("NavigationPath", new string[] { "Name", "Namespace", "Page" });
query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace");
query = queryBuilder.OrderBy(query, new string[] { "Namespace", "Name", "Number" }, new Ordering[] { Ordering.Asc, Ordering.Asc, Ordering.Asc });
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));
DbCommand command = builder.GetCommand(connString, query, parameters);
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(10);
string prevName = "|||";
string name;
string actualNamespace = "";
List pages = new List(10);
while(reader.Read()) {
name = reader["Name"] as string;
if(name != prevName) {
actualNamespace = reader["Namespace"] as string;
if(prevName != "|||") {
result[result.Count - 1].Pages = pages.ToArray();
pages.Clear();
}
result.Add(new NavigationPath(NameTools.GetFullName(actualNamespace, name), this));
}
prevName = name;
pages.Add(NameTools.GetFullName(actualNamespace, reader["Page"] as string));
}
if(result.Count > 0) {
result[result.Count - 1].Pages = pages.ToArray();
}
CloseReader(command, reader);
return result.ToArray();
}
else return null;
}
///
/// Adds a new Navigation Path.
///
/// A database transaction.
/// The target namespace (null for the root).
/// The Name of the Path.
/// The Pages array.
/// The correct object.
private NavigationPath AddNavigationPath(DbTransaction transaction, string nspace, string name, PageInfo[] pages) {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query, finalQuery = "";
List parameters = new List(3 * pages.Length);
int count = 0;
string countString;
foreach(PageInfo page in pages) {
countString = count.ToString();
query = queryBuilder.InsertInto("NavigationPath", new string[] { "Name", "Namespace", "Page", "Number" },
new string[] { "Name" + countString, "Namespace" + countString, "Page" + countString, "Number" + countString });
parameters.Add(new Parameter(ParameterType.String, "Name" + countString, name));
parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
parameters.Add(new Parameter(ParameterType.String, "Page" + countString, NameTools.GetLocalName(page.FullName)));
parameters.Add(new Parameter(ParameterType.Int32, "Number" + countString, (short)count));
finalQuery = queryBuilder.AppendForBatch(finalQuery, query);
count++;
}
DbCommand command = builder.GetCommand(transaction, finalQuery, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == pages.Length) {
NavigationPath result = new NavigationPath(NameTools.GetFullName(nspace, name), this);
result.Pages = Array.ConvertAll(pages, (x) => { return x.FullName; });
return result;
}
else return null;
}
///
/// Adds a new Navigation Path.
///
/// The target namespace (null for the root).
/// The Name of the Path.
/// The Pages array.
/// The correct object.
/// If or are null.
/// If or are empty.
public NavigationPath AddNavigationPath(string nspace, string name, PageInfo[] pages) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(pages == null) throw new ArgumentNullException("pages");
if(pages.Length == 0) throw new ArgumentException("Pages cannot be empty");
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
foreach(PageInfo page in pages) {
if(page == null) {
RollbackTransaction(transaction);
throw new ArgumentNullException("pages");
}
if(GetPage(transaction, page.FullName) == null) {
RollbackTransaction(transaction);
throw new ArgumentException("Page not found", "pages");
}
}
NavigationPath path = AddNavigationPath(transaction, nspace, name, pages);
if(path != null) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return path;
}
///
/// Modifies an existing navigation path.
///
/// The navigation path to modify.
/// The new pages array.
/// The correct object.
/// If or are null.
/// If is empty.
public NavigationPath ModifyNavigationPath(NavigationPath path, PageInfo[] pages) {
if(path == null) throw new ArgumentNullException("path");
if(pages == null) throw new ArgumentNullException("pages");
if(pages.Length == 0) throw new ArgumentException("Pages cannot be empty");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
foreach(PageInfo page in pages) {
if(page == null) {
RollbackTransaction(transaction);
throw new ArgumentNullException("pages");
}
if(GetPage(transaction, page.FullName) == null) {
RollbackTransaction(transaction);
throw new ArgumentException("Page not found", "pages");
}
}
if(RemoveNavigationPath(transaction, path)) {
string nspace, name;
NameTools.ExpandFullName(path.FullName, out nspace, out name);
if(nspace == null) nspace = "";
NavigationPath result = AddNavigationPath(transaction, nspace, name, pages);
if(result != null) {
CommitTransaction(transaction);
return result;
}
else {
RollbackTransaction(transaction);
return null;
}
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// Removes a Navigation Path.
///
/// A database transaction.
/// The navigation path to remove.
/// true if the path is removed, false otherwise.
private bool RemoveNavigationPath(DbTransaction transaction, NavigationPath path) {
string nspace, name;
NameTools.ExpandFullName(path.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("NavigationPath");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows > 0;
}
///
/// Removes a Navigation Path.
///
/// A database connection.
/// The navigation path to remove.
/// true if the path is removed, false otherwise.
private bool RemoveNavigationPath(DbConnection connection, NavigationPath path) {
string nspace, name;
NameTools.ExpandFullName(path.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("NavigationPath");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows > 0;
}
///
/// Removes a Navigation Path.
///
/// The navigation path to remove.
/// true if the path is removed, false otherwise.
/// If is null.
public bool RemoveNavigationPath(NavigationPath path) {
if(path == null) throw new ArgumentNullException("path");
string nspace, name;
NameTools.ExpandFullName(path.FullName, out nspace, out name);
if(nspace == null) nspace = "";
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
bool removed = RemoveNavigationPath(connection, path);
CloseConnection(connection);
return removed;
}
///
/// Gets all the snippets.
///
/// All the snippets, sorted by name.
public Snippet[] GetSnippets() {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = QueryBuilder.NewQuery(builder);
string query = queryBuilder.SelectFrom("Snippet");
query = queryBuilder.OrderBy(query, new[] { "Name" }, new[] { Ordering.Asc });
DbCommand command = builder.GetCommand(connString, query, new List());
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(10);
while(reader.Read()) {
result.Add(new Snippet(reader["Name"] as string, reader["Content"] as string, this));
}
CloseReader(command, reader);
return result.ToArray();
}
else return null;
}
///
/// Adds a new snippet.
///
/// A database transaction.
/// The name of the snippet.
/// The content of the snippet.
/// The correct object.
private Snippet AddSnippet(DbTransaction transaction, string name, string content) {
ICommandBuilder builder = GetCommandBuilder();
string query = QueryBuilder.NewQuery(builder).InsertInto("Snippet", new string[] { "Name", "Content" }, new string[] { "Name", "Content" });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Content", content));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == 1) {
return new Snippet(name, content, this);
}
else return null;
}
///
/// Adds a new snippet.
///
/// A database connection.
/// The name of the snippet.
/// The content of the snippet.
/// The correct object.
private Snippet AddSnippet(DbConnection connection, string name, string content) {
ICommandBuilder builder = GetCommandBuilder();
string query = QueryBuilder.NewQuery(builder).InsertInto("Snippet", new string[] { "Name", "Content" }, new string[] { "Name", "Content" });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Content", content));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == 1) {
return new Snippet(name, content, this);
}
else return null;
}
///
/// Adds a new snippet.
///
/// The name of the snippet.
/// The content of the snippet.
/// The correct object.
/// If or are null.
/// If is empty.
public Snippet AddSnippet(string name, string content) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(content == null) throw new ArgumentNullException("content"); // content can be empty
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
Snippet snippet = AddSnippet(connection, name, content);
CloseConnection(connection);
return snippet;
}
///
/// Modifies an existing snippet.
///
/// The name of the snippet to modify.
/// The content of the snippet.
/// The correct object.
/// If or are null.
/// If is empty.
public Snippet ModifySnippet(string name, string content) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(content == null) throw new ArgumentNullException("content"); // content can be empty
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(RemoveSnippet(transaction, name)) {
Snippet result = AddSnippet(transaction, name, content);
if(result != null) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return result;
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// Removes a new Snippet.
///
/// A database transaction.
/// The Name of the Snippet to remove.
/// true if the snippet is removed, false otherwise.
private bool RemoveSnippet(DbTransaction transaction, string name) {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Snippet");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows == 1;
}
///
/// Removes a new Snippet.
///
/// A database connection.
/// The Name of the Snippet to remove.
/// true if the snippet is removed, false otherwise.
private bool RemoveSnippet(DbConnection connection, string name) {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("Snippet");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows == 1;
}
///
/// Removes a new Snippet.
///
/// The Name of the Snippet to remove.
/// true if the snippet is removed, false otherwise.
/// If is null.
/// If is empty.
public bool RemoveSnippet(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
bool removed = RemoveSnippet(connection, name);
CloseConnection(connection);
return removed;
}
///
/// Gets all the content templates.
///
/// All the content templates, sorted by name.
public ContentTemplate[] GetContentTemplates() {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = QueryBuilder.NewQuery(builder);
string query = queryBuilder.SelectFrom("ContentTemplate");
query = queryBuilder.OrderBy(query, new[] { "Name" }, new[] { Ordering.Asc });
DbCommand command = builder.GetCommand(connString, query, new List());
DbDataReader reader = ExecuteReader(command);
if(reader != null) {
List result = new List(10);
while(reader.Read()) {
result.Add(new ContentTemplate(reader["Name"] as string, reader["Content"] as string, this));
}
CloseReader(command, reader);
return result.ToArray();
}
else return null;
}
///
/// Adds a new content template.
///
/// A database transaction.
/// The name of template.
/// The content of the template.
/// The correct object.
private ContentTemplate AddContentTemplate(DbTransaction transaction, string name, string content) {
ICommandBuilder builder = GetCommandBuilder();
string query = QueryBuilder.NewQuery(builder).InsertInto("ContentTemplate", new string[] { "Name", "Content" }, new string[] { "Name", "Content" });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Content", content));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == 1) {
return new ContentTemplate(name, content, this);
}
else return null;
}
///
/// Adds a new content template.
///
/// A database connection.
/// The name of template.
/// The content of the template.
/// The correct object.
private ContentTemplate AddContentTemplate(DbConnection connection, string name, string content) {
ICommandBuilder builder = GetCommandBuilder();
string query = QueryBuilder.NewQuery(builder).InsertInto("ContentTemplate", new string[] { "Name", "Content" }, new string[] { "Name", "Content" });
List parameters = new List(2);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
parameters.Add(new Parameter(ParameterType.String, "Content", content));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
if(rows == 1) {
return new ContentTemplate(name, content, this);
}
else return null;
}
///
/// Adds a new content template.
///
/// The name of template.
/// The content of the template.
/// The correct object.
/// If or are null.
/// If is empty.
public ContentTemplate AddContentTemplate(string name, string content) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(content == null) throw new ArgumentNullException("content"); // content can be empty
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
ContentTemplate template = AddContentTemplate(connection, name, content);
CloseConnection(connection);
return template;
}
///
/// Modifies an existing content template.
///
/// The name of the template to modify.
/// The content of the template.
/// The correct object.
/// If or are null.
/// If is empty.
public ContentTemplate ModifyContentTemplate(string name, string content) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(content == null) throw new ArgumentNullException("content"); // content can be empty
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
DbTransaction transaction = BeginTransaction(connection);
if(RemoveContentTemplate(transaction, name)) {
ContentTemplate template = AddContentTemplate(transaction, name, content);
if(template != null) CommitTransaction(transaction);
else RollbackTransaction(transaction);
return template;
}
else {
RollbackTransaction(transaction);
return null;
}
}
///
/// Removes a content template.
///
/// A database transaction.
/// The name of the template to remove.
/// true if the template is removed, false otherwise.
private bool RemoveContentTemplate(DbTransaction transaction, string name) {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("ContentTemplate");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
DbCommand command = builder.GetCommand(transaction, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows == 1;
}
///
/// Removes a content template.
///
/// A database connection.
/// The name of the template to remove.
/// true if the template is removed, false otherwise.
private bool RemoveContentTemplate(DbConnection connection, string name) {
ICommandBuilder builder = GetCommandBuilder();
QueryBuilder queryBuilder = new QueryBuilder(builder);
string query = queryBuilder.DeleteFrom("ContentTemplate");
query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
List parameters = new List(1);
parameters.Add(new Parameter(ParameterType.String, "Name", name));
DbCommand command = builder.GetCommand(connection, query, parameters);
int rows = ExecuteNonQuery(command, false);
return rows == 1;
}
///
/// Removes a content template.
///
/// The name of the template to remove.
/// true if the template is removed, false otherwise.
/// If is null.
/// If is empty.
public bool RemoveContentTemplate(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
ICommandBuilder builder = GetCommandBuilder();
DbConnection connection = builder.GetConnection(connString);
bool removed = RemoveContentTemplate(connection, name);
CloseConnection(connection);
return removed;
}
#endregion
#region IStorageProvider Members
///
/// 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 { return false; }
}
#endregion
}
}