using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; namespace CommonLibrary { internal class ProgressStream : Stream { private readonly Stream insideStream; private readonly IProgress insideReadProgress; private readonly IProgress insideWriteProgress; public ProgressStream(Stream stream, IProgress readProgress, IProgress writeProgress) { insideStream = stream; insideReadProgress = readProgress; insideWriteProgress = writeProgress; } public override bool CanRead => insideStream.CanRead; public override bool CanSeek => insideStream.CanSeek; public override bool CanWrite => insideStream.CanWrite; public override long Length => insideStream.Length; public override long Position { get => insideStream.Position; set => insideStream.Position = value; } public override void Flush() => insideStream.Flush(); public override long Seek(long offset, SeekOrigin origin) => insideStream.Seek(offset, origin); public override void SetLength(long value) => insideStream.SetLength(value); public override int Read(byte[] buffer, int offset, int count) { int bytesRead = insideStream.Read(buffer, offset, count); insideReadProgress?.Report(bytesRead); return bytesRead; } public override void Write(byte[] buffer, int offset, int count) { insideStream.Write(buffer, offset, count); insideWriteProgress?.Report(count); } } public class ProgressPacker { public void Pack(string sourceDirectoryName, string packFileName, IProgress progress) { sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); FileInfo[] sourceFiles = new DirectoryInfo(sourceDirectoryName).GetFiles("*", SearchOption.AllDirectories); double totalBytes = sourceFiles.Sum(f => f.Length); long currentBytes = 0; using (ZipArchive zipArchive = ZipFile.Open(packFileName, ZipArchiveMode.Create)) { foreach (FileInfo file in sourceFiles) { // 경로 구분자를 '/' 로 통일 (Windows 탐색기 호환) string entryName = file.FullName.Substring(sourceDirectoryName.Length + 1) .Replace(Path.DirectorySeparatorChar, '/'); ZipArchiveEntry entry = zipArchive.CreateEntry(entryName); entry.LastWriteTime = file.LastWriteTime; using (Stream inputStream = file.OpenRead()) using (Stream outputStream = entry.Open()) using (Stream progressStream = new ProgressStream(inputStream, new BasicProgress(i => { currentBytes += i; progress?.Report(currentBytes / totalBytes); }), null)) { // ✅ 방향 수정: inputStream → progressStream → outputStream progressStream.CopyTo(outputStream); } } } } public void UnPack(string packFileName, string destinationDirectoryName, IProgress progress) { using (ZipArchive zipArchive = ZipFile.OpenRead(packFileName)) { double totalBytes = zipArchive.Entries.Sum(f => f.Length); long currentBytes = 0; foreach (ZipArchiveEntry entry in zipArchive.Entries) { // 디렉터리 엔트리 건너뜀 if (string.IsNullOrEmpty(entry.Name)) continue; string fileName = Path.Combine(destinationDirectoryName, entry.FullName); Directory.CreateDirectory(Path.GetDirectoryName(fileName)); using (Stream inputStream = entry.Open()) using (Stream outputStream = File.OpenWrite(fileName)) using (Stream progressStream = new ProgressStream(outputStream, null, new BasicProgress(i => { currentBytes += i; progress?.Report(currentBytes / totalBytes); }))) { inputStream.CopyTo(progressStream); } File.SetLastWriteTime(fileName, entry.LastWriteTime.LocalDateTime); } } } public List GetTopLevelItem(string packFileName) { List packLists = new List(); using (ZipArchive archive = ZipFile.OpenRead(packFileName)) { // 최상위 항목들(폴더/파일명) 추출 var results = new List(); var seen = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var entry in archive.Entries) { string path = entry.FullName.Replace('\\', '/'); if (string.IsNullOrEmpty(path)) continue; int firstSlash = path.IndexOf('/'); if (firstSlash < 0) { // ZIP 루트에 바로 들어있는 항목 if (string.IsNullOrEmpty(entry.Name)) { // 디렉터리 엔트리 if (seen.Add(path.TrimEnd('/'))) results.Add(new PackList { IsDirectory = true, Name = path.TrimEnd('/') }); } else { // 파일 if (seen.Add(path)) results.Add(new PackList { IsDirectory = false, Name = path }); } } else { // 하위 경로가 있음 → 최상위는 폴더 string top = path.Substring(0, firstSlash); if (!string.IsNullOrEmpty(top) && seen.Add(top)) { results.Add(new PackList { IsDirectory = true, Name = top }); } } } return results; } } public struct PackList { public bool IsDirectory { get; set; } public string Name { get; set; } } } public class BasicProgress : IProgress { private readonly Action actionHandler; public BasicProgress(Action handler) { actionHandler = handler; } public void Report(T value) { actionHandler(value); } } }