using System; using System.Collections.Generic; using System.Data.Common; using System.Text; using ScrewTurn.Wiki.PluginFramework; namespace ScrewTurn.Wiki.Plugins.SqlCommon { /// /// Implements a base class for a SQL files storage provider. /// public abstract class SqlFilesStorageProviderBase : SqlStorageProviderBase, IFilesStorageProviderV30 { private const int MaxFileSize = 52428800; // 50 MB #region IFilesStorageProvider Members /// /// Prepares the directory name. /// /// The directory to prepare. /// The prepared directory, for example "/" or "/my/directory/". private static string PrepareDirectory(string directory) { if(string.IsNullOrEmpty(directory)) return "/"; else { return (!directory.StartsWith("/") ? "/" : "") + directory + (!directory.EndsWith("/") ? "/" : ""); } } /// /// Determines whether a directory exists. /// /// A database connection. /// The directory, for example "/my/directory". /// true if the directory exists, false otherwise. /// The root directory always exists. private bool DirectoryExists(DbConnection connection, string directory) { directory = PrepareDirectory(directory); ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectCountFrom("Directory"); query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "FullPath"); List parameters = new List(1); parameters.Add(new Parameter(ParameterType.String, "FullPath", directory)); DbCommand command = builder.GetCommand(connection, query, parameters); int count = ExecuteScalar(command, -1, false); return count == 1; } /// /// Determines whether a directory exists. /// /// A database transaction. /// The directory, for example "/my/directory". /// true if the directory exists, false otherwise. /// The root directory always exists. private bool DirectoryExists(DbTransaction transaction, string directory) { directory = PrepareDirectory(directory); ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectCountFrom("Directory"); query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "FullPath"); List parameters = new List(1); parameters.Add(new Parameter(ParameterType.String, "FullPath", directory)); DbCommand command = builder.GetCommand(transaction, query, parameters); int count = ExecuteScalar(command, -1, false); return count == 1; } /// /// Splits a file full name into the directory and file parts. /// /// The file full name, for example "/file.txt" or "/directory/file.txt". /// The resulting directory path, for example "/" or "/directory/". /// The file name, for example "file.txt". private static void SplitFileFullName(string fullName, out string directory, out string file) { directory = fullName.Substring(0, fullName.LastIndexOf("/") + 1); directory = PrepareDirectory(directory); file = fullName.Substring(fullName.LastIndexOf("/") + 1); } /// /// Determines whether a file exists. /// /// A database connection. /// The file full name, for example "/file.txt" or "/directory/file.txt". /// true if the file exists, false otherwise. private bool FileExists(DbConnection connection, string fullName) { string directory, file; SplitFileFullName(fullName, out directory, out file); ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectCountFrom("File"); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", file)); parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); DbCommand command = builder.GetCommand(connection, query, parameters); int count = ExecuteScalar(command, -1, false); return count == 1; } /// /// Determines whether a file exists. /// /// A database transaction. /// The file full name, for example "/file.txt" or "/directory/file.txt". /// true if the file exists, false otherwise. private bool FileExists(DbTransaction transaction, string fullName) { string directory, file; SplitFileFullName(fullName, out directory, out file); ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectCountFrom("File"); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", file)); parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); DbCommand command = builder.GetCommand(transaction, query, parameters); int count = ExecuteScalar(command, -1, false); return count == 1; } /// /// Lists the Files in the specified Directory. /// /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. /// The list of Files in the directory. /// If does not exist. public string[] ListFiles(string directory) { directory = PrepareDirectory(directory); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); if(!DirectoryExists(connection, directory)) { CloseConnection(connection); throw new ArgumentException("Directory does not exist", "directory"); } QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("File", new string[] { "Name" }); query = queryBuilder.Where(query, "Directory", WhereOperator.Equals, "Directory"); query = queryBuilder.OrderBy(query, new [] { "Name" }, new[] { Ordering.Asc }); List parameters = new List(1); parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); DbCommand command = builder.GetCommand(connection, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { List result = new List(20); while(reader.Read()) { result.Add(directory + reader["Name"] as string); } CloseReader(command, reader); return result.ToArray(); } else return null; } /// /// Lists the Directories in the specified directory. /// /// An open connection. /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. /// The list of Directories in the Directory. private string[] ListDirectories(DbConnection connection, string directory) { directory = PrepareDirectory(directory); ICommandBuilder builder = GetCommandBuilder(); if(!DirectoryExists(connection, directory)) { CloseConnection(connection); throw new ArgumentException("Directory does not exist", "directory"); } QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("Directory", new string[] { "FullPath" }); query = queryBuilder.Where(query, "Parent", WhereOperator.Equals, "Parent"); query = queryBuilder.OrderBy(query, new[] { "FullPath" }, new[] { Ordering.Asc }); List parameters = new List(1); parameters.Add(new Parameter(ParameterType.String, "Parent", directory)); DbCommand command = builder.GetCommand(connection, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { List result = new List(20); while(reader.Read()) { result.Add(reader["FullPath"] as string); } CloseReader(reader); return result.ToArray(); } else return null; } /// /// Lists the Directories in the specified directory. /// /// A database transaction. /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. /// The list of Directories in the Directory. private string[] ListDirectories(DbTransaction transaction, string directory) { directory = PrepareDirectory(directory); ICommandBuilder builder = GetCommandBuilder(); if(!DirectoryExists(transaction, directory)) { RollbackTransaction(transaction); throw new ArgumentException("Directory does not exist", "directory"); } QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("Directory", new string[] { "FullPath" }); query = queryBuilder.Where(query, "Parent", WhereOperator.Equals, "Parent"); List parameters = new List(1); parameters.Add(new Parameter(ParameterType.String, "Parent", directory)); query = queryBuilder.OrderBy(query, new[] { "FullPath" }, new[] { Ordering.Asc }); DbCommand command = builder.GetCommand(transaction, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { List result = new List(20); while(reader.Read()) { result.Add(reader["FullPath"] as string); } CloseReader(reader); return result.ToArray(); } else return null; } /// /// Lists the Directories in the specified directory. /// /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. /// The list of Directories in the Directory. /// If does not exist. public string[] ListDirectories(string directory) { directory = PrepareDirectory(directory); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); if(!DirectoryExists(connection, directory)) { CloseConnection(connection); throw new ArgumentException("Directory does not exist", "directory"); } string[] result = ListDirectories(connection, directory); CloseConnection(connection); return result; } /// /// Stores a file. /// /// The full name of the file. /// A Stream object used as source of a byte stream, /// i.e. the method reads from the Stream and stores the content properly. /// true to overwrite an existing file. /// true if the File is stored, false otherwise. /// If overwrite is false and File already exists, the method returns false. /// If os are null. /// If is empty or does not support reading. 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 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(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(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; } /// /// Retrieves a File. /// /// The full name of the File. /// A Stream object used as destination of a byte stream, /// i.e. the method writes to the Stream the file content. /// A value indicating whether or not to count this retrieval in the statistics. /// true if the file is retrieved, false otherwise. /// If os are null. /// If is empty or does not support writing. public bool RetrieveFile(string fullName, System.IO.Stream destinationStream, bool countHit) { if(fullName == null) throw new ArgumentNullException("fullName"); if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); if(destinationStream == null) throw new ArgumentNullException("destinationStream"); if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); DbTransaction transaction = BeginTransaction(connection); if(!FileExists(transaction, fullName)) { RollbackTransaction(transaction); CloseConnection(connection); throw new ArgumentException("File does not exist", "fullName"); } string directory, filename; SplitFileFullName(fullName, out directory, out filename); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("File", new string[] { "Size", "Data" }); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", filename)); parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); DbCommand command = builder.GetCommand(transaction, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { bool done = false; if(reader.Read()) { int read = ReadBinaryColumn(reader, "Data", destinationStream); done = (long)read == (long)reader["Size"]; } CloseReader(reader); if(!done) { RollbackTransaction(transaction); return false; } } else { RollbackTransaction(transaction); return false; } if(countHit) { // Update download count query = queryBuilder.UpdateIncrement("File", "Downloads", 1); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", filename)); parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); command = builder.GetCommand(transaction, query, parameters); int rows = ExecuteNonQuery(command, false); if(rows != 1) { RollbackTransaction(transaction); return false; } } CommitTransaction(transaction); return true; } /// /// Sets the number of times a file was retrieved. /// /// The full name of the file. /// The count to set. /// If is null. /// If is empty. /// If is less than zero. 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 parameters = new List(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); } /// /// Gets the details of a file. /// /// The full name of the file. /// The details, or null if the file does not exist. /// If is null. /// If is empty. public FileDetails GetFileDetails(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(); QueryBuilder queryBuilder = new QueryBuilder(builder); string directory, filename; SplitFileFullName(fullName, out directory, out filename); string query = queryBuilder.SelectFrom("File", new string[] { "Size", "Downloads", "LastModified" }); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", filename)); parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); DbCommand command = builder.GetCommand(connString, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { FileDetails details = null; if(reader.Read()) { details = new FileDetails((long)reader["Size"], (DateTime)reader["LastModified"], (int)reader["Downloads"]); } CloseReader(command, reader); return details; } else return null; } /// /// Deletes a File. /// /// The full name of the File. /// true if the File is deleted, false otherwise. /// If is null. /// If is empty or it does not exist. public bool DeleteFile(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); DbTransaction transaction = BeginTransaction(connection); if(!FileExists(transaction, fullName)) { RollbackTransaction(transaction); throw new ArgumentException("File does not exist", "fullName"); } QueryBuilder queryBuilder = new QueryBuilder(builder); string directory, filename; SplitFileFullName(fullName, out directory, out filename); string query = queryBuilder.DeleteFrom("File"); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", filename)); parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); DbCommand command = builder.GetCommand(transaction, query, parameters); int rows = ExecuteNonQuery(command, false); if(rows == 1) CommitTransaction(transaction); else RollbackTransaction(transaction); return rows == 1; } /// /// Renames or moves a File. /// /// The old full name of the File. /// The new full name of the File. /// true if the File is renamed, false otherwise. /// If or are null. /// If or are empty, or if the old file does not exist, or if the new file already exist. 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 parameters = new List(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; } /// /// Creates a new Directory. /// /// The path to create the new Directory in. /// The name of the new Directory. /// true if the Directory is created, false otherwise. /// If path is "/my/directory" and name is "newdir", a new directory named "/my/directory/newdir" is created. /// If or are null. /// If is empty or if the directory does not exist, or if the new directory already exists. public bool CreateDirectory(string path, string name) { if(path == null) throw new ArgumentNullException("path"); if(name == null) throw new ArgumentNullException("name"); if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); path = PrepareDirectory(path); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); DbTransaction transaction = BeginTransaction(connection); if(!DirectoryExists(transaction, path)) { RollbackTransaction(transaction); throw new ArgumentException("Directory does not exist", "path"); } string newDirectoryFullPath = PrepareDirectory(path + name); if(DirectoryExists(transaction, newDirectoryFullPath)) { RollbackTransaction(transaction); throw new ArgumentException("Directory already exists", "name"); } string query = QueryBuilder.NewQuery(builder).InsertInto("Directory", new string[] { "FullPath", "Parent" }, new string[] { "FullPath", "Parent" }); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "FullPath", newDirectoryFullPath)); parameters.Add(new Parameter(ParameterType.String, "Parent", path)); DbCommand command = builder.GetCommand(transaction, query, parameters); int rows = ExecuteNonQuery(command, false); if(rows == 1) CommitTransaction(transaction); else RollbackTransaction(transaction); return rows == 1; } /// /// Deletes a directory and all its contents. /// /// The current transaction to use. /// The full path of the directory. /// true if the directory is deleted, false otherwise. private bool DeleteDirectory(DbTransaction transaction, string fullPath) { string[] dirs = ListDirectories(transaction, fullPath); foreach(string dir in dirs) { if(!DeleteDirectory(transaction, dir)) { return false; } } ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.DeleteFrom("Directory"); query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "FullPath"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "FullPath", fullPath)); DbCommand command = builder.GetCommand(transaction, query, parameters); int rows = ExecuteNonQuery(command, false); return rows > 0; } /// /// Deletes a Directory and all of its content. /// /// The full path of the Directory. /// true if the Directory is deleted, false otherwise. /// If is null. /// If is empty or if it equals '/' or it does not exist. public bool DeleteDirectory(string fullPath) { if(fullPath == null) throw new ArgumentNullException("fullPath"); if(fullPath.Length == 0) throw new ArgumentException("Full Path cannot be empty", "fullPath"); if(fullPath == "/") throw new ArgumentException("Cannot delete the root directory", "fullPath"); fullPath = PrepareDirectory(fullPath); // /dir/ // /dir/sub/ // /dir/sub/blah/ // /dir/file.txt // /dir/sub/file.txt // etc. // 1. Delete sub-directories (recursively, depth first) // 2. Delete directory ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); DbTransaction transaction = BeginTransaction(connection); if(!DirectoryExists(transaction, fullPath)) { RollbackTransaction(transaction); throw new ArgumentException("Directory does not exist"); } bool done = DeleteDirectory(transaction, fullPath); if(done) CommitTransaction(transaction); else RollbackTransaction(transaction); return done; } /// /// Renames or moves a Directory. /// /// The current transaction to use. /// The old full path of the Directory. /// The new full path of the Directory. /// true if the Directory is renamed, false otherwise. 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 parameters = new List(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; } /// /// Renames or moves a Directory. /// /// The old full path of the Directory. /// The new full path of the Directory. /// true if the Directory is renamed, false otherwise. /// If or are null. /// If or are empty or equal to '/', /// or if the old directory does not exist or the new directory already exists. public bool RenameDirectory(string oldFullPath, string newFullPath) { if(oldFullPath == null) throw new ArgumentNullException("oldFullPath"); if(oldFullPath.Length == 0) throw new ArgumentException("Old Full Path cannot be empty", "oldFullPath"); if(oldFullPath == "/") throw new ArgumentException("Cannot rename the root directory", "oldFullPath"); if(newFullPath == null) throw new ArgumentNullException("newFullPath"); if(newFullPath.Length == 0) throw new ArgumentException("New Full Path cannot be empty", "newFullPath"); if(newFullPath == "/") throw new ArgumentException("Cannot rename directory to the root directory", "newFullPath"); oldFullPath = PrepareDirectory(oldFullPath); newFullPath = PrepareDirectory(newFullPath); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); DbTransaction transaction = BeginTransaction(connection); if(!DirectoryExists(transaction, oldFullPath)) { RollbackTransaction(transaction); throw new ArgumentException("Directory does not exist", "oldFullPath"); } if(DirectoryExists(transaction, newFullPath)) { RollbackTransaction(transaction); throw new ArgumentException("Directory already exists", "newFullPath"); } // /dir/ // /dir/sub/ // /dir/sub/blah/ // /dir/file.txt // /dir/sub/file.txt // etc. // 1. Rename sub-directories (recursively, depth first) // 2. Rename directory bool done = RenameDirectory(transaction, oldFullPath, newFullPath); if(done) CommitTransaction(transaction); else RollbackTransaction(transaction); return done; } /// /// The the names of the pages with attachments. /// /// The names of the pages with attachments. public string[] GetPagesWithAttachments() { ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("Attachment", new string[] { "Page" }); query = queryBuilder.GroupBy(query, new[] { "Page" }); query = queryBuilder.OrderBy(query, new[] { "Page" }, new[] { Ordering.Asc }); DbCommand command = builder.GetCommand(connString, query, new List()); DbDataReader reader = ExecuteReader(command); if(reader != null) { List result = new List(100); while(reader.Read()) { result.Add(reader["Page"] as string); } CloseReader(command, reader); return result.ToArray(); } else return null; } /// /// Returns the names of the Attachments of a Page. /// /// A database transaction. /// The Page Info object that owns the Attachments. /// The names, or an empty list. private string[] ListPageAttachments(DbTransaction transaction, PageInfo pageInfo) { ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("Attachment", new string[] { "Name" }); query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); query = queryBuilder.OrderBy(query, new[] { "Name" }, new[] { Ordering.Asc }); List parameters = new List(1); parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); DbCommand command = builder.GetCommand(transaction, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { List result = new List(10); while(reader.Read()) { result.Add(reader["Name"] as string); } CloseReader(reader); return result.ToArray(); } else return null; } /// /// Returns the names of the Attachments of a Page. /// /// A database connection. /// The Page Info object that owns the Attachments. /// The names, or an empty list. private string[] ListPageAttachments(DbConnection connection, PageInfo pageInfo) { ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("Attachment", new string[] { "Name" }); query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); List parameters = new List(1); parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); 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(10); while(reader.Read()) { result.Add(reader["Name"] as string); } CloseReader(reader); return result.ToArray(); } else return null; } /// /// Returns the names of the Attachments of a Page. /// /// The Page Info object that owns the Attachments. /// The names, or an empty list. /// If is null. public string[] ListPageAttachments(PageInfo pageInfo) { if(pageInfo == null) throw new ArgumentNullException("pageInfo"); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); string[] result = ListPageAttachments(connection, pageInfo); CloseConnection(connection); return result; } /// /// Determines whether a page attachment exists. /// /// A database connection. /// The page. /// The attachment. /// true if the attachment exists, false otherwise. private bool AttachmentExists(DbConnection connection, PageInfo page, string name) { ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectCountFrom("Attachment"); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", name)); parameters.Add(new Parameter(ParameterType.String, "Page", page.FullName)); DbCommand command = builder.GetCommand(connection, query, parameters); int count = ExecuteScalar(command, -1, false); return count == 1; } /// /// Determines whether a page attachment exists. /// /// A database transaction. /// The page. /// The attachment. /// true if the attachment exists, false otherwise. private bool AttachmentExists(DbTransaction transaction, PageInfo page, string name) { ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectCountFrom("Attachment"); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", name)); parameters.Add(new Parameter(ParameterType.String, "Page", page.FullName)); DbCommand command = builder.GetCommand(transaction, query, parameters); int count = ExecuteScalar(command, -1, false); return count == 1; } /// /// Stores a Page Attachment. /// /// The Page Info that owns the Attachment. /// The name of the Attachment, for example "myfile.jpg". /// A Stream object used as source of a byte stream, /// i.e. the method reads from the Stream and stores the content properly. /// true to overwrite an existing Attachment. /// true if the Attachment is stored, false otherwise. /// If overwrite is false and Attachment already exists, the method returns false. /// If , or are null. /// If is empty or if does not support reading. public bool StorePageAttachment(PageInfo pageInfo, string name, System.IO.Stream sourceStream, bool overwrite) { 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", "name"); if(sourceStream == null) throw new ArgumentNullException("sourceStream"); if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream"); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); DbTransaction transaction = BeginTransaction(connection); bool attachmentExists = AttachmentExists(transaction, pageInfo, name); if(attachmentExists && !overwrite) { RollbackTransaction(transaction); return false; } // To achieve decent performance, an UPDATE query is issued if the attachment exists, // otherwise an INSERT query is issued QueryBuilder queryBuilder = new QueryBuilder(builder); string query; List parameters; byte[] attachmentData = null; int size = Tools.ReadStream(sourceStream, ref attachmentData, MaxFileSize); if(size < 0) { RollbackTransaction(transaction); throw new ArgumentException("Source Stream contains too much data", "sourceStream"); } if(attachmentExists) { query = queryBuilder.Update("Attachment", new string[] { "Size", "LastModified", "Data" }, new string[] { "Size", "LastModified", "Data" }); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); parameters = new List(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", attachmentData)); parameters.Add(new Parameter(ParameterType.String, "Name", name)); parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); } else { query = queryBuilder.InsertInto("Attachment", new string[] { "Name", "Page", "Size", "Downloads", "LastModified", "Data" }, new string[] { "Name", "Page", "Size", "Downloads", "LastModified", "Data" }); parameters = new List(6); parameters.Add(new Parameter(ParameterType.String, "Name", name)); parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); 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", attachmentData)); } DbCommand command = builder.GetCommand(transaction, query, parameters); int rows = ExecuteNonQuery(command, false); if(rows == 1) CommitTransaction(transaction); else RollbackTransaction(transaction); return rows == 1; } /// /// Retrieves a Page Attachment. /// /// The Page Info that owns the Attachment. /// The name of the Attachment, for example "myfile.jpg". /// A Stream object used as destination of a byte stream, /// i.e. the method writes to the Stream the file content. /// A value indicating whether or not to count this retrieval in the statistics. /// true if the Attachment is retrieved, false otherwise. /// If , or are null. /// If is empty or if does not support writing, /// or if the page does not have attachments or if the attachment does not exist. public bool RetrievePageAttachment(PageInfo pageInfo, string name, System.IO.Stream destinationStream, bool countHit) { 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", "name"); if(destinationStream == null) throw new ArgumentNullException("destinationStream"); if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); DbTransaction transaction = BeginTransaction(connection); if(!AttachmentExists(transaction, pageInfo, name)) { RollbackTransaction(transaction); throw new ArgumentException("Attachment does not exist", "name"); } QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("Attachment", new string[] { "Size", "Data" }); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", name)); parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); DbCommand command = builder.GetCommand(transaction, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { bool done = false; if(reader.Read()) { int read = ReadBinaryColumn(reader, "Data", destinationStream); done = (long)read == (long)reader["Size"]; } CloseReader(reader); if(!done) { RollbackTransaction(transaction); return false; } } else { RollbackTransaction(transaction); return false; } if(countHit) { // Update download count query = queryBuilder.UpdateIncrement("Attachment", "Downloads", 1); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", name)); parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); command = builder.GetCommand(transaction, query, parameters); int rows = ExecuteNonQuery(command, false); if(rows != 1) { RollbackTransaction(transaction); return false; } } CommitTransaction(transaction); return true; } /// /// Sets the number of times a page attachment was retrieved. /// /// The page. /// The name of the attachment. /// The count to set. /// If or are null. /// If is empty. /// If is less than zero. 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 parameters = new List(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); } /// /// Gets the details of a page attachment. /// /// The page that owns the attachment. /// The name of the attachment, for example "myfile.jpg". /// The details of the attachment, or null if the attachment does not exist. /// If or are null. /// If is empty. public FileDetails GetPageAttachmentDetails(PageInfo pageInfo, string name) { 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"); ICommandBuilder builder = GetCommandBuilder(); QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.SelectFrom("Attachment", new string[] { "Size", "Downloads", "LastModified" }); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", name)); parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); DbCommand command = builder.GetCommand(connString, query, parameters); DbDataReader reader = ExecuteReader(command); if(reader != null) { FileDetails details = null; if(reader.Read()) { details = new FileDetails((long)reader["Size"], (DateTime)reader["LastModified"], (int)reader["Downloads"]); } CloseReader(command, reader); return details; } else return null; } /// /// Deletes a Page Attachment. /// /// The Page Info that owns the Attachment. /// The name of the Attachment, for example "myfile.jpg". /// true if the Attachment is deleted, false otherwise. /// If or are null. /// If is empty or if the page or attachment do not exist. public bool DeletePageAttachment(PageInfo pageInfo, string name) { 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"); ICommandBuilder builder = GetCommandBuilder(); DbConnection connection = builder.GetConnection(connString); DbTransaction transaction = BeginTransaction(connection); if(!AttachmentExists(transaction, pageInfo, name)) { RollbackTransaction(transaction); throw new ArgumentException("Attachment does not exist", "name"); } QueryBuilder queryBuilder = new QueryBuilder(builder); string query = queryBuilder.DeleteFrom("Attachment"); query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); List parameters = new List(2); parameters.Add(new Parameter(ParameterType.String, "Name", name)); 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; } /// /// Renames a Page Attachment. /// /// The Page Info that owns the Attachment. /// The old name of the Attachment. /// The new name of the Attachment. /// true if the Attachment is renamed, false otherwise. /// If , or are null. /// If , or are empty, /// or if the page or old attachment do not exist, or the new attachment name already exists. 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 parameters = new List(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; } /// /// Notifies the Provider that a Page has been renamed. /// /// The old Page Info object. /// The new Page Info object. /// If or are null. /// If the new page is already in use. 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 parameters = new List(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); } #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 } }