Last active
January 5, 2026 10:38
-
-
Save gekomad/209df0d6a532bb87d4a087dcc4ddf948 to your computer and use it in GitHub Desktop.
Scala Zip with Cats and FS2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| object ZipUtil { | |
| import cats.effect.{IO, Resource} | |
| import cats.implicits.* | |
| import fs2.io.{readInputStream, writeOutputStream} | |
| import java.io.{File, FileInputStream, FileOutputStream} | |
| import java.util.zip.{ZipEntry, ZipOutputStream} | |
| private val bufferSize = 8192 | |
| def zipFiles(sources: Set[String], dest: String): IO[String] = | |
| zipOutputStreamResource(dest).use { zipOut => | |
| sources.map(new File(_)).toList.traverse_(addFileToZip(zipOut, "")) | |
| }.as(dest) | |
| def zipFile(source: String, dest: String): IO[String] = | |
| zipFiles(Set(source), dest).as(dest) | |
| private def zipOutputStreamResource(path: String): Resource[IO, ZipOutputStream] = | |
| Resource | |
| .make(IO(new FileOutputStream(path))) { fos => | |
| IO(fos.close()) | |
| } | |
| .flatMap { fos => | |
| Resource.make(IO(new ZipOutputStream(fos))) { zos => | |
| IO(zos.close()) | |
| } | |
| } | |
| private def addFileToZip(zipOut: ZipOutputStream, parentPath: String)(file: File): IO[Unit] = { | |
| val entryName = if (parentPath.isEmpty) file.getName else s"$parentPath/${file.getName}" | |
| if (file.isDirectory) { | |
| val dirEntryName = if (entryName.endsWith("/")) entryName else s"$entryName/" | |
| IO(zipOut.putNextEntry(new ZipEntry(dirEntryName))) *> | |
| IO(zipOut.closeEntry()) *> | |
| IO(Option(file.listFiles()).getOrElse(Array.empty[File]).toList) | |
| .flatMap(_.traverse_(addFileToZip(zipOut, dirEntryName))) | |
| } else { | |
| val entry = new ZipEntry(entryName) | |
| for { | |
| _ <- IO(zipOut.putNextEntry(entry)) | |
| _ <- copyFileToZip(zipOut)(file) | |
| _ <- IO(zipOut.closeEntry()) | |
| } yield () | |
| } | |
| } | |
| private def copyFileToZip(zipOut: ZipOutputStream)(file: File): IO[Unit] = { | |
| val inputStreamResource: Resource[IO, FileInputStream] = | |
| Resource.make(IO(new FileInputStream(file))) { fis => | |
| IO(fis.close()) | |
| } | |
| inputStreamResource.use { fis => | |
| readInputStream(IO(fis), chunkSize = bufferSize) | |
| .through(writeOutputStream(IO(zipOut), closeAfterUse = false)) | |
| .compile | |
| .drain | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment