Files
AIExpo/Assets/Scripts/UVC/Util/Zipper.cs
geondo55 1a79c9ffe3 최종
2025-09-18 10:58:34 +09:00

248 lines
12 KiB
C#

using Cysharp.Threading.Tasks;
using System;
namespace UVC.Util
{
/// <summary>
/// Zipper 클래스는 압축 파일(Zip 및 7z)의 압축 해제 기능을 제공합니다.
/// 7zip 라이브러리를 사용하며, 압축 해제 진행률을 실시간으로 추적할 수 있습니다.
/// 7zip 사용. https://assetstore.unity.com/packages/tools/input-management/7zip-lzma-lz4-fastlz-zip-gzip-brotli-multiplatform-plugins-12674
/// </summary>
public class Zipper
{
// zip 보관 파일을 압축하지 않은 현재 파일 수로 변경하는 단일 항목 정수 배열입니다.
// decompress_File 기능을 실행할 때 getTotalFiles 기능으로 반환된 총 파일 수와 이 int를 비교하여 zip에 여러 개의 파일이 포함된 경우 추출의 진행 상황을 파악한다.
// 여러 개의 스레드를 사용하는 경우 다른 스레드에 다른 진행 정수를 사용해야 합니다.그렇지 않으면 공유 위반이 발생합니다.
private int[] progress;
/// <summary>
/// 개별 파일의 압축 해제 진행률(바이트 단위)을 나타내는 배열입니다.
/// </summary>
private ulong[] progress2;
/// <summary>
/// 압축 파일의 경로를 저장합니다.
/// </summary>
private string zipFilePath;
/// <summary>
/// 압축 해제가 완료되었는지 여부를 나타냅니다.
/// </summary>
private bool isComplete = false;
/// <summary>
/// 압축 해제 진행률(0.0 ~ 1.0)을 나타냅니다.
/// </summary>
private float percent = 0;
/// <summary>
/// 압축 해제 진행 상황을 콜백으로 전달하는 액션입니다.
/// </summary>
private Action<long, long, float> OnProgress;
/// <summary>
/// 압축 파일을 해제합니다.
/// 파일 형식(Zip 또는 7z)에 따라 적절한 메서드를 호출합니다.
/// </summary>
/// <param name="zipFilePath">압축 파일 경로</param>
/// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
/// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
/// <returns>압축 해제 결과 메시지</returns>
/// <example>
/// Zipper zipper = new Zipper();
/// string result = await zipper.Decompress("path/to/file.zip", "path/to/output", (current, total, percent) =>
/// {
/// Debug.Log($"Progress: {percent * 100}%");
/// });
/// if (result == null)
/// {
/// Debug.Log("Decompression successful!");
/// }
/// else
/// {
/// Debug.LogError($"Decompression failed: {result}");
/// }
/// </example>
public async UniTask<string> Decompress(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
{
if (zipFilePath.ToLower().Contains(".zip"))
{
return await DecompressZip(zipFilePath, decompressFolderPath, OnProgress);
}
else if (zipFilePath.ToLower().Contains(".7z"))
{
return await Decompress7Zip(zipFilePath, decompressFolderPath, OnProgress);
}
return "Unsupported compressed file";
}
/// <summary>
/// Zip 파일을 압축 해제합니다.
/// </summary>
/// <param name="zipFilePath">압축 파일 경로</param>
/// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
/// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
/// <returns>압축 해제 결과 메시지</returns>
/// <example>
/// Zipper zipper = new Zipper();
/// string result = await zipper.DecompressZip("path/to/file.zip", "path/to/output", (current, total, percent) =>
/// {
/// Debug.Log($"Progress: {percent * 100}%");
/// });
/// if (result == null)
/// {
/// Debug.Log("Decompression successful!");
/// }
/// else
/// {
/// Debug.LogError($"Decompression failed: {result}");
/// }
/// </example>
public async UniTask<string> DecompressZip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
{
progress = new int[1];
progress2 = new ulong[1];
isComplete = false;
this.zipFilePath = zipFilePath;
this.OnProgress = OnProgress;
if (lzip.validateFile(zipFilePath))
{
//getFileInfo zip 내의 콘텐츠의 총 압축되지 않은 바이트를 반환한다.
ulong totalBytes = lzip.getFileInfo(zipFilePath);
CountPercentZipAsync(totalBytes).Forget();
int result = await UniTask.RunOnThreadPool(() =>
{
return lzip.decompress_File(zipFilePath, decompressFolderPath, progress, null, progress2);
});
isComplete = true;
if (result == 1) //success
{
return null;
}
else
{
//-1 = could not initialize zip archive.
//-2 = failed extraction
//- 6 = IntPtr buffer had no valid buffer length parameter passed.
string errorMessage = "Unknown error";
if (result == -1) errorMessage = "could not initialize zip archive.";
else if (result == -2) errorMessage = "failed extraction.";
else if (result == -6) errorMessage = "IntPtr buffer had no valid buffer length parameter passed.";
return errorMessage;
}
}
return "invalidate File";
}
/// <summary>
/// Zip 파일의 압축 해제 진행률을 계산합니다.
/// </summary>
/// <param name="totalBytes">압축 해제 대상 파일의 총 바이트 수</param>
private async UniTaskVoid CountPercentZipAsync(ulong totalBytes)
{
percent = (float)progress2[0] / (float)totalBytes;
//Debug.Log($"CountPercentAsync totalBytes:{totalBytes} progress:{progress[0]} progress2:{progress2[0]} {percent}");
if (OnProgress != null) OnProgress((long)progress2[0], (long)totalBytes, percent);
if (percent == 1) isComplete = true;
if (!isComplete)
{
await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
CountPercentZipAsync(totalBytes).Forget();
}
}
/// <summary>
/// 7z 파일을 압축 해제합니다.
/// </summary>
/// <param name="zipFilePath">압축 파일 경로</param>
/// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
/// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
/// <returns>압축 해제 결과 메시지</returns>
/// <example>
/// Zipper zipper = new Zipper();
/// string result = await zipper.Decompress7Zip("path/to/file.7z", "path/to/output", (current, total, percent) =>
/// {
/// Debug.Log($"Progress: {percent * 100}%");
/// });
/// if (result == null)
/// {
/// Debug.Log("Decompression successful!");
/// }
/// else
/// {
/// Debug.LogError($"Decompression failed: {result}");
/// }
/// </example>
public async UniTask<string> Decompress7Zip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
{
progress = new int[1] { 0 };
isComplete = false;
this.zipFilePath = zipFilePath;
this.OnProgress = OnProgress;
// 참조된 progress int는 압축 해제 중인 파일의 현재 인덱스를 나타냅니다. 별도의 스레드에서 사용하여 실시간으로 표시합니다.
// (thread에서) 실시간 바이트 레벨 압축해제 진행을 얻기 위해서는 두 가지 방법이 있습니다.
// 1. lzma.get7zSize 함수를 사용하여 파일의 총 압축되지 않은 크기를 구하고 lzma.getBytesWritten 함수를 호출하여 실시간으로 작성된 바이트들과 비교한다.
// 2. lzma.getFileSize(또는 FileBuffers의 버퍼 길이)를 사용하여 파일 크기를 구하고 lzma.getBytesRead 기능을 호출하여 실시간으로 읽는 바이트들과 비교한다.
//FileInfo fileInfo = new(zipFilePath);
//FileSecurity fsecurity = fileInfo.GetAccessControl();
//var id = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Trim();
//var name = id.Split("\\");
//fsecurity.AddAccessRule(new FileSystemAccessRule(new NTAccount(name[1]), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.None, AccessControlType.Allow));
//fileInfo.SetAccessControl(fsecurity);
long totalBytes = lzma.getFileSize(zipFilePath);
CountPercent7ZipAsync(totalBytes, true).Forget();
int result = await UniTask.RunOnThreadPool(() =>
{
return lzma.doDecompress7zip(zipFilePath, decompressFolderPath, progress, true, true);
});
isComplete = true;
if (result == 1) //success
{
return null;
}
else
{
//1 : OK
//2 : Could not find requested file in archive
//- 1 : Could not open input(7z) file
//- 2 : Decoder doesn't support this archive
//- 3 : Can not allocate memory
//-5 : Unknown error
//-6 : File IO error
string errorMessage = "Unknown error";
if (result == 2) errorMessage = "Could not find requested file in archive";
else if (result == -1) errorMessage = "Could not open input(7z) file";
else if (result == -2) errorMessage = "Decoder doesn't support this archive";
else if (result == -3) errorMessage = "Can not allocate memory";
else if (result == -6) errorMessage = "File IO error";
return errorMessage;
}
}
/// <summary>
/// 7z 파일의 압축 해제 진행률을 계산합니다.
/// </summary>
/// <param name="totalBytes">압축 해제 대상 파일의 총 바이트 수</param>
/// <param name="isFirst">첫 번째 호출 여부</param>
private async UniTaskVoid CountPercent7ZipAsync(long totalBytes, bool isFirst = false)
{
if (!isFirst)
{
long readByted = (long)lzma.getBytesRead();
percent = (float)readByted / (float)totalBytes;
//Debug.Log($"CountPercent7ZipAsync totalBytes:{totalBytes} progress:{progress}");
if (OnProgress != null) OnProgress(readByted, totalBytes, percent);
if (percent == 1) isComplete = true;
}
if (!isComplete)
{
await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
CountPercent7ZipAsync(totalBytes).Forget();
}
}
}
}