From 4949a7071e685a9703f1a1853b40817f8092502d Mon Sep 17 00:00:00 2001 From: Caadiq Date: Fri, 26 Dec 2025 18:37:34 +0900 Subject: [PATCH] =?UTF-8?q?feat(IconExporter):=20ZIP=EC=97=90=20metadata.j?= =?UTF-8?q?son=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=95=95=EC=B6=95=20?= =?UTF-8?q?=ED=9B=84=20=ED=8F=B4=EB=8D=94=20=EC=9E=90=EB=8F=99=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - metadata.json에 mod_id, icon_size, exported_at, total_count, skipped_count 포함 - ZIP 압축 완료 후 원본 폴더 자동 삭제 - 특정 모드/전체 모드 압축 시 경로 구조 개선 --- .../iconexporter/export/IconExportManager.kt | 104 ++++++++++++++++-- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/IconExporter/src/main/kotlin/com/beemer/iconexporter/export/IconExportManager.kt b/IconExporter/src/main/kotlin/com/beemer/iconexporter/export/IconExportManager.kt index 0c52522..09e745c 100644 --- a/IconExporter/src/main/kotlin/com/beemer/iconexporter/export/IconExportManager.kt +++ b/IconExporter/src/main/kotlin/com/beemer/iconexporter/export/IconExportManager.kt @@ -271,38 +271,118 @@ object IconExportManager { return null } + val modName = currentModFilter ?: "all" + // ZIP 파일명 생성 val timestamp = java.time.LocalDateTime.now() .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) - val modName = currentModFilter ?: "all" val zipFileName = "${modName}_$timestamp.zip" // icons 폴더 안에 ZIP 생성 val zipPath = iconsDir.resolve(zipFileName) IconExporter.LOGGER.info("ZIP 압축 생성 중: {}", zipPath) + // 압축할 모드 폴더 경로 (특정 모드 또는 전체) + val targetDir = + if (currentModFilter != null) { + iconsDir.resolve(currentModFilter!!) + } else { + iconsDir + } + + if (!targetDir.exists()) { + IconExporter.LOGGER.error("대상 폴더가 없습니다: {}", targetDir) + return null + } + ZipOutputStream(FileOutputStream(zipPath.toFile())).use { zos -> - Files.walk(iconsDir).use { paths -> - paths.filter { Files.isRegularFile(it) && it.name.endsWith(".png") }.forEach { file - -> - try { - // ZIP 내 상대 경로: //.png - val relativePath = iconsDir.relativize(file).pathString.replace("\\", "/") - zos.putNextEntry(ZipEntry(relativePath)) - Files.copy(file, zos) - zos.closeEntry() - } catch (e: Exception) { - IconExporter.LOGGER.error("ZIP에 파일 추가 실패: {} - {}", file, e.message) + // metadata.json 추가 + val metadata = buildString { + append("{\n") + append(" \"mod_id\": \"$modName\",\n") + append(" \"icon_size\": $iconSize,\n") + append(" \"exported_at\": \"${java.time.LocalDateTime.now()}\",\n") + append(" \"total_count\": $successCount,\n") + append(" \"skipped_count\": $skipCount\n") + append("}") + } + zos.putNextEntry(ZipEntry("metadata.json")) + zos.write(metadata.toByteArray(Charsets.UTF_8)) + zos.closeEntry() + + // PNG 파일 추가 + if (currentModFilter != null) { + // 특정 모드만 압축 + Files.walk(targetDir).use { paths -> + paths.filter { Files.isRegularFile(it) && it.name.endsWith(".png") }.forEach { + file -> + try { + // ZIP 내 상대 경로: blocks/.png 또는 items/.png + val relativePath = + targetDir.relativize(file).pathString.replace("\\", "/") + zos.putNextEntry(ZipEntry(relativePath)) + Files.copy(file, zos) + zos.closeEntry() + } catch (e: Exception) { + IconExporter.LOGGER.error("ZIP에 파일 추가 실패: {} - {}", file, e.message) + } + } + } + } else { + // 전체 압축 + Files.walk(iconsDir).use { paths -> + paths.filter { Files.isRegularFile(it) && it.name.endsWith(".png") }.forEach { + file -> + try { + val relativePath = + iconsDir.relativize(file).pathString.replace("\\", "/") + zos.putNextEntry(ZipEntry(relativePath)) + Files.copy(file, zos) + zos.closeEntry() + } catch (e: Exception) { + IconExporter.LOGGER.error("ZIP에 파일 추가 실패: {} - {}", file, e.message) + } } } } } IconExporter.LOGGER.info("ZIP 압축 완료: {}", zipPath) + + // 압축 완료 후 원본 폴더 삭제 + try { + if (currentModFilter != null) { + // 특정 모드 폴더만 삭제 + deleteDirectory(targetDir) + IconExporter.LOGGER.info("원본 폴더 삭제됨: {}", targetDir) + } else { + // 전체 모드인 경우 각 모드 폴더 삭제 (ZIP 파일 제외) + Files.list(iconsDir).use { dirs -> + dirs.filter { Files.isDirectory(it) }.forEach { dir -> deleteDirectory(dir) } + } + IconExporter.LOGGER.info("모든 원본 폴더 삭제됨") + } + } catch (e: Exception) { + IconExporter.LOGGER.error("원본 폴더 삭제 실패: {}", e.message) + } + return zipPath } + /** 디렉토리 재귀 삭제 */ + private fun deleteDirectory(dir: Path) { + if (!dir.exists()) return + + Files.walk(dir).sorted(Comparator.reverseOrder()).forEach { path -> + try { + Files.delete(path) + } catch (e: Exception) { + IconExporter.LOGGER.error("파일 삭제 실패: {} - {}", path, e.message) + } + } + } + /** 작업 취소 */ fun cancel() { if (!isRunning) return