using System; using System.Collections.Generic; using System.IO; using System.Text; using ScrewTurn.Wiki.PluginFramework; namespace ScrewTurn.Wiki { /// <summary> /// Implements a Local Files Storage Provider. /// </summary> public class FilesStorageProvider : IFilesStorageProviderV30 { private readonly ComponentInformation info = new ComponentInformation("Local Files Provider", "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null); // The following strings MUST terminate with DirectorySeparatorPath in order to properly work // in BuildFullPath method private readonly string UploadDirectory = "Upload" + Path.DirectorySeparatorChar; private readonly string AttachmentsDirectory = "Attachments" + Path.DirectorySeparatorChar; private const string FileDownloadsFile = "FileDownloads.cs"; private const string AttachmentDownloadsFile = "AttachmentDownloads.cs"; // 16 KB buffer used in the StreamCopy method // 16 KB seems to be the best break-even between performance and memory usage private const int BufferSize = 16384; private IHostV30 host; private string GetFullPath(string finalChunk) { return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), finalChunk); } /// <summary> /// Initializes the Storage Provider. /// </summary> /// <param name="host">The Host of the Component.</param> /// <param name="config">The Configuration data, if any.</param> /// <exception cref="ArgumentNullException">If <paramref name="host"/> or <paramref name="config"/> are <c>null</c>.</exception> /// <exception cref="InvalidConfigurationException">If <paramref name="config"/> is not valid or is incorrect.</exception> public void Init(IHostV30 host, string config) { if(host == null) throw new ArgumentNullException("host"); if(config == null) throw new ArgumentNullException("config"); this.host = host; if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) { throw new InvalidConfigurationException("Cannot write into the public directory - check permissions"); } // Create directories, if needed if(!Directory.Exists(GetFullPath(UploadDirectory))) { Directory.CreateDirectory(GetFullPath(UploadDirectory)); } if(!Directory.Exists(GetFullPath(AttachmentsDirectory))) { Directory.CreateDirectory(GetFullPath(AttachmentsDirectory)); } if(!File.Exists(GetFullPath(FileDownloadsFile))) { File.Create(GetFullPath(FileDownloadsFile)).Close(); } if(!File.Exists(GetFullPath(AttachmentDownloadsFile))) { File.Create(GetFullPath(AttachmentDownloadsFile)).Close(); } } /// <summary> /// Method invoked on shutdown. /// </summary> /// <remarks>This method might not be invoked in some cases.</remarks> public void Shutdown() { // Nothing to do } /// <summary> /// Gets the Information about the Provider. /// </summary> public ComponentInformation Information { get { return info; } } /// <summary> /// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed. /// </summary> public string ConfigHelpHtml { get { return null; } } /// <summary> /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. /// </summary> public bool ReadOnly { get { return false; } } /// <summary> /// Builds a full path from a provider-specific partial path. /// </summary> /// <param name="partialPath">The partial path.</param> /// <returns>The full path.</returns> /// <remarks>For example: if <b>partialPath</b> is "/my/directory", the method returns /// "C:\Inetpub\wwwroot\Wiki\public\Upload\my\directory", assuming the Wiki resides in "C:\Inetpub\wwwroot\Wiki".</remarks> private string BuildFullPath(string partialPath) { if(partialPath == null) partialPath = ""; partialPath = partialPath.Replace("/", Path.DirectorySeparatorChar.ToString()).TrimStart(Path.DirectorySeparatorChar); string up = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), UploadDirectory); return Path.Combine(up, partialPath); // partialPath CANNOT start with "\" -> Path.Combine does not work } /// <summary> /// Builds a full path from a provider-specific partial path. /// </summary> /// <param name="partialPath">The partial path.</param> /// <returns>The full path.</returns> /// <remarks>For example: if <b>partialPath</b> is "/my/directory", the method returns /// "C:\Inetpub\wwwroot\Wiki\public\Attachments\my\directory", assuming the Wiki resides in "C:\Inetpub\wwwroot\Wiki".</remarks> private string BuildFullPathForAttachments(string partialPath) { if(partialPath == null) partialPath = ""; partialPath = partialPath.Replace("/", Path.DirectorySeparatorChar.ToString()).TrimStart(Path.DirectorySeparatorChar); string up = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), AttachmentsDirectory); return Path.Combine(up, partialPath); // partialPath CANNOT start with "\" -> Path.Combine does not work } /// <summary> /// Lists the Files in the specified Directory. /// </summary> /// <param name="directory">The full directory name, for example "/my/directory". Null, empty or "/" for the root directory.</param> /// <returns>The list of Files in the directory.</returns> /// <exception cref="ArgumentException">If <paramref name="directory"/> does not exist.</exception> public string[] ListFiles(string directory) { string d = BuildFullPath(directory); if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "directory"); string[] temp = Directory.GetFiles(d); // Result must be transformed in the form /my/dir/file.ext List<string> res = new List<string>(temp.Length); string root = GetFullPath(UploadDirectory); foreach(string s in temp) { // root = C:\blah\ - ends with '\' res.Add(s.Substring(root.Length - 1).Replace(Path.DirectorySeparatorChar, '/')); } return res.ToArray(); } /// <summary> /// Lists the Directories in the specified directory. /// </summary> /// <param name="directory">The full directory name, for example "/my/directory". Null, empty or "/" for the root directory.</param> /// <returns>The list of Directories in the Directory.</returns> /// <exception cref="ArgumentException">If <paramref name="directory"/> does not exist.</exception> public string[] ListDirectories(string directory) { string d = BuildFullPath(directory); if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "directory"); string[] temp = Directory.GetDirectories(d); // Result must be transformed in the form /my/dir List<string> res = new List<string>(temp.Length); string root = GetFullPath(UploadDirectory); foreach(string s in temp) { // root = C:\blah\ - ends with '\' res.Add(s.Substring(root.Length - 1).Replace(Path.DirectorySeparatorChar, '/') + "/"); } return res.ToArray(); } /// <summary> /// Copies data from a Stream to another. /// </summary> /// <param name="source">The Source stream.</param> /// <param name="destination">The destination Stream.</param> private static void StreamCopy(Stream source, Stream destination) { byte[] buff = new byte[BufferSize]; int copied = 0; do { copied = source.Read(buff, 0, buff.Length); if(copied > 0) { destination.Write(buff, 0, copied); } } while(copied > 0); } /// <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 <typeparamref name="fullName"/> os <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, 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 filename = BuildFullPath(fullName); // Abort if the file already exists and overwrite is false if(File.Exists(filename) && !overwrite) return false; FileStream fs = null; bool done = false; try { fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); // StreamCopy content (throws exception in case of error) StreamCopy(sourceStream, fs); done = true; } catch(IOException) { done = false; } finally { try { fs.Close(); } catch { } } return done; } /// <summary> /// Retrieves a File. /// </summary> /// <param name="fullName">The full name of the File.</param> /// <param name="destinationStream">A Stream object used as <b>destination</b> of a byte stream, /// i.e. the method writes to the Stream the file content.</param> /// <param name="countHit">A value indicating whether or not to count this retrieval in the statistics.</param> /// <returns><c>true</c> if the file is retrieved, <c>false</c> otherwise.</returns> /// <exception cref="ArgumentNullException">If <typeparamref name="fullName"/> os <paramref name="destinationStream"/> are <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty or <paramref name="destinationStream"/> does not support writing, or if <paramref name="fullName"/> does not exist.</exception> public bool RetrieveFile(string fullName, 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"); string filename = BuildFullPath(fullName); if(!File.Exists(filename)) throw new ArgumentException("File does not exist", "fullName"); FileStream fs = null; bool done = false; try { fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); // StreamCopy content (throws exception in case of error) StreamCopy(fs, destinationStream); done = true; } catch(IOException) { done = false; } finally { try { fs.Close(); } catch { } } if(countHit) { AddDownloadHit(fullName, GetFullPath(FileDownloadsFile)); } return done; } /// <summary> /// Adds a download hit for the specified item in the specified output file. /// </summary> /// <param name="itemName">The item.</param> /// <param name="outputFile">The full path to the output file.</param> private void AddDownloadHit(string itemName, string outputFile) { lock(this) { string[] lines = File.ReadAllLines(outputFile); string lowercaseItemName = itemName.ToLowerInvariant(); string[] fields; bool found = false; for(int i = 0; i < lines.Length; i++) { fields = lines[i].Split('|'); if(fields[0].ToLowerInvariant() == lowercaseItemName) { int count = 0; int.TryParse(fields[1], out count); count = count + 1; lines[i] = itemName + "|" + count.ToString(); found = true; } } if(!found) { // Add a new line for the current item string[] newLines = new string[lines.Length + 1]; Array.Copy(lines, 0, newLines, 0, lines.Length); newLines[newLines.Length - 1] = itemName + "|1"; lines = newLines; } // Overwrite file with updated data File.WriteAllLines(outputFile, lines); } } /// <summary> /// Sets the download hits for the specified item in the specified file. /// </summary> /// <param name="itemName">The item.</param> /// <param name="outputFile">The full path of the output file.</param> /// <param name="count">The hit count to set.</param> private void SetDownloadHits(string itemName, string outputFile, int count) { lock(this) { string[] lines = File.ReadAllLines(outputFile); List<string> outputLines = new List<string>(lines.Length); string lowercaseItemName = itemName.ToLowerInvariant(); string[] fields; foreach(string line in lines) { fields = line.Split('|'); if(fields[0].ToLowerInvariant() == lowercaseItemName) { // Set the new count outputLines.Add(fields[0] + "|" + count.ToString()); } else { // Copy data with no modification outputLines.Add(line); } } File.WriteAllLines(outputFile, outputLines.ToArray()); } } /// <summary> /// Clears the download hits for the items that match <b>itemName</b> in the specified file. /// </summary> /// <param name="itemName">The first part of the item name.</param> /// <param name="outputFile">The full path of the output file.</param> private void ClearDownloadHitsPartialMatch(string itemName, string outputFile) { lock(this) { string[] lines = File.ReadAllLines(outputFile); List<string> newLines = new List<string>(lines.Length); string lowercaseItemName = itemName.ToLowerInvariant(); string[] fields; foreach(string line in lines) { fields = line.Split('|'); if(!fields[0].ToLowerInvariant().StartsWith(lowercaseItemName)) { newLines.Add(line); } } File.WriteAllLines(outputFile, newLines.ToArray()); } } /// <summary> /// Renames an item of the download count list in the specified file. /// </summary> /// <param name="oldItemName">The old item name.</param> /// <param name="newItemName">The new item name.</param> /// <param name="outputFile">The full path of the output file.</param> private void RenameDownloadHitsItem(string oldItemName, string newItemName, string outputFile) { lock(this) { string[] lines = File.ReadAllLines(outputFile); string lowercaseOldItemName = oldItemName.ToLowerInvariant(); string[] fields; bool found = false; for(int i = 0; i < lines.Length; i++) { fields = lines[i].Split('|'); if(fields[0].ToLowerInvariant() == lowercaseOldItemName) { lines[i] = newItemName + "|" + fields[1]; found = true; break; } } if(found) { File.WriteAllLines(outputFile, lines); } } } /// <summary> /// Renames an item of the download count list in the specified file. /// </summary> /// <param name="oldItemName">The initial part of the old item name.</param> /// <param name="newItemName">The corresponding initial part of the new item name.</param> /// <param name="outputFile">The full path of the output file.</param> private void RenameDownloadHitsItemPartialMatch(string oldItemName, string newItemName, string outputFile) { lock(this) { string[] lines = File.ReadAllLines(outputFile); string lowercaseOldItemName = oldItemName.ToLowerInvariant(); string[] fields; bool found = false; for(int i = 0; i < lines.Length; i++) { fields = lines[i].Split('|'); if(fields[0].ToLowerInvariant().StartsWith(lowercaseOldItemName)) { lines[i] = newItemName + fields[0].Substring(lowercaseOldItemName.Length) + "|" + fields[1]; found = true; } } if(found) { File.WriteAllLines(outputFile, lines); } } } /// <summary> /// Gets the number of times a file was retrieved. /// </summary> /// <param name="fullName">The full name of the file.</param> /// <returns>The number of times the file was retrieved.</returns> private int GetFileRetrievalCount(string fullName) { if(fullName == null) throw new ArgumentNullException("fullName"); if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); lock(this) { // Format // /Full/Path/To/File.txt|DownloadCount string[] lines = File.ReadAllLines(GetFullPath(FileDownloadsFile)); string lowercaseFullName = fullName.ToLowerInvariant(); string[] fields; foreach(string line in lines) { fields = line.Split('|'); if(fields[0].ToLowerInvariant() == lowercaseFullName) { int res = 0; if(int.TryParse(fields[1], out res)) return res; else return 0; } } } return 0; } /// <summary> /// Clears 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"); SetDownloadHits(fullName, GetFullPath(FileDownloadsFile), 0); } /// <summary> /// Gets the details of a file. /// </summary> /// <param name="fullName">The full name of the file.</param> /// <returns>The details, or <c>null</c> if the file does not exist.</returns> /// <exception cref="ArgumentNullException">If <paramref name="fullName"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty.</exception> 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"); string n = BuildFullPath(fullName); if(!File.Exists(n)) return null; FileInfo fi = new FileInfo(n); return new FileDetails(fi.Length, fi.LastWriteTime, GetFileRetrievalCount(fullName)); } /// <summary> /// Deletes a File. /// </summary> /// <param name="fullName">The full name of the File.</param> /// <returns><c>true</c> if the File is deleted, <c>false</c> otherwise.</returns> /// <exception cref="ArgumentNullException">If <paramref name="fullName"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty or it does not exist.</exception> 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"); string n = BuildFullPath(fullName); if(!File.Exists(n)) throw new ArgumentException("File does not exist", "fullName"); try { File.Delete(n); SetDownloadHits(fullName, GetFullPath(FileDownloadsFile), 0); return true; } catch(IOException) { return false; } } /// <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"); string oldFilename = BuildFullPath(oldFullName); string newFilename = BuildFullPath(newFullName); if(!File.Exists(oldFilename)) throw new ArgumentException("Old File does not exist", "oldFullName"); if(File.Exists(newFilename)) throw new ArgumentException("New File already exists", "newFullName"); try { File.Move(oldFilename, newFilename); RenameDownloadHitsItem(oldFullName, newFullName, GetFullPath(FileDownloadsFile)); return true; } catch(IOException) { return false; } } /// <summary> /// Creates a new Directory. /// </summary> /// <param name="path">The path to create the new Directory in.</param> /// <param name="name">The name of the new Directory.</param> /// <returns><c>true</c> if the Directory is created, <c>false</c> otherwise.</returns> /// <remarks>If <b>path</b> is "/my/directory" and <b>name</b> is "newdir", a new directory named "/my/directory/newdir" is created.</remarks> /// <exception cref="ArgumentNullException">If <paramref name="path"/> or <paramref name="name"/> are <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="name"/> is empty or if the directory does not exist, or if the new directory already exists.</exception> 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"); if(!Directory.Exists(BuildFullPath(path))) throw new ArgumentException("Directory does not exist", "path"); string partialPath = path + (!path.EndsWith("/") ? "/" : "") + name; string d = BuildFullPath(partialPath); if(Directory.Exists(d)) throw new ArgumentException("Directory already exists", "name"); try { Directory.CreateDirectory(d); return true; } catch(IOException) { return false; } } /// <summary> /// Deletes a Directory and <b>all of its content</b>. /// </summary> /// <param name="fullPath">The full path of the Directory.</param> /// <returns><c>true</c> if the Directory is delete, <c>false</c> otherwise.</returns> /// <exception cref="ArgumentNullException">If <paramref name="fullPath"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="fullPath"/> is empty or if it equals '/' or it does not exist.</exception> 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"); string d = BuildFullPath(fullPath); if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "fullPath"); try { Directory.Delete(d, true); // Make sure tht fullPath ends with "/" so that the method does not clear wrong items if(!fullPath.EndsWith("/")) fullPath += "/"; ClearDownloadHitsPartialMatch(fullPath, GetFullPath(FileDownloadsFile)); return true; } catch(IOException) { return false; } } /// <summary> /// Renames or moves a Directory. /// </summary> /// <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> /// <exception cref="ArgumentNullException">If <paramref name="oldFullPath"/> or <paramref name="newFullPath"/> are <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="oldFullPath"/> or <paramref name="newFullPath"/> are empty or equal to '/', /// or if the old directory does not exist or the new directory already exists.</exception> 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"); string olddir = BuildFullPath(oldFullPath); string newdir = BuildFullPath(newFullPath); if(!Directory.Exists(olddir)) throw new ArgumentException("Directory does not exist", "oldFullPath"); if(Directory.Exists(newdir)) throw new ArgumentException("Directory already exists", "newFullPath"); try { Directory.Move(olddir, newdir); // Make sure that oldFullPath and newFullPath end with "/" so that the method does not rename wrong items if(!oldFullPath.EndsWith("/")) oldFullPath += "/"; if(!newFullPath.EndsWith("/")) newFullPath += "/"; RenameDownloadHitsItemPartialMatch(oldFullPath, newFullPath, GetFullPath(FileDownloadsFile)); return true; } catch(IOException) { return false; } } /// <summary> /// Gets the name of the Directory containing the Attachments of a Page. /// </summary> /// <param name="pageInfo">The Page Info.</param> /// <returns>The name of the Directory (not the full path) that contains the Attachments of the specified Page.</returns> private string GetPageAttachmentDirectory(PageInfo pageInfo) { // Use the Hash to avoid problems with special chars and the like // Using the hash prevents GetPageWithAttachments to work //return Hash.Compute(pageInfo.FullName); return pageInfo.FullName; } /// <summary> /// The the names of the pages with attachments. /// </summary> /// <returns>The names of the pages with attachments.</returns> public string[] GetPagesWithAttachments() { string[] directories = Directory.GetDirectories(GetFullPath(AttachmentsDirectory)); string[] result = new string[directories.Length]; for(int i = 0; i < result.Length; i++) { result[i] = Path.GetFileName(directories[i]); } return result; } /// <summary> /// Returns the names of the Attachments of a Page. /// </summary> /// <param name="pageInfo">The Page Info object that owns the Attachments.</param> /// <returns>The names, or an empty list.</returns> /// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/> is <c>null</c>.</exception> public string[] ListPageAttachments(PageInfo pageInfo) { if(pageInfo == null) throw new ArgumentNullException("pageInfo"); string dir = BuildFullPathForAttachments(GetPageAttachmentDirectory(pageInfo)); if(!Directory.Exists(dir)) return new string[0]; string[] files = Directory.GetFiles(dir); // Result must contain only the filename, not the full path List<string> result = new List<string>(files.Length); foreach(string f in files) { result.Add(Path.GetFileName(f)); } return result.ToArray(); } /// <summary> /// Stores a Page Attachment. /// </summary> /// <param name="pageInfo">The Page Info that owns the Attachment.</param> /// <param name="name">The name of the Attachment, for example "myfile.jpg".</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 Attachment.</param> /// <returns><c>true</c> if the Attachment is stored, <c>false</c> otherwise.</returns> /// <remarks>If <b>overwrite</b> is <c>false</c> and Attachment already exists, the method returns <c>false</c>.</remarks> /// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/>, <paramref name="name"/> or <paramref name="sourceStream"/> are <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="name"/> is empty or if <paramref name="sourceStream"/> does not support reading.</exception> public bool StorePageAttachment(PageInfo pageInfo, string name, 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"); string filename = BuildFullPathForAttachments(GetPageAttachmentDirectory(pageInfo) + "/" + name); if(!Directory.Exists(Path.GetDirectoryName(filename))) { try { Directory.CreateDirectory(Path.GetDirectoryName(filename)); } catch(IOException) { // Cannot create attachments dir return false; } } if(File.Exists(filename) && !overwrite) return false; FileStream fs = null; bool done = false; try { fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); // StreamCopy content (throws exception in case of error) StreamCopy(sourceStream, fs); done = true; } catch(IOException) { return false; } finally { try { fs.Close(); } catch { } } return done; } /// <summary> /// Retrieves a Page Attachment. /// </summary> /// <param name="pageInfo">The Page Info that owns the Attachment.</param> /// <param name="name">The name of the Attachment, for example "myfile.jpg".</param> /// <param name="destinationStream">A Stream object used as <b>destination</b> of a byte stream, /// i.e. the method writes to the Stream the file content.</param> /// <param name="countHit">A value indicating whether or not to count this retrieval in the statistics.</param> /// <returns><c>true</c> if the Attachment is retrieved, <c>false</c> otherwise.</returns> /// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/>, <paramref name="name"/> or <paramref name="destinationStream"/> are <c>null</c>.</exception> /// <exception cref="ArgumentException">If <paramref name="name"/> is empty or if <paramref name="destinationStream"/> does not support writing, /// or if the page does not have attachments or if the attachment does not exist.</exception> public bool RetrievePageAttachment(PageInfo pageInfo, string name, 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"); string d = GetPageAttachmentDirectory(pageInfo); if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("No attachments for Page", "pageInfo"); string filename = BuildFullPathForAttachments(d + "/" + name); if(!File.Exists(filename)) throw new ArgumentException("Attachment does not exist", "name"); FileStream fs = null; bool done = false; try { fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); // StreamCopy content (throws exception in case of error) StreamCopy(fs, destinationStream); done = true; } catch(IOException) { done = false; } finally { try { fs.Close(); } catch { } } if(countHit) { AddDownloadHit(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile)); } return done; } /// <summary> /// Gets 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> /// <returns>The number of times the attachment was retrieved.</returns> private int GetPageAttachmentRetrievalCount(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", "name"); lock(this) { // Format // PageName.File|DownloadCount string[] lines = File.ReadAllLines(GetFullPath(AttachmentDownloadsFile)); string lowercaseFullName = pageInfo.FullName + "." + name; lowercaseFullName = lowercaseFullName.ToLowerInvariant(); string[] fields; foreach(string line in lines) { fields = line.Split('|'); if(fields[0].ToLowerInvariant() == lowercaseFullName) { int count; if(int.TryParse(fields[1], out count)) return count; else return 0; } } } return 0; } /// <summary> /// Set 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"); SetDownloadHits(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile), count); } /// <summary> /// Gets the details of a page attachment. /// </summary> /// <param name="pageInfo">The page that owns the attachment.</param> /// <param name="name">The name of the attachment, for example "myfile.jpg".</param> /// <returns>The details of the attachment, or <c>null</c> if the attachment does not exist.</returns> /// <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> 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"); string d = GetPageAttachmentDirectory(pageInfo); if(!Directory.Exists(BuildFullPathForAttachments(d))) return null; string filename = BuildFullPathForAttachments(d + "/" + name); if(!File.Exists(filename)) return null; FileInfo fi = new FileInfo(filename); return new FileDetails(fi.Length, fi.LastWriteTime, GetPageAttachmentRetrievalCount(pageInfo, name)); } /// <summary> /// Deletes a Page Attachment. /// </summary> /// <param name="pageInfo">The Page Info that owns the Attachment.</param> /// <param name="name">The name of the Attachment, for example "myfile.jpg".</param> /// <returns><c>true</c> if the Attachment is deleted, <c>false</c> otherwise.</returns> /// <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 or if the page or attachment do not exist.</exception> 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"); string d = GetPageAttachmentDirectory(pageInfo); if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("Page does not exist", "pageInfo"); string filename = BuildFullPathForAttachments(d + "/" + name); if(!File.Exists(filename)) throw new ArgumentException("Attachment does not exist", "name"); try { File.Delete(filename); SetDownloadHits(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile), 0); return true; } catch(IOException) { return false; } } /// <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, <c>false</c> 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"); string d = GetPageAttachmentDirectory(pageInfo); if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("Page does not exist", "pageInfo"); string oldFilename = BuildFullPathForAttachments(d + "/" + oldName); if(!File.Exists(oldFilename)) throw new ArgumentException("Attachment does not exist", "oldName"); string newFilename = BuildFullPathForAttachments(d + "/" + newName); if(File.Exists(newFilename)) throw new ArgumentException("Attachment already exists", "newName"); try { File.Move(oldFilename, newFilename); RenameDownloadHitsItem(pageInfo.FullName + "." + oldName, pageInfo.FullName + "." + newName, GetFullPath(AttachmentDownloadsFile)); return true; } catch(IOException) { return false; } } /// <summary> /// Notifies to 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"); string oldName = GetPageAttachmentDirectory(oldPage); string newName = GetPageAttachmentDirectory(newPage); string oldDir = BuildFullPathForAttachments(oldName); string newDir = BuildFullPathForAttachments(newName); if(!Directory.Exists(oldDir)) return; // Nothing to do if(Directory.Exists(newDir)) throw new ArgumentException("New Page already exists", "newPage"); try { Directory.Move(oldDir, newDir); RenameDownloadHitsItemPartialMatch(oldPage.FullName + ".", newPage.FullName + ".", GetFullPath(AttachmentDownloadsFile)); } catch(IOException) { } } } }