20230409
This commit is contained in:
@@ -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; }
|
||||
}
|
346
FileSplitterShared/Classes/FileSplitterClass.cs
Normal file
346
FileSplitterShared/Classes/FileSplitterClass.cs
Normal 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;
|
||||
}
|
||||
}
|
17
FileSplitterShared/Classes/FileSplitterConstants.cs
Normal file
17
FileSplitterShared/Classes/FileSplitterConstants.cs
Normal 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" };
|
||||
}
|
35
FileSplitterShared/Classes/FileSplitterOnProgress.cs
Normal file
35
FileSplitterShared/Classes/FileSplitterOnProgress.cs
Normal 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; }
|
||||
}
|
31
FileSplitterShared/Classes/Localization.cs
Normal file
31
FileSplitterShared/Classes/Localization.cs
Normal 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 = { "байт", "Кб", "Мб", "Гб", "Тб" };
|
||||
}
|
@@ -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>
|
||||
/// Размер части
|
||||
|
@@ -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; }
|
||||
}
|
Reference in New Issue
Block a user