using Cysharp.Threading.Tasks; using System; namespace UVC.Util { /// /// Zipper 클래스는 압축 파일(Zip 및 7z)의 압축 해제 기능을 제공합니다. /// 7zip 라이브러리를 사용하며, 압축 해제 진행률을 실시간으로 추적할 수 있습니다. /// 7zip 사용. https://assetstore.unity.com/packages/tools/input-management/7zip-lzma-lz4-fastlz-zip-gzip-brotli-multiplatform-plugins-12674 /// public class Zipper { // zip 보관 파일을 압축하지 않은 현재 파일 수로 변경하는 단일 항목 정수 배열입니다. // decompress_File 기능을 실행할 때 getTotalFiles 기능으로 반환된 총 파일 수와 이 int를 비교하여 zip에 여러 개의 파일이 포함된 경우 추출의 진행 상황을 파악한다. // 여러 개의 스레드를 사용하는 경우 다른 스레드에 다른 진행 정수를 사용해야 합니다.그렇지 않으면 공유 위반이 발생합니다. private int[] progress; /// /// 개별 파일의 압축 해제 진행률(바이트 단위)을 나타내는 배열입니다. /// private ulong[] progress2; /// /// 압축 파일의 경로를 저장합니다. /// private string zipFilePath; /// /// 압축 해제가 완료되었는지 여부를 나타냅니다. /// private bool isComplete = false; /// /// 압축 해제 진행률(0.0 ~ 1.0)을 나타냅니다. /// private float percent = 0; /// /// 압축 해제 진행 상황을 콜백으로 전달하는 액션입니다. /// private Action OnProgress; /// /// 압축 파일을 해제합니다. /// 파일 형식(Zip 또는 7z)에 따라 적절한 메서드를 호출합니다. /// /// 압축 파일 경로 /// 압축 해제 대상 폴더 경로 /// 압축 해제 진행 상황을 전달하는 콜백 /// 압축 해제 결과 메시지 /// /// 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}"); /// } /// public async UniTask Decompress(string zipFilePath, string decompressFolderPath, Action OnProgress) { #if UNITY_WEBGL && !UNITY_EDITOR return "Decompression not supported on WebGL platform."; #else 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"; #endif } /// /// Zip 파일을 압축 해제합니다. /// /// 압축 파일 경로 /// 압축 해제 대상 폴더 경로 /// 압축 해제 진행 상황을 전달하는 콜백 /// 압축 해제 결과 메시지 /// /// 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}"); /// } /// public async UniTask DecompressZip(string zipFilePath, string decompressFolderPath, Action OnProgress) { #if UNITY_WEBGL && !UNITY_EDITOR return "Decompression not supported on WebGL platform."; #else 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"; #endif } /// /// Zip 파일의 압축 해제 진행률을 계산합니다. /// /// 압축 해제 대상 파일의 총 바이트 수 private async UniTaskVoid CountPercentZipAsync(ulong totalBytes) { #if UNITY_WEBGL && !UNITY_EDITOR #else 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(); } #endif } /// /// 7z 파일을 압축 해제합니다. /// /// 압축 파일 경로 /// 압축 해제 대상 폴더 경로 /// 압축 해제 진행 상황을 전달하는 콜백 /// 압축 해제 결과 메시지 /// /// 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}"); /// } /// public async UniTask Decompress7Zip(string zipFilePath, string decompressFolderPath, Action OnProgress) { #if UNITY_WEBGL && !UNITY_EDITOR return "Decompression not supported on WebGL platform."; #else 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; } #endif } /// /// 7z 파일의 압축 해제 진행률을 계산합니다. /// /// 압축 해제 대상 파일의 총 바이트 수 /// 첫 번째 호출 여부 private async UniTaskVoid CountPercent7ZipAsync(long totalBytes, bool isFirst = false) { #if UNITY_WEBGL && !UNITY_EDITOR #else 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(); } #endif } } }