This commit is contained in:
470
server/AyaNova/util/FileUtil.cs
Normal file
470
server/AyaNova/util/FileUtil.cs
Normal file
@@ -0,0 +1,470 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace AyaNova.Util
|
||||
{
|
||||
/*
|
||||
- Quickly generate large files in windows: http://tweaks.com/windows/62755/quickly-generate-large-test-files-in-windows/
|
||||
|
||||
*/
|
||||
|
||||
internal static class FileUtil
|
||||
{
|
||||
|
||||
#region Folder ensurance
|
||||
/// <summary>
|
||||
/// Ensurs folders exist and are not identical
|
||||
/// Throws an exception of they are found to be identical preventing startup
|
||||
/// The reason for this is to prevent a future erase database operation (which erases all attachment files)
|
||||
/// from erasing backups which might prevent recovery in case someone accidentally erases their database
|
||||
/// </summary>
|
||||
/// <param name="contentRootPath"></param>
|
||||
/// <returns></returns>
|
||||
internal static void EnsureUserAndUtilityFoldersExistAndAreNotIdentical(string contentRootPath)
|
||||
{
|
||||
|
||||
//UserFiles
|
||||
if (string.IsNullOrWhiteSpace(ServerBootConfig.AYANOVA_FOLDER_USER_FILES))
|
||||
{
|
||||
ServerBootConfig.AYANOVA_FOLDER_USER_FILES = Path.Combine(contentRootPath, "userfiles");
|
||||
}
|
||||
|
||||
//BackupFiles
|
||||
if (ServerBootConfig.AYANOVA_FOLDER_BACKUP_FILES == null)
|
||||
{
|
||||
ServerBootConfig.AYANOVA_FOLDER_BACKUP_FILES = Path.Combine(contentRootPath, "backupfiles");
|
||||
}
|
||||
|
||||
//Prevent using the same folder for both
|
||||
if (string.Equals(Path.GetFullPath(ServerBootConfig.AYANOVA_FOLDER_USER_FILES), Path.GetFullPath(ServerBootConfig.AYANOVA_FOLDER_BACKUP_FILES), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new System.NotSupportedException("E1040: The configuration settings AYANOVA_FOLDER_USER_FILES and the AYANOVA_FOLDER_BACKUP_FILES must not point to the exact same location");
|
||||
}
|
||||
|
||||
EnsurePath(ServerBootConfig.AYANOVA_FOLDER_USER_FILES);
|
||||
EnsurePath(ServerBootConfig.AYANOVA_FOLDER_BACKUP_FILES);
|
||||
}
|
||||
|
||||
//create path if doesn't exist already
|
||||
private static void EnsurePath(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
#endregion folder ensurance
|
||||
|
||||
#region Utility file handling
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a path combining supplied file name and backup files folder
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string GetFullPathForUtilityFile(string fileName)
|
||||
{
|
||||
return Path.Combine(UtilityFilesFolder, fileName);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get backup folder
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string UtilityFilesFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServerBootConfig.AYANOVA_FOLDER_BACKUP_FILES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete a utility file (backup folder file)
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
internal static void DeleteUtilityFile(string fileName)
|
||||
{
|
||||
var utilityFilePath = GetFullPathForUtilityFile(fileName);
|
||||
if (File.Exists(utilityFilePath))
|
||||
{
|
||||
File.Delete(utilityFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of files in the utility folder
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="searchPattern">search pattern for files desired, leave blank for any </param>
|
||||
/// <returns></returns>
|
||||
internal static List<string> UtilityFileList(string searchPattern = "*")
|
||||
{
|
||||
List<string> returnList = new List<string>();
|
||||
foreach (string file in Directory.EnumerateFiles(UtilityFilesFolder, searchPattern))
|
||||
{
|
||||
returnList.Add(Path.GetFileName(file));
|
||||
}
|
||||
returnList.Sort();
|
||||
return returnList;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Confirm if a file exists in the utility folder
|
||||
/// </summary>
|
||||
/// <param name="fileName">name of utility file </param>
|
||||
/// <returns>duh!</returns>
|
||||
internal static bool UtilityFileExists(string fileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
return false;
|
||||
|
||||
var utilityFilePath = GetFullPathForUtilityFile(fileName);
|
||||
return File.Exists(utilityFilePath);
|
||||
|
||||
}
|
||||
|
||||
#endregion Utility file handling
|
||||
|
||||
#region Zip handling
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//ZIP handling
|
||||
|
||||
/// <summary>
|
||||
/// Get zip entries for a utlity file
|
||||
/// </summary>
|
||||
/// <param name="zipFileName"></param>
|
||||
/// <returns></returns>
|
||||
internal static List<string> ZipGetUtilityFileEntries(string zipFileName)
|
||||
{
|
||||
return ZipGetEntries(GetFullPathForUtilityFile(zipFileName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get zip entries for full path and file name
|
||||
/// returns the entry fullname sorted alphabetically so that folders stay together
|
||||
/// </summary>
|
||||
/// <param name="zipPath"></param>
|
||||
/// <returns></returns>
|
||||
internal static List<string> ZipGetEntries(string zipPath)
|
||||
{
|
||||
List<string> zipEntries = new List<string>();
|
||||
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
zipEntries.Add(entry.FullName);
|
||||
}
|
||||
}
|
||||
zipEntries.Sort();
|
||||
return zipEntries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import utility - get individual files specified in zip archive as JSON objects
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="zipFileName">Name of utility zip import file</param>
|
||||
/// <param name="entryList">Name of entries in utility file archive to fetch</param>
|
||||
/// <returns></returns>
|
||||
internal static List<JObject> ZipGetUtilityArchiveEntriesAsJsonObjects(List<string> entryList, string zipFileName)
|
||||
{
|
||||
List<JObject> jList = new List<JObject>();
|
||||
var zipPath = GetFullPathForUtilityFile(zipFileName);
|
||||
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
|
||||
{
|
||||
foreach (string importFileName in entryList)
|
||||
{
|
||||
ZipArchiveEntry entry = archive.GetEntry(importFileName);
|
||||
if (entry != null)
|
||||
{
|
||||
//stream entry into a new jobject and add it to the list
|
||||
StreamReader reader = new StreamReader(entry.Open());
|
||||
string text = reader.ReadToEnd();
|
||||
var j = JObject.Parse(text);
|
||||
|
||||
//Here add v7 import file name as sometimes it's needed later (locales)
|
||||
j.Add("V7_SOURCE_FILE_NAME", JToken.FromObject(importFileName));
|
||||
jList.Add(j);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return jList;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endregion Zip handling
|
||||
|
||||
#region Attachment file handling
|
||||
|
||||
/// <summary>
|
||||
/// Get user folder
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string UserFilesFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServerBootConfig.AYANOVA_FOLDER_USER_FILES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a random file name
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string NewRandomFileName
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetRandomFileName();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a random file name with path to attachments folder
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string NewRandomAttachmentFileName
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(UserFilesFolder, NewRandomFileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Store a file attachment
|
||||
/// </summary>
|
||||
/// <param name="tempFilePath"></param>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="attachToObject"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
internal static FileAttachment storeFileAttachment(string tempFilePath, string contentType, string fileName, long userId, AyaTypeId attachToObject, AyContext ct)
|
||||
{
|
||||
//calculate hash
|
||||
var hash = FileHash.GetChecksum(tempFilePath);
|
||||
|
||||
//Move to folder based on hash
|
||||
var permanentPath = GetPermanentAttachmentPath(hash);
|
||||
EnsurePath(permanentPath);
|
||||
|
||||
var permanentFilePath = Path.Combine(permanentPath, hash);
|
||||
|
||||
//See if the file was already uploaded, if so then ignore it for now
|
||||
if (File.Exists(permanentFilePath))
|
||||
{
|
||||
//delete the temp file, it's already stored
|
||||
File.Delete(tempFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.File.Move(tempFilePath, permanentFilePath);
|
||||
}
|
||||
|
||||
|
||||
//Build AyFileInfo
|
||||
FileAttachment fi = new FileAttachment()
|
||||
{
|
||||
OwnerId = userId,
|
||||
StoredFileName = hash,
|
||||
DisplayFileName = fileName,
|
||||
Notes = string.Empty,
|
||||
ContentType = contentType,
|
||||
AttachToObjectId = attachToObject.ObjectId,
|
||||
AttachToObjectType = attachToObject.ObjectType
|
||||
};
|
||||
|
||||
//Store in DB
|
||||
ct.FileAttachment.Add(fi);
|
||||
ct.SaveChanges();
|
||||
|
||||
//Return AyFileInfo object
|
||||
return fi;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///use first three characters for name of folders one character per folder, i.e.:
|
||||
///if the checksum is f6a5b1236dbba1647257cc4646308326
|
||||
///it would be stored in userfiles/f/6/a/f6a5b1236dbba1647257cc4646308326
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns>Path without the file</returns>
|
||||
internal static string GetPermanentAttachmentPath(string hash)
|
||||
{
|
||||
return Path.Combine(UserFilesFolder, hash[0].ToString(), hash[1].ToString(), hash[2].ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the whole path including file name not just the folder
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
internal static string GetPermanentAttachmentFilePath(string hash)
|
||||
{
|
||||
return Path.Combine(UserFilesFolder, hash[0].ToString(), hash[1].ToString(), hash[2].ToString(), hash);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete a file attachment
|
||||
/// checks ref count and if would be zero deletes file physically
|
||||
/// otherwise just deletes pointer in db
|
||||
/// </summary>
|
||||
/// <param name="fileAttachmentToBeDeleted"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
internal static FileAttachment deleteFileAttachment(FileAttachment fileAttachmentToBeDeleted, AyContext ct)
|
||||
{
|
||||
|
||||
//check ref count of file
|
||||
var count = ct.FileAttachment.Count(w => w.StoredFileName == fileAttachmentToBeDeleted.StoredFileName);
|
||||
|
||||
//Store in DB
|
||||
ct.FileAttachment.Remove(fileAttachmentToBeDeleted);
|
||||
ct.SaveChanges();
|
||||
|
||||
if (count < 2)
|
||||
{
|
||||
//remove the file completely
|
||||
var permanentPath = GetPermanentAttachmentPath(fileAttachmentToBeDeleted.StoredFileName);
|
||||
var permanentFilePath = Path.Combine(permanentPath, fileAttachmentToBeDeleted.StoredFileName);
|
||||
|
||||
if (File.Exists(permanentFilePath))
|
||||
{
|
||||
//delete the temp file, it's already stored
|
||||
File.Delete(permanentFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
//Return AyFileInfo object
|
||||
return fileAttachmentToBeDeleted;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DANGER: Erases all user files
|
||||
/// </summary>
|
||||
internal static void EraseEntireContentsOfUserFilesFolder()
|
||||
{
|
||||
System.IO.DirectoryInfo di = new DirectoryInfo(UserFilesFolder);
|
||||
foreach (FileInfo file in di.EnumerateFiles())
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
foreach (DirectoryInfo dir in di.EnumerateDirectories())
|
||||
{
|
||||
dir.Delete(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion attachment stuff
|
||||
|
||||
#region General utilities
|
||||
|
||||
/// <summary>
|
||||
/// Attachments / user files folder size info
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static FolderSizeInfo GetAttachmentFolderSizeInfo()
|
||||
{
|
||||
return GetDirectorySize(new DirectoryInfo(UserFilesFolder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility / backup folder file size info
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static FolderSizeInfo GetUtilityFolderSizeInfo()
|
||||
{
|
||||
return GetDirectorySize(new DirectoryInfo(UtilityFilesFolder));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculate disk space usage under <paramref name="root"/>. If <paramref name="levels"/> is provided,
|
||||
/// then return subdirectory disk usages as well, up to <paramref name="levels"/> levels deep.
|
||||
/// If levels is not provided or is 0, return a list with a single element representing the
|
||||
/// directory specified by <paramref name="root"/>.
|
||||
///
|
||||
/// FROM https://stackoverflow.com/a/28094795/8939
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static FolderSizeInfo GetDirectorySize(DirectoryInfo root, int levels = 0)
|
||||
{
|
||||
var currentDirectory = new FolderSizeInfo();
|
||||
|
||||
// Add file sizes.
|
||||
FileInfo[] fis = root.GetFiles();
|
||||
currentDirectory.Size = 0;
|
||||
foreach (FileInfo fi in fis)
|
||||
{
|
||||
currentDirectory.Size += fi.Length;
|
||||
}
|
||||
|
||||
// Add subdirectory sizes.
|
||||
DirectoryInfo[] dis = root.GetDirectories();
|
||||
|
||||
currentDirectory.Path = root;
|
||||
currentDirectory.SizeWithChildren = currentDirectory.Size;
|
||||
currentDirectory.DirectoryCount = dis.Length;
|
||||
currentDirectory.DirectoryCountWithChildren = dis.Length;
|
||||
currentDirectory.FileCount = fis.Length;
|
||||
currentDirectory.FileCountWithChildren = fis.Length;
|
||||
|
||||
if (levels >= 0)
|
||||
currentDirectory.Children = new List<FolderSizeInfo>();
|
||||
|
||||
foreach (DirectoryInfo di in dis)
|
||||
{
|
||||
var dd = GetDirectorySize(di, levels - 1);
|
||||
if (levels >= 0)
|
||||
currentDirectory.Children.Add(dd);
|
||||
|
||||
currentDirectory.SizeWithChildren += dd.SizeWithChildren;
|
||||
currentDirectory.DirectoryCountWithChildren += dd.DirectoryCountWithChildren;
|
||||
currentDirectory.FileCountWithChildren += dd.FileCountWithChildren;
|
||||
}
|
||||
|
||||
return currentDirectory;
|
||||
}
|
||||
|
||||
public class FolderSizeInfo
|
||||
{
|
||||
public DirectoryInfo Path { get; set; }
|
||||
public long SizeWithChildren { get; set; }
|
||||
public long Size { get; set; }
|
||||
public int DirectoryCount { get; set; }
|
||||
public int DirectoryCountWithChildren { get; set; }
|
||||
public int FileCount { get; set; }
|
||||
public int FileCountWithChildren { get; set; }
|
||||
public List<FolderSizeInfo> Children { get; set; }
|
||||
}
|
||||
#endregion general utilities
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
Reference in New Issue
Block a user