using System;
using System.Threading.Tasks;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
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
///
/// 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
///
///
///
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
///
/// Get a path combining supplied file name and backup files folder
///
///
internal static string GetFullPathForUtilityFile(string fileName)
{
return Path.Combine(UtilityFilesFolder, fileName);
}
///
/// Get backup folder
///
///
internal static string UtilityFilesFolder
{
get
{
return ServerBootConfig.AYANOVA_FOLDER_BACKUP_FILES;
}
}
///
/// Delete a utility file (backup folder file)
///
///
internal static void DeleteUtilityFile(string fileName)
{
var utilityFilePath = GetFullPathForUtilityFile(fileName);
if (File.Exists(utilityFilePath))
{
File.Delete(utilityFilePath);
}
}
///
/// Get a list of files in the utility folder
///
///
/// search pattern for files desired, leave blank for any
///
internal static List UtilityFileList(string searchPattern = "*")
{
List returnList = new List();
foreach (string file in Directory.EnumerateFiles(UtilityFilesFolder, searchPattern))
{
returnList.Add(Path.GetFileName(file));
}
returnList.Sort();
return returnList;
}
///
/// Confirm if a file exists in the utility folder
///
/// name of utility file
/// duh!
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
///
/// Get zip entries for a utlity file
///
///
///
internal static List ZipGetUtilityFileEntries(string zipFileName)
{
return ZipGetEntries(GetFullPathForUtilityFile(zipFileName));
}
///
/// Get zip entries for full path and file name
/// returns the entry fullname sorted alphabetically so that folders stay together
///
///
///
internal static List ZipGetEntries(string zipPath)
{
List zipEntries = new List();
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
zipEntries.Add(entry.FullName);
}
}
zipEntries.Sort();
return zipEntries;
}
///
/// Import utility - get individual files specified in zip archive as JSON objects
///
///
/// Name of utility zip import file
/// Name of entries in utility file archive to fetch
///
internal static List ZipGetUtilityArchiveEntriesAsJsonObjects(List entryList, string zipFileName)
{
List jList = new List();
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 (Translations)
j.Add("V7_SOURCE_FILE_NAME", JToken.FromObject(importFileName));
jList.Add(j);
}
}
}
return jList;
}
#endregion Zip handling
#region Attachment file handling
///
/// Get user folder
///
///
internal static string UserFilesFolder
{
get
{
return ServerBootConfig.AYANOVA_FOLDER_USER_FILES;
}
}
///
/// Get a random file name
///
///
internal static string NewRandomFileName
{
get
{
return Path.GetRandomFileName();
}
}
///
/// Get a random file name with path to attachments folder
///
///
internal static string NewRandomAttachmentFileName
{
get
{
return Path.Combine(UserFilesFolder, NewRandomFileName);
}
}
///
/// Store a file attachment
///
///
internal static async Task StoreFileAttachmentAsync(string tempFilePath, string contentType, string fileName, DateTime lastModified,
AyaTypeId attachToObject, string notes, 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 = notes,
ContentType = contentType,
AttachToObjectId = attachToObject.ObjectId,
AttachToObjectType = attachToObject.ObjectType,
LastModified = lastModified
};
//Store in DB
await ct.FileAttachment.AddAsync(fi);
await ct.SaveChangesAsync();
//Return AyFileInfo object
return fi;
}
///
///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
///
///
/// Path without the file
internal static string GetPermanentAttachmentPath(string hash)
{
return Path.Combine(UserFilesFolder, hash[0].ToString(), hash[1].ToString(), hash[2].ToString());
}
///
/// Get the whole path including file name not just the folder
///
///
///
internal static string GetPermanentAttachmentFilePath(string hash)
{
return Path.Combine(UserFilesFolder, hash[0].ToString(), hash[1].ToString(), hash[2].ToString(), hash);
}
///
/// Delete a file attachment
/// checks ref count and if would be zero deletes file physically
/// otherwise just deletes pointer in db
///
///
///
///
internal static async Task DeleteFileAttachmentAsync(FileAttachment fileAttachmentToBeDeleted, AyContext ct)
{
//check ref count of file
var count = await ct.FileAttachment.LongCountAsync(w => w.StoredFileName == fileAttachmentToBeDeleted.StoredFileName);
//Store in DB
ct.FileAttachment.Remove(fileAttachmentToBeDeleted);
await ct.SaveChangesAsync();
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;
}
///
/// DANGER: Erases all user files
///
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
///
/// Attachments / user files folder size info
///
///
internal static FolderSizeInfo GetAttachmentFolderSizeInfo()
{
return GetDirectorySize(new DirectoryInfo(UserFilesFolder));
}
///
/// Utility / backup folder file size info
///
///
internal static FolderSizeInfo GetUtilityFolderSizeInfo()
{
return GetDirectorySize(new DirectoryInfo(UtilityFilesFolder));
}
///
/// Calculate disk space usage under . If is provided,
/// then return subdirectory disk usages as well, up to levels deep.
/// If levels is not provided or is 0, return a list with a single element representing the
/// directory specified by .
///
/// FROM https://stackoverflow.com/a/28094795/8939
///
///
///
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();
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 Children { get; set; }
}
#endregion general utilities
}//eoc
}//eons