diff --git a/.gitignore b/.gitignore index c0b4b082..f011936d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ target/ .sbtserver .scala-build/ .bsp/ +.metals/ +.vscode/ project/.sbtserver tags nohup.out diff --git a/.mill-version b/.mill-version index ac454c6a..7bfd8360 100644 --- a/.mill-version +++ b/.mill-version @@ -1 +1 @@ -0.12.0 +0.12.8 diff --git a/.scalafmt.conf b/.scalafmt.conf index aa39efda..ff248aa4 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -21,4 +21,4 @@ runner.dialect = scala213 project.excludePaths = [ "glob:**/src-3/**" -] \ No newline at end of file +] diff --git a/os/src/ZipOps.scala b/os/src/ZipOps.scala index 317b27a8..b6f6e99c 100644 --- a/os/src/ZipOps.scala +++ b/os/src/ZipOps.scala @@ -2,7 +2,7 @@ package os import java.net.URI import java.nio.file.{FileSystem, FileSystems, Files} -import java.nio.file.attribute.{BasicFileAttributeView, FileTime, PosixFilePermissions} +import java.nio.file.attribute.{BasicFileAttributeView, FileTime, PosixFilePermission, PosixFilePermissions} import java.util.zip.{ZipEntry, ZipFile, ZipInputStream, ZipOutputStream} import scala.collection.JavaConverters._ import scala.util.matching.Regex @@ -149,29 +149,20 @@ object zip { preserveMtimes: Boolean, zipOut: ZipOutputStream ) = { - - val mtimeOpt = if (preserveMtimes) Some(os.mtime(file)) else None - - val fis = if (os.isFile(file)) Some(os.read.inputStream(file)) else None - try makeZipEntry0(sub, fis, mtimeOpt, zipOut) - finally fis.foreach(_.close()) - } - - private def makeZipEntry0( - sub: os.SubPath, - is: Option[java.io.InputStream], - preserveMtimes: Option[Long], - zipOut: ZipOutputStream - ) = { val zipEntry = new ZipEntry(sub.toString) - - preserveMtimes match { - case Some(mtime) => zipEntry.setTime(mtime) - case None => zipEntry.setTime(0) - } - + if (preserveMtimes) zipEntry.setTime(os.mtime(file)) else zipEntry.setTime(0) zipOut.putNextEntry(zipEntry) - is.foreach(os.Internals.transfer(_, zipOut, close = false)) + if (os.isFile(file)) { + val fis = os.read.inputStream(file) + try os.Internals.transfer(fis, zipOut, close = false) + finally fis.close() + } + // TODO make it conditional ??? + if (!scala.util.Properties.isWin) { + val filePerms = os.perms(file) + val perms = PosixFilePermissions.fromString(filePerms.toString) + Files.setAttribute(sub.toNIO, "zip:permissions", perms); + } } /** @@ -280,6 +271,11 @@ object unzip { os.Internals.transfer(zipInputStream, outputStream, close = false) outputStream.close() } + // TODO optional ??? + if (!scala.util.Properties.isWin) { + val perms = Files.getAttribute(java.nio.file.Paths.get(zipEntry.getName), "zip:permissions").asInstanceOf[java.util.Set[PosixFilePermission]] + os.perms.set(newFile, PermSet.fromSet(perms)) + } } } diff --git a/os/test/src-jvm/ZipOpJvmTests.scala b/os/test/src-jvm/ZipOpJvmTests.scala index dfe4bad2..764760ac 100644 --- a/os/test/src-jvm/ZipOpJvmTests.scala +++ b/os/test/src-jvm/ZipOpJvmTests.scala @@ -133,6 +133,53 @@ object ZipOpJvmTests extends TestSuite { test("append") - prep { wd => zipAndUnzipDontPreserveMtimes(wd, true) } } + test("zipAndUnzipPreservePermissions") - prep { wd => + if (Unix()) { + // Zipping files and folders in a new zip file + val zipFileName = "zip-file-test.zip" + os.perms.set(wd / "File.txt", "rwxr-xr-x") + os.perms.set(wd / "folder1/nestedA", "rw-rw-rw-") + val zipFile1: os.Path = os.zip( + dest = wd / zipFileName, + sources = Seq( + wd / "File.txt", + wd / "folder1" + ) + ) + // Adding files and folders to an existing zip file + os.zip( + dest = zipFile1, + sources = Seq( + wd / "folder2", + wd / "Multi Line.txt" + ) + ) + + // Unzip file to a destination folder + val unzippedFolder = os.unzip( + source = wd / zipFileName, + dest = wd / "unzipped folder" + ) + + val paths = os.walk(unzippedFolder) + val expected = Seq( + // Files get included in the zip root using their name + wd / "unzipped folder/File.txt", + wd / "unzipped folder/Multi Line.txt", + // Folder contents get included relative to the source root + wd / "unzipped folder/nestedA", + wd / "unzipped folder/nestedB", + wd / "unzipped folder/one.txt", + wd / "unzipped folder/nestedA/a.txt", + wd / "unzipped folder/nestedB/b.txt" + ) + assert(paths.sorted == expected) + + os.perms(wd / "unzipped folder/File.txt") ==> "rwxr-xr-x" + os.perms(wd / "unzipped folder/nestedA") ==> "rw-rw-rw-" + } + } + test("deletePatterns") - prep { wd => val amxFile = "File.amx" os.copy(wd / "File.txt", wd / amxFile)