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 { 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 files = HttpContext.Current.Session["TempFiles"] as Dictionary; if (files == null) { files = new Dictionary(); 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 files = HttpContext.Current.Session["TempFiles"] as Dictionary; 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 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) && string.IsNullOrEmpty(x.Link)).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 files = HttpContext.Current.Session["TempFiles"] as Dictionary; 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; newRec.Link = null; DbContext.Attachments.Add(newRec); // Delete file metadata from session if ((files != null) && files.ContainsKey(fileId)) files.Remove(fileId); try { File.Delete(tmpFilePath); } catch { tmpFileDeleted = false; } return tmpFileDeleted; } public IList MoveToPermanentStorage(IList 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 undeletedTempFiles = new List(); 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 files = HttpContext.Current.Session["TempFiles"] as Dictionary; 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) && string.IsNullOrEmpty(x.Link)); 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) && string.IsNullOrEmpty(x.Link)); 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 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) && string.IsNullOrEmpty(x.Link)).Count(); return itemsCount > 0; } public IList GetPermanentFilesByHolder(Guid holderId, System.Type holderType = null) { return this.GetPermanentFilesByHolder(new List() { holderId }, holderType); } public IList GetPermanentFilesByHolder(IEnumerable holderIds, System.Type holderType = null) { List attachs; if (holderType != null) attachs = DbContext.Attachments.Where(x => holderIds.Contains(x.ParentId) && x.ParentType.Equals(holderType.FullName) && string.IsNullOrEmpty(x.Link)).Select(y => y.Id).ToList(); else attachs = DbContext.Attachments.Where(x => holderIds.Contains(x.ParentId) && string.IsNullOrEmpty(x.Link)) .Select(y => y.Id).ToList(); return attachs; } /// /// Changes parent for existing permanent file /// /// Id of the file /// New parent Id or Guid.Empty for no change /// New parent type or null for no change 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) && string.IsNullOrEmpty(x.Link)); 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; } } /// /// Changes parent for a group of existing of permanent files /// /// List of file models /// New parent Id or Guid.Empty for no change /// New parent type or null for no change public void ChangeFilesParent(IList 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) && string.IsNullOrEmpty(x.Link)); 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, Link=null }; DbContext.Attachments.Add(newRec); AttachmentModel newFileModel = (AttachmentModel)newRec; return newFileModel; } public IList CopyPermanentFiles(IList 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 newModels = new List(); 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 queuedFiles = new List(); public void QueuePermanentFilesToDelete(IEnumerable holderIds) { queuedFiles = new List(); var attachs = DbContext.Attachments.Where(x => holderIds.Contains(x.ParentId) && string.IsNullOrEmpty(x.Link)).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 } }