using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
namespace MergeHub.Services
{
///
/// 뽑은 변경분을 ZIP 하나로도 묶는다(11차 A-4, 2026-09-22 사용자 결정: 폴더에 더해 ZIP 도).
///
/// 장비에는 폴더를 넣고, 메일 · 메신저로 보낼 때는 ZIP 을 쓴다. ZIP 안에는 뽑은 폴더와 안내 파일을
/// 나란히 넣는다 - 풀면 폴더로 뽑은 것과 같은 모양이 되고, 안내 파일이 소스 틈에 섞이지 않는다(A-2 결정).
/// 파일은 바이트 그대로 넣고, 안의 경로는 / 로 나눈다. 한글 이름은 UTF-8 로 적고 그렇다고 표시한다
/// (.NET 의 가 ASCII 가 아닌 이름에 범용 플래그 bit 11 을 켠다).
///
public static class ExportZip
{
/// 뽑은 폴더 옆, 폴더 이름에 .zip.
public static string PathFor(string folder) =>
folder.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + ".zip";
///
/// ZIP 을 만든다. 넣은 파일 수(안내 포함)를 돌려준다.
/// 이미 같은 이름이 있으면 덮지 않고 예외 - 자리는 가 비워 둔다.
///
/// 뽑은 폴더. ZIP 안에서 이 폴더 이름이 맨 위 폴더가 된다.
/// 안내 파일. 없거나 null 이면 넣지 않는다.
public static int Create(string folder, string? notePath, string zipPath)
{
var root = folder.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var top = Path.GetFileName(root);
var count = 0;
// 쓰다 막히면 반쯤 된 ZIP 을 남기지 않는다 - 옆 이름에 쓰고 다 되면 옮긴다.
var temp = zipPath + ".part";
try
{
using (var stream = new FileStream(temp, FileMode.CreateNew, FileAccess.Write))
using (var zip = new ZipArchive(stream, ZipArchiveMode.Create))
{
foreach (var file in Directory.GetFiles(root, "*", SearchOption.AllDirectories).OrderBy(f => f, StringComparer.Ordinal))
{
var relative = file.Substring(root.Length + 1).Replace(Path.DirectorySeparatorChar, '/');
Add(zip, file, top + "/" + relative);
count++;
}
if (!string.IsNullOrEmpty(notePath) && File.Exists(notePath))
{
Add(zip, notePath!, Path.GetFileName(notePath));
count++;
}
}
File.Move(temp, zipPath);
return count;
}
catch
{
try { if (File.Exists(temp)) File.Delete(temp); } catch { /* 지우지 못해도 원래 오류를 알린다 */ }
throw;
}
}
private static void Add(ZipArchive zip, string file, string entryName)
{
var entry = zip.CreateEntry(entryName, CompressionLevel.Optimal);
entry.LastWriteTime = File.GetLastWriteTime(file);
using var source = File.OpenRead(file);
using var target = entry.Open();
source.CopyTo(target);
}
}
}