screwturn-4/Core/FilesStorageProvider.cs
2009-09-30 13:47:13 +00:00

989 lines
41 KiB
C#

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) { }
}
}
}