/// <summary>
		/// Modifies a Message.
		/// </summary>
		/// <param name="page">The Page.</param>
		/// <param name="id">The ID of the Message to modify.</param>
		/// <param name="username">The Username.</param>
		/// <param name="subject">The Subject.</param>
		/// <param name="dateTime">The Date/Time.</param>
		/// <param name="body">The Body.</param>
		/// <returns>True if the Message is modified successfully.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/>, <paramref name="username"/>, <paramref name="subject"/> or <paramref name="body"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="id"/> is less than zero.</exception>
		/// <exception cref="ArgumentException">If <paramref name="username"/> or <paramref name="subject"/> are empty.</exception>
		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( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) 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<Parameter> parameters = new List<Parameter>(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;
			}
		}
		/// <summary>
		/// Renames a namespace.
		/// </summary>
		/// <param name="nspace">The namespace to rename.</param>
		/// <param name="newName">The new name of the namespace.</param>
		/// <returns>The correct <see cref="T:NamespaceInfo"/> object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="nspace"/> or <paramref name="newName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="newName"/> is empty.</exception>
		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<Parameter> parameters = new List<Parameter>(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;
			}
		}
		/// <summary>
		/// Renames a Page.
		/// </summary>
		/// <param name="page">The Page to rename.</param>
		/// <param name="newName">The new Name.</param>
		/// <returns>The correct <see cref="T:PageInfo"/> object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> or <paramref name="newName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="newName"/> is empty.</exception>
		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( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			CategoryInfo[] currCategories = GetCategories(transaction, nspace == " " ? null : GetNamespace(transaction, nspace));
			string lowerPageName = page.FullName.ToLowerInvariant();
			List<string> pageCategories = new List<string>(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<Parameter> parameters = new List<Parameter>(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;
			}
		}
		/// <summary>
		/// Removes a Message.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="page">The Page.</param>
		/// <param name="id">The ID of the Message to remove.</param>
		/// <param name="removeReplies">A value specifying whether or not to remove the replies.</param>
		/// <returns>True if the Message is removed successfully.</returns>
		private bool RemoveMessage(DbTransaction transaction, PageInfo page, int id, bool removeReplies) {
			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) 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<Parameter> parameters = new List<Parameter>(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<Parameter>(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;
		}
		/// <summary>
		/// Modifies a user group.
		/// </summary>
		/// <param name="group">The group to modify.</param>
		/// <param name="description">The new description of the group.</param>
		/// <returns>The correct <see cref="T:UserGroup"/> object or <c>null</c>.</returns>
		/// <exception cref="ArgumentNullException">If <b>group</b> or <b>description</b> are <c>null</c>.</exception>
		public UserGroup ModifyUserGroup(UserGroup group, string description) {
			if(group == null) throw new ArgumentNullException("group");
			if(description == null) throw new ArgumentNullException("description");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("UserGroup",
				new string[] { "Description" }, new string[] { "Description" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Description", description));
			parameters.Add(new Parameter(ParameterType.String, "Name", group.Name));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) {
				UserGroup result = new UserGroup(group.Name, description, this);
				result.Users = GetUserGroupUsers(transaction, group.Name);

				CommitTransaction(transaction);
				return result;
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}
		/// <summary>
		/// Renames a Category.
		/// </summary>
		/// <param name="category">The Category to rename.</param>
		/// <param name="newName">The new Name.</param>
		/// <returns>The correct CategoryInfo object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="category"/> or <paramref name="newName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="newName"/> is empty.</exception>
		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( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) 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<Parameter> parameters = new List<Parameter>(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;
			}
		}
		/// <summary>
		/// Renames or moves a File.
		/// </summary>
		/// <param name="oldFullName">The old full name of the File.</param>
		/// <param name="newFullName">The new full name of the File.</param>
		/// <returns><c>true</c> if the File is renamed, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="oldFullName"/> or <paramref name="newFullName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="oldFullName"/> or <paramref name="newFullName"/> are empty, or if the old file does not exist, or if the new file already exist.</exception>
		public bool RenameFile(string oldFullName, string newFullName) {
			if(oldFullName == null) throw new ArgumentNullException("oldFullName");
			if(oldFullName.Length == 0) throw new ArgumentException("Old Full Name cannot be empty", "oldFullName");
			if(newFullName == null) throw new ArgumentNullException("newFullName");
			if(newFullName.Length == 0) throw new ArgumentException("New Full Name cannot be empty", "newFullName");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			if(!FileExists(transaction, oldFullName)) {
				RollbackTransaction(transaction);
				throw new ArgumentException("File does not exist", "oldFullName");
			}
			if(FileExists(transaction, newFullName)) {
				RollbackTransaction(transaction);
				throw new ArgumentException("File already exists", "newFullPath");
			}

			string oldDirectory, newDirectory, oldFilename, newFilename;
			SplitFileFullName(oldFullName, out oldDirectory, out oldFilename);
			SplitFileFullName(newFullName, out newDirectory, out newFilename);

			string query = queryBuilder.Update("File", new string[] { "Name" }, new string[] { "NewName" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
			query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "OldDirectory");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "NewName", newFilename));
			parameters.Add(new Parameter(ParameterType.String, "OldName", oldFilename));
			parameters.Add(new Parameter(ParameterType.String, "OldDirectory", oldDirectory));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);
			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Renames a ACL resource.
		/// </summary>
		/// <param name="resource">The resource to rename.</param>
		/// <param name="newName">The new name of the resource.</param>
		/// <returns><c>true</c> if one or more entries weere updated, <c>false</c> otherwise.</returns>
		private bool RenameAclResource(string resource, string newName) {
			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("AclEntry", new[] { "Resource" }, new[] { "ResourceNew" });
			query = queryBuilder.Where(query, "Resource", WhereOperator.Equals, "ResourceOld");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "ResourceNew", newName));
			parameters.Add(new Parameter(ParameterType.String, "ResourceOld", resource));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			if(ExecuteNonQuery(command, false) <= 0) {
				RollbackTransaction(transaction);
				return false;
			}

			CommitTransaction(transaction);

			return true;
		}
		/// <summary>
		/// Stores a file.
		/// </summary>
		/// <param name="fullName">The full name of the file.</param>
		/// <param name="sourceStream">A Stream object used as <b>source</b> of a byte stream,
		/// i.e. the method reads from the Stream and stores the content properly.</param>
		/// <param name="overwrite"><c>true</c> to overwrite an existing file.</param>
		/// <returns><c>true</c> if the File is stored, <c>false</c> otherwise.</returns>
		/// <remarks>If <b>overwrite</b> is <c>false</c> and File already exists, the method returns <c>false</c>.</remarks>
		/// <exception cref="ArgumentNullException">If <paramref name="fullName"/> or <paramref name="sourceStream"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty or <paramref name="sourceStream"/> does not support reading.</exception>
		public bool StoreFile(string fullName, System.IO.Stream sourceStream, bool overwrite) {
			if(fullName == null) throw new ArgumentNullException("fullName");
			if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
			if(sourceStream == null) throw new ArgumentNullException("sourceStream");
			if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream");

			string directory, filename;
			SplitFileFullName(fullName, out directory, out filename);

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			bool fileExists = FileExists(transaction, fullName);

			if(fileExists && !overwrite) {
				RollbackTransaction(transaction);
				return false;
			}

			// To achieve decent performance, an UPDATE query is issued if the file exists,
			// otherwise an INSERT query is issued

			string query;
			List<Parameter> parameters;

			byte[] fileData = null;
			int size = Tools.ReadStream(sourceStream, ref fileData, MaxFileSize);
			if(size < 0) {
				RollbackTransaction(transaction);
				throw new ArgumentException("Source Stream contains too much data", "sourceStream");
			}

			if(fileExists) {
				query = queryBuilder.Update("File", new string[] { "Size", "LastModified", "Data" }, new string[] { "Size", "LastModified", "Data" });
				query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
				query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory");

				parameters = new List<Parameter>(5);
				parameters.Add(new Parameter(ParameterType.Int64, "Size", (long)size));
				parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", DateTime.Now));
				parameters.Add(new Parameter(ParameterType.ByteArray, "Data", fileData));
				parameters.Add(new Parameter(ParameterType.String, "Name", filename));
				parameters.Add(new Parameter(ParameterType.String, "Directory", directory));
			}
			else {
				query = queryBuilder.InsertInto("File", new string[] { "Name", "Directory", "Size", "Downloads", "LastModified", "Data" },
					new string[] { "Name", "Directory", "Size", "Downloads", "LastModified", "Data" });

				parameters = new List<Parameter>(6);
				parameters.Add(new Parameter(ParameterType.String, "Name", filename));
				parameters.Add(new Parameter(ParameterType.String, "Directory", directory));
				parameters.Add(new Parameter(ParameterType.Int64, "Size", (long)size));
				parameters.Add(new Parameter(ParameterType.Int32, "Downloads", 0));
				parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", DateTime.Now));
				parameters.Add(new Parameter(ParameterType.ByteArray, "Data", fileData));
			}

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);
			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Sets the number of times a file was retrieved.
		/// </summary>
		/// <param name="fullName">The full name of the file.</param>
		/// <param name="count">The count to set.</param>
		/// <exception cref="ArgumentNullException">If <paramref name="fullName"/> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="count"/> is less than zero.</exception>
		public void SetFileRetrievalCount(string fullName, int count) {
			if(fullName == null) throw new ArgumentNullException("fullName");
			if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
			if(count < 0) throw new ArgumentOutOfRangeException("count", "Count must be greater than or equal to zero");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string directory, filename;
			SplitFileFullName(fullName, out directory, out filename);

			string query = queryBuilder.Update("File", new string[] { "Downloads" }, new string[] { "Downloads" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", filename));
			parameters.Add(new Parameter(ParameterType.String, "Directory", directory));
			parameters.Add(new Parameter(ParameterType.Int32, "Downloads", count));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			ExecuteNonQuery(command);
		}
		/// <summary>
		/// Notifies the Provider that a Page has been renamed.
		/// </summary>
		/// <param name="oldPage">The old Page Info object.</param>
		/// <param name="newPage">The new Page Info object.</param>
		/// <exception cref="ArgumentNullException">If <paramref name="oldPage"/> or <paramref name="newPage"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If the new page is already in use.</exception>
		public void NotifyPageRenaming(PageInfo oldPage, PageInfo newPage) {
			if(oldPage == null) throw new ArgumentNullException("oldPage");
			if(newPage == null) throw new ArgumentNullException("newPage");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(ListPageAttachments(transaction, newPage).Length > 0) {
				RollbackTransaction(transaction);
				throw new ArgumentException("New Page already exists", "newPage");
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Attachment", new string[] { "Page" }, new string[] { "NewPage" });
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "OldPage");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "NewPage", newPage.FullName));
			parameters.Add(new Parameter(ParameterType.String, "OldPage", oldPage.FullName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows != -1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);
		}
		/// <summary>
		/// Renames a Page Attachment.
		/// </summary>
		/// <param name="pageInfo">The Page Info that owns the Attachment.</param>
		/// <param name="oldName">The old name of the Attachment.</param>
		/// <param name="newName">The new name of the Attachment.</param>
		/// <returns><c>true</c> if the Attachment is renamed, false otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/>, <paramref name="oldName"/> or <paramref name="newName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="pageInfo"/>, <paramref name="oldName"/> or <paramref name="newName"/> are empty,
		/// or if the page or old attachment do not exist, or the new attachment name already exists.</exception>
		public bool RenamePageAttachment(PageInfo pageInfo, string oldName, string newName) {
			if(pageInfo == null) throw new ArgumentNullException("pageInfo");
			if(oldName == null) throw new ArgumentNullException("oldName");
			if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName");
			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(!AttachmentExists(transaction, pageInfo, oldName)) {
				RollbackTransaction(transaction);
				throw new ArgumentException("Attachment does not exist", "name");
			}
			if(AttachmentExists(transaction, pageInfo, newName)) {
				RollbackTransaction(transaction);
				throw new ArgumentException("Attachment already exists", "name");
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Attachment", new string[] { "Name" }, new string[] { "NewName" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
			query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "NewName", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldName", oldName));
			parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);
			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Sets the number of times a page attachment was retrieved.
		/// </summary>
		/// <param name="pageInfo">The page.</param>
		/// <param name="name">The name of the attachment.</param>
		/// <param name="count">The count to set.</param>
		/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/> or <paramref name="name"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="name"/> is empty.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="count"/> is less than zero.</exception>
		public void SetPageAttachmentRetrievalCount(PageInfo pageInfo, string name, int count) {
			if(pageInfo == null) throw new ArgumentNullException("pageInfo");
			if(name == null) throw new ArgumentNullException("name");
			if(name.Length == 0) throw new ArgumentException("Name cannot be empty");
			if(count < 0) throw new ArgumentOutOfRangeException("Count must be greater than or equal to zero", "count");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Attachment", new string[] { "Downloads" }, new string[] { "Downloads" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName));
			parameters.Add(new Parameter(ParameterType.Int32, "Downloads", count));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			ExecuteNonQuery(command);
		}
		/// <summary>
		/// Sets the default page of a namespace.
		/// </summary>
		/// <param name="nspace">The namespace of which to set the default page.</param>
		/// <param name="page">The page to use as default page, or <c>null</c>.</param>
		/// <returns>The correct <see cref="T:NamespaceInfo"/> object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="nspace"/> is <c>null</c>.</exception>
		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<Parameter> parameters = new List<Parameter>(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;
			}
		}
		/// <summary>
		/// Renames or moves a Directory.
		/// </summary>
		/// <param name="transaction">The current transaction to use.</param>
		/// <param name="oldFullPath">The old full path of the Directory.</param>
		/// <param name="newFullPath">The new full path of the Directory.</param>
		/// <returns><c>true</c> if the Directory is renamed, <c>false</c> otherwise.</returns>
		private bool RenameDirectory(DbTransaction transaction, string oldFullPath, string newFullPath) {
			string[] directories = ListDirectories(transaction, oldFullPath);
			foreach(string dir in directories) {
				string trimmed = dir.Trim('/');
				string name = trimmed.Substring(trimmed.LastIndexOf("/") + 1);

				string newFullPathSub = PrepareDirectory(newFullPath + name);

				RenameDirectory(dir, newFullPathSub);
			}

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Directory", new string[] { "FullPath" }, new string[] { "NewDirectory1" });
			query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "OldDirectory1");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "NewDirectory1", newFullPath));
			parameters.Add(new Parameter(ParameterType.String, "OldDirectory1", oldFullPath));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			return rows > 0;
		}
		/// <summary>
		/// Updates all outgoing links data for a page rename.
		/// </summary>
		/// <param name="oldName">The old page name.</param>
		/// <param name="newName">The new page name.</param>
		/// <returns><c>true</c> if the data is updated, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <b>oldName</b> or <b>newName</b> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>oldName</b> or <b>newName</b> are empty.</exception>
		public bool UpdateOutgoingLinksForRename(string oldName, string newName) {
			if(oldName == null) throw new ArgumentNullException("oldName");
			if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName");
			if(newName == null) throw new ArgumentNullException("newName");
			if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");

			// 1. Rename sources
			// 2. Rename destinations

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("OutgoingLink", new string[] { "Source" }, new string[] { "NewSource" });
			query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "OldSource");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "NewSource", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldSource", oldName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == -1) {
				RollbackTransaction(transaction);
				return false;
			}

			bool somethingUpdated = rows > 0;

			query = queryBuilder.Update("OutgoingLink", new string[] { "Destination" }, new string[] { "NewDestination" });
			query = queryBuilder.Where(query, "Destination", WhereOperator.Equals, "OldDestination");

			parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "NewDestination", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldDestination", oldName));

			command = builder.GetCommand(transaction, query, parameters);

			rows = ExecuteNonQuery(command, false);

			if(rows >= 0) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return somethingUpdated || rows > 0;
		}
		/// <summary>
		/// Moves a page from its namespace into another.
		/// </summary>
		/// <param name="page">The page to move.</param>
		/// <param name="destination">The destination namespace (<c>null</c> for the root).</param>
		/// <param name="copyCategories">A value indicating whether to copy the page categories in the destination
		/// namespace, if not already available.</param>
		/// <returns>The correct instance of <see cref="T:PageInfo"/>.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> is <c>null</c>.</exception>
		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 == " "||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<string> pageCategories = new List<string>(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++) {
					string catName = NameTools.GetFullName(tempName, pageCategories[i]);
					if(GetCategory(transaction, catName) == null) {
						CategoryInfo added = AddCategory(tempName, pageCategories[i]);
					}
					newCategories[i] = catName;
				}
			}

			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<Parameter> parameters = new List<Parameter>(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;
			}
		}
		/// <summary>
		/// Sets the status of a plugin.
		/// </summary>
		/// <param name="typeName">The Type name of the plugin.</param>
		/// <param name="enabled">The plugin status.</param>
		/// <returns><c>true</c> if the status is stored, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <b>typeName</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>typeName</b> is empty.</exception>
		public bool SetPluginStatus(string typeName, bool enabled) {
			if(typeName == null) throw new ArgumentNullException("typeName");
			if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			PreparePluginStatusRow(transaction, typeName);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("PluginStatus", new string[] { "Enabled" }, new string[] { "Enabled" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.Int32, "Enabled", enabled?1:0));
			parameters.Add(new Parameter(ParameterType.String, "Name", typeName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Modifies a User.
		/// </summary>
		/// <param name="user">The Username of the user to modify.</param>
		/// <param name="newDisplayName">The new display name (can be <c>null</c>).</param>
		/// <param name="newPassword">The new Password (<c>null</c> or blank to keep the current password).</param>
		/// <param name="newEmail">The new Email address.</param>
		/// <param name="newActive">A value indicating whether the account is active.</param>
		/// <returns>The correct <see cref="T:UserInfo"/> object or <c>null</c>.</returns>
		/// <exception cref="ArgumentNullException">If <b>user</b> or <b>newEmail</b> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>newEmail</b> is empty.</exception>
		public UserInfo ModifyUser(UserInfo user, string newDisplayName, string newPassword, string newEmail, bool newActive) {
			if(user == null) throw new ArgumentNullException("user");
			if(newEmail == null) throw new ArgumentNullException("newEmail");
			if(newEmail.Length == 0) throw new ArgumentException("New Email cannot be empty", "newEmail");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = "";
			if(string.IsNullOrEmpty(newPassword)) {
				query = queryBuilder.Update("User",
					new string[] { "DisplayName", "Email", "Active", },
					new string[] { "DisplayName", "Email", "Active", });
			}
			else {
				query = queryBuilder.Update("User",
					new string[] { "PasswordHash", "DisplayName", "Email", "Active", },
					new string[] { "PasswordHash", "DisplayName", "Email", "Active", });
			}
			query = queryBuilder.Where(query, "Username", WhereOperator.Equals, "Username");

			List<Parameter> parameters = new List<Parameter>(5);
			if(!string.IsNullOrEmpty(newPassword)) {
				parameters.Add(new Parameter(ParameterType.String, "PasswordHash", Hash.Compute(newPassword)));
			}
			if(string.IsNullOrEmpty(newDisplayName)) parameters.Add(new Parameter(ParameterType.String, "DisplayName", DBNull.Value));
			else parameters.Add(new Parameter(ParameterType.String, "DisplayName", newDisplayName));
			parameters.Add(new Parameter(ParameterType.String, "Email", newEmail));
			parameters.Add(new Parameter(ParameterType.Int32, "Active", newActive?1:0));
			parameters.Add(new Parameter(ParameterType.String, "Username", user.Username));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) {
				UserInfo result = new UserInfo(user.Username, newDisplayName, newEmail, newActive, user.DateTime, this);
				result.Groups = GetUserGroups(transaction, user.Username);

				CommitTransaction(transaction);
				return result;
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}