.NET Core 환경에서 SharpZipLib 를 활용한 멀티 파일 압축 구현

서론

.NET Core 프로젝트에서 여러 파일을 단일 아카이브로 묶어야 하는 경우가 종종 발생합니다. 다양한 라이브러리 중에서 ICSharpCode.SharpZipLib 는 성능과 호환성 측면에서 신뢰할 수 있는 선택지입니다. 본 가이드에서는 해당 라이브러리를 이용하여 로컬 폴더 압축, 원격 파일 통합 압축, 그리고 압축 파일 내부의 폴더 구조 유지 방법을 설명합니다.

1. 패키지 설치

프로젝트에 SharpZipLib 를 추가하기 위해 NuGet 패키지 관리자 또는 CLI 를 사용합니다.

Install-Package ICSharpCode.SharpZipLib

2. 로컬 폴더 재귀 압축

지정한 디렉토리 내의 모든 하위 파일 및 폴더를 포함하여 ZIP 파일로 생성하는 로직입니다. 경로 결합 시 Path.Combine 을 사용하여 운영체제 호환성을 높였습니다.

public static string CompressDirectory(string sourcePath, string outputZipPath)
{
    if (!Directory.Exists(sourcePath))
        throw new DirectoryNotFoundException($"Source path not found: {sourcePath}");

    string directoryPath = Path.GetDirectoryName(outputZipPath);
    if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
    {
        Directory.CreateDirectory(directoryPath);
    }

    using (FileStream fs = File.Create(outputZipPath))
    using (ZipOutputStream zipStream = new ZipOutputStream(fs))
    {
        zipStream.SetLevel(6); // 압축 수준 설정 (0-9)
        ProcessFolder(sourcePath, zipStream, sourcePath);
        zipStream.Finish();
    }

    return outputZipPath;
}

private static void ProcessFolder(string path, ZipOutputStream zipStream, string rootPath)
{
    string[] entries = Directory.GetFileSystemEntries(path);

    foreach (string entry in entries)
    {
        if (Directory.Exists(entry))
        {
            ProcessFolder(entry, zipStream, rootPath);
        }
        else
        {
            using (FileStream fileStream = File.OpenRead(entry))
            {
                string relativePath = entry.Substring(rootPath.Length).TrimStart('\\', '/');
                ZipEntry zipEntry = new ZipEntry(relativePath)
                {
                    DateTime = File.GetLastWriteTime(entry),
                    Size = fileStream.Length
                };

                zipStream.PutNextEntry(zipEntry);
                fileStream.CopyTo(zipStream);
                zipStream.CloseEntry();
            }
        }
    }
}

3. 원격 파일 목록 압축

URL 목록을 입력받아 각 파일을 다운로드한 후 단일 ZIP 파일로 저장합니다. WebClient 대신 현대적인 HttpClient 를 사용하여 비동기 처리를 고려한 구조로 개선하였습니다.

public static void ArchiveRemoteFiles(List<string> urls, string destinationPath)
{
    string dirPath = Path.GetDirectoryName(destinationPath);
    if (!string.IsNullOrEmpty(dirPath) && !Directory.Exists(dirPath))
    {
        Directory.CreateDirectory(dirPath);
    }

    using (HttpClient client = new HttpClient())
    using (FileStream fs = File.Create(destinationPath))
    using (ZipOutputStream zos = new ZipOutputStream(fs))
    {
        zos.SetLevel(9);

        foreach (string url in urls)
        {
            byte[] fileData = client.GetByteArrayAsync(url).Result;
            string fileName = ExtractFileNameFromUri(url);
            
            ZipEntry entry = new ZipEntry(fileName)
            {
                DateTime = DateTime.Now,
                Size = fileData.Length
            };

            zos.PutNextEntry(entry);
            zos.Write(fileData, 0, fileData.Length);
            zos.CloseEntry();
        }
        zos.Finish();
    }
}

private static string ExtractFileNameFromUri(string uri)
{
    if (string.IsNullOrWhiteSpace(uri)) return "unknown.dat";

    char separator = uri.Contains("/") ? '/' : '\\';
    return uri.Substring(uri.LastIndexOf(separator) + 1);
}

4. 압축 내부 폴더 구조 정의

단순 파일 목록이 아닌, ZIP 파일 내부에 특정 폴더 계층 구조를 생성하려면 파일 항목 모델에 내부 경로를 포함해야 합니다. 이를 위해 ArchiveResource 클래스를 정의합니다.

public class ArchiveResource
{
    public string InternalPath { get; set; } // ZIP 내부에 저장될 경로 (예: images/logo.png)
    public string SourceUrl { get; set; }    // 실제 파일 소스 URL
}

이 모델을 사용하여 압축을 수행하는 메서드는 다음과 같습니다.

public static string CreateStructuredZip(string zipFileName, List<ArchiveResource> resources, string baseSavePath)
{
    string datePath = Path.Combine(DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString(), DateTime.Now.Day.ToString());
    string targetDirectory = Path.Combine(baseSavePath, "archives", datePath);
    
    if (!Directory.Exists(targetDirectory))
        Directory.CreateDirectory(targetDirectory);

    string fullPath = Path.Combine(targetDirectory, zipFileName);

    try
    {
        using (HttpClient client = new HttpClient())
        using (FileStream fs = File.Create(fullPath))
        using (ZipOutputStream zos = new ZipOutputStream(fs))
        {
            zos.SetLevel(9);

            foreach (var resource in resources)
            {
                byte[] data = client.GetByteArrayAsync(resource.SourceUrl).Result;
                
                ZipEntry entry = new ZipEntry(resource.InternalPath)
                {
                    DateTime = DateTime.Now,
                    Size = data.Length
                };

                zos.PutNextEntry(entry);
                zos.Write(data, 0, data.Length);
                zos.CloseEntry();
            }
            zos.Finish();
        }
        return fullPath;
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException($"Archive creation failed: {ex.Message}", ex);
    }
}

호출 시 다음과 같은 형태로 데이터를 전달하여 ZIP 내부에 원하는 폴더 트리를 구성할 수 있습니다.

var resources = new List<ArchiveResource>
{
    new ArchiveResource { InternalPath = "docs/readme.txt", SourceUrl = "https://example.com/files/readme.txt" },
    new ArchiveResource { InternalPath = "images/photo.jpg", SourceUrl = "https://example.com/files/photo.jpg" }
};

CreateStructuredZip("project_bundle.zip", resources, "/var/storage");

태그: dotnet-core sharpziplib c-sharp file-compression zip-archive

9월 10일 17:26에 게시됨