This commit is contained in:
2023-04-09 21:25:08 +03:00
parent 66b3a8cc77
commit b2ea5c61d1
17 changed files with 658 additions and 72 deletions

View File

@@ -5,20 +5,27 @@ namespace FileSplitter.Shared.Classes;
/// <summary>
/// Информация о разбиении
/// </summary>
public class FileSplitInfo
public sealed class FileSplitInfo
{
public FileSplitInfo()
{
SplitFiles = new();
TargetFile = new TargetFileInfo();
FileSplitterVersion = FileSplitterConstants.CurrentSplitterVersion;
}
/// <summary>
/// Версия программы, необходимая для сборки файла
/// </summary>
public string? FileSplitterVersion { get; set; }
public string FileSplitterVersion { get; set; }
/// <summary>
/// Информация о собираемом файле
/// </summary>
public ITargetFileInfo? TargetFile { get; set; }
public TargetFileInfo TargetFile { get; set; }
/// <summary>
/// Информация о частях файла
/// </summary>
public List<ISplitFile>? SplitFiles { get; set; }
public List<SplitFile> SplitFiles { get; set; }
}

View File

@@ -0,0 +1,346 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Text;
using anbs_cp.Classes;
using anbs_cp.Classes.Encrypt;
using FileSplitter.Shared.Enums;
using FileSplitter.Shared.Interfaces;
using Newtonsoft.Json;
namespace FileSplitter.Shared.Classes;
/// <summary>
/// Класс операций
/// </summary>
public static class FileSplitterClass
{
/// <summary>
/// Событие прогресса
/// </summary>
public static event EventHandler<FileSplitterOnProgress>? OnProgress;
/// <summary>
/// Разделение файла на части
/// </summary>
/// <param name="fileName">Имя файла для разделения</param>
/// <param name="targetDir">Папка, в которую нужно сохранить файлы</param>
/// <param name="partSize">Размер части в байтах</param>
/// <param name="options">Параметры разбиения</param>
public static void SplitFile (string fileName, string targetDir, long partSize, ReadOnlyCollection<FileSplitOptions> options)
{
//Задаю обработчик для распаковки
EventHandler<FileSplitterOnProgress>? onProgressEvent = OnProgress;
//Инициализирующее сообщение
onProgressEvent?.Invoke(null,
new(0, 0,
string.Format(Localization.MessageStartSplit, fileName,
new FileSizeConverter(Localization.FileSizeStrings).Convert(partSize),
targetDir
)));
//Создаю результат
FileSplitInfo splitInfo = new()
{
FileSplitterVersion = FileSplitterConstants.CurrentSplitterVersion
};
//Если целевая папка не создана
if (!Directory.Exists(targetDir))
//- то создаём её
Directory.CreateDirectory(targetDir);
//Получаю размер текущего файла
long fileSize = FileExtension.FileSize(fileName);
//Получаю информацию о целевом файле
splitInfo.TargetFile = new()
{
FileName = Path.GetFileName(fileName),
FileSize = fileSize,
FileHash = new FileHash(fileName).Hash,
// ReSharper disable once PossibleLossOfFraction
SplitCount = (long)Math.Round(fileSize / (decimal)partSize, MidpointRounding.ToPositiveInfinity)
};
//Открываю файл
BinaryReader fileReader = new(File.Open(fileName, FileMode.Open));
//Разделяю файлы
for (int i = 0; i < splitInfo.TargetFile.SplitCount; i++)
{
//Сообщение прогресса
onProgressEvent?.Invoke(null,
new(i + 1, splitInfo.TargetFile.SplitCount,
string.Format(Localization.MessageSplitFilePart, i + 1, splitInfo.TargetFile.SplitCount)));
//Имя части
string partName = $"{Path.GetFileName(fileName)}.part{i + 1}.fsf";
//Полный путь к файлу части
string partFileName = $"{LikeDelphi.IncludeTrailingBackslash(targetDir)}{partName}";
//Смещение части от начала исходного файла
int startPos = (int)(i * partSize);
//Размер части
long bufferSize = fileSize - startPos > 0 ? partSize : partSize + (fileSize - startPos);
//Считываю значения
byte[] buffer = fileReader.ReadBytes((int)bufferSize);
//Записываю значение в файл
//- открываю файл
BinaryWriter fileWriter = new(File.Create(partFileName, (int)bufferSize));
//- записываю файл
fileWriter.Write(buffer);
//- закрываю файл
fileWriter.Close();
//Создаю информацию о части
SplitFile fileInfo = new()
{
Num = i + 1,
Offset = startPos,
Size = new FileInfo(partFileName).Length,
Hash = new FileHash(partFileName).Hash
};
//Добавляю в файл информации
splitInfo.SplitFiles.Add(fileInfo);
}
//Закрываю исходный файл
fileReader.Close();
//- и освобождаю память
fileReader.Dispose();
//Инициализирующее сообщение
onProgressEvent?.Invoke(null,
new(0, 0,
string.Format(Localization.MessageSplitComplete, fileName)));
//Проверка сумм
if (!options.Contains(FileSplitOptions.fsoNoCheckHash))
//- и если не прошли проверку
if (!CheckHash(targetDir, Path.GetFileName(fileName), splitInfo.SplitFiles))
//- то удаляем файлы разбиения
DeleteSplitFiles(Path.GetFileName(fileName), targetDir, splitInfo.TargetFile.SplitCount);
//Имя файла информации о разбиении
string reportFileName = $"{LikeDelphi.IncludeTrailingBackslash(targetDir)}{Path.GetFileName(fileName)}.fsi";
//Записываю информацию о разбиении
//- создаю файл
StreamWriter writer = new(reportFileName, Encoding.UTF8, new() { Access = FileAccess.Write, Mode = FileMode.Create });
//- создаю данные для записи файла
string data = options.Contains(FileSplitOptions.fsoEncryptInfoFile)
? StringEncrypt.Encrypt(JsonConvert.SerializeObject(splitInfo))
: JsonConvert.SerializeObject(splitInfo);
//- записываю в файл
writer.WriteLine(data);
//- закрываю файл
writer.Close();
//- освобождаю память
writer.Dispose();
//Если в опциях активирована опция удаления исходного файла
if (options.Contains(FileSplitOptions.fsoDeleteSourceFileAfterSplit))
//- то удаляю его
File.Delete(fileName);
}
/// <summary>
/// Объединение разъединенного файла
/// </summary>
/// <param name="infoFileName">Имя файла с информацией о разделении</param>
/// <param name="targetFileName">Целевой файл</param>
/// <param name="options">Параметры объединения</param>
public static void JoinFile (string infoFileName, string targetFileName, ReadOnlyCollection<FileJoinOptions> options)
{
//Задаю обработчик для распаковки
EventHandler<FileSplitterOnProgress>? onProgressEvent = OnProgress;
//Инициализирующее сообщение
onProgressEvent?.Invoke(null,
new(0, 0,
string.Format(Localization.MessageStartJoin, infoFileName, targetFileName)));
//Открываю файл для чтения
StreamReader reader = new(infoFileName, Encoding.UTF8);
//Читаю строку
string tLine = reader.ReadLine() ?? "{}";
//Освобождаю reader
reader.Close();
reader.Dispose();
//Содержимое файла информации о разбиении
string jsonData = options.Contains(FileJoinOptions.fjoInfoFileIsEncrypted)
? StringEncrypt.Decrypt(tLine)
: tLine;
//Создаю переменную для информации о разбиении
FileSplitInfo splitInfo = new();
try
{
//Получаю информацию о разбиении
splitInfo = JsonConvert.DeserializeObject<FileSplitInfo>(jsonData) ?? splitInfo;
}
catch (Exception)
{
onProgressEvent?.Invoke(null,
new(0, 0, Localization.MessageJoinUnknownFile));
Environment.Exit(2);
}
//Проверяю версию
if (!options.Contains(FileJoinOptions.fjoIgnoreSupportedScriptVersion) &&
!FileSplitterConstants.SupportSplitterVersion.Contains(splitInfo.FileSplitterVersion))
{
//Вывожу сообщение
onProgressEvent?.Invoke(null,
new(0, 0, string.Format(Localization.MessageJoinUnsupportedFile, splitInfo.FileSplitterVersion)));
//Прерываю
return;
}
//Получаю папку с разделёнными файлами
string sourceDir = LikeDelphi.IncludeTrailingBackslash(Path.GetDirectoryName(infoFileName) ?? "");
//Проверяю хэш
if (!options.Contains(FileJoinOptions.fjoNoCheckHash) && !CheckHash(sourceDir,
Path.GetFileName(splitInfo.TargetFile.FileName), splitInfo.SplitFiles))
{
//Вывожу сообщение
onProgressEvent?.Invoke(null, new(0, 0, Localization.MessageJoinHashError));
//Прерываю
return;
}
//Открываю файл
BinaryWriter fileWriter = new(File.Create(targetFileName, (int)splitInfo.TargetFile.FileSize));
//Объединяю файлы
for (int i = 1; i <= splitInfo.TargetFile.SplitCount; i++)
{
//Вывожу сообщение
onProgressEvent?.Invoke(null,
new(i, splitInfo.TargetFile.SplitCount,
string.Format(Localization.MessageJoinFilePart, i, splitInfo.TargetFile.SplitCount)));
//Имя файла части
string partFileName = $"{sourceDir}{Path.GetFileName(splitInfo.TargetFile.FileName)}.part{i}.fsf";
//Параметры части
ISplitFile partInfo = splitInfo.SplitFiles.FirstOrDefault(file => file.Num == i) ?? new SplitFile();
//Читаю часть
//- открываю файл для чтения
BinaryReader fileReader = new(File.Open(partFileName, FileMode.Open));
//- считываю содержимое
byte[] buffer = fileReader.ReadBytes((int)partInfo.Size);
//- закрываю файл
fileReader.Close();
//- освобождаю память
fileReader.Dispose();
//Записываю часть в общий файл
fileWriter.Write(buffer);
}
//Закрываю целевой файл
fileWriter.Close();
//Освобождаю память
fileWriter.Dispose();
//Вывожу сообщение
onProgressEvent?.Invoke(null, new(0, 0, Localization.MessageJoinComplete));
//Удаляю разделённые файлы
if (options.Contains(FileJoinOptions.fjoDeleteSourceFilesAfterJoin))
DeleteSplitFiles(splitInfo.TargetFile.FileName, sourceDir, splitInfo.TargetFile.SplitCount);
}
/// <summary>
/// Удаление разделённых файлов
/// </summary>
/// <param name="fileName">Исходное имя файла (без пути!)</param>
/// <param name="targetDir">Целевая директория</param>
/// <param name="splitCount">Число разбиения</param>
private static void DeleteSplitFiles (string fileName, string targetDir, long splitCount)
{
//Перебираю части
for (int i = 1; i < splitCount; i++)
{
//Имя части
string partName = $"{fileName}.part{i}.fsf";
//Полный путь к файлу части
string partFileName = $"{LikeDelphi.IncludeTrailingBackslash(targetDir)}{partName}";
//Удаляю файл
File.Delete(partFileName);
}
//Имя файла информации о разбиении
string reportFileName = $"{LikeDelphi.IncludeTrailingBackslash(targetDir)}{Path.GetFileName(fileName)}.fsi";
//Удаляю файл информации
File.Delete(reportFileName);
}
/// <summary>
/// Проверяет соответствие хэша файлов
/// </summary>
/// <param name="baseDir">Базовая папка с файлами</param>
/// <param name="baseName">Базовое имя файлов</param>
/// <param name="fileList">Список параметров файлов разбиения</param>
/// <returns>Прошли ли файлы проверку</returns>
private static bool CheckHash (string baseDir, string baseName, List<SplitFile> fileList)
{
//Задаю обработчик для распаковки
EventHandler<FileSplitterOnProgress>? onProgressEvent = OnProgress;
//Инициализирующее сообщение
onProgressEvent?.Invoke(null,
new(0, 0,
Localization.MessageHashCheckStart));
//Перебираю все части
foreach (SplitFile file in fileList)
{
//Полный путь к файлу части
string partFileName = $"{LikeDelphi.IncludeTrailingBackslash(baseDir)}{baseName}.part{file.Num}.fsf";
//Получаю хэш
IStructuralEquatable fileHashEq = new FileHash(partFileName).Hash;
//Если не равен
if (!fileHashEq.Equals(file.Hash, StructuralComparisons.StructuralEqualityComparer))
{
//- то сообщаю
onProgressEvent?.Invoke(null,
new(0, 0,
string.Format(Localization.MessageHashCheckFail, partFileName)));
//- и прерываю
return false;
}
}
//Везде хэш был равен
//- выдаю сообщение
onProgressEvent?.Invoke(null,
new(0, 0,
Localization.MessageHashCheckComplete));
//- и возвращаю результат
return true;
}
}

View File

@@ -0,0 +1,17 @@
namespace FileSplitter.Shared.Classes;
/// <summary>
/// Неизменяемые константы программы
/// </summary>
public static class FileSplitterConstants
{
/// <summary>
/// Текущая версия информационного файла
/// </summary>
public static string CurrentSplitterVersion = "20230409";
/// <summary>
/// Все поддерживаемые версии информационного файла
/// </summary>
public static string[] SupportSplitterVersion = { "20230409" };
}

View File

@@ -0,0 +1,35 @@
namespace FileSplitter.Shared.Classes;
/// <summary>
/// Параметры события OnProgress
/// </summary>
public class FileSplitterOnProgress: EventArgs
{
/// <summary>
/// Конструктор
/// </summary>
/// <param name="current">Текущий прогресс</param>
/// <param name="total">Максимальные прогресс</param>
/// <param name="message">Сообщение</param>
public FileSplitterOnProgress (long current, long total, string message)
{
CurrentProgress = current;
MaxProgress = total;
Message = message;
}
/// <summary>
/// Текущий прогресс
/// </summary>
public long CurrentProgress { get; }
/// <summary>
/// Максимальные прогресс
/// </summary>
public long MaxProgress { get; }
/// <summary>
/// Сообщение
/// </summary>
public string Message { get; }
}

View File

@@ -0,0 +1,31 @@
namespace FileSplitter.Shared.Classes;
public static class Localization
{
//Параметры приложения
public const string AppTitle = "ANB Software Делитель файлов";
public const string AppVersion = "Версия: {0}";
public const string AppCopyrights = "Авторские права (C) {0}, Александр Бабаев.";
//Сообщения
public const string MessageNoParameters =
"Нет параметров. Информацию о работе с программой смотрите в файле \"Help\\Russian\\Console.html\"";
public const string MessageNoParametersForSplit =
"Не заданы параметры разбиения. Информацию о работе с программой смотрите в файле \"Help\\Russian\\Console.html\"";
public const string MessageStartSplit = "Запущено разбиение файла {0} на части размером {1} и сохранением их в директории {2}.";
public const string MessageSplitFilePart = "Идёт обработка части {0} из {1}...";
public const string MessageSplitComplete = "Разбиение файла {0} завершено!";
public const string MessageHashCheckStart = "Начинаю проверку хэша разбиения";
public const string MessageHashCheckFail = "К сожалению, файл \"{0}\" не прошёл проверку хэша!";
public const string MessageHashCheckComplete = "Проверка хэша завершена!";
public const string MessageStartJoin = "Запущено объединение файлов, заданных файлом информации {0}, в файл {1}.";
public const string MessageJoinUnsupportedFile = "К сожалению, версия файла информации {0} не поддерживается!";
public const string MessageJoinUnknownFile = "Не могу прочитать файл информации! Возможно он зашифрован?";
public const string MessageJoinHashError = "К сожалению, разделённые файлы не прошли проверку. Возможно они повреждены?";
public const string MessageJoinFilePart = "Идёт обработка части {0} из {1}...";
public const string MessageJoinComplete = "Объединение файла {0} завершено!";
//Константы размеров
public static readonly string[] FileSizeStrings = { "байт", "Кб", "Мб", "Гб", "Тб" };
}

View File

@@ -5,12 +5,12 @@ namespace FileSplitter.Shared.Classes;
/// <summary>
/// Разделенный файл
/// </summary>
public class SplitFile: ISplitFile
public sealed class SplitFile: ISplitFile
{
/// <summary>
/// Порядок части
/// </summary>
public byte Num { get; set; }
public int Num { get; set; }
/// <summary>
/// Размер части

View File

@@ -5,12 +5,21 @@ namespace FileSplitter.Shared.Classes;
/// <summary>
/// Информация о целевом файле
/// </summary>
public class TargetFileInfo: ITargetFileInfo
public sealed class TargetFileInfo: ITargetFileInfo
{
/// <summary>
/// Конструктор
/// </summary>
internal TargetFileInfo()
{
FileName = string.Empty;
FileHash = new byte[]{};
}
/// <summary>
/// Имя файла
/// </summary>
public string? FileName { get; set; }
public string FileName { get; set; }
/// <summary>
/// Размер файла
@@ -20,10 +29,10 @@ public class TargetFileInfo: ITargetFileInfo
/// <summary>
/// md5-сумма файла для проверки
/// </summary>
public byte[]? FileHash { get; set; }
public byte[] FileHash { get; set; }
/// <summary>
/// Количество частей
/// </summary>
public byte SplitCount { get; set; }
public long SplitCount { get; set; }
}