From 1af33d458bafab9198a2a40cdcde52454c517652 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 13 Sep 2017 15:55:59 -0400 Subject: [PATCH 1/5] implement POXIS permission --- io/src/main/scala/sbt/io/Path.scala | 75 ++++++++++++++++++++++++- io/src/test/scala/sbt/io/FileSpec.scala | 27 +++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 io/src/test/scala/sbt/io/FileSpec.scala diff --git a/io/src/main/scala/sbt/io/Path.scala b/io/src/main/scala/sbt/io/Path.scala index df664ef9..67608438 100644 --- a/io/src/main/scala/sbt/io/Path.scala +++ b/io/src/main/scala/sbt/io/Path.scala @@ -7,8 +7,12 @@ import java.io.File import java.net.URL import scala.collection.mutable import IO.wrapNull +import java.nio.file.attribute.{ PosixFilePermission, PosixFilePermissions } +import java.nio.file.{ Path => NioPath, LinkOption } +import java.nio.file.Files +import scala.collection.JavaConverters._ -final class RichFile(val asFile: File) extends AnyVal { +final class RichFile(val asFile: File) extends AnyVal with RichNioPath { def /(component: String): File = if (component == ".") asFile else new File(asFile, component) /** True if and only if the wrapped file exists.*/ @@ -64,6 +68,74 @@ final class RichFile(val asFile: File) extends AnyVal { def hash: Array[Byte] = Hash(asFile) def hashString: String = Hash.toHex(hash) def hashStringHalf: String = Hash.halve(hashString) + + override def asPath: NioPath = asFile.toPath + + private[sbt] override def linkOptions: Vector[LinkOption] = Vector.empty + + def withLinkOptions(linkOption: LinkOption*): LinkOptionPath = + new LinkOptionPath(asPath, linkOption.toVector) +} + +final class LinkOptionPath(p: NioPath, lo: Vector[LinkOption]) extends RichNioPath { + override val asPath: NioPath = p + private[sbt] val linkOptions: Vector[LinkOption] = lo +} + +sealed trait RichNioPath extends Any { + def asPath: NioPath + + private[sbt] def linkOptions: Vector[LinkOption] + + def permissions: Set[PosixFilePermission] = + Files.getPosixFilePermissions(asPath, linkOptions: _*).asScala.toSet + + def permissionsAsString: String = + PosixFilePermissions.toString(permissions.asJava) + + def setPermissions(permissions: Set[PosixFilePermission]): Unit = { + Files.setPosixFilePermissions(asPath, permissions.asJava) + () + } + + def addPermission(permission: PosixFilePermission): Unit = + setPermissions(permissions + permission) + + def removePermission(permission: PosixFilePermission): Unit = + setPermissions(permissions - permission) + + /** + * test if file has this permission + */ + def testPermission(permission: PosixFilePermission): Boolean = + permissions(permission) + + def isOwnerReadable: Boolean = + testPermission(PosixFilePermission.OWNER_READ) + + def isOwnerWritable: Boolean = + testPermission(PosixFilePermission.OWNER_WRITE) + + def isOwnerExecutable: Boolean = + testPermission(PosixFilePermission.OWNER_EXECUTE) + + def isGroupReadable: Boolean = + testPermission(PosixFilePermission.GROUP_READ) + + def isGroupWritable: Boolean = + testPermission(PosixFilePermission.GROUP_WRITE) + + def isGroupExecutable: Boolean = + testPermission(PosixFilePermission.GROUP_EXECUTE) + + def isOtherReadable: Boolean = + testPermission(PosixFilePermission.OTHERS_READ) + + def isOtherWritable: Boolean = + testPermission(PosixFilePermission.OTHERS_WRITE) + + def isOtherExecutable: Boolean = + testPermission(PosixFilePermission.OTHERS_EXECUTE) } object Path extends Mapper { @@ -87,6 +159,7 @@ object Path extends Mapper { def toURLs(files: Seq[File]): Array[URL] = files.map(_.toURI.toURL).toArray + private[sbt] val defaultLinkOptions: Vector[LinkOption] = Vector.empty } object PathFinder { diff --git a/io/src/test/scala/sbt/io/FileSpec.scala b/io/src/test/scala/sbt/io/FileSpec.scala new file mode 100644 index 00000000..ceda671d --- /dev/null +++ b/io/src/test/scala/sbt/io/FileSpec.scala @@ -0,0 +1,27 @@ +package sbt.io + +import java.io.{ File => JFile } +import org.scalatest._ +import sbt.io.syntax._ +import java.nio.file.attribute.PosixFilePermission + +class FileSpec extends FlatSpec with Matchers { + "files" should "set/unset permissions" in { + IO.withTemporaryDirectory { dir => + val t1 = dir / "foo.txt" + IO.write(t1, "foo") + + //an[UnsupportedOperationException] should be thrownBy t1.dosAttributes + t1.permissions(PosixFilePermission.OWNER_EXECUTE) shouldBe false + + t1.addPermission(PosixFilePermission.OWNER_EXECUTE) + t1.addPermission(PosixFilePermission.GROUP_WRITE) + t1.testPermission(PosixFilePermission.OWNER_EXECUTE) shouldBe true + t1.permissionsAsString shouldBe "rwxrw-r--" + + t1.removePermission(PosixFilePermission.OWNER_EXECUTE) + t1.isOwnerExecutable shouldBe false + t1.permissionsAsString shouldBe "rw-rw-r--" + } + } +} From 5f1d69413892b306a147d9125bb94274e1a165d1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 13 Sep 2017 16:56:33 -0400 Subject: [PATCH 2/5] implement owner --- io/src/main/scala/sbt/io/IO.scala | 26 ++++++++++++++ io/src/main/scala/sbt/io/Path.scala | 45 +++++++++++++++++++++++-- io/src/test/scala/sbt/io/FileSpec.scala | 22 ++++++------ 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/io/src/main/scala/sbt/io/IO.scala b/io/src/main/scala/sbt/io/IO.scala index 63819b01..bdbbe323 100644 --- a/io/src/main/scala/sbt/io/IO.scala +++ b/io/src/main/scala/sbt/io/IO.scala @@ -18,6 +18,7 @@ import java.io.{ import java.io.{ ObjectInputStream, ObjectStreamClass } import java.net.{ URI, URISyntaxException, URL } import java.nio.charset.Charset +import java.nio.file.FileSystems import java.util.Properties import java.util.jar.{ Attributes, JarEntry, JarOutputStream, Manifest } import java.util.zip.{ CRC32, ZipEntry, ZipInputStream, ZipOutputStream } @@ -27,6 +28,7 @@ import scala.collection.mutable.{ HashMap, HashSet } import scala.reflect.{ Manifest => SManifest } import scala.util.control.NonFatal import scala.util.control.Exception._ +import scala.collection.JavaConverters._ import Function.tupled /** A collection of File, URL, and I/O utility methods.*/ @@ -1009,4 +1011,28 @@ object IO { } } + /** Returns `true` if the filesystem supports POSIX file attribute view. */ + def isPosix: Boolean = hasPosixFileAttributeView + + /** Returns `true` if the filesystem supports POSIX file attribute view. */ + lazy val hasPosixFileAttributeView: Boolean = supportedFileAttributeViews.contains("posix") + + /** Returns `true` if the filesystem supports file owner attribute view. */ + lazy val hasFileOwnerAttributeView: Boolean = supportedFileAttributeViews.contains("owner") + + /** Returns `true` if the filesystem supports DOS file attribute view. */ + lazy val hasDosFileAttributeView: Boolean = supportedFileAttributeViews.contains("dos") + + /** Returns `true` if the filesystem supports ACL file attribute view. */ + lazy val hasAclFileAttributeView: Boolean = supportedFileAttributeViews.contains("acl") + + /** Returns `true` if the filesystem supports basic file attribute view. */ + lazy val hasBasicFileAttributeView: Boolean = supportedFileAttributeViews.contains("basic") + + /** Returns `true` if the filesystem supports user-defined file attribute view. */ + lazy val hasUserDefinedFileAttributeView: Boolean = supportedFileAttributeViews.contains("user") + + private[this] lazy val supportedFileAttributeViews: Set[String] = { + FileSystems.getDefault.supportedFileAttributeViews.asScala.toSet + } } diff --git a/io/src/main/scala/sbt/io/Path.scala b/io/src/main/scala/sbt/io/Path.scala index 67608438..dbe7a789 100644 --- a/io/src/main/scala/sbt/io/Path.scala +++ b/io/src/main/scala/sbt/io/Path.scala @@ -7,9 +7,8 @@ import java.io.File import java.net.URL import scala.collection.mutable import IO.wrapNull -import java.nio.file.attribute.{ PosixFilePermission, PosixFilePermissions } -import java.nio.file.{ Path => NioPath, LinkOption } -import java.nio.file.Files +import java.nio.file.attribute._ +import java.nio.file.{ Path => NioPath, LinkOption, FileSystem, Files } import scala.collection.JavaConverters._ final class RichFile(val asFile: File) extends AnyVal with RichNioPath { @@ -136,6 +135,46 @@ sealed trait RichNioPath extends Any { def isOtherExecutable: Boolean = testPermission(PosixFilePermission.OTHERS_EXECUTE) + + def attributes: BasicFileAttributes = + Files.readAttributes(asPath, classOf[BasicFileAttributes], linkOptions: _*) + + def posixAttributes: PosixFileAttributes = + Files.readAttributes(asPath, classOf[PosixFileAttributes], linkOptions: _*) + + def dosAttributes: DosFileAttributes = + Files.readAttributes(asPath, classOf[DosFileAttributes], linkOptions: _*) + + def aclFileAttributeView: AclFileAttributeView = + Files.getFileAttributeView(asPath, classOf[AclFileAttributeView], linkOptions: _*) + + /** Returns the owner of a file. */ + def owner: UserPrincipal = + Files.getOwner(asPath, linkOptions: _*) + + /** Returns the owner of a file. */ + def ownerName: String = owner.getName + + /** Returns the group owner of the file. */ + def group: GroupPrincipal = posixAttributes.group() + + /** Returns the group owner of the file. */ + def groupName: String = group.getName + + /** Updates the file owner. */ + def setOwner(owner: String): Unit = { + val fileSystem: FileSystem = asPath.getFileSystem + Files.setOwner(asPath, fileSystem.getUserPrincipalLookupService.lookupPrincipalByName(owner)) + () + } + + /** Updates the group owner of the file. */ + def setGroup(group: String): Unit = { + val fileSystem: FileSystem = asPath.getFileSystem + Files.setOwner(asPath, + fileSystem.getUserPrincipalLookupService.lookupPrincipalByGroupName(group)) + () + } } object Path extends Mapper { diff --git a/io/src/test/scala/sbt/io/FileSpec.scala b/io/src/test/scala/sbt/io/FileSpec.scala index ceda671d..235c333e 100644 --- a/io/src/test/scala/sbt/io/FileSpec.scala +++ b/io/src/test/scala/sbt/io/FileSpec.scala @@ -1,6 +1,5 @@ package sbt.io -import java.io.{ File => JFile } import org.scalatest._ import sbt.io.syntax._ import java.nio.file.attribute.PosixFilePermission @@ -10,18 +9,19 @@ class FileSpec extends FlatSpec with Matchers { IO.withTemporaryDirectory { dir => val t1 = dir / "foo.txt" IO.write(t1, "foo") + if (IO.isPosix) { + //an[UnsupportedOperationException] should be thrownBy t1.dosAttributes + t1.permissions(PosixFilePermission.OWNER_EXECUTE) shouldBe false - //an[UnsupportedOperationException] should be thrownBy t1.dosAttributes - t1.permissions(PosixFilePermission.OWNER_EXECUTE) shouldBe false + t1.addPermission(PosixFilePermission.OWNER_EXECUTE) + t1.addPermission(PosixFilePermission.GROUP_WRITE) + t1.testPermission(PosixFilePermission.OWNER_EXECUTE) shouldBe true + t1.permissionsAsString shouldBe "rwxrw-r--" - t1.addPermission(PosixFilePermission.OWNER_EXECUTE) - t1.addPermission(PosixFilePermission.GROUP_WRITE) - t1.testPermission(PosixFilePermission.OWNER_EXECUTE) shouldBe true - t1.permissionsAsString shouldBe "rwxrw-r--" - - t1.removePermission(PosixFilePermission.OWNER_EXECUTE) - t1.isOwnerExecutable shouldBe false - t1.permissionsAsString shouldBe "rw-rw-r--" + t1.removePermission(PosixFilePermission.OWNER_EXECUTE) + t1.isOwnerExecutable shouldBe false + t1.permissionsAsString shouldBe "rw-rw-r--" + } else () } } } From ec990731839b416dafcc51712021289a76db9868 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 13 Sep 2017 17:51:02 -0400 Subject: [PATCH 3/5] implement chmod, chown, and chgrp --- io/src/main/scala/sbt/io/IO.scala | 57 +++++++++++++++++++++++++++++ io/src/main/scala/sbt/io/Path.scala | 34 ++++++++++++++--- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/io/src/main/scala/sbt/io/IO.scala b/io/src/main/scala/sbt/io/IO.scala index bdbbe323..c7d6b3a4 100644 --- a/io/src/main/scala/sbt/io/IO.scala +++ b/io/src/main/scala/sbt/io/IO.scala @@ -19,6 +19,7 @@ import java.io.{ ObjectInputStream, ObjectStreamClass } import java.net.{ URI, URISyntaxException, URL } import java.nio.charset.Charset import java.nio.file.FileSystems +import java.nio.file.attribute.PosixFilePermissions import java.util.Properties import java.util.jar.{ Attributes, JarEntry, JarOutputStream, Manifest } import java.util.zip.{ CRC32, ZipEntry, ZipInputStream, ZipOutputStream } @@ -1035,4 +1036,60 @@ object IO { private[this] lazy val supportedFileAttributeViews: Set[String] = { FileSystems.getDefault.supportedFileAttributeViews.asScala.toSet } + + /** + * Updates permission of this file. + * This operation requires underlying filesystem to support `IO.isPosix`. + * + * @param permissions Must be 9 character POSIX permission representation e.g. "rwxr-x---" + * @param file + */ + def setPermissions(permissions: String, file: File): Unit = { + Path(file).setPermissions(PosixFilePermissions.fromString(permissions).asScala.toSet) + } + + /** + * An alias for `setPermissions`. Updates permission of this file. + * This operation requires underlying filesystem to support `IO.isPosix`. + * + * @param permissions Must be 9 character POSIX permission representation e.g. "rwxr-x---" + * @param file + */ + def chmod(permissions: String, file: File): Unit = setPermissions(permissions, file) + + /** + * Updates the file owner. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + * + * @param owner + * @param file + */ + def setOwner(owner: String, file: File): Unit = Path(file).setOwner(owner) + + /** + * An alias for `setOwner`. Updates the file owner. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + * + * @param owner + * @param file + */ + def chown(owner: String, file: File): Unit = setOwner(owner, file) + + /** + * Updates the group owner of the file. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + * + * @param group + * @param file + */ + def setGroup(group: String, file: File): Unit = Path(file).setGroup(group) + + /** + * An alias for setGroup. Updates the group owner of the file. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + * + * @param group + * @param file + */ + def chgrp(group: String, file: File): Unit = setGroup(group, file) } diff --git a/io/src/main/scala/sbt/io/Path.scala b/io/src/main/scala/sbt/io/Path.scala index dbe7a789..e18abfc0 100644 --- a/io/src/main/scala/sbt/io/Path.scala +++ b/io/src/main/scala/sbt/io/Path.scala @@ -148,27 +148,49 @@ sealed trait RichNioPath extends Any { def aclFileAttributeView: AclFileAttributeView = Files.getFileAttributeView(asPath, classOf[AclFileAttributeView], linkOptions: _*) - /** Returns the owner of a file. */ + /** + * Returns the owner of a file. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + */ def owner: UserPrincipal = Files.getOwner(asPath, linkOptions: _*) - /** Returns the owner of a file. */ + /** + * Returns the owner of a file. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + */ def ownerName: String = owner.getName - /** Returns the group owner of the file. */ + /** + * Returns the group owner of a file. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + */ def group: GroupPrincipal = posixAttributes.group() - /** Returns the group owner of the file. */ + /** + * Returns the group owner of a file. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + */ def groupName: String = group.getName - /** Updates the file owner. */ + /** + * Updates the file owner. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + * + * @param owner + */ def setOwner(owner: String): Unit = { val fileSystem: FileSystem = asPath.getFileSystem Files.setOwner(asPath, fileSystem.getUserPrincipalLookupService.lookupPrincipalByName(owner)) () } - /** Updates the group owner of the file. */ + /** + * Updates the group owner of the file. + * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. + * + * @param group + */ def setGroup(group: String): Unit = { val fileSystem: FileSystem = asPath.getFileSystem Files.setOwner(asPath, From 3199e660d064549b4bd686da03d9529a4e54e48b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 13 Sep 2017 18:00:43 -0400 Subject: [PATCH 4/5] update Scaladocs --- io/src/main/scala/sbt/io/Path.scala | 73 +++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/io/src/main/scala/sbt/io/Path.scala b/io/src/main/scala/sbt/io/Path.scala index e18abfc0..3849d379 100644 --- a/io/src/main/scala/sbt/io/Path.scala +++ b/io/src/main/scala/sbt/io/Path.scala @@ -86,54 +86,119 @@ sealed trait RichNioPath extends Any { private[sbt] def linkOptions: Vector[LinkOption] + /** + * Returns this file's POSIX permissions. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def permissions: Set[PosixFilePermission] = Files.getPosixFilePermissions(asPath, linkOptions: _*).asScala.toSet + /** + * Returns this file's POSIX permissions. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def permissionsAsString: String = PosixFilePermissions.toString(permissions.asJava) + /** + * Updates permission of this file. + * This operation requires underlying filesystem to support `IO.isPosix`. + * + * @param permissions + */ def setPermissions(permissions: Set[PosixFilePermission]): Unit = { Files.setPosixFilePermissions(asPath, permissions.asJava) () } + /** + * Adds permission to this file. + * This operation requires underlying filesystem to support `IO.isPosix`. + * + * @param permission + */ def addPermission(permission: PosixFilePermission): Unit = setPermissions(permissions + permission) + /** + * Removes permission from this file. + * This operation requires underlying filesystem to support `IO.isPosix`. + * + * @param permission + */ def removePermission(permission: PosixFilePermission): Unit = setPermissions(permissions - permission) /** - * test if file has this permission + * Tests if this file has the given permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + * + * @param permission */ def testPermission(permission: PosixFilePermission): Boolean = permissions(permission) + /** + * Tests if this file has the owner+read permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def isOwnerReadable: Boolean = testPermission(PosixFilePermission.OWNER_READ) + /** + * Tests if this file has the owner+write permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def isOwnerWritable: Boolean = testPermission(PosixFilePermission.OWNER_WRITE) + /** + * Tests if this file has the owner+execute permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def isOwnerExecutable: Boolean = testPermission(PosixFilePermission.OWNER_EXECUTE) + /** + * Tests if this file has the group+read permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def isGroupReadable: Boolean = testPermission(PosixFilePermission.GROUP_READ) + /** + * Tests if this file has the group+write permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def isGroupWritable: Boolean = testPermission(PosixFilePermission.GROUP_WRITE) + /** + * Tests if this file has the group+execute permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ def isGroupExecutable: Boolean = testPermission(PosixFilePermission.GROUP_EXECUTE) - def isOtherReadable: Boolean = + /** + * Tests if this file has the others+read permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ + def isOthersReadable: Boolean = testPermission(PosixFilePermission.OTHERS_READ) - def isOtherWritable: Boolean = + /** + * Tests if this file has the others+write permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ + def isOthersWritable: Boolean = testPermission(PosixFilePermission.OTHERS_WRITE) - def isOtherExecutable: Boolean = + /** + * Tests if this file has the others+execute permission. + * This operation requires underlying filesystem to support `IO.isPosix`. + */ + def isOthersExecutable: Boolean = testPermission(PosixFilePermission.OTHERS_EXECUTE) def attributes: BasicFileAttributes = From 71bd6cad84257ef9bb30c1b63b7f076d7cd88659 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 13 Sep 2017 18:05:47 -0400 Subject: [PATCH 5/5] flip the parameters for setOwner etc --- io/src/main/scala/sbt/io/IO.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/io/src/main/scala/sbt/io/IO.scala b/io/src/main/scala/sbt/io/IO.scala index c7d6b3a4..a21e8069 100644 --- a/io/src/main/scala/sbt/io/IO.scala +++ b/io/src/main/scala/sbt/io/IO.scala @@ -1041,10 +1041,10 @@ object IO { * Updates permission of this file. * This operation requires underlying filesystem to support `IO.isPosix`. * - * @param permissions Must be 9 character POSIX permission representation e.g. "rwxr-x---" * @param file + * @param permissions Must be 9 character POSIX permission representation e.g. "rwxr-x---" */ - def setPermissions(permissions: String, file: File): Unit = { + def setPermissions(file: File, permissions: String): Unit = { Path(file).setPermissions(PosixFilePermissions.fromString(permissions).asScala.toSet) } @@ -1055,16 +1055,16 @@ object IO { * @param permissions Must be 9 character POSIX permission representation e.g. "rwxr-x---" * @param file */ - def chmod(permissions: String, file: File): Unit = setPermissions(permissions, file) + def chmod(permissions: String, file: File): Unit = setPermissions(file, permissions) /** * Updates the file owner. * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. * - * @param owner * @param file + * @param owner */ - def setOwner(owner: String, file: File): Unit = Path(file).setOwner(owner) + def setOwner(file: File, owner: String): Unit = Path(file).setOwner(owner) /** * An alias for `setOwner`. Updates the file owner. @@ -1073,16 +1073,16 @@ object IO { * @param owner * @param file */ - def chown(owner: String, file: File): Unit = setOwner(owner, file) + def chown(owner: String, file: File): Unit = setOwner(file, owner) /** * Updates the group owner of the file. * This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`. * - * @param group * @param file + * @param group */ - def setGroup(group: String, file: File): Unit = Path(file).setGroup(group) + def setGroup(file: File, group: String): Unit = Path(file).setGroup(group) /** * An alias for setGroup. Updates the group owner of the file. @@ -1091,5 +1091,5 @@ object IO { * @param group * @param file */ - def chgrp(group: String, file: File): Unit = setGroup(group, file) + def chgrp(group: String, file: File): Unit = setGroup(file, group) }