Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get/set file permission functionality #1751

Merged
merged 2 commits into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions io/src/main/scala-2.12/fs2/io/CollectionCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ private[fs2] object CollectionCompat {
implicit class JIterableOps[A](private val self: java.lang.Iterable[A]) extends AnyVal {
def asScala: Iterable[A] = iterableAsScalaIterable(self)
}
implicit class JSetOps[A](private val self: java.util.Set[A]) extends AnyVal {
def asScala: Set[A] = asScalaSet(self).toSet
}
implicit class ListOps[A](private val self: List[A]) extends AnyVal {
def asJava: java.util.List[A] = seqAsJavaList(self)
}
implicit class SetOps[A](private val self: Set[A]) extends AnyVal {
def asJava: java.util.Set[A] = setAsJavaSet(self)
}
implicit class EnumerationOps[A](private val self: java.util.Enumeration[A]) extends AnyVal {
def asScala: Iterator[A] = enumerationAsScalaIterator(self)
}
Expand Down
6 changes: 6 additions & 0 deletions io/src/main/scala-2.13/fs2/io/CollectionCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ private[fs2] object CollectionCompat {
implicit class JIterableOps[A](private val self: java.lang.Iterable[A]) extends AnyVal {
def asScala: Iterable[A] = IterableHasAsScala(self).asScala
}
implicit class JSetOps[A](private val self: java.util.Set[A]) extends AnyVal {
def asScala: Set[A] = SetHasAsScala(self).asScala.toSet
}
implicit class ListOps[A](private val self: List[A]) extends AnyVal {
def asJava: java.util.List[A] = SeqHasAsJava(self).asJava
}
implicit class SetOps[A](private val self: Set[A]) extends AnyVal {
def asJava: java.util.Set[A] = SetHasAsJava(self).asJava
}
implicit class EnumerationOps[A](private val self: java.util.Enumeration[A]) extends AnyVal {
def asScala: Iterator[A] = EnumerationHasAsScala(self).asScala
}
Expand Down
24 changes: 22 additions & 2 deletions io/src/main/scala/fs2/io/file/file.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package fs2
package io

import java.nio.file._
import java.nio.file.attribute.FileAttribute
import java.nio.file.attribute.{FileAttribute, PosixFilePermission}
import java.util.stream.{Stream => JStream}

import scala.concurrent.duration._
Expand Down Expand Up @@ -179,6 +179,26 @@ package object file {
): F[Boolean] =
blocker.delay(Files.exists(path, flags: _*))

/**
* Get file permissions as set of [[PosixFilePermission]]
*/
def permissions[F[_]: Sync: ContextShift](
blocker: Blocker,
path: Path,
flags: Seq[LinkOption] = Seq.empty
): F[Set[PosixFilePermission]] =
blocker.delay(Files.getPosixFilePermissions(path, flags: _*).asScala)

/**
* Set file permissions from set of [[PosixFilePermission]]
*/
def setPermissions[F[_]: Sync: ContextShift](
blocker: Blocker,
path: Path,
permissions: Set[PosixFilePermission]
): F[Path] =
blocker.delay(Files.setPosixFilePermissions(path, permissions.asJava))

/**
* Copies a file from the source to the target path,
*
Expand All @@ -195,7 +215,7 @@ package object file {
/**
* Deletes a file.
*
* If the file is a directory then the directory must be empty for this action to succed.
* If the file is a directory then the directory must be empty for this action to succeed.
* This action will fail if the path doesn't exist.
*/
def delete[F[_]: Sync: ContextShift](blocker: Blocker, path: Path): F[Unit] =
Expand Down
64 changes: 58 additions & 6 deletions io/src/test/scala/fs2/io/file/FileSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package fs2
package io
package file

import java.nio.file.StandardOpenOption
import java.nio.file.{Paths, StandardOpenOption}
import java.nio.file.attribute.PosixFilePermissions

import cats.effect.{Blocker, IO}
import cats.effect.concurrent.Ref
import cats.effect.{Blocker, IO}
import cats.implicits._
import fs2.io.CollectionCompat._

import scala.concurrent.duration._
import java.nio.file.Paths

class FileSpec extends BaseFileSpec {
"readAll" - {
Expand Down Expand Up @@ -117,7 +118,7 @@ class FileSpec extends BaseFileSpec {
}

"exists" - {
"returns false on a non existant file" in {
"returns false on a non existent file" in {
Blocker[IO].use(b => file.exists[IO](b, Paths.get("nothing"))).unsafeRunSync shouldBe false
}
"returns true on an existing file" in {
Expand All @@ -127,6 +128,57 @@ class FileSpec extends BaseFileSpec {
}
}

"permissions" - {
"should fail for a non existent file" in {
Blocker[IO]
.use(b => file.permissions[IO](b, Paths.get("nothing")))
.attempt
.unsafeRunSync()
.isLeft shouldBe true
}
"should return permissions for existing file" in {
val permissions = PosixFilePermissions.fromString("rwxrwxr-x").asScala
Blocker[IO]
.use { b =>
tempFile
.evalMap(p => file.setPermissions[IO](b, p, permissions) >> file.permissions[IO](b, p))
.compile
.lastOrError
}
.unsafeRunSync() shouldBe permissions
}
}

"setPermissions" - {
"should fail for a non existent file" in {
Blocker[IO]
.use(b => file.setPermissions[IO](b, Paths.get("nothing"), Set.empty))
.attempt
.unsafeRunSync()
.isLeft shouldBe true
}
"should correctly change file permissions for existing file" in {
val permissions = PosixFilePermissions.fromString("rwxrwxr-x").asScala
val (initial, updated) = Blocker[IO]
.use { b =>
tempFile
.evalMap { p =>
for {
initialPermissions <- file.permissions[IO](b, p)
_ <- file.setPermissions[IO](b, p, permissions)
updatedPermissions <- file.permissions[IO](b, p)
} yield (initialPermissions -> updatedPermissions)
}
.compile
.lastOrError
}
.unsafeRunSync()

initial should not be updated
updated shouldBe permissions
}
}

"copy" - {
"returns a path to the new file" in {
(for {
Expand All @@ -140,7 +192,7 @@ class FileSpec extends BaseFileSpec {
}

"deleteIfExists" - {
"should result in non existant file" in {
"should result in non existent file" in {
tempFile
.flatMap(path =>
Stream
Expand All @@ -154,7 +206,7 @@ class FileSpec extends BaseFileSpec {
}

"delete" - {
"should fail on a non existant file" in {
"should fail on a non existent file" in {
Blocker[IO]
.use(blocker => file.delete[IO](blocker, Paths.get("nothing")))
.attempt
Expand Down