FileChecker.cs
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Linq;
using System.IO;
using OCUnion.Transfer.Model;
using OCUnion.Transfer;
using System.Text;
using System;
using System.Threading.Tasks;
namespace OCUnion.Common
{
public static clast FileChecker
{
// 1. Проверяет все моды в папке Mods
// 2. Проверяет все моды в папке SteamWorkShop
// формирует таблицу вида: Имя файла | контрольная сумма.
// на серверной и клиентской стороне
// затем на серверной стороне формируется в ответ список: файл , массив байт. если файл подлежит замене и HashSize = 0 - файл подлежит удалению
// также в ответ отправляется список модов и порядок включения
///
/// Расширения файлов исключаемые из проверки
///
public static readonly string[] IgnoredModFiles = new string[] { ".cs", ".csproj", ".sln", ".gitignore", ".gitattributes" };
public static readonly string[] IgnoredConfigFiles = { "KeyPrefs.xml", "Knowledge.xml", "LastPlayedVersion.txt", "Prefs.xml" };
private clast FastComputeHash
{
public Task ReadFile;
public Task GetHash;
public void Wait()
{
if (GetHash != null) GetHash.Wait();
}
}
public static string GetCheckSum(string data)
{
var sha = SHA512.Create();
return Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(data)));
}
private static void GetCheckSum(ModelFileInfo mfi, string fileName, FastComputeHash computeHash)
{
try
{
if (computeHash.ReadFile != null) computeHash.ReadFile.Wait();
computeHash.ReadFile = Task.Run(() =>
{
try
{
if (!File.Exists(fileName)) return null;
var fileData = File.ReadAllBytes(fileName);
mfi.Size = fileData.Length;
return fileData;
}
catch (Exception exp)
{
ExceptionUtil.ExceptionLog(exp, "GetCheckSum 2 " + fileName);
}
return null;
});
computeHash.GetHash = computeHash.ReadFile.ContinueWith((task) =>
{
try
{
if (task.Result == null)
{
mfi.Hash = null;
return;
}
var sha = SHA512.Create();
mfi.Hash = sha.ComputeHash(task.Result);
}
catch(Exception exp)
{
ExceptionUtil.ExceptionLog(exp, "GetCheckSum 3 " + fileName);
}
});
/*
var sha = SHA512.Create();
using (var fs = new FileStream(fileName, FileMode.Open))
{
return sha.ComputeHash(fileData);
}
*/
}
catch(Exception exp)
{
ExceptionUtil.ExceptionLog(exp, "GetCheckSum 1 " + fileName);
}
}
public static void FileSynchronization(string modsDir, ModelModsFiles serverFiles)
{
restoreFolderTree(modsDir, serverFiles.FoldersTree);
foreach (var serverFile in serverFiles.Files)
{
var fullName = Path.Combine(modsDir, serverFile.FileName);
// Имя присутствует в списке, файл необходимо будет удалить ( или заменить)
if (File.Exists(fullName))
{
File.Delete(fullName);
}
if (serverFile.Hash == null)
{
continue;
}
// Create the file, or overwrite if the file must exist.
using (FileStream fs = File.Create(fullName))
{
Loger.Log("Restore: " + fullName);
if (serverFile.Hash.Length > 0)
{
fs.Write(serverFile.Hash, 0, serverFile.Hash.Length);
}
}
}
}
///
/// Generate hash for all files contains in root folder
///
/// can be null, then calc hash for all folder
///
/// string :
///
public static List GenerateHashFiles(string rootFolder, Action onStartUpdateFolder)
{
var result = new List();
if (string.IsNullOrEmpty(rootFolder) || !Directory.Exists(rootFolder))
{
Loger.Log($"Directory not found {rootFolder}");
return result;
}
generateHashFiles(result, ref rootFolder, rootFolder, true);
var checkFolders = Directory.GetDirectories(rootFolder);
// Файл который есть у клиента не найден, проверяем Первую директорию
for (var i = 0; i < checkFolders.Length; i++)
{
var folder = checkFolders[i];
onStartUpdateFolder?.Invoke(folder, i);
#if DEBUG
var di = new DirectoryInfo(folder);
if ("OnlineCity".Equals(di.Name))
continue;
#endif
var checkFolder = Path.Combine(rootFolder, folder);
if (Directory.Exists(checkFolder))
{
generateHashFiles(result, ref rootFolder, checkFolder);
}
else
{
Loger.Log($"Directory not found {checkFolder}");
}
}
return result;
}
private static void restoreFolderTree(string modsDir, FoldersTree foldersTree)
{
if (foldersTree.SubDirs == null)
{
return;
}
foreach (var folder in foldersTree.SubDirs)
{
var dirName = Path.Combine(modsDir, folder.directoryName);
if (!Directory.Exists(dirName))
{
Loger.Log($"Create directory: {dirName}");
Directory.CreateDirectory(dirName);
}
// check and create subdirs
restoreFolderTree(dirName, folder);
}
}
///
/// Check hash mods
///
///
///
///
private static void generateHashFiles(List result, ref string rootFolder, string folder, bool level0 = false)
{
if (!level0)
{
var dirs = Directory.GetDirectories(folder);
foreach (var subDir in dirs)
{
generateHashFiles(result, ref rootFolder, subDir);
}
}
var files = Directory.GetFiles(folder);
int fileNamePos = rootFolder.Length + 1;
var computeHash = new FastComputeHash();
foreach (var file in files.Where(x => ApproveExt(x)))
{
var mfi = new ModelFileInfo()
{
FileName = file.Substring(fileNamePos)
};
GetCheckSum(mfi, file, computeHash);
#if DEBUG
/*
if (mfi.FileName.Contains("armony"))
{
Loger.Log($"generateHashFiles Harmony: {FileName} {Hash}");
}*/
#endif
result.Add(mfi);
}
computeHash.Wait();
}
public static void ReHashFiles(List rep, string folder, List fileNames)
{
var dir = rep.ToDictionary(f => f.FileName);
var computeHash = new FastComputeHash();
foreach (var fileName in fileNames)
{
ModelFileInfo mfi;
if (!dir.TryGetValue(fileName, out mfi))
{
mfi = new ModelFileInfo()
{
FileName = fileName,
};
rep.Add(mfi);
}
var file = Path.Combine(folder, fileName);
var oldHash = mfi.Hash;
//if (MainHelper.DebugMode)
Loger.Log($"ReHashFile b {file} {(oldHash == null ? "" : Convert.ToBase64String(oldHash))}" + $"->{(mfi.Hash == null ? "" : Convert.ToBase64String(mfi.Hash))}");
GetCheckSum(mfi, file, computeHash);
//if (MainHelper.DebugMode)
Loger.Log($"ReHashFile e {file} {(oldHash == null ? "" : Convert.ToBase64String(oldHash))}" + $"->{(mfi.Hash == null ? "" : Convert.ToBase64String(mfi.Hash))}");
}
computeHash.Wait();
for (int i = 0; i < rep.Count; i++)
{
if (rep[i].Hash == null)
{
rep.RemoveAt(i--);
}
}
computeHash.Wait();
}
private static bool ApproveExt(string fileName)
{
foreach (var ext in IgnoredModFiles)
{
if (fileName.EndsWith(ext))
{
return false;
}
}
return true;
}
///
/// Create txt file contains directory list as byte array
/// Создает текстовый файл содержащий список разрешенных директорий в виде массива байт для отправки
///
///
///
public static byte[] CreateListFolder(string directory)
{
var dirs = Directory.GetDirectories(directory).OrderBy(x => x);
var sb = new StringBuilder();
foreach (var dir in dirs)
{
// только для дебага, а то папка Online city каждый раз обновляется
var di = new DirectoryInfo(dir);
#if DEBUG
if (di.Name.Equals("OnlineCity"))
continue;
#endif
sb.AppendLine(di.Name);
}
var txt = sb.ToString();
var diRoot = new DirectoryInfo(directory);
File.WriteAllText(Path.Combine(Loger.PathLog, diRoot.Name + ".txt"), txt);
return Encoding.ASCII.GetBytes(txt);
}
}
}