EnVisageOnline/Main-RMO/Source/EnVisage/Code/BLL/FileManager.cs

866 lines
24 KiB
C#

using EnVisage.Code;
using EnVisage.Code.BLL;
using EnVisage.Properties;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.SessionState;
namespace EnVisage.Models
{
public class FileDescriptor
{
public Guid Id;
public Guid HolderId;
public AttachmentModel Meta;
public Stream Content;
}
public class FileManager : ManagerBase<Attachment, AttachmentModel>
{
public const string C_TEMPORARY_FILE_EXTENSION = ".tmp";
public const string C_PERMANENT_FILE_EXTENSION = ".file";
#region Constructors
public FileManager(EnVisageEntities dbContext)
: base(dbContext)
{
this.UserId = SecurityManager.GetUserPrincipal();
this.Init();
}
public FileManager(EnVisageEntities dbContext, Guid userId)
: base(dbContext)
{
this.UserId = userId;
this.Init();
}
public FileManager(EnVisageEntities dbContext, Guid userId, string permanentStorageDir)
: base(dbContext)
{
this.UserId = userId;
this.PermanentStoragePath = permanentStorageDir;
this.Init();
}
public FileManager(EnVisageEntities dbContext, Guid userId, string permanentStorageDir,
string tempStorageDir)
: base(dbContext)
{
this.UserId = userId;
this.PermanentStoragePath = permanentStorageDir;
this.TempStoragePath = tempStorageDir;
this.Init();
}
#endregion
#region User Folders
protected Guid userId = Guid.Empty;
public Guid UserId
{
get { return userId; }
set
{
if (value.Equals(Guid.Empty))
throw new ArgumentNullException("User Id must be non empty");
userId = value;
}
}
public void Init()
{
if (String.IsNullOrEmpty(permStoragePath))
this.PermanentStoragePath = Settings.Default.AttachmentsPath;
if (String.IsNullOrEmpty(tempStoragePath))
{
if (!String.IsNullOrEmpty(Settings.Default.UploadTempPath))
this.TempStoragePath = Settings.Default.UploadTempPath;
else
SetDefaultTempDirectory();
}
}
#endregion
#region Temporary Storage
private Object sessionLock = new Object();
protected string tempStoragePath = String.Empty;
public string TempStoragePath
{
get { return tempStoragePath; }
set
{
if (String.IsNullOrEmpty(value))
throw new ArgumentNullException("Temporary files folder must be not empty");
string path = value;
if (VirtualPathUtility.IsAppRelative(value))
path = HttpContext.Current.Server.MapPath(value);
CheckFolderAccess(path, true);
tempStoragePath = path;
}
}
protected void SetDefaultTempDirectory()
{
tempStoragePath = permStoragePath;
}
protected void CheckFolderAccess(string path, bool createIfNotExists = false)
{
if (String.IsNullOrEmpty(path))
throw new ArgumentNullException("Path for check access is empty");
string errorMessage;
if (!Directory.Exists(path))
if (createIfNotExists)
{
try
{
Directory.CreateDirectory(path);
}
catch (Exception ex)
{
errorMessage = String.Format("Can't create folder '{0}'", path);
throw new IOException(errorMessage, ex);
}
}
else
{
errorMessage = String.Format("Folder '{0}' doesn't exist", path);
throw new IOException(errorMessage);
}
}
protected string GetTempFileName(Guid fileId)
{
string fileName = String.Format("{0}{1}", fileId.ToString("N"), C_TEMPORARY_FILE_EXTENSION);
return fileName;
}
protected string GetTempFilePath(string fileName)
{
string path = GetUserTempPath();
CheckFolderAccess(path, true);
path = String.Format("{0}\\{1}", path, fileName);
return path;
}
protected string GetUserTempPath()
{
string userFolder = userId.ToString("N");
string path = String.Format("{0}\\{1}", VirtualPathUtility.RemoveTrailingSlash(tempStoragePath),
userFolder);
return path;
}
public AttachmentModel CreateTempFile(HttpPostedFileBase file)
{
if (file == null)
throw new ArgumentNullException("Can't create temporary file for empty upload file");
string errorMessage;
Guid fileId = Guid.NewGuid();
string fileName = this.GetTempFileName(fileId);
string filePath = GetTempFilePath(fileName);
if (File.Exists(filePath))
{
errorMessage = String.Format("Name for new temporary file was generated, " +
"but file already was found in the storage (file name: {0})", filePath);
throw new IOException(errorMessage);
}
try
{
using (FileStream writableFile =
new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
{
// SA. ENV-958. Fix of file access errors with different processes
file.InputStream.CopyTo(writableFile);
writableFile.Flush();
writableFile.Close();
}
}
catch (Exception ex)
{
errorMessage = String.Format("Can't create temporary file. File path: {0}", filePath);
IOException exception = new IOException(errorMessage, ex);
throw exception;
}
AttachmentModel result = new AttachmentModel()
{
Id = fileId,
Name = file.FileName,
Size = file.ContentLength,
ContentType = file.ContentType,
IsNew = true
};
lock (sessionLock)
{
// Save file metadata to session
Dictionary<Guid, AttachmentModel> files =
HttpContext.Current.Session["TempFiles"] as Dictionary<Guid, AttachmentModel>;
if (files == null)
{
files = new Dictionary<Guid, AttachmentModel>();
HttpContext.Current.Session["TempFiles"] = files;
}
files.Add(fileId, result);
}
return result;
}
public void DeleteTempFile(Guid fileId)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate and delete temporary file for empty identifier");
string errorMessage;
string fileName = this.GetTempFileName(fileId);
string filePath = GetTempFilePath(fileName);
if (!File.Exists(filePath))
{
errorMessage = String.Format("Can't delete temporary file '{0}'. File not found", filePath);
throw new FileNotFoundException(errorMessage);
}
lock (sessionLock)
{
// Delete file metadata from session
Dictionary<Guid, AttachmentModel> files =
HttpContext.Current.Session["TempFiles"] as Dictionary<Guid, AttachmentModel>;
if ((files != null) && files.ContainsKey(fileId))
files.Remove(fileId);
}
try
{
File.Delete(filePath);
}
catch (Exception ex)
{
errorMessage = String.Format("Can't delete temporary file '{0}'", filePath);
throw new IOException(errorMessage, ex);
}
}
public void DeleteTempFiles(IList<Guid> files, bool silent = false)
{
if (files == null)
throw new ArgumentNullException("The list of temporary files to delete is null");
foreach (Guid fileId in files)
try
{
this.DeleteTempFile(fileId);
}
catch (Exception ex)
{
if (!silent)
throw ex;
}
}
public bool MoveToPermanentStorage(Guid fileId, Guid holderId, System.Type holderType)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate temporary file for empty identifier");
if (holderId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't move temporary file to permanent storage. File holder ID is empty");
if (holderType == null)
throw new ArgumentNullException("Can't move temporary file to permanent storage. File holder TYPE is not specified");
bool tmpFileDeleted = true;
string errorMessage;
string tmpFileName = this.GetTempFileName(fileId);
string tmpFilePath = GetTempFilePath(tmpFileName);
if (!File.Exists(tmpFilePath))
{
errorMessage = String.Format("Can't move temporary file '{0}' to permanent storage. File not found", tmpFilePath);
throw new FileNotFoundException(errorMessage);
}
// Check there is no record for this file in the database
int foundRecordsCount = DbContext.Attachments.Where(x => x.Id.Equals(fileId)).Count();
if (foundRecordsCount > 0)
{
errorMessage = String.Format("Can't move temporary file '{0}' to permanent storage. Attachment already exists", tmpFilePath);
throw new Exception(errorMessage);
}
// Get metadata from session
Dictionary<Guid, AttachmentModel> files =
HttpContext.Current.Session["TempFiles"] as Dictionary<Guid, AttachmentModel>;
if ((files == null) || !files.ContainsKey(fileId))
{
errorMessage = String.Format("Can't move temporary file '{0}' to permanent storage. File metadata not found", tmpFilePath);
throw new Exception(errorMessage);
}
string newFileName = GetPermanentFileName(fileId);
string newFilePath = GetPermanentFilePath(newFileName);
try
{
using (FileStream inputFile =
new FileStream(tmpFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete))
{
using (FileStream outputFile =
new FileStream(newFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
inputFile.CopyTo(outputFile);
outputFile.Flush();
outputFile.Close();
}
inputFile.Close();
}
}
catch (Exception ex)
{
errorMessage = String.Format("Can't copy temporary file '{0}' to permanent storage file '{1}'",
tmpFilePath, newFilePath);
throw new IOException(errorMessage, ex);
}
// Save descriptor to DB
AttachmentModel fileDescriptor = files[fileId];
Attachment newRec = new Attachment();
fileDescriptor.CopyTo(newRec);
newRec.ParentId = holderId;
newRec.ParentType = holderType.FullName;
newRec.FileName = newFilePath;
newRec.Created = DateTime.UtcNow;
DbContext.Attachments.Add(newRec);
// Delete file metadata from session
if ((files != null) && files.ContainsKey(fileId))
files.Remove(fileId);
try
{
File.Delete(tmpFilePath);
}
catch (Exception ex)
{
tmpFileDeleted = false;
}
return tmpFileDeleted;
}
public IList<Guid> MoveToPermanentStorage(IList<AttachmentModel> files, Guid holderId, System.Type holderType)
{
if (files == null)
throw new ArgumentNullException("The list of temporary files to move to permanent storage is null");
// TODO: First check all files can be moved
List<Guid> undeletedTempFiles = new List<Guid>();
foreach (AttachmentModel model in files)
{
bool deleted = MoveToPermanentStorage(model.Id, holderId, holderType);
if (!deleted)
undeletedTempFiles.Add(model.Id);
}
return undeletedTempFiles;
}
public FileDescriptor GetTempFileContent(Guid fileId)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate temporary file for empty identifier");
string errorMessage;
FileDescriptor descriptor = new FileDescriptor()
{
Id = fileId
};
string fileName = this.GetTempFileName(fileId);
string filePath = GetTempFilePath(fileName);
if (!File.Exists(filePath))
{
errorMessage = String.Format("Temporary file '{0}' not found in the temporary storage", filePath);
throw new FileNotFoundException(errorMessage);
}
try
{
descriptor.Content = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception ex)
{
errorMessage = String.Format("Can't read temporary file '{0}' content", filePath);
Exception exception = new IOException(errorMessage, ex);
throw exception;
}
lock (sessionLock)
{
// Get file metadata
string contentType = String.Empty;
Dictionary<Guid, AttachmentModel> files =
HttpContext.Current.Session["TempFiles"] as Dictionary<Guid, AttachmentModel>;
if ((files != null) && files.ContainsKey(fileId))
descriptor.Meta = files[fileId];
}
return descriptor;
}
public void EmptyUserTempFolder()
{
string path = GetUserTempPath();
CheckFolderAccess(path, true);
// Delete all temporary files in user folder
string fileMask = String.Format("*{0}", C_TEMPORARY_FILE_EXTENSION);
string[] foundTempFiles = Directory.GetFiles(path, fileMask);
foreach (string fileName in foundTempFiles)
try
{
File.Delete(fileName);
}
catch
{
}
lock (sessionLock)
{
if (HttpContext.Current.Session["TempFiles"] != null)
HttpContext.Current.Session["TempFiles"] = null;
}
}
public bool IsTempFile(Guid fileId)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate temporary file for empty identifier");
string fileName = this.GetTempFileName(fileId);
string filePath = GetTempFilePath(fileName);
return File.Exists(filePath);
}
#endregion
#region Permanent Storage
protected string permStoragePath = String.Empty;
public string PermanentStoragePath
{
get { return permStoragePath; }
set
{
if (String.IsNullOrEmpty(value))
throw new ArgumentNullException("Permanent files storage folder must be non-empty");
string path = value;
if (VirtualPathUtility.IsAppRelative(value))
path = HttpContext.Current.Server.MapPath(value);
CheckFolderAccess(path, true);
permStoragePath = path;
}
}
protected string GetPermanentFileName(Guid fileId)
{
string fileName = String.Format("{0}{1}", fileId.ToString("N"), C_PERMANENT_FILE_EXTENSION);
return fileName;
}
protected string GetPermanentFilePath(string fileName)
{
string path = GetUserPermanentPath();
CheckFolderAccess(path, true);
path = String.Format("{0}\\{1}", path, fileName);
return path;
}
protected string GetUserPermanentPath()
{
string userFolder = userId.ToString("N");
string path = String.Format("{0}\\{1}", VirtualPathUtility.RemoveTrailingSlash(permStoragePath),
userFolder);
return path;
}
public FileDescriptor GetPermanentFileContent(Guid fileId)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate and permanent file for empty identifier");
string errorMessage;
var foundAttachments = DbContext.Attachments.Where(x=>x.Id.Equals(fileId));
if (foundAttachments.Count() < 1)
{
errorMessage = String.Format("File not found in permanent storage (Id = '{0}')", fileId.ToString());
throw new FileNotFoundException(errorMessage);
}
Attachment dbRecord = foundAttachments.First();
FileDescriptor descriptor = new FileDescriptor()
{
Id = fileId,
HolderId = dbRecord.ParentId,
};
AttachmentModel model = (AttachmentModel)dbRecord;
descriptor.Meta = model;
string filePath = dbRecord.FileName;
if (!File.Exists(filePath))
{
errorMessage = String.Format("File '{0}' not found in permanent storage", filePath);
throw new FileNotFoundException(errorMessage);
}
try
{
descriptor.Content = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception ex)
{
errorMessage = String.Format("Can't read file '{0}' content in permanent storage", filePath);
Exception exception = new IOException(errorMessage, ex);
throw exception;
}
return descriptor;
}
public void DeletePermanentFile(Guid fileId)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate and delete permanent file for empty identifier");
string errorMessage;
string fileName = this.GetPermanentFileName(fileId);
string filePath = GetPermanentFilePath(fileName);
// Delete file metadata from database
var filesToDelete = DbContext.Attachments.Where(x => x.Id.Equals(fileId));
DbContext.Attachments.RemoveRange(filesToDelete);
if (!File.Exists(filePath))
{
errorMessage = String.Format("Can't delete file '{0}' from permanent storage. File not found", filePath);
throw new FileNotFoundException(errorMessage);
}
try
{
File.Delete(filePath);
}
catch (Exception ex)
{
errorMessage = String.Format("Can't delete file '{0}' from permanent storage", filePath);
throw new IOException(errorMessage, ex);
}
}
public void DeletePermanentFiles(IEnumerable<Guid> files, bool silent = false)
{
if (files == null)
throw new ArgumentNullException("The list of permanent files to delete is null");
// TODO: First check all files can be deleted
foreach (Guid fileId in files)
try
{
this.DeletePermanentFile(fileId);
}
catch (Exception ex)
{
if (!silent)
throw ex;
}
}
public bool IsPermanentFile(Guid fileId)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate permanent storage file for empty identifier");
int itemsCount = DbContext.Attachments.Where(x => x.Id.Equals(fileId)).Count();
return itemsCount > 0;
}
public IList<Guid> GetPermanentFilesByHolder(Guid holderId, System.Type holderType = null)
{
return this.GetPermanentFilesByHolder(new List<Guid>() { holderId }, holderType);
}
public IList<Guid> GetPermanentFilesByHolder(IEnumerable<Guid> holderIds, System.Type holderType = null)
{
List<Guid> attachs;
if (holderType != null)
attachs = DbContext.Attachments.Where(x => holderIds.Contains(x.ParentId) &&
x.ParentType.Equals(holderType.FullName)).Select(y => y.Id).ToList();
else
attachs = DbContext.Attachments.Where(x => holderIds.Contains(x.ParentId))
.Select(y => y.Id).ToList();
return attachs;
}
/// <summary>
/// Changes parent for existing permanent file
/// </summary>
/// <param name="permanentFileId">Id of the file</param>
/// <param name="newParentId">New parent Id or Guid.Empty for no change</param>
/// <param name="newParentType">New parent type or null for no change</param>
public void ChangeFileParent(Guid permanentFileId, Guid newParentId, System.Type newParentType = null)
{
if (permanentFileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate permanent storage file for empty identifier");
var attachment = DbContext.Attachments.FirstOrDefault(x => x.Id.Equals(permanentFileId));
if ((attachment == null) || (attachment.Id.Equals(Guid.Empty)))
{
string errorMessage = String.Format("Can't change parent for file '{0}'. File not found", permanentFileId);
throw new FileNotFoundException(errorMessage);
}
if (!newParentId.Equals(Guid.Empty) && !attachment.ParentId.Equals(newParentId))
{
attachment.ParentId = newParentId;
}
if (newParentType != null)
{
string newParentTypeName = newParentType.FullName;
if (attachment.ParentType != newParentTypeName)
attachment.ParentType = newParentTypeName;
}
}
/// <summary>
/// Changes parent for a group of existing of permanent files
/// </summary>
/// <param name="permanentFiles">List of file models</param>
/// <param name="newParentId">New parent Id or Guid.Empty for no change</param>
/// <param name="newParentType">New parent type or null for no change</param>
public void ChangeFilesParent(IList<AttachmentModel> permanentFiles, Guid newParentId, System.Type newParentType = null)
{
if (permanentFiles == null)
throw new ArgumentNullException("List of permanent storage files must be not null");
foreach (AttachmentModel fileModel in permanentFiles)
this.ChangeFileParent(fileModel.Id, newParentId, newParentType);
}
public AttachmentModel CopyPermanentFile(Guid fileId, Guid newHolderId, System.Type newHolderType)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate permanent file for empty identifier");
if (newHolderId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't copy permanent file. File new holder ID is empty");
if (newHolderType == null)
throw new ArgumentNullException("Can't copy permanent file. File new holder TYPE is not specified");
string errorMessage;
var sourceFileMeta = DbContext.Attachments.FirstOrDefault(x => x.Id.Equals(fileId));
if ((sourceFileMeta == null) || sourceFileMeta.Id.Equals(Guid.Empty))
{
errorMessage = String.Format("Can't copy permanent file '{0}'. File not found", fileId);
throw new FileNotFoundException(errorMessage);
}
string sourceFilePath = sourceFileMeta.FileName;
if (!File.Exists(sourceFilePath))
{
errorMessage = String.Format("Can't copy permanent file '{0}'. File not found", sourceFilePath);
throw new FileNotFoundException(errorMessage);
}
Guid newFileId = Guid.NewGuid();
string newFileName = GetPermanentFileName(newFileId);
string newFilePath = GetPermanentFilePath(newFileName);
try
{
using (FileStream inputFile =
new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Delete))
{
using (FileStream outputFile =
new FileStream(newFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
inputFile.CopyTo(outputFile);
outputFile.Flush();
outputFile.Close();
}
inputFile.Close();
}
}
catch (Exception ex)
{
errorMessage = String.Format("Can't copy permanent file '{0}' to '{1}'", sourceFilePath, newFilePath);
throw new IOException(errorMessage, ex);
}
// Save descriptor to DB
Attachment newRec = new Attachment()
{
Id = newFileId,
ContentType = sourceFileMeta.ContentType,
Created = DateTime.UtcNow,
SourceFileName = sourceFileMeta.SourceFileName,
FileSize = sourceFileMeta.FileSize,
ParentId = newHolderId,
ParentType = newHolderType.FullName,
FileName = newFilePath
};
DbContext.Attachments.Add(newRec);
AttachmentModel newFileModel = (AttachmentModel)newRec;
return newFileModel;
}
public IList<AttachmentModel> CopyPermanentFiles(IList<AttachmentModel> files, Guid newHolderId, System.Type newHolderType)
{
if (files == null)
throw new ArgumentNullException("The list of permanent files to copy is null");
// TODO: First check all files can be moved
List<AttachmentModel> newModels = new List<AttachmentModel>();
foreach (AttachmentModel model in files)
{
AttachmentModel newFileModel = CopyPermanentFile(model.Id, newHolderId, newHolderType);
newModels.Add(newFileModel);
}
return newModels;
}
// SA: Don't delete. Writed for future functionality
//public void DeleteUserUnattachedPermanentFiles()
//{
// // Remove attachments, that have no parent object
// var unattachedFiles = DbContext.BLL_UnattachedFiles.AsNoTracking().Select(x=>x.Id).ToList();
// this.DeletePermanentFiles(unattachedFiles, true);
// unattachedFiles = null;
// // Remove permanent files, that have no corresponding database records
// var registeredFiles = DbContext.Attachments.AsNoTracking().Select(x => x.FileName).ToList();
// string fileMask = String.Format("*{0}", C_PERMANENT_FILE_EXTENSION);
// string userPath = this.GetUserPermanentPath();
// string[] foundTempFiles = Directory.GetFiles(userPath, fileMask);
// foreach (string fileName in foundTempFiles)
// if (!registeredFiles.Contains(fileName, StringComparer.InvariantCultureIgnoreCase))
// try
// {
// if (File.Exists(fileName))
// File.Delete(fileName);
// }
// catch
// {
// }
//}
#endregion
#region Queue
protected List<string> queuedFiles = new List<string>();
public void QueuePermanentFilesToDelete(IEnumerable<Guid> holderIds)
{
queuedFiles = new List<string>();
var attachs = DbContext.Attachments.Where(x => holderIds.Contains(x.ParentId)).ToList();
foreach (Attachment item in attachs)
queuedFiles.Add(item.FileName);
}
public void DeleteQueuedFiles(bool silent = false)
{
if ((queuedFiles == null) || (queuedFiles.Count < 1))
return;
foreach (string fileName in queuedFiles)
try
{
if (File.Exists(fileName))
File.Delete(fileName);
}
catch (Exception ex)
{
if (!silent)
throw ex;
}
queuedFiles.Clear();
}
#endregion
#region Other public
public FileDescriptor GetFileContent(Guid fileId)
{
if (fileId.Equals(Guid.Empty))
throw new ArgumentNullException("Can't locate file for empty identifier");
if (this.IsPermanentFile(fileId))
return this.GetPermanentFileContent(fileId);
if (this.IsTempFile(fileId))
return this.GetTempFileContent(fileId);
throw new FileNotFoundException(String.Format("File (Id = {0}) not found", fileId));
}
#endregion
}
}