From 4914f4a27dd2d6d605e7068635e75ec8b13fa9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Fri, 26 May 2023 12:17:59 +0200 Subject: [PATCH] unzip --- .../admin/domain/service/ZipUtility.scala | 33 +++++++++++++ .../webapi/util/ZScopedJavaIoStreams.scala | 7 +++ .../admin/domain/service/ZipUtilitySpec.scala | 46 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ZipUtilitySpec.scala diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ZipUtility.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ZipUtility.scala index 874916c936..4f302fce4c 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ZipUtility.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ZipUtility.scala @@ -9,6 +9,7 @@ import zio._ import zio.nio.file._ import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream import org.knora.webapi.util.ZScopedJavaIoStreams @@ -70,4 +71,36 @@ object ZipUtility { private def getZipEntry(entry: Path, srcFolder: Path) = new ZipEntry(srcFolder.relativize(entry).toString()) + /** + * Unzips a zip file to the specified destination folder. + * + * @param zipFile The path to the zip file to be unzipped. + * @param destinationFolder The path to the folder where the content of the zip file is written to. + */ + def unzipFile(zipFile: Path, destinationFolder: Path): Task[Path] = ZIO.scoped { + ZScopedJavaIoStreams.zipInputStream(zipFile).flatMap(unzip(_, destinationFolder)).as(destinationFolder) + } + private def unzip(zipInput: ZipInputStream, destinationFolder: Path): Task[Path] = + unzipNextEntry(zipInput, destinationFolder) + // recursively unpack the rest of the stream + .flatMap(entry => unzip(zipInput, destinationFolder).when(entry.isDefined)) + .as(destinationFolder) + private def unzipNextEntry(zipInput: ZipInputStream, destinationFolder: Path) = { + val acquire = ZIO.attemptBlocking(Option(zipInput.getNextEntry)) + val release = ZIO.attemptBlocking(zipInput.closeEntry()).logError.ignore + val getEntry = ZIO.acquireRelease(acquire)(_ => release) + val createParentIfNotExists = (path: Path) => + path.parent.map(d => Files.createDirectories(d).whenZIO(Files.notExists(d)).unit).getOrElse(ZIO.unit) + val unzipToFile = (path: Path) => + ZScopedJavaIoStreams.fileOutputStream(path).flatMap(fos => ZIO.attemptBlocking(zipInput.transferTo(fos))) + ZIO.scoped { + getEntry.tapSome { case Some(entry) => + val targetPath = destinationFolder / entry.getName + entry match { + case _ if entry.isDirectory => Files.createDirectories(targetPath) + case _ => createParentIfNotExists(targetPath) *> unzipToFile(targetPath) + } + } + } + } } diff --git a/webapi/src/main/scala/org/knora/webapi/util/ZScopedJavaIoStreams.scala b/webapi/src/main/scala/org/knora/webapi/util/ZScopedJavaIoStreams.scala index 8a930da26d..dfd887daac 100644 --- a/webapi/src/main/scala/org/knora/webapi/util/ZScopedJavaIoStreams.scala +++ b/webapi/src/main/scala/org/knora/webapi/util/ZScopedJavaIoStreams.scala @@ -10,6 +10,7 @@ import zio._ import java.io._ import java.nio.file.Files import java.nio.file.Path +import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream object ZScopedJavaIoStreams { @@ -58,6 +59,12 @@ object ZScopedJavaIoStreams { fileOutputStream(file).flatMap(fos => ZIO.acquireRelease(acquire(fos))(release)) } + def zipInputStream(path: nio.file.Path): ZIO[Scope, Throwable, ZipInputStream] = zipInputStream(path.toFile) + def zipInputStream(file: File): ZIO[Scope, Throwable, ZipInputStream] = { + def acquire(fis: InputStream) = ZIO.attempt(new ZipInputStream(fis)) + fileInputStream(file).flatMap(fis => ZIO.acquireRelease(acquire(fis))(release)) + } + /** * Opens or creates a file, returning an output stream that may be used to write bytes to the file. * Truncates and overwrites an existing file, or create the file if it doesn't initially exist. diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ZipUtilitySpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ZipUtilitySpec.scala new file mode 100644 index 0000000000..84db75ea47 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ZipUtilitySpec.scala @@ -0,0 +1,46 @@ +package org.knora.webapi.slice.admin.domain.service + +import zio._ +import zio.nio.file._ +import zio.test._ + +object ZipUtilitySpec extends ZIOSpecDefault { + private val testFolderName = "test-folder" + private val testFileName = "test-file.txt" + private val testFileContent = List("This is a test file.", "Content is not important.") + + private val createTestDirectory = for { + tmp <- Files.createTempDirectoryScoped(Some("tmp"), fileAttributes = Nil).logError + _ <- Files.createDirectory(tmp / testFolderName) + _ <- Files.createFile(tmp / testFolderName / testFileName) + _ <- Files.writeLines(tmp / testFolderName / testFileName, testFileContent) + } yield tmp + + private def verifyUnzipped(path: Path) = for { + folderExists <- Files.isDirectory(path / testFolderName) + _ <- ZIO.logError(s"Folder does not exist: ${path / testFolderName}").when(!folderExists) + expectedFile = path / testFolderName / testFileName + fileContentIsCorrect <- + Files.isRegularFile(expectedFile) && Files.readAllLines(expectedFile).map(_ == testFileContent) + _ <- ZIO.logError(s"File content $expectedFile is not correct").when(!fileContentIsCorrect) + } yield folderExists && fileContentIsCorrect + + override val spec: Spec[Scope, Throwable] = suite("ZipUtility")( + test("creating the test directory should work") { + for { + tmp <- createTestDirectory + createdCorrectly <- verifyUnzipped(tmp) + } yield assertTrue(createdCorrectly) + }, + test("should zip/unzip a folder with a single file") { + for { + zipThis <- createTestDirectory + tmpZipped <- Files.createTempDirectoryScoped(Some("zipped"), fileAttributes = Nil) + zipped <- ZipUtility.zipFolder(zipThis, tmpZipped) + tmpUnzipped <- Files.createTempDirectoryScoped(Some("unzipped"), fileAttributes = Nil) + unzipped <- ZipUtility.unzipFile(zipped, tmpUnzipped) + result <- verifyUnzipped(unzipped) + } yield assertTrue(result) + } + ) +}