468 lines
16 KiB
C#
468 lines
16 KiB
C#
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="attachToObject"></param>
|
|
/// <param name="ct"></param>
|
|
/// <returns></returns>
|
|
internal static FileAttachment storeFileAttachment(string tempFilePath, string contentType, string fileName, 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()
|
|
{
|
|
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 |